pcbjam/docs/features/fork-cleanup/11-ngspice-simulator.md

66 lines
3.8 KiB
Markdown
Raw Normal View History

2026-06-15 11:32:12 +02:00
# 11 — Re-enable the SPICE simulator
> **DONE (2026-07-17), as a worker service rather than a static link — see
> [docs/features/ngspice-split/](../ngspice-split/README.md).** 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=OFF` and the model-data stubs are gone.
2026-06-15 11:32:12 +02:00
## Current state (more nuanced than "KICAD_SPICE=OFF")
- `-DKICAD_SPICE=OFF` in `build-kicad-target.sh` is **vestigial** — there's no such option in
this KiCad (only `KICAD_SPICE_QA`). The simulator is *always* built; all `eeschema/sim/`
sources compile for wasm.
- The dep is satisfied by a header-only stub: `wasm/cmake/Findngspice.cmake` points includes
at `wasm/stubs/ngspice/sharedspice.h` with an empty link line.
- **Runtime kill-point:** `SIMULATOR_FRAME` ctor → `NGSPICE` init → `ngspice.cpp:474`
`m_dll.Load(wxDynamicLibrary::CanonicalizeName("ngspice"))` fails (no `dlopen` without
`-sMAIN_MODULE`) → throws → caught at `eeschema/eeschema.cpp:208-221` → simulator silently
returns `nullptr`.
## The groundwork already here
- **A wasm ngspice build script exists:** `scripts/deps/build-ngspice.sh` (ngspice 45.2,
static, cider + xspice, pthread; opt-in via `build-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 911 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
1. Verify `scripts/deps/build-ngspice.sh` completes for 45.2.
2. Make `wasm/cmake/Findngspice.cmake` return the **real** sysroot lib + headers when present
(fall back to the stub otherwise).
3. **The one unavoidable upstream-file edit:** a `#ifdef __EMSCRIPTEN__` branch in
`eeschema/sim/ngspice.cpp` `init_dll()` that binds the `m_ngSpice_*` pointers directly to
the statically-linked symbols instead of via `wxDynamicLibrary` (~25 lines, one block).
4. Ship `spinit` / `.cm` codemodels into MEMFS if xspice device models are wanted.
5. **Retest the model-data stubs** — the "too many locals" failure predates the now-default
post-asyncify `wasm-opt -O2` pass that solved the same engine-limit family elsewhere (see
the `chrome-asyncify-rewind-crash` memory). 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](../async/README.md) 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.