From 3f4eb06b47500e9c29242de83bcac0bdf4eb3652 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Mon, 8 Jun 2026 08:39:26 +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 结论:补齐 LinuxCNC tooldata_common.cc 随机换刀器 load/save 验证边界,WASM/SDK/OPFS 只透传 random-toolchanger 配置;native、WASM、OPFS 和浏览器 smoke 验证已通过。 --- wasm-port/docs/compatibility-validation.md | 16 +++-- wasm-port/docs/drift-report.md | 2 +- wasm-port/docs/source-reuse-map.md | 9 +-- .../linuxcnc_wrap/linuxcnc_interp_wasm.cpp | 35 +++++++--- .../opfs/linuxcnc-machine-session-bridge.js | 1 + .../opfs/linuxcnc-tool-table-bridge.js | 4 +- wasm-port/runtime/sdk/README.md | 6 +- wasm-port/runtime/sdk/src/linuxcnc-interp.js | 7 +- wasm-port/tests/browser/interp_smoke.html | 66 +++++++++++++++++++ .../tests/opfs/node/verify_file_service.mjs | 41 ++++++++++-- .../tests/wasm/node/verify_interp_wasm.mjs | 50 ++++++++++++++ wasm-port/tools/build_wasm_core.sh | 2 +- 12 files changed, 211 insertions(+), 28 deletions(-) diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index fff2915..b2ac3f3 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -243,17 +243,21 @@ restore/save behavior through the exported C ABI, including out-of-order file rejection, missing-file success, required numeric parameter writeback, removal of named-parameter-only lines from saved output, and the `.bak` backup produced by vendored `save_parameters()`. It also covers LinuxCNC tool-table -load/save behavior through vendored `tooldata_common.cc`, with the SDK only -copying text into the Emscripten filesystem and calling the exported C ABI. +load/save behavior through vendored `tooldata_common.cc`, including the +non-random and random-toolchanger `tooldata_init()` branches, with the SDK +only copying text into the Emscripten filesystem and calling the exported C +ABI. 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 output, the Node parameter/tool-table bridges that copy OPFS text through the SDK into vendored LinuxCNC file APIs, the Node machine-session bridge that -groups INI, parameter, and tool-table loading, and a browser interpreter smoke -that uses the same session bridge before saving OPFS-backed parameter and -tool-table text through vendored LinuxCNC file APIs. Full browser coverage, -full SDK coverage, and richer machine-state validation remain future work. +groups INI, parameter, and tool-table loading, the random-toolchanger flag as +a host boundary passed through to the LinuxCNC-backed tool-table ABI, and a +browser interpreter smoke that uses the same session bridge before saving +OPFS-backed parameter and tool-table text through vendored LinuxCNC file APIs. +Full browser coverage, full SDK coverage, and richer machine-state validation +remain future work. ## WASM/Browser Fixture Matrix diff --git a/wasm-port/docs/drift-report.md b/wasm-port/docs/drift-report.md index 2dea950..b9e5e1b 100644 --- a/wasm-port/docs/drift-report.md +++ b/wasm-port/docs/drift-report.md @@ -28,7 +28,7 @@ semantic rewrites: | Canonical output | Canonical calls are captured as test events instead of driving hardware. | | Feed-rate state | `SET_FEED_RATE` updates standalone canonical runtime state so vendored `convert_length_units()` can read it back through `GET_EXTERNAL_FEED_RATE()` during G20/G21 changes. | | Tool-data reload | LinuxCNC `RELOAD_TOOLDATA` is captured as a canonical test event; browser/native tool-table reload plumbing remains a future host/runtime adapter. | -| 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 and exposes load/save through the WASM C ABI. | +| 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` guards 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. | diff --git a/wasm-port/docs/source-reuse-map.md b/wasm-port/docs/source-reuse-map.md index 6fa4a1b..9a80abe 100644 --- a/wasm-port/docs/source-reuse-map.md +++ b/wasm-port/docs/source-reuse-map.md @@ -47,7 +47,7 @@ Current validation is intentionally mechanical: | 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 | | 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` | -| 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, while OPFS remains a host-side persistence boundary | Vendor byte sync, `linuxcnc_tooldata_common_source_probe`, `tests/wasm/node/verify_interp_wasm.sh` 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` | ## Dependency Matrix @@ -75,9 +75,10 @@ Current validation is intentionally mechanical: the current interpreter-core smoke scope. The interpreter SDK is a thin allocation, filesystem, and C ABI wrapper over vendored LinuxCNC execution paths, including parameter-file restore/save through vendored - `rs274ngc_pre.cc` and tool-table load/save through vendored - `tooldata_common.cc`; it does not define G-code, parameter, or tool-table - semantics. Full planner/session SDK coverage remains future work. + `rs274ngc_pre.cc` and non-random/random tool-table load/save through + vendored `tooldata_common.cc`; it does not define G-code, parameter, or + tool-table semantics. Full planner/session SDK coverage remains future + work. - OPFS persistence is connected to the INI panel through the host-side `runtime/opfs/file-service.js` adapter. `runtime/opfs/path-model.js` now defines paths for INI, tool table, parameter file, G-code program, 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 a749ddc..064b219 100644 --- a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_wasm.cpp +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_wasm.cpp @@ -172,6 +172,13 @@ void append_tool_table_state(std::ostringstream &output) if (standalone::get_tool_entry(&tool, 2) == 0) { append_tool_entry(output, "tool_2", tool); } + for (int idx = 0; idx < CANON_POCKETS_MAX; ++idx) { + if (standalone::get_tool_entry(&tool, idx) == 0 && tool.toolno != -1) { + std::ostringstream prefix; + prefix << "tool_pocket_" << idx; + append_tool_entry(output, prefix.str().c_str(), tool); + } + } output << "tool_index_for_tool_2=" << standalone::find_tool_index_for_tool(2) << "\n"; } @@ -196,6 +203,20 @@ void apply_parameter_assignments(Interp &interp, const char *assignments) } } +char *load_tool_table_with_mode(const char *path, bool random_tool_changer) +{ + standalone::reset_tool_adapter(); + tooldata_init(random_tool_changer); + tooldata_set_db(DB_NOTUSED); + + std::ostringstream output; + output << "tooldata_random_toolchanger=" << (random_tool_changer ? 1 : 0) << "\n"; + const int rc = tooldata_load(path); + output << "tooldata_load=" << rc << "\n"; + append_tool_table_state(output); + return copy_result(output.str()); +} + } // namespace extern "C" { @@ -350,15 +371,13 @@ char *lcinterp_save_parameters(const char *path, const char *assignments) EMSCRIPTEN_KEEPALIVE char *lcinterp_load_tool_table(const char *path) { - standalone::reset_tool_adapter(); - tooldata_init(false); - tooldata_set_db(DB_NOTUSED); + return load_tool_table_with_mode(path, false); +} - std::ostringstream output; - const int rc = tooldata_load(path); - output << "tooldata_load=" << rc << "\n"; - append_tool_table_state(output); - return copy_result(output.str()); +EMSCRIPTEN_KEEPALIVE +char *lcinterp_load_tool_table_random(const char *path) +{ + return load_tool_table_with_mode(path, true); } EMSCRIPTEN_KEEPALIVE diff --git a/wasm-port/runtime/opfs/linuxcnc-machine-session-bridge.js b/wasm-port/runtime/opfs/linuxcnc-machine-session-bridge.js index fc876d9..569efef 100644 --- a/wasm-port/runtime/opfs/linuxcnc-machine-session-bridge.js +++ b/wasm-port/runtime/opfs/linuxcnc-machine-session-bridge.js @@ -45,6 +45,7 @@ export async function loadMachineSessionFromOpfs(interp, machineId, options = {} storage: options.storage, opfsPath: paths.toolTableOpfsPath, wasmPath: paths.toolTableWasmPath, + randomToolChanger: options.randomToolChanger === true, }); return { diff --git a/wasm-port/runtime/opfs/linuxcnc-tool-table-bridge.js b/wasm-port/runtime/opfs/linuxcnc-tool-table-bridge.js index 99ccb74..90c83cc 100644 --- a/wasm-port/runtime/opfs/linuxcnc-tool-table-bridge.js +++ b/wasm-port/runtime/opfs/linuxcnc-tool-table-bridge.js @@ -25,7 +25,9 @@ export async function loadMachineToolTableFromOpfs(interp, machineId, options = return { opfsPath, wasmPath, - result: interp.loadToolTable(wasmPath), + result: interp.loadToolTable(wasmPath, { + randomToolChanger: options.randomToolChanger === true, + }), }; } diff --git a/wasm-port/runtime/sdk/README.md b/wasm-port/runtime/sdk/README.md index 15a1370..350ba68 100644 --- a/wasm-port/runtime/sdk/README.md +++ b/wasm-port/runtime/sdk/README.md @@ -40,7 +40,11 @@ vendored LinuxCNC RS274NGC sources and exposes: - `runFileWithIni(path, iniPath)` - `restoreParameters(path)` - `saveParameters(path, values)` -- `loadToolTable(path)` +- `loadToolTable(path, options)` - `saveToolTable(path)` - `writeTextFile(path, text)` - `readTextFile(path)` + +`loadToolTable()` accepts `{ randomToolChanger: true }` to select the +LinuxCNC random-toolchanger branch before calling vendored +`tooldata_load()`. The SDK only forwards that runtime boundary flag. diff --git a/wasm-port/runtime/sdk/src/linuxcnc-interp.js b/wasm-port/runtime/sdk/src/linuxcnc-interp.js index 1844a79..ce1c42d 100644 --- a/wasm-port/runtime/sdk/src/linuxcnc-interp.js +++ b/wasm-port/runtime/sdk/src/linuxcnc-interp.js @@ -85,8 +85,11 @@ export async function createLinuxCncInterpSdk(moduleOptions = {}) { return callStringResult(mod, "lcinterp_save_parameters", path, assignments); }, - loadToolTable(path) { - return callStringResult(mod, "lcinterp_load_tool_table", path); + loadToolTable(path, options = {}) { + const functionName = options.randomToolChanger + ? "lcinterp_load_tool_table_random" + : "lcinterp_load_tool_table"; + return callStringResult(mod, functionName, path); }, saveToolTable(path) { diff --git a/wasm-port/tests/browser/interp_smoke.html b/wasm-port/tests/browser/interp_smoke.html index 831afbe..1d3c0a8 100644 --- a/wasm-port/tests/browser/interp_smoke.html +++ b/wasm-port/tests/browser/interp_smoke.html @@ -235,6 +235,72 @@ ].join("\n"), ); + await saveMachineTextFiles("browser-random-toolchanger", { + ini: "[EMC]\nMACHINE = browser-random-toolchanger\n", + toolTable: [ + "T2 P7 Z3.125 D1.5 I12 J34 Q4 ;browser random finish tool", + "T5 P9 X1 Y2 Z3 ;browser random rough tool", + "", + ].join("\n"), + parameters: [ + "5161 10.5", + "5162 20.25", + "5220 1", + "5221 2.25", + "5399 44", + "", + ].join("\n"), + }); + const loadedRandomSession = await loadMachineSessionFromOpfs( + interp, + "browser-random-toolchanger", + { + iniWasmPath: "/work/browser-random-machine.ini", + parameterWasmPath: "/work/browser-random-linuxcnc.var", + toolTableWasmPath: "/work/browser-random-tool.tbl", + randomToolChanger: true, + }, + ); + verifyExpectedOutput( + "opfs_load_random_tool_table", + loadedRandomSession.toolTable.result, + [ + "tooldata_random_toolchanger=1", + "tooldata_load=0", + "tool_pocket_7.toolno=2", + "tool_pocket_7.pocketno=7", + "tool_pocket_7.z=3.125", + "tool_pocket_7.diameter=1.5", + "tool_pocket_7.frontangle=12", + "tool_pocket_7.backangle=34", + "tool_pocket_7.orientation=4", + "tool_pocket_7.comment=browser random finish tool", + "tool_pocket_9.toolno=5", + "tool_pocket_9.pocketno=9", + "tool_index_for_tool_2=7", + ].join("\n"), + ); + await saveMachineToolTableToOpfs( + interp, + "browser-random-toolchanger", + { wasmPath: "/work/browser-random-tool.tbl" }, + ); + const savedRandomMachineFiles = await loadMachineTextFiles( + "browser-random-toolchanger", + ); + verifyExpectedOutput( + "opfs_saved_random_tool_table_text", + savedRandomMachineFiles.toolTable, + [ + "T2", + "P7", + ";browser random finish tool", + "T5", + "P9", + ";browser random rough tool", + ].join("\n"), + ); + status.textContent = "browser_interp_smoke=ok"; } catch (error) { status.textContent = `browser_interp_smoke=fail ${error.stack || error.message}`; diff --git a/wasm-port/tests/opfs/node/verify_file_service.mjs b/wasm-port/tests/opfs/node/verify_file_service.mjs index 003a32e..24142e7 100644 --- a/wasm-port/tests/opfs/node/verify_file_service.mjs +++ b/wasm-port/tests/opfs/node/verify_file_service.mjs @@ -183,8 +183,13 @@ const bridgeInterp = { bridgeFiles.set(`${path}.bak`, "5161 0.0\n5162 0.0\n"); return `save_parameters=0\nsave_path=${path}\nvalues=${Object.keys(values).join(",")}\n`; }, - loadToolTable(path) { - return `tooldata_load=0\nload_tool_path=${path}\n`; + loadToolTable(path, options = {}) { + return [ + "tooldata_load=0", + `load_tool_path=${path}`, + `random_toolchanger=${options.randomToolChanger === true ? 1 : 0}`, + "", + ].join("\n"); }, saveToolTable(path) { bridgeFiles.set(path, "T2 P2 Z+1.250000 D+0.250000 ;loaded by LinuxCNC\n"); @@ -230,10 +235,22 @@ const loadedToolTable = await loadMachineToolTableFromOpfs( assert.deepEqual(loadedToolTable, { opfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl", wasmPath: "/work/bridge-tool.tbl", - result: "tooldata_load=0\nload_tool_path=/work/bridge-tool.tbl\n", + result: "tooldata_load=0\nload_tool_path=/work/bridge-tool.tbl\nrandom_toolchanger=0\n", }); assert.equal(bridgeFiles.get("/work/bridge-tool.tbl"), "T0 P0 ; no tool\nT2 P2 Z1.25 D0.25\n"); +const loadedRandomToolTable = await loadMachineToolTableFromOpfs( + bridgeInterp, + "xyzab-tdr", + { storage, wasmPath: "/work/random-bridge-tool.tbl", randomToolChanger: true }, +); +assert.deepEqual(loadedRandomToolTable, { + opfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl", + wasmPath: "/work/random-bridge-tool.tbl", + result: + "tooldata_load=0\nload_tool_path=/work/random-bridge-tool.tbl\nrandom_toolchanger=1\n", +}); + const savedToolTable = await saveMachineToolTableToOpfs( bridgeInterp, "xyzab-tdr", @@ -271,7 +288,7 @@ assert.deepEqual(loadedSession, { toolTable: { opfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl", wasmPath: "/work/session-tool.tbl", - result: "tooldata_load=0\nload_tool_path=/work/session-tool.tbl\n", + result: "tooldata_load=0\nload_tool_path=/work/session-tool.tbl\nrandom_toolchanger=0\n", }, }); assert.equal(bridgeFiles.get("/work/session.ini"), "[EMC]\nMACHINE = xyzab-tdr\n"); @@ -281,6 +298,22 @@ assert.equal( "T2 P2 Z+1.250000 D+0.250000 ;loaded by LinuxCNC\n", ); +const loadedRandomSession = await loadMachineSessionFromOpfs( + bridgeInterp, + "xyzab-tdr", + { + storage, + iniWasmPath: "/work/random-session.ini", + parameterWasmPath: "/work/random-session.var", + toolTableWasmPath: "/work/random-session-tool.tbl", + randomToolChanger: true, + }, +); +assert.equal( + loadedRandomSession.toolTable.result, + "tooldata_load=0\nload_tool_path=/work/random-session-tool.tbl\nrandom_toolchanger=1\n", +); + await assert.rejects( () => loadTextFile("linuxcnc/machines/missing.ini", storage), /missing file/, diff --git a/wasm-port/tests/wasm/node/verify_interp_wasm.mjs b/wasm-port/tests/wasm/node/verify_interp_wasm.mjs index 2620716..62b1e8e 100644 --- a/wasm-port/tests/wasm/node/verify_interp_wasm.mjs +++ b/wasm-port/tests/wasm/node/verify_interp_wasm.mjs @@ -256,4 +256,54 @@ assert.equal(savedToolTable.includes(";finish tool"), true); assert.equal(savedToolTable.includes("T5"), true); assert.equal(savedToolTable.includes("P9"), true); assert.equal(savedToolTable.includes(";rough tool"), true); + +const randomToolTablePath = "/work/random-tool.tbl"; +interp.writeTextFile( + randomToolTablePath, + [ + "T2 P7 Z3.125 D1.5 I12 J34 Q4 ;random finish tool", + "T5 P9 X1 Y2 Z3 ;random rough tool", + "", + ].join("\n"), +); +verifyExpectedOutput( + "load_tool_table_random", + interp.loadToolTable(randomToolTablePath, { randomToolChanger: true }), + [ + "tooldata_random_toolchanger=1", + "tooldata_load=0", + "tooldata_last_index=9", + "tool_1.toolno=-1", + "tool_2.toolno=-1", + "tool_pocket_7.toolno=2", + "tool_pocket_7.pocketno=7", + "tool_pocket_7.z=3.125", + "tool_pocket_7.diameter=1.5", + "tool_pocket_7.frontangle=12", + "tool_pocket_7.backangle=34", + "tool_pocket_7.orientation=4", + "tool_pocket_7.comment=random finish tool", + "tool_pocket_9.toolno=5", + "tool_pocket_9.pocketno=9", + "tool_pocket_9.z=3", + "tool_pocket_9.comment=random rough tool", + "tool_index_for_tool_2=7", + ].join("\n"), +); +verifyExpectedOutput( + "save_tool_table_random", + interp.saveToolTable(randomToolTablePath), + [ + "tooldata_save=0", + "tool_pocket_7.toolno=2", + "tool_pocket_9.toolno=5", + ].join("\n"), +); +const savedRandomToolTable = interp.readTextFile(randomToolTablePath); +assert.equal(savedRandomToolTable.includes("T2"), true); +assert.equal(savedRandomToolTable.includes("P7"), true); +assert.equal(savedRandomToolTable.includes(";random finish tool"), true); +assert.equal(savedRandomToolTable.includes("T5"), true); +assert.equal(savedRandomToolTable.includes("P9"), true); +assert.equal(savedRandomToolTable.includes(";random rough tool"), true); 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 382d6e0..a41a545 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_save_tool_table","_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_free_string"]' \ -s EXPORTED_RUNTIME_METHODS='["FS","UTF8ToString","stringToUTF8","lengthBytesUTF8"]'