From fd680f75153d3b4e035abbe6b7a26a4fcda803f5 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Mon, 8 Jun 2026 05:21:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=A7=A3=E9=87=8A=E5=99=A8WA?= =?UTF-8?q?SM=E6=96=87=E4=BB=B6=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论:解释器核心WASM新增基于LinuxCNC Interp::open/read/execute的文件运行入口,并覆盖file_open_reset、percent_file_finish和oword_subroutine夹具。 --- wasm-port/docs/compatibility-validation.md | 15 ++-- .../linuxcnc_wrap/linuxcnc_interp_wasm.cpp | 85 +++++++++++++++++-- .../tests/wasm/node/verify_interp_wasm.mjs | 45 ++++++++++ wasm-port/tools/build_wasm_core.sh | 5 +- 4 files changed, 135 insertions(+), 15 deletions(-) diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index 94fa335..b8f38ca 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -76,7 +76,10 @@ vendored LinuxCNC interpreter source set used by the native minimal interpreter harness. It loads the module in Node, runs the first WASM interpreter fixture group through `Interp::execute()`, and compares emitted canonical events plus required LinuxCNC `_setup` state readback with the -matching files in `tests/fixtures/canon/`. +matching files in `tests/fixtures/canon/`. It also writes selected G-code +fixtures into the Emscripten filesystem and runs them through LinuxCNC +`Interp::open()`, `Interp::read()`, and `Interp::execute()` to validate the +file execution path. The OPFS host-boundary script validates the JavaScript file-service adapter with a Node mock of the browser File System Access handles. It covers nested @@ -144,7 +147,7 @@ The validation fails if: | Harness | Purpose | | --- | --- | | `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded through the JS SDK in Node, and queried through the exported C ABI. | -| `tests/wasm/node/verify_interp_wasm.sh` | Validates the initial interpreter-core WASM module can be built from vendored LinuxCNC interpreter source, run the first fixture group through `Interp::execute()`, and match the native canonical event plus required state readback fixtures. | +| `tests/wasm/node/verify_interp_wasm.sh` | Validates the initial interpreter-core WASM module can be built from vendored LinuxCNC interpreter source, run the first fixture group through `Interp::execute()` and selected file fixtures through `Interp::open()`/`read()`/`execute()`, and match the native canonical event plus required state readback fixtures. | | `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store, machine file store, and G-code text store used by the browser INI panel without moving file persistence into the WASM core. | | `tests/browser/verify_ini_panel_browser.sh` | Validates the INI SDK, WASM module loading, OPFS text-file round trip, generic session snapshot round trip, machine file text round trip, and G-code text round trip in a real browser runtime. | | `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, OPFS, and browser smoke validation with shared WASM builds. | @@ -197,9 +200,11 @@ for `minimal_linear`, `arc_semantics`, `length_units`, `modal_incremental`, `feed_control_modes`, `position_params`, `probe_semantics`, `spindle_orient`, `comment_logging`, and `numbered_params`, plus the `g1_zero_feed`, `arc_radius_mismatch`, `arc_zero_radius`, and `g53_incremental` negative -fixtures. OPFS validation is limited to the JavaScript host-boundary adapter -plus the INI browser smoke harness. Full browser coverage, full SDK coverage, -and full machine-session validation remain future work. +fixtures. The WASM interpreter file path additionally covers +`file_open_reset`, `percent_file_finish`, and `oword_subroutine`. OPFS +validation is limited to the JavaScript host-boundary adapter plus the INI +browser smoke harness. Full browser coverage, full SDK coverage, and full +machine-session validation remain future work. The current fixture expectations validate standalone behavior against both the vendored LinuxCNC source path and an upstream `rs274` side-by-side baseline for 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 41d6dc0..827f188 100644 --- a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_wasm.cpp +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_wasm.cpp @@ -113,6 +113,19 @@ char *copy_result(const std::string &result) return out; } +void append_events_and_state(std::ostringstream &output, const Interp &interp) +{ + for (const auto &event : standalone::canon_events()) { + output << "canon_event=" << event << "\n"; + } + output << "setup.current_x=" << interp._setup.current_x << "\n"; + output << "setup.current_y=" << interp._setup.current_y << "\n"; + output << "setup.current_z=" << interp._setup.current_z << "\n"; + output << "setup.parameter_5420=" << interp._setup.parameters[5420] << "\n"; + output << "setup.parameter_5421=" << interp._setup.parameters[5421] << "\n"; + output << "setup.parameter_5422=" << interp._setup.parameters[5422] << "\n"; +} + } // namespace extern "C" { @@ -143,15 +156,71 @@ char *lcinterp_run_program(const char *program_text) } } - for (const auto &event : standalone::canon_events()) { - output << "canon_event=" << event << "\n"; + append_events_and_state(output, interp); + return copy_result(output.str()); +} + +EMSCRIPTEN_KEEPALIVE +char *lcinterp_run_file(const char *path) +{ + Interp interp; + standalone::reset_canon_events(); + initialize_minimal_interp(interp); + + std::ostringstream output; + const int open_rc = interp.open(path); + output << "file_open=" << open_rc << "\n"; + if (open_rc != INTERP_OK) { + if (open_rc > INTERP_MIN_ERROR) { + char error_buf[LINELEN] = {0}; + interp.error_text(open_rc, error_buf, sizeof(error_buf)); + output << "file_error_text=" << error_buf << "\n"; + } + append_events_and_state(output, interp); + return copy_result(output.str()); } - output << "setup.current_x=" << interp._setup.current_x << "\n"; - output << "setup.current_y=" << interp._setup.current_y << "\n"; - output << "setup.current_z=" << interp._setup.current_z << "\n"; - output << "setup.parameter_5420=" << interp._setup.parameters[5420] << "\n"; - output << "setup.parameter_5421=" << interp._setup.parameters[5421] << "\n"; - output << "setup.parameter_5422=" << interp._setup.parameters[5422] << "\n"; + + int file_read_count = 0; + int file_execute_count = 0; + while (true) { + const int read_rc = interp.read(); + if (read_rc == INTERP_ENDFILE) { + output << "file_read_eof=" << read_rc << "\n"; + break; + } + + ++file_read_count; + output << "file_read_" << file_read_count << "=" << read_rc << "\n"; + if ((read_rc != INTERP_OK) && (read_rc != INTERP_EXECUTE_FINISH)) { + if (read_rc > INTERP_MIN_ERROR) { + char error_buf[LINELEN] = {0}; + interp.error_text(read_rc, error_buf, sizeof(error_buf)); + output << "file_error_text=" << error_buf << "\n"; + } + break; + } + + const int execute_rc = interp.execute(); + ++file_execute_count; + output << "file_execute_" << file_execute_count << "=" << execute_rc << "\n"; + if (execute_rc > INTERP_MIN_ERROR) { + char error_buf[LINELEN] = {0}; + interp.error_text(execute_rc, error_buf, sizeof(error_buf)); + output << "file_error_text=" << error_buf << "\n"; + } + if ((execute_rc != INTERP_OK) && + (execute_rc != INTERP_EXECUTE_FINISH) && + (execute_rc != INTERP_EXIT)) { + break; + } + if (execute_rc == INTERP_EXIT) { + break; + } + } + + output << "file_read_count=" << file_read_count << "\n"; + output << "file_execute_count=" << file_execute_count << "\n"; + append_events_and_state(output, interp); return copy_result(output.str()); } diff --git a/wasm-port/tests/wasm/node/verify_interp_wasm.mjs b/wasm-port/tests/wasm/node/verify_interp_wasm.mjs index b517a37..c5e2763 100644 --- a/wasm-port/tests/wasm/node/verify_interp_wasm.mjs +++ b/wasm-port/tests/wasm/node/verify_interp_wasm.mjs @@ -27,6 +27,29 @@ function runProgram(mod, programText) { } } +function ensureDir(mod, path) { + try { + mod.FS.mkdir(path); + } catch { + // Directory already exists. + } +} + +function runFile(mod, path) { + const pathPtr = allocCString(mod, path); + let resultPtr = 0; + try { + resultPtr = mod._lcinterp_run_file(pathPtr); + assert.notEqual(resultPtr, 0); + return mod.UTF8ToString(resultPtr); + } finally { + if (resultPtr) { + mod._lcinterp_free_string(resultPtr); + } + mod._free(pathPtr); + } +} + function verifyExpectedOutput(fixtureName, output, expectedText) { const expectedLines = expectedText.split("\n").filter(Boolean); @@ -110,4 +133,26 @@ for (const fixtureName of errorFixtureNames) { verifyExpectedOutput(fixtureName, runProgram(interp, programText), expectedOutput); } + +const fileFixtureNames = [ + "file_open_reset", + "percent_file_finish", + "oword_subroutine", +]; + +ensureDir(interp, "/work"); +for (const fixtureName of fileFixtureNames) { + const programPath = `/work/${fixtureName}.ngc`; + const programText = readFileSync( + resolve(rootDir, `tests/fixtures/gcode/${fixtureName}.ngc`), + "utf8", + ); + const expectedEvents = readFileSync( + resolve(rootDir, `tests/fixtures/canon/${fixtureName}.events`), + "utf8", + ).trimEnd(); + + interp.FS.writeFile(programPath, programText, { encoding: "utf8" }); + verifyExpectedOutput(fixtureName, runFile(interp, programPath), expectedEvents); +} console.log("interp_wasm_node_smoke=ok"); diff --git a/wasm-port/tools/build_wasm_core.sh b/wasm-port/tools/build_wasm_core.sh index 0309f45..3198db0 100755 --- a/wasm-port/tools/build_wasm_core.sh +++ b/wasm-port/tools/build_wasm_core.sh @@ -94,5 +94,6 @@ link_wasm_module \ -s ALLOW_MEMORY_GROWTH=1 \ -s STACK_SIZE=2MB \ -s NO_EXIT_RUNTIME=1 \ - -s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_free_string"]' \ - -s EXPORTED_RUNTIME_METHODS='["UTF8ToString","stringToUTF8","lengthBytesUTF8"]' + -s FORCE_FILESYSTEM=1 \ + -s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_run_file","_lcinterp_free_string"]' \ + -s EXPORTED_RUNTIME_METHODS='["FS","UTF8ToString","stringToUTF8","lengthBytesUTF8"]'