From d2d0ce8bc1f6b59fb52d61586df26ede878c9017 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Mon, 8 Jun 2026 11:25:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8C=89=E8=A7=84=E5=88=92=E7=BB=A7=E7=BB=AD?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论:新增 rotary indexer 的 WASM 和浏览器验证,继续通过 vendored LinuxCNC 解释器路径覆盖运行边界。 --- wasm-port/docs/compatibility-validation.md | 10 ++++- wasm-port/docs/drift-report.md | 2 +- wasm-port/docs/source-reuse-map.md | 2 +- .../linuxcnc_wrap/linuxcnc_interp_wasm.cpp | 38 +++++++++++++++++++ wasm-port/runtime/sdk/src/linuxcnc-interp.js | 4 ++ wasm-port/tests/browser/interp_smoke.html | 16 ++++++++ .../tests/wasm/node/verify_interp_wasm.mjs | 16 ++++++++ wasm-port/tools/build_wasm_core.sh | 2 +- 8 files changed, 85 insertions(+), 5 deletions(-) diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index 09e621e..619f8bc 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -91,6 +91,9 @@ required LinuxCNC `_setup` state readback with the matching files in `Interp::synch()` through the WASM C ABI to validate the initialization canonical boundary, metric/inch machine-unit status edge, and current/selected tool slot status synchronization already covered by the native init harness. +It also calls the vendored single-axis rotary indexer path through +`Interp::execute()` to validate the `UNLOCK_ROTARY`/`LOCK_ROTARY` canonical +runtime boundary already covered by the native indexer harness. It also writes selected G-code fixtures into the Emscripten filesystem through the SDK and runs them through LinuxCNC `Interp::open()`, `Interp::read()`, and `Interp::execute()` to validate the @@ -142,7 +145,8 @@ named-parameter file path and negative interpreter fixtures with expected error text plus absent canonical motion output. It also validates vendored `Interp::init()` and `Interp::synch()` through the same SDK/C ABI path, including initialization canonical events, metric/inch machine units, and tool -slot readback. It also uses real browser OPFS +slot readback, and checks the vendored rotary-indexer `G0 A...` execution path +for `UNLOCK_ROTARY`/`LOCK_ROTARY` boundary events. It also uses real browser OPFS storage plus the interpreter SDK to restore and save a LinuxCNC parameter file through vendored `Interp::restore_parameters()` and `Interp::save_parameters()`, and directly checks missing-file success plus out-of-order parameter-file @@ -291,7 +295,9 @@ result before OPFS persistence writes the saved table text back. The same Node WASM and browser interpreter smokes now validate the exported `Interp::init()`/`Interp::synch()` probe for initialization canonical events, metric/inch `emcStatus` machine-unit conversion, and current/selected tool -slot synchronization. +slot synchronization. They also validate the exported rotary-indexer probe for +vendored LinuxCNC `UNLOCK_ROTARY`/`LOCK_ROTARY` dispatch around a single-axis +`G0 A...` move. OPFS validation covers the JavaScript host-boundary adapter, the INI browser smoke harness, the INI panel UI's machine-session load and G-code run buttons, the raw canonical-event display fed directly by LinuxCNC interpreter WASM diff --git a/wasm-port/docs/drift-report.md b/wasm-port/docs/drift-report.md index 49a4e48..6d3bd93 100644 --- a/wasm-port/docs/drift-report.md +++ b/wasm-port/docs/drift-report.md @@ -31,7 +31,7 @@ semantic rewrites: | Tool table storage | Vendored LinuxCNC `tooldata_common.cc` owns tool-table parsing and formatting; the standalone boundary only supplies the in-memory `tooldata_get()`/`tooldata_put()` callbacks, exposes load/save through the WASM C ABI, and forwards the host random-toolchanger flag to LinuxCNC `tooldata_init()`. | | Tool slot status | Standalone tool adapter state supplies `GET_EXTERNAL_TOOL_SLOT()` and `GET_EXTERNAL_SELECTED_TOOL_SLOT()` for vendored `Interp::synch()`; `linuxcnc_interp_init_harness` plus Node WASM and browser interpreter smokes guard current/selected slot synchronization. | | Interpreter state tags | LinuxCNC `UPDATE_TAG` callbacks are captured from the state tags packed by vendored interpreter code; standalone code does not derive modal state. | -| Rotary indexer lock state | LinuxCNC `UNLOCK_ROTARY` and `LOCK_ROTARY` callbacks are captured from the vendored single-axis indexer path; standalone code does not implement rotary-indexing semantics. | +| Rotary indexer lock state | LinuxCNC `UNLOCK_ROTARY` and `LOCK_ROTARY` callbacks are captured from the vendored single-axis indexer path; standalone code does not implement rotary-indexing semantics, and native, Node WASM, and browser interpreter smokes validate the same boundary events. | | File flush | LinuxCNC `FINISH` is captured as a canonical test event on the vendored `%`-delimited file-reading path. | | WASM parameter-file backup | LinuxCNC `save_parameters()` calls `link()` to create the `.bak` parameter-file backup; `runtime/core/linuxcnc_wrap/linuxcnc_wasm_posix_stubs.cpp` maps that WASM/MEMFS edge to a file copy so the vendored save path can run unchanged. | | Interpreter reset | LinuxCNC `ON_RESET` is captured as a canonical test event on the vendored interpreter reset/file-open path. | diff --git a/wasm-port/docs/source-reuse-map.md b/wasm-port/docs/source-reuse-map.md index ffe653e..aa892ae 100644 --- a/wasm-port/docs/source-reuse-map.md +++ b/wasm-port/docs/source-reuse-map.md @@ -45,7 +45,7 @@ Current validation is intentionally mechanical: | Ruckig C planner support | Selected `src/emc/tp/cruckig/*.c` and `*.h` files in the manifest | Copy unchanged | Used as LinuxCNC planner support code through vendored TP sources | Vendor byte sync, per-file source probes | | Posemath | `src/libnml/posemath/posemath.cc`, `_posemath.c`, `gomath.c`, `sincos.c`, and matching headers | Copy unchanged | `gomath.c` is compiled as C; `rtapi.h` shim is C/C++ compatible for this boundary | Vendor byte sync, per-file source probes, TP probe | | RS274 interpreter state and parser | `src/emc/rs274ngc/modal_state.*`, `interp_internal.*`, `interp_read.cc`, `interp_check.cc`, `interp_execute.cc`, `interp_find.cc`, `interp_array.cc`, `interp_queue.*`, `rs274ngc*`, `units.h` | Copy unchanged | Python/remap/runtime edges are isolated in standalone wrappers and shims; parser and execution logic remain LinuxCNC source | Vendor byte sync, per-file source probes, interpreter harness fixtures | -| RS274 conversion semantics | `src/emc/rs274ngc/interp_convert.cc`, `interp_arc.cc`, `interp_inverse.cc`, `interp_cycles.cc`, `interp_g7x.cc`, `interp_o_word.cc`, `interp_write.cc` | Copy unchanged | Canonical calls are captured by standalone event sink functions; conversion behavior stays in vendored LinuxCNC files; feed-rate state is read back through the canonical runtime boundary during length-unit conversion | Vendor byte sync, per-file source probes, canonical fixture suite, no standalone `Interp::convert_g()` guard | +| RS274 conversion semantics | `src/emc/rs274ngc/interp_convert.cc`, `interp_arc.cc`, `interp_inverse.cc`, `interp_cycles.cc`, `interp_g7x.cc`, `interp_o_word.cc`, `interp_write.cc` | Copy unchanged | Canonical calls are captured by standalone event sink functions; conversion behavior stays in vendored LinuxCNC files; feed-rate state is read back through the canonical runtime boundary during length-unit conversion; single-axis rotary indexer lock/unlock dispatch remains vendored LinuxCNC `issue_straight_index()` behavior | Vendor byte sync, per-file source probes, canonical fixture suite, no standalone `Interp::convert_g()` guard, `linuxcnc_indexer_harness`, `tests/wasm/node/verify_interp_wasm.sh` and `tests/browser/verify_interp_browser.sh` rotary-indexer assertions | | Named parameters and tool slot status | `src/emc/rs274ngc/interp_namedparams.cc`, related interpreter headers | Copy unchanged | `_ini[...]` and `_hal[...]` resolve through standalone INI/HAL adapters while lookup order stays LinuxCNC-derived; current/selected tool slot reads for vendored `Interp::synch()` come from the standalone tool adapter | Vendor byte sync, source probe, `linuxcnc_namedparam_harness`, `linuxcnc_interp_init_harness`, `tests/wasm/node/verify_interp_wasm.sh` and `tests/browser/verify_interp_browser.sh` `Interp::synch()` tool-slot assertions | | Tool table parsing and formatting | `src/emc/tooldata/tooldata_common.cc` | Copy unchanged plus standalone storage callbacks | Native file parsing/formatting stays in vendored LinuxCNC source; the standalone tool adapter supplies `tooldata_get()`/`tooldata_put()` storage and index lookup, the WASM boundary only selects the LinuxCNC non-random or random-toolchanger branch via `tooldata_init()`, and OPFS remains a host-side persistence boundary | Vendor byte sync, `linuxcnc_tooldata_common_source_probe`, `tests/wasm/node/verify_interp_wasm.sh` non-random/random load/save tool-table assertions | | Dynamic interpreter base | `src/emc/rs274ngc/interp_base.*` | Copy unchanged | `interp_base.cc` source probe uses standalone `EMC2_HOME` compile-time path boundary for LinuxCNC dynamic interpreter lookup | Vendor byte sync, `linuxcnc_interp_base_source_probe` | diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_wasm.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_wasm.cpp index 404b46b..59b41c4 100644 --- a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_wasm.cpp +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_wasm.cpp @@ -92,6 +92,26 @@ void initialize_minimal_interp(Interp &interp) std::memcpy(interp._readers, Interp::default_readers, sizeof(Interp::default_readers)); } +void initialize_indexer_interp(Interp &interp) +{ + standalone::reset_hal_adapter(); + standalone::reset_tool_adapter(); + + interp._setup.length_units = CANON_UNITS_MM; + interp._setup.distance_mode = DISTANCE_MODE::ABSOLUTE; + interp._setup.ijk_distance_mode = DISTANCE_MODE::ABSOLUTE; + interp._setup.feed_mode = FEED_MODE::UNITS_PER_MINUTE; + interp._setup.plane = CANON_PLANE::XY; + interp._setup.motion_mode = G_0; + interp._setup.percent_flag = false; + interp._setup.sequence_number = 1; + interp._setup.parameter_occurrence = 0; + interp._setup.num_spindles = 1; + interp._setup.a_indexer_jnum = 0; + interp._setup.parameters[5599] = 1.0; + std::memcpy(interp._readers, Interp::default_readers, sizeof(Interp::default_readers)); +} + std::vector split_program_lines(const char *program_text) { std::vector lines; @@ -447,6 +467,24 @@ char *lcinterp_probe_init_and_synch() return copy_result(output.str()); } +EMSCRIPTEN_KEEPALIVE +char *lcinterp_probe_indexer() +{ + Interp interp; + standalone::reset_canon_events(); + initialize_indexer_interp(interp); + + std::ostringstream output; + const int rc = interp.execute("G0 A90\n"); + output << "execute=" << rc << "\n"; + output << "post_a=" << interp._setup.AA_current << "\n"; + output << "canon_event_count=" << standalone::canon_events().size() << "\n"; + for (const auto &event : standalone::canon_events()) { + output << "canon_event=" << event << "\n"; + } + return copy_result(output.str()); +} + EMSCRIPTEN_KEEPALIVE void lcinterp_free_string(char *value) { diff --git a/wasm-port/runtime/sdk/src/linuxcnc-interp.js b/wasm-port/runtime/sdk/src/linuxcnc-interp.js index b8afca8..82d6d5f 100644 --- a/wasm-port/runtime/sdk/src/linuxcnc-interp.js +++ b/wasm-port/runtime/sdk/src/linuxcnc-interp.js @@ -99,5 +99,9 @@ export async function createLinuxCncInterpSdk(moduleOptions = {}) { probeInitAndSynch() { return callStringResult(mod, "lcinterp_probe_init_and_synch"); }, + + probeIndexer() { + return callStringResult(mod, "lcinterp_probe_indexer"); + }, }; } diff --git a/wasm-port/tests/browser/interp_smoke.html b/wasm-port/tests/browser/interp_smoke.html index 815b65b..30dc46a 100644 --- a/wasm-port/tests/browser/interp_smoke.html +++ b/wasm-port/tests/browser/interp_smoke.html @@ -156,6 +156,22 @@ ].join("\n"), ); + verifyExpectedOutput( + "probe_indexer", + interp.probeIndexer(), + [ + "execute=0", + "post_a=90", + "canon_event=SET_MOTION_CONTROL_MODE mode=2 tolerance=0", + "canon_event=UNLOCK_ROTARY line=1 joint=0", + "canon_event=STRAIGHT_TRAVERSE line=1 x=0 y=0 z=0 a=90 b=0 c=0 u=0 v=0 w=0", + "canon_event=LOCK_ROTARY line=1 joint=0", + "canon_event=SET_MOTION_CONTROL_MODE mode=1 tolerance=0", + "canon_event=SET_NAIVECAM_TOLERANCE tolerance=0", + "canon_event=UPDATE_TAG line=1", + ].join("\n"), + ); + for (const fixtureName of INTERP_BROWSER_MDI_FIXTURES) { const programText = await fetchText(`../fixtures/gcode/${fixtureName}.ngc`); const expectedText = await fetchText(`../fixtures/canon/${fixtureName}.events`); diff --git a/wasm-port/tests/wasm/node/verify_interp_wasm.mjs b/wasm-port/tests/wasm/node/verify_interp_wasm.mjs index d3d7a41..c2d762c 100644 --- a/wasm-port/tests/wasm/node/verify_interp_wasm.mjs +++ b/wasm-port/tests/wasm/node/verify_interp_wasm.mjs @@ -114,6 +114,22 @@ verifyExpectedOutput( ].join("\n"), ); +verifyExpectedOutput( + "probe_indexer", + interp.probeIndexer(), + [ + "execute=0", + "post_a=90", + "canon_event=SET_MOTION_CONTROL_MODE mode=2 tolerance=0", + "canon_event=UNLOCK_ROTARY line=1 joint=0", + "canon_event=STRAIGHT_TRAVERSE line=1 x=0 y=0 z=0 a=90 b=0 c=0 u=0 v=0 w=0", + "canon_event=LOCK_ROTARY line=1 joint=0", + "canon_event=SET_MOTION_CONTROL_MODE mode=1 tolerance=0", + "canon_event=SET_NAIVECAM_TOLERANCE tolerance=0", + "canon_event=UPDATE_TAG line=1", + ].join("\n"), +); + for (const fixtureName of INTERP_MDI_FIXTURES) { const programText = readFileSync( resolve(rootDir, `tests/fixtures/gcode/${fixtureName}.ngc`), diff --git a/wasm-port/tools/build_wasm_core.sh b/wasm-port/tools/build_wasm_core.sh index c42a9bf..e872e36 100755 --- a/wasm-port/tools/build_wasm_core.sh +++ b/wasm-port/tools/build_wasm_core.sh @@ -96,5 +96,5 @@ link_wasm_module \ -s STACK_SIZE=2MB \ -s NO_EXIT_RUNTIME=1 \ -s FORCE_FILESYSTEM=1 \ - -s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_run_program_with_ini","_lcinterp_run_file","_lcinterp_run_file_with_ini","_lcinterp_restore_parameters","_lcinterp_save_parameters","_lcinterp_load_tool_table","_lcinterp_load_tool_table_random","_lcinterp_save_tool_table","_lcinterp_probe_init_and_synch","_lcinterp_free_string"]' \ + -s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_run_program_with_ini","_lcinterp_run_file","_lcinterp_run_file_with_ini","_lcinterp_restore_parameters","_lcinterp_save_parameters","_lcinterp_load_tool_table","_lcinterp_load_tool_table_random","_lcinterp_save_tool_table","_lcinterp_probe_init_and_synch","_lcinterp_probe_indexer","_lcinterp_free_string"]' \ -s EXPORTED_RUNTIME_METHODS='["FS","UTF8ToString","stringToUTF8","lengthBytesUTF8"]'