magic/npm
Enno Schnackenberg 6b3b2f5162 Add TCL-embedded WASM build variant alongside the existing non-TCL build
Bump VERSION to 8.3.645.

magic.wasm can now be built as two variants packaged in the same npm
release: notcl/ (legacy, magic's own parser) and tcl/ (intubun/tcl 9.x
statically linked, commands evaluated by Tcl_EvalEx). The TCL fork is
pinned via npm/tcl.ref and cloned/built by magic itself — the tcl/
checkout is treated as read-only and built out-of-source into
magic/build-tcl-wasm/.

Configure layer:
- New usingTk variable decoupled from usingTcl in scripts/configure.in
  + scripts/configure, so --with-tcl --without-tk is finally a valid
  combination. Native Linux Tcl+Tk builds keep their previous behaviour
  (both flags default to enabled).
- When usingTk is empty, configure passes -DMAGIC_NO_TK so the small
  number of remaining Tk callsites in tcltk/tclmagic.{h,c} compile out,
  and TKCOMMON_SRCS / USE_TK_STUBS are omitted from the link.

WASM build orchestration:
- toolchains/emscripten/build-tcl-wasm.sh builds libtcl9.x.a + libtclstub.a
  + tclConfig.sh out-of-source from a pristine intubun/tcl checkout.
- npm/build.sh grew a --variant=<tcl|notcl|both> flag and writes its
  outputs into npm/tcl/ and npm/notcl/. It also clones intubun/tcl with
  autocrlf=false at the SHA pinned by npm/tcl.ref.
- magic/Makefile (WASM block only): magicWasm.o is now compiled with
  DFLAGS_NOSTUB so Tcl_CreateInterp resolves to libtcl9.x directly
  before tclStubsPtr is set. magic.js link pulls in LIB_SPECS_NOSTUB
  and -ltclstub. After rules.mak include, magic: is a phony alias for
  magic.js so the generic ${MODULE} recipe doesn't fight it.
- toolchains/emscripten/defs.mak: add -sUSE_ZLIB=1 (libtcl9 references
  zlib), replace -sSTACK_SIZE=N with -Wl,-z,stack-size=N (emcc >=5
  rejects the setting form).
- magic/magicWasm.c bootstraps the embedded interp under MAGIC_WRAPPER
  (Tcl_CreateInterp -> Tcl_Init -> Tclmagic_Init) and routes
  run_command through Tcl_EvalEx.
- magic/magicTop.c: gate MagicVersion/Revision/CompileTime on
  !MAGIC_WRAPPER so they don't collide with the copies in
  tcltk/tclmagic.c when both objects land in the same wasm binary.

npm package:
- Subpath exports: ".", "./tcl", "./notcl". Default import keeps the
  pre-existing non-TCL behaviour for backward compatibility.
- examples/smoke-tcl.mjs exercises the TCL variant.

CI:
- main-wasm.yml clones intubun/tcl at the pinned ref, builds both
  variants via npm/build.sh --variant=both, runs the existing notcl
  test suite and the new TCL smoke test, and publishes only on a
  v<x.y.z>... git tag. Tag name (minus the leading v) becomes the
  npm version.
2026-05-21 12:42:43 +02:00
..
examples Add TCL-embedded WASM build variant alongside the existing non-TCL build 2026-05-21 12:42:43 +02:00
.gitignore Add TCL-embedded WASM build variant alongside the existing non-TCL build 2026-05-21 12:42:43 +02:00
LICENSE Add npm package and CI workflows 2026-05-11 14:20:47 -04:00
README.md Change npm examples to scmos examples 2026-05-11 14:20:47 -04:00
build.sh Add TCL-embedded WASM build variant alongside the existing non-TCL build 2026-05-21 12:42:43 +02:00
index.d.ts Add npm package and CI workflows 2026-05-11 14:20:47 -04:00
index.js Add TCL-embedded WASM build variant alongside the existing non-TCL build 2026-05-21 12:42:43 +02:00
notcl.js Add TCL-embedded WASM build variant alongside the existing non-TCL build 2026-05-21 12:42:43 +02:00
pack.sh Rework WASM CI structure 2026-05-11 14:20:47 -04:00
package.json Add TCL-embedded WASM build variant alongside the existing non-TCL build 2026-05-21 12:42:43 +02:00
tcl.js Add TCL-embedded WASM build variant alongside the existing non-TCL build 2026-05-21 12:42:43 +02:00
tcl.ref Add TCL-embedded WASM build variant alongside the existing non-TCL build 2026-05-21 12:42:43 +02:00

README.md

magic-vlsi-wasm

Magic VLSI layout tool, compiled to WebAssembly as a headless library. Runs in Node.js, browsers, and Web Workers — no X11, no Tk, no native dependencies.

Use it to programmatically read and write .mag, .gds, .cif, .ext, and SPICE netlists; run DRC; extract parasitics — anywhere JavaScript runs.

Install

npm install magic-vlsi-wasm

Requires Node.js 18 or newer.

Quick start

import createMagic from 'magic-vlsi-wasm';

const { runCommand, FS } = await createMagic();

// Drop a layout into Magic's virtual filesystem
FS.mkdirTree('/work');
FS.writeFile('/work/inv.mag', layoutBytes);

// Run Magic commands — scmos is built into the WASM binary, no tech file needed
runCommand('tech load scmos');
runCommand('load /work/inv');
runCommand('gds write /work/inv');

// Read the result back out
const gdsBytes = FS.readFile('/work/inv.gds');

The scmos technology family (scmos, minimum, nmos, ...) is embedded in the WASM binary and available out of the box — those names work without writing any tech file. To use a custom technology, write its .tech file into the VFS at /magic/sys/current/<name>.tech before calling tech load <name>.

API

createMagic(options?): Promise<MagicInstance>

options is forwarded to the underlying Emscripten module. Useful keys:

Key Default Purpose
wasmBinary fetched lazily Pre-fetched ArrayBuffer of magic.wasm (skips a network round-trip in browsers)
print console.log Callback for each stdout line
printErr console.error Callback for each stderr line

The returned MagicInstance exposes:

Method Description
runCommand(cmd: string) Dispatch a single Magic command. Returns 0 on success.
sourceFile(path: string) Execute a script from the virtual filesystem. Returns 0 on success, -1 if the file could not be opened.
init() Force initialization. Idempotent — runCommand and sourceFile call it for you.
update() Drive a display-update cycle. No-op in this headless build.
FS Emscripten virtual filesystem. See the Emscripten docs.

Full TypeScript types ship in index.d.ts.

Low-level access

createMagic() is a thin convenience wrapper over the underlying Emscripten module. If you need direct access — for example to call cwrap yourself or to drive magic_wasm_init manually — import the module factory directly:

import createMagicModule from 'magic-vlsi-wasm/magic.js';

const module = await createMagicModule({ wasmBinary, print, printErr });
module._magic_wasm_init();
const run = module.cwrap('magic_wasm_run_command', 'number', ['string']);
run('tech load scmos');

The bundled examples use this lower-level path together with a small helper class (examples/helpers.js) that adds a runScript(text) convenience method — it splits a multi-line Tcl block, strips comments, and dispatches each line via runCommand. Useful when you have a script as a string rather than as a file in the VFS.

Examples

The package ships runnable examples for the most common workflows. After installing, run one directly:

node node_modules/magic-vlsi-wasm/examples/extract.js
node node_modules/magic-vlsi-wasm/examples/gds.js
node node_modules/magic-vlsi-wasm/examples/drc.js
node node_modules/magic-vlsi-wasm/examples/cif.js

Or, when developing inside this repo:

npm test           # full suite (extract, gds, drc, cif)
npm run example    # extract.js — RC extraction + SPICE netlist
npm run test:gds   # GDS write only
npm run test:drc   # DRC check only
npm run test:cif   # CIF write only

Each example loads the bundled min.mag (a small NPN transistor cell from Magic's own scmos test suite) under the built-in scmos technology — no external tech file required. See examples/ for the source; example.js is the simplest entry point (GDS → CIF conversion in ~40 lines).

Build from source

If you want to rebuild the WASM module yourself, see toolchains/emscripten/README.md. The short version:

bash npm/build.sh           # debug build, copies magic.js + magic.wasm into npm/
bash npm/build.sh --release # optimized
bash npm/build.sh --test    # build + run tests
bash npm/build.sh --pack    # build + produce magic-vlsi-wasm-<version>.tgz

You will need an activated emsdk checkout (Magic pins emsdk 3.1.56 — see the comment in npm/build.sh).

Limitations

  • Headless only. There is no display driver, so commands that draw to a window (view, findbox, interactive macros) are no-ops.
  • WASM memory starts at 32 MB and grows as needed. Very large GDS imports may need INITIAL_MEMORY bumped (rebuild required).
  • Single-threaded. WASM modules are not thread-safe — create one instance per worker.

License

HPND — Copyright (C) 1985, 1990 Regents of the University of California.

Bundled test layout

The example layout examples/min.mag is taken from Magic's own scmos test suite (scmos/examples/bipolar/min.mag) and is included here as a runnable smoke test for the WASM build. The scmos technology it targets is compiled into the WASM binary, so no external tech file is shipped.