Files
wasm-simulator/docs/linuxcnc-porting.md

13 KiB

LinuxCNC to WASM porting steps

This file defines the incremental path for replacing the temporary smoke parser with the LinuxCNC interpreter.

Step 0: Stable simulator ABI

Status: done.

The browser calls only cnc_sim_* functions from core/include/cnc_sim_api.h. This ABI remains stable while the backend changes.

Test:

g++ -std=c++17 -I core/include core/src/cnc_sim_api.cpp core/tests/cnc_sim_api_smoke.cpp -o ./cnc_sim_api_smoke
./cnc_sim_api_smoke

Step 1: Temporary event parser

Status: in progress.

This parser only exists to test the ABI and UI before Emscripten and LinuxCNC are wired in. It is not a production feature implementation. Do not extend it with independently designed G-code behavior; when a behavior needs to change, first locate the LinuxCNC source implementation and either route to that source-backed backend or port the corresponding source logic. It currently handles:

  • multiple G/M words on one line
  • G0, G1, G2, G3
  • G17, G18, G19
  • G90.1, G91.1 arc IJK distance mode
  • G20, G21, G70, G71
  • G7, G8 lathe diameter/radius mode for X-axis input
  • G90, G91
  • G4 P...
  • G38.2, G38.3, G38.4, G38.5
  • F, S, T, M3, M4, M5, M6, M2, M30
  • spindle modes G96, G97
  • IJK and R arcs
  • numbered and named parameter assignment/reference such as #1 = ..., #<name> = ..., X#1, and nested expressions like X[[#<name> + 2] * 3]
  • expression functions ABS[], SQRT[], EXP[], LN[], degree-based SIN[]/COS[]/TAN[]/ASIN[]/ACOS[], LinuxCNC-style ATAN[]/[], FIX[]/FUP[]/ROUND[], and source-linked mod/**/logical/relational operator precedence
  • numeric and named O-word subprograms such as O100 call and O<name> call
  • canned cycles G73, G74, G81, G82, G83, G84, G85, G86, G87, G88, G89 with G80, G98, G99, L
  • coordinate offset events for G10 L2, G10 L20, G54-G59.3, G52, G92, G92.1, G92.2, and G92.3

It is not the production interpreter. Any remaining smoke-only behavior is technical debt to replace with LinuxCNC source-backed code.

Step 2: Canon event sink

Status: started.

Create a C++ file that implements the Canon functions declared by LinuxCNC src/emc/nml_intf/canon.hh.

High-priority callbacks:

  • INIT_CANON
  • USE_LENGTH_UNITS
  • SELECT_PLANE
  • SET_FEED_RATE
  • SET_SPINDLE_SPEED
  • SELECT_TOOL
  • CHANGE_TOOL
  • STRAIGHT_TRAVERSE
  • STRAIGHT_FEED
  • ARC_FEED
  • DWELL
  • PROGRAM_END
  • FINISH

The sink will translate these callbacks to CncSimEvent.

Current bridge files:

  • core/src/canon_event_sink.h
  • core/src/canon_event_sink.cpp
  • core/src/linuxcnc_canon_bridge.h
  • core/src/linuxcnc_canon_bridge.cpp

The LinuxCNC bridge is optional and not built by default:

cmake -S core -B build/native-linuxcnc \
  -DCNC_SIM_ENABLE_LINUXCNC_BRIDGE=ON \
  -DCNC_SIM_LINUXCNC_ROOT=/path/to/linuxcnc

This bridge is intentionally thin. The production parser still needs the rs274ngc interpreter linked on top of it.

Bridge smoke test:

./test-linuxcnc-bridge-native.sh

Set LINUXCNC_ROOT=/path/to/linuxcnc if the LinuxCNC source tree is not next to wasm-simulator.

Step 3a: Native librs274 runner

Status: started.

Before porting rs274ngc sources to wasm, use the already-built native LinuxCNC librs274 to validate the bridge and event schema:

./test-linuxcnc-rs274-native.sh

This builds core/tools/linuxcnc_rs274_dump.cpp, links LinuxCNC librs274, and writes:

  • run-local output files under a per-run temporary build_dir/outputs/
  • example files: cnc_sim_linuxcnc_basic_motion.json, cnc_sim_linuxcnc_basic_mill.json

This is a native-only stepping stone. Once stable, the same event sink is used by the wasm build.

T... M6 now works in the native runner after initializing LinuxCNC mmap tooldata with a minimal tool table.

Step 3b: API-level native librs274 backend

Status: started.

The public cnc_sim_api defaults to the LinuxCNC RS274 backend and can still select backends through config JSON:

{"backend":"linuxcnc-rs274"}

API-level native smoke:

./test-linuxcnc-api-native.sh

Step 4a: Source compilation map

Status: started.

Before replacing librs274 with source-level compilation, keep the source manifest and syntax probe green:

./test-linuxcnc-source-syntax.sh
./test-linuxcnc-source-objects.sh

The manifest is:

  • linuxcnc-rs274-source-files.txt
  • docs/linuxcnc-rs274-source-map.md

The first compile condition discovered is that LinuxCNC user-space source probes need -DULAPI.

Step 3: Native LinuxCNC interpreter comparison

Build a native adapter that links:

  • src/emc/rs274ngc
  • src/emc/nml_intf
  • the Canon event sink

Then run the same G-code corpus through both the temporary parser and LinuxCNC-backed parser. Numeric differences are expected around arc canonicalization; they must be recorded and bounded.

Step 4: Remove browser-hostile dependencies

LinuxCNC interpreter sources currently involve Python/Boost.Python remap paths. For the first WASM target:

  • disable Python remap
  • disable dynamic module loading
  • route LinuxCNC parameter-file reads and writes through an OPFS-backed wasm workspace, preserving rs274ngc.var and .bak persistence in browser runs as defined by src/emc/rs274ngc/interp_internal.hh and exercised by Interp::restore_parameters/Interp::save_parameters in src/emc/rs274ngc/rs274ngc_pre.cc
  • provide browser-safe tool table and INI/config loading through JSON or OPFS-backed files

Step 5: Emscripten build

Generate:

  • web/public/cnc_sim.js
  • web/public/cnc_sim.wasm

Command:

./build-wasm.sh

Step 6: Controller dialects

Fanuc and Siemens support should remain outside the LinuxCNC core as preprocessors/adapters. They normalize controller-specific constructs into the internal event/interpreter input layer.

Functional parity matrix

This matrix tracks LinuxCNC feature coverage for the web/WASM simulator. A feature is not considered covered until it has a native regression in at least the librs274 runner and the source-link runner.

Area Status Regression
Basic modal motion G0/G1/G2/G3 covered, including G18/G19 arc plane mapping and G90.1/G91.1 IJK modes tests/gcode/linuxcnc_basic_motion.ngc, tests/gcode/basic_mill.ngc, tests/gcode/linuxcnc_arc_planes.ngc, tests/gcode/linuxcnc_arc_distance_modes.ngc
Predefined position moves G28/G28.1, G30/G30.1 covered for stored position, waypoint, selected-axis return, and all-axis return tests/gcode/linuxcnc_predefined_positions.ngc
Machine-coordinate move G53 covered for explicit/modal G0, G1, and incremental-mode rejection in smoke parser tests/gcode/linuxcnc_machine_coordinates.ngc
Tool select/change T... M6 covered with minimal native tooldata tests/gcode/basic_mill.ngc
Tool length offset G43/G43.1/G43.2/G49 covered with nonzero tool-table offset, dynamic replacement, additive axis offsets, G43.2 H..., and clear through LinuxCNC native/source backends tests/gcode/linuxcnc_tool_length.ngc, tests/gcode/linuxcnc_dynamic_tool_length.ngc
RTCP controls G43.4/G43.5/G49 covered as simulator-owned control lines tests/gcode/linuxcnc_rtcp_controls.ngc
Kinematics switch M428/M429/M430 covered as simulator-owned control lines tests/gcode/linuxcnc_rtcp_controls.ngc
Spindle, coolant, program stops, and current tool number M3/M4/M5, M7/M8/M9, M0/M1/M60/M30, M61 Q... covered through LinuxCNC native/source backends tests/gcode/linuxcnc_spindle_direction.ngc, tests/gcode/linuxcnc_coolant.ngc, tests/gcode/linuxcnc_program_stops.ngc, tests/gcode/linuxcnc_tool_number.ngc
Spindle speed mode G96/G97 covered with Canon SET_SPINDLE_MODE preserved as mode state events, including G96 D... max-RPM value tests/gcode/linuxcnc_spindle_modes.ngc
Spindle orientation M19 R... P... Q... covered with Canon ORIENT_SPINDLE and WAIT_SPINDLE_ORIENT_COMPLETE preserved as temporary mode state events tests/gcode/linuxcnc_spindle_orient.ngc
Spindle-synchronized feed and threading G33/G33.1/G76 covered through smoke, LinuxCNC native, and source-linked paths with Canon START_SPEED_FEED_SYNCH/STOP_SPEED_FEED_SYNCH preserved, rigid-tap line numbers normalized, and G76 multi-pass threading smoke-covered tests/gcode/linuxcnc_threading_sync.ngc, tests/gcode/linuxcnc_threading_cycle.ngc, smoke API regression
Lathe diameter/radius mode G7/G8 covered through LinuxCNC native/source backends for same-block X scaling, G76 I/J/K threading geometry interaction, and #<_lathe_diameter_mode>/#<_lathe_radius_mode> readonly named-parameter state tests/gcode/linuxcnc_lathe_diameter_mode.ngc, tests/gcode/linuxcnc_lathe_diameter_g76.ngc
Digital/analog I/O M62-M68 covered with Canon digital output, analog output, and input wait callbacks preserved as temporary state events tests/gcode/linuxcnc_io_controls.ngc
Override controls M48-M53 covered with Canon feed override, spindle speed override, adaptive feed, and feed hold callbacks preserved as temporary state events tests/gcode/linuxcnc_override_controls.ngc
Modal state save/restore M70-M73 covered for main-program save/invalidate/restore plus O-word subroutine autorestore in smoke parser, with native/source regressions checking restored distance mode, units, and XY plane motion tests/gcode/linuxcnc_modal_state.ngc, tests/gcode/linuxcnc_modal_autorestore.ngc
LinuxCNC read-only named parameters common modal/unit/feed/spindle reads are covered through LinuxCNC native/source backends, with additional ccomp/spindle-mode readonly reads source-linked tests/gcode/linuxcnc_readonly_named_parameters.ngc, tests/gcode/linuxcnc_readonly_named_parameters_extra.ngc, tests/gcode/linuxcnc_lathe_diameter_mode.ngc
Feed and motion control modes G93/G94/G95, G61/G61.1/G64 covered through LinuxCNC native/source backends tests/gcode/linuxcnc_feed_modes.ngc, tests/gcode/linuxcnc_motion_modes.ngc
Canned cycle G81/G80 covered for drilling expand-to-canon path, including G18/G19 plane-specific depth-axis mapping for representative drilling/boring cycles tests/gcode/linuxcnc_canned_cycle.ngc, tests/gcode/linuxcnc_canned_cycle_planes.ngc
Coordinate offset Canon events G10 L2, G10 L20, G10 P0 active-system targeting, G54-G59.3 selection, G52, G92/G92.1/G92.2/G92.3 covered through LinuxCNC native/source backends, including all nine G5X indices tests/gcode/linuxcnc_coordinate_offsets.ngc, tests/gcode/linuxcnc_coordinate_l20.ngc, tests/gcode/linuxcnc_coordinate_p0.ngc, tests/gcode/linuxcnc_coordinate_select_all.ngc, tests/gcode/linuxcnc_g92_restore.ngc, tests/gcode/linuxcnc_g52_offset.ngc
Cutter compensation covered for tool-table G41/G42 D... and explicit-radius G41.1/G42.1/G40 through LinuxCNC native/source backends tests/gcode/linuxcnc_cutter_comp.ngc
Probe moves G38.2/G38.3/G38.4/G38.5 covered as distinct probe events with LinuxCNC probe_type preserved through smoke, native, and source-linked paths tests/gcode/linuxcnc_probe_no_error.ngc
O-word subroutines, calls, and control flow covered for numeric and named O... sub/call/return/endsub plus if/elseif/else, while, repeat, do/while, break, and continue through LinuxCNC file mode; smoke parser also covers named O<name> sub/call/return/endsub and loop-control forms tests/gcode/smoke_oword_subprogram.ngc, tests/gcode/linuxcnc_named_oword_subprogram.ngc, tests/gcode/linuxcnc_oword_control_flow.ngc
Parameter expressions and operators covered for parameter references, source-linked mod, **, logical, and relational operator precedence, plus LinuxCNC divide-by-zero, negative-power, malformed ATAN[]/[], and unary operation error paths from interp_read.cc/interp_execute.cc tests/gcode/linuxcnc_parameter_expression_assignment.ngc, tests/gcode/linuxcnc_expression_operators.ngc, tests/gcode/linuxcnc_divide_by_zero_error.ngc, tests/gcode/linuxcnc_negative_power_error.ngc, tests/gcode/linuxcnc_atan_missing_slash_error.ngc, tests/gcode/linuxcnc_atan_missing_bracket_error.ngc, tests/gcode/linuxcnc_unary_missing_bracket_error.ngc, tests/gcode/linuxcnc_unary_unknown_word_error.ngc
Broader canned cycles G73, G74, G82-G89 partially covered: G73, G74, G82, G83, G84, G85, G86, G87, G88, G89; smoke parser maps supported cycles across G17/G18/G19, including LinuxCNC's special non-XY G74/G84 tapping argument order and G87 plane-specific I/J/K mapping; native/source regressions now check non-XY G74/G84/G86/G87/G88 sequences tests/gcode/linuxcnc_canned_cycles_extended.ngc, tests/gcode/linuxcnc_canned_cycle_planes.ngc, smoke API regression
Full source-level wasm build covered for the current wasm-safe RS274 source set, with remaining work focused on broadening browser-hostile dependency replacement and feature coverage ./build-wasm.sh, test-linuxcnc-wasm-runtime-link.sh, test-web-wasm-node-smoke.sh, test-web-wasm-browser-smoke.sh