3.8 KiB
11 — Re-enable the SPICE simulator
DONE (2026-07-17), as a worker service rather than a static link — see docs/features/ngspice-split/. The analysis below predates the implementation; the "static link into eeschema.wasm" recipe it recommends was superseded by the occ_service-style split (crash isolation, lazy loading, and an in-service solution for XSPICE's .cm dlopen via a static code-model registry). The ~25-line
init_dll()edit happened as predicted;KICAD_SPICE=OFFand the model-data stubs are gone.
Current state (more nuanced than "KICAD_SPICE=OFF")
-DKICAD_SPICE=OFFinbuild-kicad-target.shis vestigial — there's no such option in this KiCad (onlyKICAD_SPICE_QA). The simulator is always built; alleeschema/sim/sources compile for wasm.- The dep is satisfied by a header-only stub:
wasm/cmake/Findngspice.cmakepoints includes atwasm/stubs/ngspice/sharedspice.hwith an empty link line. - Runtime kill-point:
SIMULATOR_FRAMEctor →NGSPICEinit →ngspice.cpp:474m_dll.Load(wxDynamicLibrary::CanonicalizeName("ngspice"))fails (nodlopenwithout-sMAIN_MODULE) → throws → caught ateeschema/eeschema.cpp:208-221→ simulator silently returnsnullptr.
The groundwork already here
- A wasm ngspice build script exists:
scripts/deps/build-ngspice.sh(ngspice 45.2, static, cider + xspice, pthread; opt-in viabuild-all-deps.sh --with-ngspice). - A known size landmine is already handled: the 4 giant model-data initializers
(
sim_model_ngspice_data_{bsim4,b3soi,b4soi,hsim}.cpp) exceeded the JS engines' "too many locals" validation limit and are stubbed (eeschema/CMakeLists.txt:292-305+wasm/stubs/eeschema_ngspice_data_stubs.cpp).
Why static linking works
KiCad resolves 9–11 ngspice function pointers via GetSymbol (ngspice.cpp:503-517) and
registers C callbacks (cbSendChar, cbBGThreadRunning, … ngspice.cpp:519). With a
statically linked libngspice.a, those become plain function pointers and direct symbol
references — no dlopen needed. Threading: KiCad drives sims via bg_run/bg_halt
(ngspice.cpp:335,342) using ngspice's internal pthread; the wasm build is already
full-pthread, and native code already assumes callbacks fire on the bg thread — same model.
Precedent (all static, OpenMP off, shared-lib mode unsupported under emscripten): wokwi/ngspice-wasm, EEcircuit (eelab-dev), danchitnis/ngspice, plus upstream ngspice WASM patches (#96, #99).
Recipe
- Verify
scripts/deps/build-ngspice.shcompletes for 45.2. - Make
wasm/cmake/Findngspice.cmakereturn the real sysroot lib + headers when present (fall back to the stub otherwise). - The one unavoidable upstream-file edit: a
#ifdef __EMSCRIPTEN__branch ineeschema/sim/ngspice.cppinit_dll()that binds them_ngSpice_*pointers directly to the statically-linked symbols instead of viawxDynamicLibrary(~25 lines, one block). - Ship
spinit/.cmcodemodels into MEMFS if xspice device models are wanted. - Retest the model-data stubs — the "too many locals" failure predates the now-default
post-asyncify
wasm-opt -O2pass that solved the same engine-limit family elsewhere (see thechrome-asyncify-rewind-crashmemory). If it's resolved, restore the 4 real model-data files; if not, the simulator works minus BSIM4/SOI/HSIM device models.
Residual risks (verify at runtime)
SIMULATOR_FRAME's plot widget behavior under wxUniversal (untested).- Asyncify vs. the ngspice bg-thread interplay during a running simulation (see the async dossier for the suspension model).
Diff impact: ~25 lines in one upstream file (ngspice.cpp) + a Findngspice.cmake change
(wasm layer). Everything else is the dep build and FS assets.