Add WASM entry point and Emscripten build wiring
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.
This commit is contained in:
parent
f4c22438c6
commit
8e8fada32f
|
|
@ -1,19 +1,33 @@
|
||||||
|
# Autoconf / configure outputs
|
||||||
defs.mak
|
defs.mak
|
||||||
*/Depend
|
!toolchains/emscripten/defs.mak
|
||||||
config.cache
|
config.cache
|
||||||
config.log
|
config.log
|
||||||
scripts/config.log
|
scripts/config.log
|
||||||
scripts/config.status
|
scripts/config.status
|
||||||
scripts/defs.mak
|
scripts/defs.mak
|
||||||
.*.swp
|
|
||||||
*.o
|
|
||||||
*.so
|
|
||||||
*~
|
|
||||||
scmos/cif_template/objs/*
|
|
||||||
database/database.h
|
|
||||||
install.log
|
install.log
|
||||||
magic/proto.magicrc
|
|
||||||
make.log
|
make.log
|
||||||
|
reconfigure.sh
|
||||||
|
|
||||||
|
# Compiled objects / libraries
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*/Depend
|
||||||
|
database/database.h
|
||||||
|
|
||||||
|
# Editor / OS cruft
|
||||||
|
.*.swp
|
||||||
|
.*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Magic runtime-generated files
|
||||||
|
magic/proto.magicrc
|
||||||
|
scmos/cif_template/objs/*
|
||||||
scmos/gdsquery.tech
|
scmos/gdsquery.tech
|
||||||
scmos/minimum.tech
|
scmos/minimum.tech
|
||||||
scmos/scmos-sub.tech
|
scmos/scmos-sub.tech
|
||||||
|
|
@ -21,14 +35,28 @@ scmos/scmos-tm.tech
|
||||||
scmos/scmos.tech
|
scmos/scmos.tech
|
||||||
scmos/scmosWR.tech
|
scmos/scmosWR.tech
|
||||||
scmos/nmos.tech
|
scmos/nmos.tech
|
||||||
|
|
||||||
|
# Native build artifacts
|
||||||
|
magic/magic
|
||||||
|
magic/tclmagic.dylib
|
||||||
tcltk/magic.sh
|
tcltk/magic.sh
|
||||||
tcltk/magic.tcl
|
tcltk/magic.tcl
|
||||||
tcltk/magicdnull
|
tcltk/magicdnull
|
||||||
tcltk/magicexec
|
tcltk/magicexec
|
||||||
tcltk/ext2spice.sh
|
tcltk/ext2spice.sh
|
||||||
tcltk/ext2sim.sh
|
tcltk/ext2sim.sh
|
||||||
magic/tclmagic.dylib
|
|
||||||
tcltk/magicdnull.dSYM/
|
tcltk/magicdnull.dSYM/
|
||||||
tcltk/magicexec.dSYM/
|
tcltk/magicexec.dSYM/
|
||||||
reconfigure.sh
|
|
||||||
pfx/
|
pfx/
|
||||||
|
|
||||||
|
# WASM build artifacts
|
||||||
|
magic/magic.js
|
||||||
|
magic/magic.js.symbols
|
||||||
|
magic/magic.symbols
|
||||||
|
magic/magic.wasm
|
||||||
|
net2ir/net2ir
|
||||||
|
net2ir/net2ir.js
|
||||||
|
net2ir/net2ir.wasm
|
||||||
|
|
||||||
|
# Generated test output
|
||||||
|
npm/examples/output/
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,14 @@
|
||||||
|
|
||||||
MODULE = magic
|
MODULE = magic
|
||||||
MAGICDIR = ..
|
MAGICDIR = ..
|
||||||
SRCS = magicTop.c
|
|
||||||
|
|
||||||
include ${MAGICDIR}/defs.mak
|
include ${MAGICDIR}/defs.mak
|
||||||
|
|
||||||
|
SRCS = magicTop.c
|
||||||
|
ifeq (${MAKE_WASM},1)
|
||||||
|
SRCS += magicWasm.c
|
||||||
|
endif
|
||||||
|
|
||||||
EXTRA_LIBS = ${MAGICDIR}/bplane/libbplane.o \
|
EXTRA_LIBS = ${MAGICDIR}/bplane/libbplane.o \
|
||||||
${MAGICDIR}/cmwind/libcmwind.o \
|
${MAGICDIR}/cmwind/libcmwind.o \
|
||||||
${MAGICDIR}/commands/libcommands.o \
|
${MAGICDIR}/commands/libcommands.o \
|
||||||
|
|
@ -37,7 +41,18 @@ LIBS += ${GR_LIBS} ${READLINE_LIBS} -lm ${LD_EXTRA_LIBS} \
|
||||||
${OA_LIBS} ${ZLIB_FLAG} ${TOP_EXTRA_LIBS}
|
${OA_LIBS} ${ZLIB_FLAG} ${TOP_EXTRA_LIBS}
|
||||||
CLEANS += tclmagic${SHDLIB_EXT} libtclmagic${SHDLIB_EXT}.a proto.magicrc
|
CLEANS += tclmagic${SHDLIB_EXT} libtclmagic${SHDLIB_EXT}.a proto.magicrc
|
||||||
|
|
||||||
|
ifeq (${MAKE_WASM},1)
|
||||||
|
magic: magic.js
|
||||||
|
magic.js: lib${MODULE}.o ${EXTRA_LIBS}
|
||||||
|
@echo --- building main magic WASM
|
||||||
|
${RM} magic.js magic.wasm
|
||||||
|
${CC} ${CFLAGS} ${CPPFLAGS} ${DFLAGS} lib${MODULE}.o ${EXTRA_LIBS} -o magic.js ${LIBS}
|
||||||
|
endif
|
||||||
|
|
||||||
main: magic proto.magicrc
|
main: magic proto.magicrc
|
||||||
|
ifeq (${MAKE_WASM},1)
|
||||||
|
@bash ${MAGICDIR}/toolchains/emscripten/post-build.sh magic.js magic.wasm
|
||||||
|
endif
|
||||||
|
|
||||||
tcl-main: tclmagic${SHDLIB_EXT} proto.magicrc
|
tcl-main: tclmagic${SHDLIB_EXT} proto.magicrc
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* magicWasm.c --
|
||||||
|
*
|
||||||
|
* Headless Emscripten entry points for running Magic without a
|
||||||
|
* terminal-driven event loop.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "utils/main.h"
|
||||||
|
#include "utils/magic.h"
|
||||||
|
#include "utils/paths.h"
|
||||||
|
#include "textio/textio.h"
|
||||||
|
#include "textio/txcommands.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
|
#include "windows/windows.h"
|
||||||
|
#include "graphics/graphics.h"
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <emscripten/emscripten.h>
|
||||||
|
#else
|
||||||
|
#define EMSCRIPTEN_KEEPALIVE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int
|
||||||
|
magicWasmEnsureCadRoot(void)
|
||||||
|
{
|
||||||
|
if (getenv("CAD_ROOT") == NULL)
|
||||||
|
{
|
||||||
|
if (setenv("CAD_ROOT", "/", 0) != 0)
|
||||||
|
{
|
||||||
|
TxError("Failed to set CAD_ROOT for the WASM runtime.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE int
|
||||||
|
magic_wasm_init(void)
|
||||||
|
{
|
||||||
|
static char *argv[] = {
|
||||||
|
"magic",
|
||||||
|
"-d",
|
||||||
|
"null",
|
||||||
|
"-T",
|
||||||
|
"minimum",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
if (magicWasmEnsureCadRoot() != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return magicMainInit(5, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE int
|
||||||
|
magic_wasm_run_command(const char *command)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
|
||||||
|
status = magic_wasm_init();
|
||||||
|
if (status != 0)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
if ((command == NULL) || (*command == '\0'))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Set the current point to the center of the screen so that
|
||||||
|
* WindSendCommand routes the command to the layout window client
|
||||||
|
* (not the window-management border client which handles point 0,0).
|
||||||
|
*/
|
||||||
|
TxSetPoint(GrScreenRect.r_xtop / 2, GrScreenRect.r_ytop / 2,
|
||||||
|
WIND_UNKNOWN_WINDOW);
|
||||||
|
|
||||||
|
return TxDispatchString(command, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE int
|
||||||
|
magic_wasm_source_file(const char *path)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
status = magic_wasm_init();
|
||||||
|
if (status != 0)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
if ((path == NULL) || (*path == '\0'))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
f = PaOpen((char *)path, "r", (char *)NULL, ".", (char *)NULL,
|
||||||
|
(char **)NULL);
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
TxError("Unable to open command file \"%s\".\n", path);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the current point to the centre of the screen so that
|
||||||
|
* WindSendCommand routes all commands from the file to the layout
|
||||||
|
* window client, just as magic_wasm_run_command does for single
|
||||||
|
* commands. Without this, commands arrive with point (0,0) and
|
||||||
|
* end up in the border/windClient context where most commands are
|
||||||
|
* unknown.
|
||||||
|
*/
|
||||||
|
TxSetPoint(GrScreenRect.r_xtop / 2, GrScreenRect.r_ytop / 2,
|
||||||
|
WIND_UNKNOWN_WINDOW);
|
||||||
|
|
||||||
|
TxDispatch(f);
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE void
|
||||||
|
magic_wasm_update(void)
|
||||||
|
{
|
||||||
|
if (magic_wasm_init() == 0)
|
||||||
|
WindUpdate();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
# Magic VLSI — Headless WASM Build
|
||||||
|
|
||||||
|
This toolchain builds Magic as a headless WebAssembly module using Emscripten.
|
||||||
|
X11, Tk, OpenGL, and readline are all disabled. The resulting `magic.js` /
|
||||||
|
`magic.wasm` pair can be loaded in Node.js, a browser, or a Web Worker.
|
||||||
|
|
||||||
|
## Quick start (npm package)
|
||||||
|
|
||||||
|
The easiest way to build and use the WASM module is through the npm package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build magic.js + magic.wasm and copy them into npm/
|
||||||
|
bash npm/build.sh
|
||||||
|
|
||||||
|
# Run the test suite (extract, GDS, DRC, CIF)
|
||||||
|
npm --prefix npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
See [`npm/examples/`](../../npm/examples/) for usage examples.
|
||||||
|
|
||||||
|
## Manual build
|
||||||
|
|
||||||
|
Prerequisites: an activated [emsdk](https://emscripten.org/docs/getting_started/downloads.html)
|
||||||
|
checkout (`emcc`, `emar`, `emranlib` on `PATH`), plus standard `make` and `gcc`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Configure for Emscripten
|
||||||
|
CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1 -g" \
|
||||||
|
emconfigure ./configure \
|
||||||
|
--without-cairo --without-opengl --without-x --without-tk --without-tcl \
|
||||||
|
--disable-readline --disable-compression \
|
||||||
|
--host=asmjs-unknown-emscripten \
|
||||||
|
--target=asmjs-unknown-emscripten
|
||||||
|
|
||||||
|
# 2. Append the Emscripten-specific make settings
|
||||||
|
cat toolchains/emscripten/defs.mak >> defs.mak
|
||||||
|
|
||||||
|
# 3. Build
|
||||||
|
emmake make depend
|
||||||
|
emmake make -j$(nproc) modules libs
|
||||||
|
emmake make techs
|
||||||
|
emmake make mains
|
||||||
|
```
|
||||||
|
|
||||||
|
The outputs are `magic/magic.js` and `magic/magic.wasm`.
|
||||||
|
|
||||||
|
## Embedded files
|
||||||
|
|
||||||
|
The following runtime files are baked directly into the WASM binary via
|
||||||
|
Emscripten's `--embed-file` mechanism and are available at startup without
|
||||||
|
any host filesystem access:
|
||||||
|
|
||||||
|
| Host path | VFS path |
|
||||||
|
|-----------|----------|
|
||||||
|
| `scmos/` | `/magic/sys/current/` |
|
||||||
|
| `windows/windows7.glyphs` | `/magic/sys/windows7.glyphs` |
|
||||||
|
| `windows/windows7.glyphs` | `/magic/sys/bw.glyphs` |
|
||||||
|
|
||||||
|
To embed a custom technology file, add an `--embed-file` entry to
|
||||||
|
`TOP_EXTRA_LIBS` in [`defs.mak`](defs.mak).
|
||||||
|
|
||||||
|
## Exported C API
|
||||||
|
|
||||||
|
The WASM module exports four functions:
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `magic_wasm_init()` | Initialize Magic (idempotent — safe to call multiple times). Returns 0 on success. |
|
||||||
|
| `magic_wasm_run_command(const char *cmd)` | Dispatch one Magic command. Calls `magic_wasm_init()` automatically if needed. Returns 0 on success. |
|
||||||
|
| `magic_wasm_source_file(const char *path)` | Read and execute a command file from the virtual filesystem. |
|
||||||
|
| `magic_wasm_update()` | Drive a display-update cycle. No-op in headless builds (null display suspends all redraws). |
|
||||||
|
|
||||||
|
### JavaScript usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
import createMagic from 'magic-vlsi-wasm';
|
||||||
|
|
||||||
|
const { runCommand, FS } = await createMagic();
|
||||||
|
|
||||||
|
// Write a layout file into the virtual filesystem
|
||||||
|
FS.writeFile('/work/inv.mag', layoutBytes);
|
||||||
|
|
||||||
|
// Run Magic commands
|
||||||
|
runCommand('tech load sky130A');
|
||||||
|
runCommand('load /work/inv');
|
||||||
|
runCommand('gds write /work/inv');
|
||||||
|
|
||||||
|
// Read the result back out
|
||||||
|
const gdsBytes = FS.readFile('/work/inv.gds');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `CAD_ROOT` is automatically set to `/` so that embedded system files are
|
||||||
|
resolved under `/magic/sys/`.
|
||||||
|
- The null display driver (`-d null`) sets `GrDisplayStatus = DISPLAY_SUSPEND`,
|
||||||
|
which causes `WindUpdate` to return immediately without invoking any display
|
||||||
|
callbacks. This is what makes the WASM build safe to run without a screen.
|
||||||
|
- All POSIX signal/timer APIs (`setitimer`, `SIGALRM`, `fcntl`) are compiled
|
||||||
|
out under `__EMSCRIPTEN__`; the display progress timer becomes a no-op.
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# WASM-specific make additions — append to the configure-generated defs.mak.
|
||||||
|
#
|
||||||
|
# Usage (matches what the CI workflow does):
|
||||||
|
#
|
||||||
|
# source <emsdk>/emsdk_env.sh
|
||||||
|
# CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1 -g" \
|
||||||
|
# emconfigure ./configure --without-cairo --without-opengl --without-x \
|
||||||
|
# --without-tk --without-tcl \
|
||||||
|
# --disable-readline --disable-compression \
|
||||||
|
# --target=asmjs-unknown-emscripten
|
||||||
|
# cat toolchains/emscripten/defs.mak >> defs.mak
|
||||||
|
# emmake make depend
|
||||||
|
# emmake make -j$(nproc) modules libs
|
||||||
|
# emmake make techs
|
||||||
|
# emmake make mains
|
||||||
|
|
||||||
|
# Activate the WASM link target in magic/Makefile.
|
||||||
|
MAKE_WASM = 1
|
||||||
|
|
||||||
|
# Emscripten linker flags.
|
||||||
|
# The link step runs from the magic/ subdirectory, so embed-file paths
|
||||||
|
# are relative to that directory (../scmos, ../windows/...).
|
||||||
|
TOP_EXTRA_LIBS += \
|
||||||
|
-sWASM=1 \
|
||||||
|
-sMODULARIZE=1 \
|
||||||
|
-sEXPORT_ES6=1 \
|
||||||
|
-sEXPORTED_FUNCTIONS=_magic_wasm_init,_magic_wasm_run_command,_magic_wasm_source_file,_magic_wasm_update \
|
||||||
|
-sEXPORTED_RUNTIME_METHODS=cwrap,ccall,FS,setValue,getValue \
|
||||||
|
-sALLOW_MEMORY_GROWTH=1 \
|
||||||
|
-sINITIAL_MEMORY=33554432 \
|
||||||
|
-sSTACK_SIZE=5242880 \
|
||||||
|
-sASSERTIONS=1 \
|
||||||
|
-sENVIRONMENT=node,web,worker \
|
||||||
|
-sFORCE_FILESYSTEM=1 \
|
||||||
|
--embed-file ../scmos@/magic/sys/current \
|
||||||
|
--embed-file ../windows/windows7.glyphs@/magic/sys/windows7.glyphs \
|
||||||
|
--embed-file ../windows/windows7.glyphs@/magic/sys/bw.glyphs
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Post-process Emscripten output for Node.js 22+ ES module compatibility.
|
||||||
|
#
|
||||||
|
# Emscripten (3.1.x) produces ES module output that still references
|
||||||
|
# `require()`, `__dirname`, and `__filename` — all of which are undefined
|
||||||
|
# in pure ESM. This script patches magic.js to:
|
||||||
|
# 1. Alias every `require("x")` call to `___cr("x")` (createRequire).
|
||||||
|
# 2. Inject ESM-safe `__filename` / `__dirname` / `require` at module top.
|
||||||
|
# 3. Sync `___emscripten_embedded_file_data` from the .wasm global.
|
||||||
|
#
|
||||||
|
# Usage: post-build.sh <magic.js> <magic.wasm>
|
||||||
|
#
|
||||||
|
# Idempotent — safe to run multiple times.
|
||||||
|
#
|
||||||
|
# WARNING: These patches depend on exact text patterns emitted by a specific
|
||||||
|
# Emscripten version. If you upgrade emsdk, verify the patches still apply
|
||||||
|
# (see .github/workflows/main.yml for the pinned version).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ $# -ne 2 ]; then
|
||||||
|
echo "Usage: $0 <magic.js> <magic.wasm>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
JS=$1
|
||||||
|
WASM=$2
|
||||||
|
|
||||||
|
if [ ! -f "$JS" ] || [ ! -f "$WASM" ]; then
|
||||||
|
echo "Error: $JS or $WASM not found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Portable in-place sed: BSD (macOS) and GNU diverge on -i.
|
||||||
|
# Using a temp file + mv keeps both happy without relying on -i at all.
|
||||||
|
_sed_inplace() {
|
||||||
|
local expr=$1 file=$2
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
sed "$expr" "$file" > "$tmp"
|
||||||
|
mv "$tmp" "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1 & 2. ES module compatibility -----------------------------------------
|
||||||
|
if ! grep -q 'createRequire' "$JS"; then
|
||||||
|
echo "[post-build] Injecting ESM createRequire + __filename/__dirname shims"
|
||||||
|
_sed_inplace 's/require("\([^"]*\)")/___cr("\1")/g' "$JS"
|
||||||
|
_sed_inplace '1s|^|\nimport{createRequire as ___cr}from"module";\nimport{fileURLToPath as ___fup}from"url";\nimport{dirname as ___dn}from"path";\nvar __filename=___fup(import.meta.url);\nvar __dirname=___dn(__filename);\n|' "$JS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure a top-level `require` binding exists (Emscripten's environment probe
|
||||||
|
# does `typeof require == "function"`, which would otherwise be "undefined").
|
||||||
|
# Skip if Emscripten already emits one (newer versions do).
|
||||||
|
if ! grep -qE 'var[[:space:]]+require[[:space:]]*=' "$JS" \
|
||||||
|
&& grep -q 'import{createRequire as ___cr}from"module";' "$JS"; then
|
||||||
|
echo "[post-build] Adding top-level require binding"
|
||||||
|
_sed_inplace 's|import{createRequire as ___cr}from"module";|import{createRequire as ___cr}from"module";var require=___cr(import.meta.url);|' "$JS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 3. Sync ___emscripten_embedded_file_data from .wasm --------------------
|
||||||
|
# Emscripten sometimes bakes a stale constant into magic.js after an
|
||||||
|
# incremental rebuild. Read the true value from the WASM global section
|
||||||
|
# (section id 6, global index 1, i32.const opcode 0x41, signed LEB128).
|
||||||
|
python3 - "$JS" "$WASM" <<'PY'
|
||||||
|
import sys, re
|
||||||
|
|
||||||
|
js_path, wasm_path = sys.argv[1], sys.argv[2]
|
||||||
|
data = open(wasm_path, 'rb').read()
|
||||||
|
|
||||||
|
def read_uleb(buf, pos):
|
||||||
|
val = shift = 0
|
||||||
|
while True:
|
||||||
|
b = buf[pos]; pos += 1
|
||||||
|
val |= (b & 0x7f) << shift
|
||||||
|
shift += 7
|
||||||
|
if not (b & 0x80):
|
||||||
|
return val, pos
|
||||||
|
|
||||||
|
def read_sleb(buf, pos):
|
||||||
|
val = shift = 0
|
||||||
|
while True:
|
||||||
|
b = buf[pos]; pos += 1
|
||||||
|
val |= (b & 0x7f) << shift
|
||||||
|
shift += 7
|
||||||
|
if not (b & 0x80):
|
||||||
|
if b & 0x40:
|
||||||
|
val |= (~0) << shift
|
||||||
|
return val & 0xffffffff, pos
|
||||||
|
|
||||||
|
# Walk sections looking for section id 6 (Global)
|
||||||
|
pos = 8 # skip magic + version
|
||||||
|
actual = None
|
||||||
|
while pos < len(data):
|
||||||
|
sid = data[pos]; pos += 1
|
||||||
|
size, pos = read_uleb(data, pos)
|
||||||
|
end = pos + size
|
||||||
|
if sid == 6:
|
||||||
|
count, p = read_uleb(data, pos)
|
||||||
|
for gi in range(count):
|
||||||
|
p += 2 # valtype + mutability
|
||||||
|
opcode = data[p]; p += 1
|
||||||
|
if opcode == 0x41: # i32.const
|
||||||
|
val, p = read_sleb(data, p)
|
||||||
|
if gi == 1:
|
||||||
|
actual = val
|
||||||
|
break
|
||||||
|
p += 1 # skip end opcode
|
||||||
|
break
|
||||||
|
pos = end
|
||||||
|
|
||||||
|
if actual is None:
|
||||||
|
sys.stderr.write(
|
||||||
|
'[post-build] WARN: could not parse global index 1 from wasm; '
|
||||||
|
'skipping ___emscripten_embedded_file_data sync\n')
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
js = open(js_path).read()
|
||||||
|
m = re.search(r'Module\[.___emscripten_embedded_file_data.\]=(\d+)', js)
|
||||||
|
if not m:
|
||||||
|
# Emscripten may have fixed the stale-constant bug in a newer version.
|
||||||
|
# Not fatal, but worth surfacing so we can drop this patch eventually.
|
||||||
|
sys.stderr.write(
|
||||||
|
'[post-build] INFO: ___emscripten_embedded_file_data not present; '
|
||||||
|
'sync patch no longer applies (likely fixed upstream)\n')
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
old = int(m.group(1))
|
||||||
|
if old != actual:
|
||||||
|
print(f'[post-build] Fixing ___emscripten_embedded_file_data: {old} -> {actual}')
|
||||||
|
js = js.replace(
|
||||||
|
f'Module["___emscripten_embedded_file_data"]={old}',
|
||||||
|
f'Module["___emscripten_embedded_file_data"]={actual}',
|
||||||
|
)
|
||||||
|
open(js_path, 'w').write(js)
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "[post-build] Done."
|
||||||
26
utils/main.c
26
utils/main.c
|
|
@ -161,6 +161,8 @@ global char *MainMouseFile = NULL;
|
||||||
global char *MainDisplayType = NULL;
|
global char *MainDisplayType = NULL;
|
||||||
global char *MainMonType = NULL;
|
global char *MainMonType = NULL;
|
||||||
|
|
||||||
|
static bool MagicIsInitialized = FALSE;
|
||||||
|
|
||||||
|
|
||||||
/* Copyright notice for the binary file. */
|
/* Copyright notice for the binary file. */
|
||||||
global char *MainCopyright = "\n--- MAGIC: Copyright (C) 1985, 1990 "
|
global char *MainCopyright = "\n--- MAGIC: Copyright (C) 1985, 1990 "
|
||||||
|
|
@ -1286,13 +1288,29 @@ magicMain(argc, argv)
|
||||||
{
|
{
|
||||||
int rstatus;
|
int rstatus;
|
||||||
|
|
||||||
if ((rstatus = mainInitBeforeArgs(argc, argv)) != 0) MainExit(rstatus);
|
if ((rstatus = magicMainInit(argc, argv)) != 0) MainExit(rstatus);
|
||||||
if ((rstatus = mainDoArgs(argc, argv)) != 0) MainExit(rstatus);
|
|
||||||
if ((rstatus = mainInitAfterArgs()) != 0) MainExit(rstatus);
|
|
||||||
if ((rstatus = mainInitFinal()) != 0) MainExit(rstatus);
|
|
||||||
TxDispatch( (FILE *) NULL);
|
TxDispatch( (FILE *) NULL);
|
||||||
mainFinished();
|
mainFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
magicMainInit(argc, argv)
|
||||||
|
int argc;
|
||||||
|
char *argv[];
|
||||||
|
{
|
||||||
|
int rstatus;
|
||||||
|
|
||||||
|
if (MagicIsInitialized)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if ((rstatus = mainInitBeforeArgs(argc, argv)) != 0) return rstatus;
|
||||||
|
if ((rstatus = mainDoArgs(argc, argv)) != 0) return rstatus;
|
||||||
|
if ((rstatus = mainInitAfterArgs()) != 0) return rstatus;
|
||||||
|
if ((rstatus = mainInitFinal()) != 0) return rstatus;
|
||||||
|
|
||||||
|
MagicIsInitialized = TRUE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ extern Transform RootToEditTransform;
|
||||||
|
|
||||||
extern void MainExit(int) ATTR_NORETURN; /* a way of exiting that cleans up after itself */
|
extern void MainExit(int) ATTR_NORETURN; /* a way of exiting that cleans up after itself */
|
||||||
extern void magicMain();
|
extern void magicMain();
|
||||||
|
extern int magicMainInit(int argc, char *argv[]);
|
||||||
|
|
||||||
/* C99 compat */
|
/* C99 compat */
|
||||||
extern int mainInitBeforeArgs();
|
extern int mainInitBeforeArgs();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue