按规划继续工作

结论:新增 rotary indexer 的 WASM 和浏览器验证,继续通过 vendored LinuxCNC 解释器路径覆盖运行边界。
This commit is contained in:
2026-06-08 11:25:59 +08:00
parent ec3d784f2b
commit d2d0ce8bc1
8 changed files with 85 additions and 5 deletions

View File

@@ -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 `Interp::synch()` through the WASM C ABI to validate the initialization
canonical boundary, metric/inch machine-unit status edge, and current/selected canonical boundary, metric/inch machine-unit status edge, and current/selected
tool slot status synchronization already covered by the native init harness. 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 It also writes selected G-code fixtures into the
Emscripten filesystem through the SDK and runs them through LinuxCNC Emscripten filesystem through the SDK and runs them through LinuxCNC
`Interp::open()`, `Interp::read()`, and `Interp::execute()` to validate the `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 error text plus absent canonical motion output. It also validates vendored
`Interp::init()` and `Interp::synch()` through the same SDK/C ABI path, `Interp::init()` and `Interp::synch()` through the same SDK/C ABI path,
including initialization canonical events, metric/inch machine units, and tool 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 storage plus the interpreter SDK to restore and save a LinuxCNC parameter file
through vendored `Interp::restore_parameters()` and `Interp::save_parameters()`, through vendored `Interp::restore_parameters()` and `Interp::save_parameters()`,
and directly checks missing-file success plus out-of-order parameter-file 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 Node WASM and browser interpreter smokes now validate the exported
`Interp::init()`/`Interp::synch()` probe for initialization canonical events, `Interp::init()`/`Interp::synch()` probe for initialization canonical events,
metric/inch `emcStatus` machine-unit conversion, and current/selected tool 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 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, 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 the raw canonical-event display fed directly by LinuxCNC interpreter WASM

View File

@@ -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 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. | | 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. | | 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. | | 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. | | 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. | | Interpreter reset | LinuxCNC `ON_RESET` is captured as a canonical test event on the vendored interpreter reset/file-open path. |

View File

@@ -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 | | 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 | | 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 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 | | 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 | | 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` | | 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` |

View File

@@ -92,6 +92,26 @@ void initialize_minimal_interp(Interp &interp)
std::memcpy(interp._readers, Interp::default_readers, sizeof(Interp::default_readers)); 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<std::string> split_program_lines(const char *program_text) std::vector<std::string> split_program_lines(const char *program_text)
{ {
std::vector<std::string> lines; std::vector<std::string> lines;
@@ -447,6 +467,24 @@ char *lcinterp_probe_init_and_synch()
return copy_result(output.str()); 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 EMSCRIPTEN_KEEPALIVE
void lcinterp_free_string(char *value) void lcinterp_free_string(char *value)
{ {

View File

@@ -99,5 +99,9 @@ export async function createLinuxCncInterpSdk(moduleOptions = {}) {
probeInitAndSynch() { probeInitAndSynch() {
return callStringResult(mod, "lcinterp_probe_init_and_synch"); return callStringResult(mod, "lcinterp_probe_init_and_synch");
}, },
probeIndexer() {
return callStringResult(mod, "lcinterp_probe_indexer");
},
}; };
} }

View File

@@ -156,6 +156,22 @@
].join("\n"), ].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) { for (const fixtureName of INTERP_BROWSER_MDI_FIXTURES) {
const programText = await fetchText(`../fixtures/gcode/${fixtureName}.ngc`); const programText = await fetchText(`../fixtures/gcode/${fixtureName}.ngc`);
const expectedText = await fetchText(`../fixtures/canon/${fixtureName}.events`); const expectedText = await fetchText(`../fixtures/canon/${fixtureName}.events`);

View File

@@ -114,6 +114,22 @@ verifyExpectedOutput(
].join("\n"), ].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) { for (const fixtureName of INTERP_MDI_FIXTURES) {
const programText = readFileSync( const programText = readFileSync(
resolve(rootDir, `tests/fixtures/gcode/${fixtureName}.ngc`), resolve(rootDir, `tests/fixtures/gcode/${fixtureName}.ngc`),

View File

@@ -96,5 +96,5 @@ link_wasm_module \
-s STACK_SIZE=2MB \ -s STACK_SIZE=2MB \
-s NO_EXIT_RUNTIME=1 \ -s NO_EXIT_RUNTIME=1 \
-s FORCE_FILESYSTEM=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"]' -s EXPORTED_RUNTIME_METHODS='["FS","UTF8ToString","stringToUTF8","lengthBytesUTF8"]'