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.
Changed the "ResDissolveContacts()" routine to avoid running both
redundant and useless code. Changed the breakpoint sorting
routine from a bubble sort to a merge sort for linked lists
longer than 16, as merge sort is more efficient for long lists.
This file exists in the magic directory, and is the one the build system uses, this file is a duplicate that has one different line "/ms { rectfill } bind def", and is not used anywhere
Keeping two duplicate files causes issues like them being out of sync, therefore it would be great if it was deleted
The postscript template had a hardcoded stipple length of 29, any more stipples would cause problems with a postscript parser. This was particularly a problem since the default mos.24bit.dstyle has 36 stipples already.
It also used a base-30 encoding.
I have updated the template to fix both issues, it now uses base-10 encoding, and can support up to 255 stipples (which is the maximum length allowed by the current approach of using fonts)
I was also wondering if it made more sense to encode this file directly into the C program as a literal, as half of it already is in the C program. (some "begin"s have an "end" in strings directly embedded in magic, so this file basically serves as a large string that has to be loaded each time from disk, while other parts of the string are stored normally in memory)
ResTriangleCheck(). By using hash tables, I reduced
ResParallelCheck() from O(N^2) to O(N), and I reduced
ResTriangleCheck() from O(N^3) to O(N^2); however, it is a bit
better than that because previously there was an N^2 loop over
items in the same list, so if one list happened to be very long,
then N^2 would be huge; whereas now the N^2 is the length of two
lists multiplied together, where both lists would have to be very
long to have the same performance impact. It appears that long
resistor lists are pathological and rare. For an example
pathological case, the extresist runtime has been reduced from
hours to seconds.
HOME was checked but no check was made on whether or not the
result was NULL. This resolves issue #490 in the github issue
tracker.
Also: Modified the way that "select visible" works to check if
labels and/or cells are rendered visible in the window. If not,
then they are not selected. Previously, cell instances and
labels would be selected whether or not they were visible.
This resolves issue #503 in the github issue tracker.
See github issue tracker issue #490. There were two cases: When
running without the Tk console, a NULL value was being passed to a
string comparison. When running with the Tk console, two variables
were not initialized. Both prolems have been fixed.
line parser does not recognize arguments that are both in the
same word and also space separated; this is easy to do from, e.g.,
a python interpreter by setting an option as a single string as
in "-d null"; it is harder to do from the command line, but can
be done with quotes, as in 'magic "-d null"'.
The utils/args.c routine parses this with ArgStr(), which recognizes
two cases, one in which the option is split across two arguments,
and one in which the option is one word without space separation.
I modified ArgStr() to accept a third syntax in which the option
is in a single argument but is also space-separated. This simply
detects a space in the third character position and moves forward
to the next non-space character and returns that position.
"cifinput"; instead of cifSrTiles(), which ignores the "area"
argument and searches over the entire plane, it should have been
using cifSrTiles2(), which translates the area to CIF coordinates
and searches only over that area. Otherwise, everything on the
same layer in a cell gets tagged, rather than just connected
material.
change, unique node names could only be enforced by running the
command "extract unique", and there was no way to ensure that
the user would run the command as part of the extraction
recipe. Now that it has been changed to an extraction option
instead of a separate command, and does not permanently alter
labels in the layout like the standalone command version did,
it is possible to include the setting in "extract do all",
making it run by default, and requiring the user to manually
turn it off for certain use cases (which are relatively rare).
the repo parsing on opencircuitdesign.com, since yesterday I found
out that the tags were not updating, caused by a documentation
change in a copy of the repository that created a merge conflict
when seen by the script. Will check tomorrow that everything ran
smoothly overnight.
All functions that received a ClientData (or FindRegion *) parameter in
commit fc21472e solely to satisfy WASM call_indirect signature matching
are now annotated so static analysers and casual readers can see the
intent has been verified:
/*ARGSUSED*/ before each such function definition
/* UNUSED */ on each unused parameter in the definition
/* UNUSED */ on matching forward declarations
Affected: calmaWriteInitFunc, cifWriteInitFunc, cmdWindSet,
dbStampFunc, dbwElementAlways1, dbwfbWindFunc, DBWHLRedrawWind,
spcnodeHierVisit, extSDTileFunc, extTransPerimFunc,
extAnnularTileFunc, extResistorTileFunc, extDefInitFunc,
extTimesInitFunc.
Mirrors the AppImage versioning form (8.3.637~20260414~d157eea) as
closely as semver allows: 8.3.637-20260414.d157eea.
- package.json: placeholder 0.0.0-dev; CI always overwrites before pack
- npm-publish.yml: version step now runs unconditionally (not just on
tags), reading VERSION + git date + git hash — dry-run artifacts are
stamped too, making them unambiguously traceable to their source commit
- npm-publish.yml: target GitHub Packages instead of npm registry; no
NPM_TOKEN needed, uses GITHUB_TOKEN
- Both workflows: emsdk defaults to 'latest' on every automated run so
CI tracks emsdk HEAD and catches breakage early; version is overridable
via workflow_dispatch input
- npm-publish.yml: add parallel ARM (ubuntu-24.04-arm) WASM build job
with Emscripten diagnostics step
- main.yml, npm-publish.yml: upgrade runners from ubuntu-22.04 to
ubuntu-latest
The user-facing layer of the WASM port: a publishable npm package
plus the GitHub Actions that build and ship it.
* npm/package.json — publishes as `magic-vlsi-wasm`, ESM-only, HPND
licensed, version tracks Magic's own VERSION file (8.3.637).
Whitelists the published files and exposes index.js + index.d.ts.
* npm/index.js, npm/index.d.ts — thin JS/TS wrapper around the four
WASM exports. createMagic(opts) returns { init, runCommand,
sourceFile, update, FS } so consumers can write into the
Emscripten virtual filesystem and dispatch Magic commands from
Node.js, browsers or Web Workers.
* npm/build.sh — end-to-end build: locates emsdk (via PATH or
EMSDK_DIR), runs distclean+configure+make in the right order
(techs before mains so embed-files are present), copies
magic.js / magic.wasm into npm/. Optional --release, --test,
--pack flags. Preserves configure's exec bits across invocations.
* npm/pack.sh — produces a reproducible npm tarball by touching
every file to the build time and exporting SOURCE_DATE_EPOCH so
pacote does not rewrite mtimes to its 1985 fallback.
* npm/examples/ — runnable smoke tests for the four common
workflows (extract, gds, drc, cif), driven by examples/all.js.
Each example is self-contained and uses the bundled siliwiz
technology. helpers.js encapsulates the boilerplate.
* npm/LICENSE, npm/README.md — license text and consumer-facing
docs (install, quick-start, API, examples, build-from-source,
license, third-party content notice).
* .github/workflows/main.yml — adds a `simple_build_wasm` job that
installs a pinned emsdk (3.1.56), builds the WASM module, runs
the example test suite and uploads the npm tarball as an
artifact. Pinned for reproducibility against the post-build.sh
patches; switchable to "latest" by commenting two lines.
* .github/workflows/main-aarch64.yml — drops the now-redundant
WASM ARM job. WASM is architecture-independent.
* .github/workflows/npm-publish.yml — new workflow. Publishes to
npm on `v*` tag pushes (manual `workflow_dispatch` supported as
a dry-run). Uses the same pinned emsdk and pack.sh.
Also sets FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 in both workflows to
silence the Node.js 20 deprecation warnings until
actions/upload-artifact@v6 ships a Node-24 release.
The pieces that make Magic actually buildable as a WASM library.
* magic/magicWasm.c — new headless entry point exporting four
functions used by the JS wrapper:
- magic_wasm_init() idempotent initialisation
- magic_wasm_run_command(s) dispatch one Magic command
- magic_wasm_source_file(p) execute a script from the VFS
- magic_wasm_update() drive a display-update cycle
Sets CAD_ROOT=/ if unset, so embedded technology files under
/magic/sys/ resolve correctly. Centers the command point inside
GrScreenRect so commands route to the layout window client
rather than the border/window-management client.
* utils/main.c, utils/main.h — split magicMain() into magicMainInit()
+ the dispatch loop. magicMainInit is idempotent (a static flag
guards against re-initialisation) so JS callers can call any of
the four wasm entry points first without sequencing.
* magic/Makefile — adds the WASM link target, gated by MAKE_WASM=1
set from toolchains/emscripten/defs.mak. Conditionally compiles
magicWasm.c into the main binary, links to magic.js and runs
post-build.sh on the result.
* toolchains/emscripten/defs.mak — Emscripten linker flags (WASM=1,
MODULARIZE, EXPORT_ES6, ALLOW_MEMORY_GROWTH, INITIAL_MEMORY=32M,
STACK_SIZE=5M), the four EXPORTED_FUNCTIONS, and the embed-file
bindings for the technology files under /magic/sys/.
* toolchains/emscripten/post-build.sh — patches Emscripten's ESM
output so it works in pure Node.js ESM: aliases require()
through createRequire, injects __filename / __dirname shims,
and resyncs the ___emscripten_embedded_file_data constant from
the wasm global section if Emscripten emitted a stale value.
Idempotent and pinned to emsdk 3.1.56 (see WARNING in the
header).
* toolchains/emscripten/README.md — full build documentation:
quick-start via npm/build.sh, manual build, list of embedded
files, exported C API, JavaScript usage example, and notes on
CAD_ROOT, DISPLAY_SUSPEND, and the signal-API stubs.
* .gitignore — adds the WASM artefacts (magic.js, magic.wasm,
magic.symbols), tightens the editor/OS cruft list, and keeps
toolchains/emscripten/defs.mak tracked despite the `defs.mak`
ignore rule.
Glue between the null display driver and the rest of Magic so that
running with -d null does not require any process-level resources
(signals, timers, stdin, an X display, or a Tcl interpreter).
* utils/signals.c — gate setitimer, fcntl-based file watches, kill
and the legacy sigsetmask/sigaction setup behind #ifdef
__EMSCRIPTEN__. Every signals path becomes a no-op in WASM.
Also fixes DBWriteBackup() being called with one argument when
its real prototype takes three.
* windows/windDisp.c — WindUpdate() returns immediately when
GrDisplayStatus == DISPLAY_SUSPEND. This is the runtime
counterpart to the null driver's DISPLAY_SUSPEND state.
* extflat/EFargs.c — EFArgs() with a missing input name no longer
jumps to "usage:" in headless WASM (which would call MainExit and
kill the process); it sets *err_result and returns NULL so the
caller can decide what to do. Native MAGIC_WRAPPER and native
non-MAGIC_WRAPPER builds keep their original behavior.
* dbwind/DBWcommands.c — registers exttosim / ext2sim / exttospice /
ext2spice in non-MAGIC_WRAPPER builds. Without this, WASM users
could not invoke these commands at all (they were previously
inside an #ifdef MAGIC_WRAPPER block). The C implementations
(CmdExtToSim / CmdExtToSpice) are linked unconditionally outside
modular builds.
* textio/txCommands.c, textio/textio.h — TxDispatchString(), a new
library-style command entry point that parses a single string,
dispatches it through WindSendCommand and returns a status code.
This is what magic_wasm_run_command() calls from JavaScript.
Magic's graphics layer routes every drawing primitive through
function pointers (GrXxxPtr / grXxxPtr) that are bound to a driver
at startup. The original null driver assigned a single 0-arg
nullDoNothing() to every pointer, which works in native builds
because of K&R loose prototype rules but fails in WASM where
call_indirect requires an exact type match between caller and
callee.
This commit:
* Adds typed no-op stubs nullDoNothingI/II/IIII/IIIIIII for
void-returning callbacks of various arities.
* Adds nullReturnFalseI/II/III for bool-returning callbacks and
nullReturnZeroI for int-returning callbacks.
* Casts each pointer assignment in nullSetDisplay() to the K&R
pointer type the public header still uses, while the underlying
function carries the correct WASM signature.
* Fills in window-management and backing-store pointers that the
original null driver left at NULL — many of these are called
unconditionally by WindUpdate paths, and need at least a no-op
to avoid traps.
* Guards the stdin watch in nullSetDisplay() with #ifndef
__EMSCRIPTEN__: WASM has no real stdin file descriptor and
TxAdd1InputDevice() / SigWatchFile() are POSIX-specific.
Native builds are unaffected: the K&R-loose prototype machinery
still accepts the previous and the new code identically.
CIFGenSubcells() and extSubtree() set GrDisplayStatus =
DISPLAY_IN_PROGRESS while they run a 5-second progress timer, then
unconditionally restore DISPLAY_IDLE on exit. In native builds the
initial state is always IDLE, so this is harmless. In WASM/headless
builds the null display driver sets DISPLAY_SUSPEND at startup, and
forcing IDLE at the end of these long operations destroys the
SUSPEND guard that protects WindUpdate() from running display
callbacks against a non-existent screen.
Save the previous status before overwriting and restore it on exit.
This is also reentrant-safe: nested DISPLAY_IN_PROGRESS scopes (e.g.
extract followed by gds write) now keep the outer state intact.
* cif/CIFhier.c — CIFGenSubcells
* extract/ExtSubtree.c — extSubtree
WASM call_indirect enforces an exact type match between the caller
and the callee. Many Magic callbacks had K&R-style () forward
declarations and a single-argument definition, but were passed to
iterators that always push a trailing ClientData argument. Native
builds tolerated the mismatch via loose prototypes; WASM traps with
"indirect call signature mismatch".
Added the missing ClientData (or, where the concrete type is known,
FindRegion *) parameter to:
* calma/CalmaRead.c, calma/CalmaWrite.c, calma/CalmaWriteZ.c —
calmaWriteInitFunc
* cif/CIFwrite.c — cifWriteInitFunc
* commands/CmdSubrs.c — cmdWindSet
* database/DBtimestmp.c — dbStampFunc
* dbwind/DBWelement.c — dbwElementAlways1
* dbwind/DBWfdback.c — dbwfbWindFunc
* dbwind/DBWhlights.c — DBWHLRedrawWind
* ext2spice/ext2hier.c — spcnodeHierVisit
* extract/ExtBasic.c — extSDTileFunc, extTransPerimFunc,
extAnnularTileFunc, extResistorTileFunc
* extract/ExtMain.c — extDefInitFunc
* extract/ExtTimes.c — extTimesInitFunc
Also adjusted commands/CmdE.c and commands/CmdTZ.c: SelectExpand was
being called with four arguments (the legacy surroundFlag), but its
real signature has been three arguments for years (the surround mode
is encoded in the expandType bit). The fourth argument was redundant
(DB_EXPAND_SURROUND in arg 2 is the source of truth) and rejected by
WASM. Native behavior is unchanged.
The added parameters are unused in the function bodies; they exist
only to satisfy the indirect-call signature.
These bugs all exist in stock Magic but were tolerated by the K&R-loose
native build. The strict WASM call_indirect type checks turned them up.
* cif/CIFhier.c: ASSERT in cifFlatMaskHints accessed
oldproprec->prop_value.prop_type, but prop_type is a top-level member
of PropertyRecord. Changed to oldproprec->prop_type.
* extflat/EFargs.c: efLoadSearchPath was assigning a pointer to a
string literal ("." in RO data), which callers later try to free or
StrDup. Replaced with StrDup(path, ".") so the pointer always lives
on the heap.
* router/rtrVia.c: rtrFollowName called RtrMilestonePrint("#"), but
the function takes no arguments.
* sim/SimSelect.c: SimAddLabels called DBWLabelChanged with five
arguments, but its real signature is (CellDef *, Label *, int).
Replaced with the equivalent DBWAreaChanged call.
* windows/windView.c: extern declaration of DBMovePoint had return
type void, but the function actually returns bool.
DEV_RSUBCKT as well as (previously implemented) DEV_RES. The
handling of DEV_RSUBCKT includes checking if the subcircuit has
a substrate pin, and zeroing the capacitance to substrate for the
resistor node, under the assumption that the parasitics are part of
the resistor subcircuit model.
There is a bug where left-clicking a hidden layer in the toolbar successfully makes the layer visible in the layout but fails to update the toolbar icon to the "active" state.
The root cause is a positional argument mismatch in magic::toolupdate. When the Magic C-core issues a callback for a "see" command with two arguments, the third argument (layerlist) is passed as an empty string ("") rather than being omitted. This prevents the Tcl procedure from falling back to its hardcoded default of "none", causing the script to skip the logic that reassigns $yesno to "yes".
This patch changes the default value of $layerlist to "" and updates the conditional check to ensure the state and layer name are correctly reassigned regardless of how the C-core signals the update.
Added logic to ensure all cells are loaded into memory to prevent corruption when saving files. This is a fix for a minor edge case when generating "netlist_to_spice" for a file that already has a netlist, and which contains cells containing other cells. Followed by "writeall force". This causes unloaded files (that are still referenced) to be loaded during save with "ignoreTech" being true, and thus disregarding the scale, corrupting all coordinates if a scale is used.
This was caused by skipping regenerating cells that already had a .mag file
due to moving away from using the node "location" in favor of a
drivepoint specified by a port or other connection point. The
"location" record can end up at the default value of "infinity",
and should not be used to set the area for searches. This fix
is in response to issue #502 on the github issue tracker, submitted
by Julian Schwarz.
code now for 10 days). The last commit fixed an error when using
drcCanonicalMaxwidth() multiple times in DRC rule checks on a single
edge. At the time, I also added a method to save some
drcCanonicalMaxwidth() results to prevent needlessly re-doing the
same (potentially expensive) computation over and over. However,
where I reused the cached result, I did not check if the number of
entries is zero. Other code does not check the number of entries
and depends on the prior behavior that drcCanonicalMaxwidth()
returns NULL when there are no entries. So the code needed to check
if the number of entries is zero before re-using the cached result.
Without the fix, any time the caching is used, the last computed
maximum area rectangle will be made active even when the current
check area does not meet the area requirement, resulting in many
false positive errors.
routine, which in turn affects various DRC rules like maxrect,
widespacing, and runlength spacing. drcTile() was computing
the drcCanonicalMaxwidth() result for the tile and reusing it,
failing to account for the fact that within the loop over
DRCCookies, other rules might require calling drcCanonicalMaxwidth()
on a neighboring tile, or on the same tile with a different width
requirement. Implemented a cached version, in which three results
are kept: One for neighboring tiles (which can never be reused on
the same edge), one for the first required use of the routine for
the tile, and one for any other use required for the tile. If
there are one or two such rules for an edge, then the routine will
work at maximum efficiency. If there are three rules, then one
will always be a cache hit and reduce the total amount of
computation, although it will still be doing a massive amount of
redundant computation. If this seems to be something that
occurs regularly, then it can be revisited. The existing
implementation works fine for all the open PDKs. Some more
advanced PDKs with a number of staged wide-spacing rules could
have issues.
"magic::add_dependency" procedure. The "check" function should always
be called first, followed by the dependencies. Because otherwise, if
an incorrect value is entered for a device parameter, then it gets
used to calculate dependent values *before* it gets corrected by the
"check" procedure, resulting in an incorrect value being displayed
for the dependent value, even after the bad entry has been corrected.
the exiting documentation does not include the handful of options
available to the macro command, nor does it explain the relationship
between tools and macro sets.