Skip to content

Add TCL support for WASM builds#519

Closed
Intubun wants to merge 19 commits into
RTimothyEdwards:masterfrom
Intubun:master
Closed

Add TCL support for WASM builds#519
Intubun wants to merge 19 commits into
RTimothyEdwards:masterfrom
Intubun:master

Conversation

@Intubun

@Intubun Intubun commented May 22, 2026

Copy link
Copy Markdown
Contributor

Hey everyone,
ive been working on adding tcl support to the magic-vlsi-wasm package. Ive added:

  • A WASM build variant alongside existing non-TCL build
  • tcl/tk gets compiled as static library and linked directly into magic.wasm
  • new --with-tk=no configure path for TCL without Tk builds, since WASM only runs headless
  • CI workflow for all these changes
  • More memory by standard (64 MB for heap and 10 MB for Stack, can be adjusted at runtime)

Matrix and all CI already ran on my fork and everything is green.

Intubun and others added 13 commits May 21, 2026 12:42
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.
…ompletes

magic_wasm_init() called Tclmagic_Init() and magicMainInit() but never
ran the command-registration loop in _magic_initialize(), so magic::tech,
magic::load, magic::gds and all other Magic commands were missing from
the Tcl interpreter.

Add TclmagicRegisterCommands() in tclmagic.c containing the
WindNextClient/WindGetCommandTable loop and call it from magic_wasm_init()
after magicMainInit() succeeds.

Also change magic_wasm_source_file() to use Tcl_EvalFile in MAGIC_WRAPPER
mode so scripts with magic:: commands are evaluated through the Tcl
interpreter instead of the plain text dispatcher.
All non-TCL tests (extract, gds, drc, cif) now also run against the TCL
variant using magic::-prefixed Tcl scripts. A new PCell test generates
two parameterized rectangle cells and verifies their GDS output.

The CI workflow runs npm run test:tcl after the TCL build and saves the
generated output files as an artifact.
…comments and naming

Rename *-magic.tcl scripts to *-tcl.tcl to match the -tcl.js test naming
convention. Delete the old bare-command pcell.tcl (used cellname create,
no magic:: prefix) and promote pcell-magic.tcl to pcell.tcl.

Fix misleading comment in magicWasm.c: Tclmagic_Init only bootstraps the
interpreter; magic:: commands are registered separately by
TclmagicRegisterCommands after magicMainInit. Align comment block
dashes in TclmagicRegisterCommands to match tclmagic.c style (62 dashes).

Replace intubun/tcl references in build scripts and CI with tcltk/tcl to
match the actual pinned repo in npm/tcl.ref. Also run both test suites
when build.sh is invoked with --test.
Replace the last three intubun/tcl mentions with tcltk/tcl in
npm/tcl.js, toolchains/emscripten/build-tcl-wasm.sh, and npm/tcl.ref.
Also remove the stale comment in tcl.ref that referenced a non-existent
update-tcl GitHub Actions workflow.
tclmagic.c: remove stray /*-----*/ line left over from a previous edit
that left a duplicate comment opener before TclmagicRegisterCommands.

magicWasm.c: move TxSetPoint inside the #else (non-TCL) branch of
magic_wasm_source_file and restore its explanation comment. TxSetPoint
routes TxDispatch commands to the layout window; it is irrelevant and
misleading in the Tcl_EvalFile path.

magic/Makefile: guard the TCL linker flags in the magic.js link rule
with ifneq (${TCL_LIB_DIR},). When building the non-TCL WASM variant
TCL_LIB_DIR is empty, so the unconditional -L${TCL_LIB_DIR} -ltclstub
expanded to a bare -L flag and a missing library, breaking the notcl
build.
… in CI

Add explicit length limit to sscanf in TclmagicRegisterCommands: %92s
instead of %s prevents a potential stack overwrite if a command name
were ever longer than the buffer. Matches the available space (keyword[100]
minus the 7-byte "magic::" prefix minus null).

Extend the CI output-display step to also iterate over output-tcl/ so
that TCL-variant test regressions are visible in the job log without
downloading artifacts.

@dlmiles dlmiles left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick skim through for some initial feedback.

Comment thread npm/build.sh Outdated

if [ "$variant" = "tcl" ]; then
ensure_tcl_built
CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1${EXTRA_CFLAGS}" \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-DEMSCRIPTEN=1${EXTRA_CFLAGS}

Is this correct without a space before the dollar ?

There is at least one more site below (please check all the files in npm/**). This may appear to work when EXTRA=CFLAGS="" but if you configure it to a non-blank string I would expect things to error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the spaces at other places and therefore it compiled correctly, but yeah, Ill fix it.

Comment thread npm/README.md Outdated

```bash
# Override the default clone location
TCL_REPO=/path/to/existing/tcl bash npm/build.sh --variant=tcl

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not clear what the function of this part is. If the WASM TCL is built, you would expect the C TCL to be installed as well. So there is a $prefix/lib/tclConfig.sh which should contain all the information a dependant project needs to consume Tcl. This config pre-dates pkg-config system that, but serves the same purpose.

If this part is for making the Tcl source available to build the WASM target. This is probably best considered being done entirely inside Tcl. For example the 'unix' subdir is for UNIX targets, the 'win' subdir is for Win32 targets, etc... I'm sure Tcl upstream project would welcome having a contribution with a 'wasm' subdir that builts Tcl for a WASM target, possibly with access to WASI (which is the closest APIs suite that should cover core functionality).

When maybe a shim for WASM in the browser. To manage WASI and non-WASI and this would be a more minor target adjustment.

This comment should not hold up the contribution here, just feedback that access to a working Tcl on WASM target might be something more Tcl users want and sits well with the construction of the project in a wasm/ subdir.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was a local override, in case there was a local version, for example for dev on the tcl branch. however i removed it after careful consideration in commit 16c0ab3 So now only with override of the version and repo to be cloned.

Comment thread npm/tcl.ref Outdated
# 3. Commit + push. CI rebuilds and republishes.

TCL_REPO_URL=https://github.com/tcltk/tcl.git
TCL_REF=84b23291b0dd811d642abef4ec7a55473c3eccb3

@dlmiles dlmiles May 22, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TCL_REF=main

Is there a reason this is pinned by default ? Ideally should this not take the latest tag of the stable branch. So by default CI will also rebuild against newer TCL.

Can the build use the environment $TCL_REF in priority to the default here, "refs/tags/9.0.3"

Can the build use the environment $TCL_REPO_URL in priority to this default here.

Can release notes be constructed around the GitHub artifact creation, so at the bottom of the workflow task. The goal is to at a glance see the versions of important things that were use by the CI.

This might be compiler, emscripten, tcl commit/tag/url.

Ideally the CI should be optimistically building for newer current stuff in low maintenance mode by default, so no one need to update the SHA here.

Maybe in this case when the GitHub workflow runs, one task is a form of information gathering, maybe to decide the TCL_REF value and report in summary the git log -n1 style output. It then sets $TCL_REF in the workflow, to this value here can be set to TCL_REF=main. The information gathering is then used to render a $GITHUB_STEP_SUMMARY to display things with the *.wasm downloads.

If the CI requires pinning and heuristics over picking a working version to use, the pinning would be done in the workflow file.

This would then extend to using on:workflow_dispatch to specify a specify TCL_REF.

All this is to help in 6 months when it breaks to have as much information as possible available in CI to spend the least amount of time rebuilding magic using the previously working TCL_REF to confirm the problem is with the current state of the upstream project, not with magic's recent changes.

Part of the reason why the CI workflow default TCL_REF should be computed on the most recent stable branch main release tag, is only use Tcl versions that the Tcl project consider release ready.

If you are locking a version (because that is needed) around the SHA please show the git log -n1 info nearby, to show the commit vital statistics (date/tag/author/comment).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ive deleted the tcl.ref and now the tcl verison is tagged to the latest stable release. You can override the verion with a manual workflow dispatch. Also a build summary was added.

@dlmiles

dlmiles commented May 22, 2026

Copy link
Copy Markdown
Collaborator

Can we resolve the Semantic Version scheme for NPM package versions as well ?

Comment added after close #512 (comment)

Specifically what is bad is the use of a raw git hash after a decimal point such as example: 8.3.799-20260622.01234cdef. This as I understand it, is the scheme used in the first release.

It is better to be 8.3.799.20260622git0123cdef where git seems to be the defacto standard in the NPM package world for this usage? as it conveys what the sequence of the following characters means ? if you recall the goal is to allow insertion of security updates (if the project wanted to) after the events in the future. The goal would be some one specifying they want magic==8.3.799 would get the latest version in the 8.3.799 series, thus picking up an inserted security update. Each part after decimal point should follow an order (as per semantic versioning), but use of the githash as .01234cdef breaks this as the next hash might be .0fedc321 but that now appears to be before.

Not thinking about this in advance, makes it impossible to fix in the future.
In practice the project may just request users to upgrade to the latest release, but having the option is always best planned for in advance.

I just have not done to research (myself) to see how other NPM projects manage including the git-hash with semantic versioning. But I know that they do manage this within their visioning scheme. But what I can see at this time is exactly what I didn't want.

@Intubun

Intubun commented May 22, 2026

Copy link
Copy Markdown
Contributor Author

There are two distinct version formats in this workflow worth separating:

Published releases are tag-driven: pushing git tag v8.3.799 causes the workflow to publish exactly 8.3.799 to GitHub Packages — clean three-component semver, no suffix. Security patches would be published as 8.3.800, 8.3.801 etc. Users who declare ~8.3.799 in their package.json receive those automatically on npm update. This is the standard npm mechanism for the use case described and is already in place.

CI snapshots (non-tag builds) are packed as artifacts for local inspection but are never published to GitHub Packages. The format has been updated from 8.3.799-20260622.01234cdef to 8.3.799+20260622.git01234cdef. The original - was the real problem: in semver it marks a pre-release, meaning the snapshot sorted before 8.3.799 rather than after. The + separator correctly marks it as build metadata ("this is 8.3.799 built from this commit"), and the git prefix makes the hash immediately recognisable. Since snapshots are never published, the hex ordering concern does not arise in practice.

Regarding 8.3.799.20260622git0123cdef — the intent is right but npm's node-semver strictly requires exactly three numeric components and rejects a fourth, so this format cannot be used regardless of convention.

@dlmiles

dlmiles commented May 22, 2026

Copy link
Copy Markdown
Collaborator

The upgrade to latest is always an option, and not the point I am drawing attention to (as it is an obvious matter).

The security update means keep the same 8.3.799 version but receive a security patch on top (the version needs to be inserted into the range). For example there maybe users who lock a specific version of magic because it has been tested/checked and because they are building chips (with a 1 year lifecycle and expensive stability concerns) who want stability not new features, so they would use magic==8.3.799 because it passed their internal processes at some point in the past.

The idea of a security (or major bugfix/correction) patch issue in this way is to insert it into the range 8.3.799 range after 8.3.800 was release (retrospectively).

Is it allowed in semantic versioning to have 8.3.799.1+20260622git0123cdef and will that version always be considered a match for magic==8.3.799 when the original release was 8.3.799+20260622git0123cdef. By using the + it seems like that manages the extra part, and by not using . we can still have a 4th part to the version to insert the security update ?


Ah then if it strictly only allows 3 parts to a version, I am saying the main release also need the +20260622git0123cdef part to allow the security/major-fix to be inserted.

@Intubun

Intubun commented May 22, 2026

Copy link
Copy Markdown
Contributor Author

npm does treat any three part sementic versioning the same, no matter the extension. npm, correct me if im wrong, does not have any way to retroactively insert security patches. The only path for patching is to increase the version number. The part after that is only for traceability. At least thats my knowledge on how the npm registry handles it.

@dlmiles

dlmiles commented May 22, 2026

Copy link
Copy Markdown
Collaborator

I shall research and test some things over the new few days.

Is there any reason why ${base}-${date}+git${hash} is a bad idea ? This would seem to address all constraints:

  • NPM semver works, as 3 parts, a + introduces supplementary information.
  • An update for the product version 8.3.799 can be inserted, as 799-20260522 is older than 799-20260801
  • The release build would use versioning in the form 8.3.799-20260522 due to this.
  • Also ${base}~${date}+git${hash} would be ok, if in NPM world use if tilde '~' is a defaco standard.

Unclear if magic>=8.2.799 matches.

As I understand it from your comments: a . after a + has a different meaning (it is not part of semver matching), but in the above scheme is was removed anyway, side-stepping the concern, but in NPM and the casual observer (who does not know/understand semver rules).

I don't see having the date on display being a bad thing, a human reading can immediately make a judgement call from seeing a date on they should consider to update, based around all the information they know from their view of the world.

@Intubun

Intubun commented May 22, 2026

Copy link
Copy Markdown
Contributor Author

yeah semver matching is different depending on the symbol. ".", "-" and "+" have different meanings. "." is for sectioning the main version, "-" can be used to declare alpha / beta version. "1.1.1-alpha.1" for example. and "+" only adds additional information, for example build time information, etc. But I should also do more research on the topic

@Intubun

Intubun commented May 26, 2026

Copy link
Copy Markdown
Contributor Author

I did my research, the "-" has to be actively selected by the user, since its only intended for prerelease version. Its mostly intended to implement patches by bumping the last part of the version. for example: magic>=8.3.799 would not match with 8.3.799-20260526, since its considered a prerelease version of a package. So Users would not get the update, unless they actively pin that version with magic@8.3.799-20260526, but it wont update the package automatically with the next npm install / npm update. And since @RTimothyEdwards magic repo is mirrored by github every day at around 9am my time, there is a more or less daily release schedule and if we ship it just as 8.3.799, then all the users would get the newest version, if they run npm install or update.

@dlmiles

dlmiles commented May 27, 2026

Copy link
Copy Markdown
Collaborator

My research gets to here.

Using a version in the form: 8.3.799020261231+git0123cdef

The inserted ZERO just makes the date easier to read. Can also be used for .1 to .9 style 4th part, the point is the scheme leaves the option open to decide another day. Which is what thinking about it now is all about (creating future options).

Version locking can be done in a number of ways, see below. <8.3.800xxxxxxxxx

VALID 8.3.799~20261231+git0123cdef -> false
VALID 8.3.799_20261231+git0123cdef -> false
VALID 8.3.799$20261231+git0123cdef -> false
VALID 8.3.799%20261231+git0123cdef -> false
VALID 8.3.799^20261231+git0123cdef -> false
VALID 8.3.799&20261231+git0123cdef -> false
VALID 8.3.799*20261231+git0123cdef -> false
VALID 8.3.799(20261231+git0123cdef -> false
VALID 8.3.799)20261231+git0123cdef -> false
VALID 8.3.799=20261231+git0123cdef -> false
VALID 8.3.799[20261231+git0123cdef -> false
VALID 8.3.799]20261231+git0123cdef -> false
VALID 8.3.799{20261231+git0123cdef -> false
VALID 8.3.799}20261231+git0123cdef -> false
VALID 8.3.799#20261231+git0123cdef -> false
VALID 8.3.799@20261231+git0123cdef -> false
VALID 8.3.799:20261231+git0123cdef -> false
VALID 8.3.799;20261231+git0123cdef -> false
VALID 8.3.799.20261231+git0123cdef -> false
VALID 8.3.799,20261231+git0123cdef -> false
VALID 8.3.799>20261231+git0123cdef -> false
VALID 8.3.799<20261231+git0123cdef -> false
VALID 8.3.799/20261231+git0123cdef -> false
VALID 8.3.799?20261231+git0123cdef -> false
VALID 8.3.799|20261231+git0123cdef -> false
VALID 8.3.799020261231+git0123cdef -> true
VALID 8.3.799020261231+git0123cdef -> true
VALID 8.3.999020261231+git0123cdef -> true
VALID 8.3.1000020261231+git0123cdef -> true
VALID 8.3.1001020261231+git0123cdef -> true
COMPARE 8.3.799 vs 8.3.799020261231+git0123cdef -> -1 BELOW
COMPARE 8.3.800 vs 8.3.799020261231+git0123cdef -> -1 BELOW
COMPARE 8.3.799020261231+git01230000 vs 8.3.799020261231+git0123cdef -> 0 EQUAL
COMPARE 8.3.799020261231+gitffff0000 vs 8.3.799020261231+git0123cdef -> 0 EQUAL
COMPARE 8.3.799020260831+git0123cdef vs 8.3.799020261231+git0123cdef -> -1 BELOW
COMPARE 8.3.799020270101+git0123cdef vs 8.3.799020261231+git0123cdef -> 1 ABOVE
COMPARE 8.3.999020270101+git0123cdef vs 8.3.799020261231+git0123cdef -> 1 ABOVE
COMPARE 8.3.1000020270101+git0123cdef vs 8.3.799020261231+git0123cdef -> 1 ABOVE
COMPARE 8.3.1001020270101+git0123cdef vs 8.3.799020261231+git0123cdef -> 1 ABOVE
MATCH version=8.3.799020261231+git0123cdef range=~8.3.799 -> true
MATCH version=8.3.800020261231+git0123cdef range=~8.3.799 -> true
MATCH version=8.3.1000020261231+git0123cdef range=~8.3.799 -> true
MATCH version=8.3.1001020261231+git0123cdef range=~8.3.799 -> true
MATCH version=8.3.799020261231+git0123cdef range=~8.3.7990 -> true
MATCH version=8.3.800020261231+git0123cdef range=~8.3.7990 -> true
MATCH version=8.3.1000020261231+git0123cdef range=~8.3.7990 -> true
MATCH version=8.3.1001020261231+git0123cdef range=~8.3.7990 -> true
MATCH version=8.3.799020271201+git0123cdef range=~8.3.799 -> true
MATCH version=8.3.799020270101+git0123cdef range=~8.3.799 -> true
MATCH version=8.3.799020270202+git0123cdef range=~8.3.799 -> true
MATCH version=8.3.799020270303+git0123cdef range=~8.3.799 -> true
MATCH version=8.3.800020270101+git0123cdef range=<=8.3.799900000000 -> false
MATCH version=8.3.801020270202+git0123cdef range=<=8.3.799900000000 -> false
MATCH version=8.3.801020270202+git0123cdef range=<=8.3.7999x0000000 -> false
MATCH version=8.3.801020270202+git0123cdef range=<=8.3.7999xxxxxxxx -> false
MATCH version=8.3.799020271201+git0123cdef range=~8.3.7990 -> true
MATCH version=8.3.799020270101+git0123cdef range=~8.3.7990 -> true
MATCH version=8.3.799020270202+git0123cdef range=~8.3.7990 -> true
MATCH version=8.3.799020270303+git0123cdef range=~8.3.7990 -> true
MATCH version=8.3.800020270101+git0123cdef range=<8.3.8000xxxxxxxx -> false
MATCH version=8.3.801020270202+git0123cdef range=<8.3.8000xxxxxxxx -> false
MATCH version=8.3.801020270202+git0123cdef range=<8.3.800xxxxxxxxx -> false

@Intubun

Intubun commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

Agreed. Let me implement it. It maintains correct orderning, while allowing small patched to be implemented on a as needed basis. Havent thought about that.

@Intubun

Intubun commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

@dlmiles Is there anything left to do from your point of view?

@dlmiles

dlmiles commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

I'll update tomorrow on this (not expected to find any concerns). Have got it locally built to look at just not tried it out.

@RTimothyEdwards any feedback from your side ?

@RTimothyEdwards

Copy link
Copy Markdown
Owner

@dlmiles : No comments from me. All the WASM stuff is entirely outside my area of expertise and I am relying on you to give it the sign-off.

@dlmiles dlmiles left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just sending this now for feedback been in draft for over 24h.

Waiting on #523 to continue testing/evaluation as it breaks WASM test at this time (see recent CI/Actions).

Comment thread npm/build.sh Outdated
# Runs the same smoke test that CI runs (see .github/workflows/main.yml).
# Runs the same smoke test that CI runs (see .github/workflows/main-wasm.yml).
if [ $OPT_TEST -eq 1 ]; then
cd "$SCRIPT_DIR"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This use of cd wants to be in a subshell (cd dir; npm command) so the pack.sh below line at 130 has a chance to run.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - fixed it. Wrapped the test step in a subshell, so cd cant leak into --pack

const { FS } = magic;
const { tech: techName, cell } = loadCell(FS, tech, magFile);

magic.runScript(loadScript('extract-tcl.tcl', techName, cell));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These example scripts need to wrap and throw runtime exceptions so that there is a readable diagnostic output when they fail. This does not seems to happen by default, not at least if you run as node examples/extract.js

They ideally need to run as many individually as possible, as in the first failure should not stop all other scripts from being processed, unless it is known to be dependant upon the output. It is still useful to know when 5 of 7 tests were successful.

This make them more useful and less cryptic in their use as a CI/fitness testing function.

Example (all bet it not a good one):

try {
  // Thing that can fail (ideally this might be the whole function not just runScript, if used for CI tests)
  magic.runScript(....);
} catch(e) {
   console.error(e.toString()); // ensure CI outputs emits error details
   console.error(e.stack?.toString());
   throw e; // ensure caller sees failure
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The example runners were calling console.error(e.message ?? e), which dropped e.stack — the part with the wasm-function[...]:0x... offsets. Added a
shared reportError() in helpers.js/helpers-tcl.js that prints the full stack:

export function reportError(e) {
console.error(e?.stack ?? String(e?.message ?? e));
}

runScript() now wraps each command so the failing step is named before the error propagates:

try {
this._run(l);
} catch (e) {
console.error([magic] command failed: ${l});
throw e;
}

All standalone runners and the suites use it, so node examples/extract.js now shows the full trace instead of a one-liner. And all.js/all-tcl.js already isolate each test and continue on failure, printing an N/M passed summary, so one failure doesn't stop the rest.

Verified on the failing extract test:
[magic] command failed: extresist all
FAIL memory access out of bounds
RuntimeError: memory access out of bounds
at magic.wasm.ResSimplifyNet (wasm-function[1504]:0x13ae6b)
at magic.wasm.ResDoSimplify (wasm-function[1513]:0x13d2ca)
at magic.wasm.ResProcessNode ...
…with gds/drc/cif still running (3/4 passed).

@dlmiles

dlmiles commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

I would expect the try/catch to show the nature of the failure in CI such as at https://github.com/RTimothyEdwards/magic/actions/runs/27055629781/job/79859362144#step:8:38 (at the moment it doesn't provide any feedback, which is really useful information).

Ideally the failure data (stdout/stderr) should be searched for something that looks like a stack trace, then it can augment, this is the standard looking output, note I don't see stdout/stdserr in the CI logs, which I think are useful to expose, ensure to build with -g and link with -gsource-map and copy magic.wasm.map alongside, I also executed with --enable-source-maps not entirely sure what is a good set of runtime options to use in CI to elicit maximum feedback on failure:

# Example of raw output from CI test run:
magic] node=0x1eaf2b0 resisptr=0x1db9e40
[magic] node=0x1ee0328 resisptr=0x1db7f50
[magic] Adding  collector; (Tnew = 1.53ps ; Tmin = 1.00ps)
[magic] Extracting emitter (Rnode = 27.00ohm ; Rthresh = 10.00ohm)
[magic] node=0x1c904e0 resisptr=0xc0000004
[magic] Aborted(Assertion failed: resptr->rn_ce != CLIENTDEFAULT, at: ResMerge.c,1126,ResCleanNode)
RuntimeError: Aborted(Assertion failed: resptr->rn_ce != CLIENTDEFAULT, at: ResMerge.c,1126,ResCleanNode)
RuntimeError: Aborted(Assertion failed: resptr->rn_ce != CLIENTDEFAULT, at: ResMerge.c,1126,ResCleanNode)
    at abort (file:///work/magic/npm/magic.js:474:41)
    at ___assert_fail (file:///work/magic/npm/magic.js:845:59)
    at magic.wasm.ResCleanNode (wasm://wasm/magic.wasm-02750336:wasm-function[1575]:0x186458)
    at magic.wasm.ResCleanUpEverything (wasm://wasm/magic.wasm-02750336:wasm-function[1491]:0x16e9cc)
    at magic.wasm.ResProcessNode (wasm://wasm/magic.wasm-02750336:wasm-function[1549]:0x17e9a7)
    at magic.wasm.ResCheckExtNodes (wasm://wasm/magic.wasm-02750336:wasm-function[1544]:0x17c1a2)
    at magic.wasm.ExtResisForDef (wasm://wasm/magic.wasm-02750336:wasm-function[1542]:0x17b947)
    at magic.wasm.CmdExtResis (wasm://wasm/magic.wasm-02750336:wasm-function[1546]:0x17dca3)
    at magic.wasm.WindExecute (wasm://wasm/magic.wasm-02750336:wasm-function[1425]:0x15d5fe)
    at magic.wasm.DBWcommands (wasm://wasm/magic.wasm-02750336:wasm-function[726]:0xaa203)

ext:   /work/magic/npm/examples/output/min.ext
Done.

This standard output above would ideally be augmented (either inline or as a copy below) with additional information to lookup original C file/line/snippet, this will save a lot of time.

Maybe this is something that can be knocked together as a python tool that can run in WASM CI and call the tooling and generate GitHub snippets.

The basic tools to run would be:

# This is a tool available after EMSDK setup and source: source emsdk/emsdk_env.sh
$ which emsymbolizer
/work/magic/emsdk/upstream/emscripten/emsymbolizer
# We can take the info from the stacktrace and resolve to C file/line/byte
$ emsymbolizer /work/magic/npm/magic.wasm 0x186458
ResCleanNode
/work/magic/resis/ResMerge.c:1126:1

So for each stack line augment it before display like (one item displayed, but all can be augmented):

    at magic.wasm.ResCleanNode (wasm://wasm/magic.wasm-02750336:wasm-function[1575]:0x186458) => ResCleanNode /work/magic/resis/ResMerge.c:1126:1

Then for each valid entry account turn into a GitHub comment snippet, maybe this needs to be displayed as GITHUB_STEP_SUMMARY information that is only generated when necessary (from the GITHUB_SHA the relative project path, line number #L1126:

while (resptr->rn_je != NULL)

or expand by 1 line either side #L1125-L1127:

magic/resis/ResMerge.c

Lines 1125 to 1127 in 43e4cf9

}
while (resptr->rn_je != NULL)
{

https://github.com/RTimothyEdwards/magic/blob/43e4cf9b038772c908eb57112a88afc0f91859ea/resis/ResMerge.c#L1125-L1127

Then have the python tool just run on the stdout/stderr output to transform the stack traces and emitted $GITHUB_STEP_SUMMARY info when something is found. This will speed up resolving issues WASM detects by transforming them into C line level information directly in CI.

GitHub document link on line highlighting https://github.com/github/docs/blob/main/content/get-started/writing-on-github/working-with-advanced-formatting/creating-a-permanent-link-to-a-code-snippet.md

Intubun and others added 3 commits June 6, 2026 21:29
The example/suite runners discarded e.stack via console.error(e.message ?? e),
hiding the wasm-function offsets that emsymbolizer needs to map an abort back
to C source. A failing test only printed a terse message like "memory access
out of bounds" with no trace.

- Add reportError() to helpers.js/helpers-tcl.js; print the full stack
  (falling back to the message).
- Wrap command execution in runScript() to name the command that aborted
  before the error propagates.
- Use reportError() in all standalone runners and in all.js/all-tcl.js
  (full stack to stderr, one-line PASS/FAIL summary kept; tests still run
  independently).
- build.sh: run the --test step in a subshell so its cd does not leak into
  the --pack step.
npm: surface readable diagnostics on WASM test failures

@RTimothyEdwards RTimothyEdwards left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After carefully looking through everything, and after having corrected the issue from my commit of a few days ago that caused the CI to fail, I approve this and will go ahead and merge it.

@RTimothyEdwards

Copy link
Copy Markdown
Owner

Pulled and merged to the repository at opencircuitdesign.com. The github mirror will update overnight.

Intubun pushed a commit to Intubun/magic that referenced this pull request Jun 10, 2026
…hyEdwards#519

from Enno Schnackenberg (adding Tcl support for WASM builds).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants