@ai-ecoverse/wasm-emscripten
Emscripten 6.0.9-git for the SLICC wasm realm. Launchers run emcc.py
(and friends) on @ai-ecoverse/wasix-python (python3 -S) with EM_CONFIG
pointing at this package's config.
emcc does not run on slicc-kernel yet (exec of the glue by path, and no
node), see slicc-kernel#390. Since 6.0.9-18 the companions are found beside this package (npm, slicc'sipk add -g, pnpm's virtual store) instead of only under/shared/lib/node_modules.
Companions
| Package | Role |
|---|---|
@ai-ecoverse/wasix-python | Python that runs em*.py |
@ai-ecoverse/wasm-clang | LLVM_ROOT (clang / wasm-ld) |
@ai-ecoverse/wasm-binaryen | BINARYEN_ROOT (wasm-opt, …) |
@ai-ecoverse/emscripten-cache | Read-only prebuilt sysroot/ |
[email protected] | SLICC node ESM/TLA transpile (compiler.mjs, …) |
[email protected] | Fallback transpile path when esbuild is unavailable |
acorn / acorn-import-phases | JS optimizer (acorn-optimizer.mjs) |
Default link (wasm realm)
Executable links (not -c / -r / -shared) inject homescoop_em_cli_ldflags
(-sENVIRONMENT=web,worker,node -sEXIT_RUNTIME=1 -sALLOW_MEMORY_GROWTH=1 -sFORCE_FILESYSTEM=1) and
lib/slicc/{slicc_spawn,slicc_exec,slicc_signals,slicc_select,slicc_libc_gaps,slicc_jobs}.o
and the archives lib/slicc/libslicc_signals.a (kill),
lib/slicc/libslicc_select.a (poll, pselect),
lib/slicc/libslicc_libc_gaps.a (nanosleep and the 8 set*id),
lib/slicc/libslicc_jobs.a (tcgetpgrp, tcsetpgrp) and
lib/slicc/libslicc_realpath.a,
with the JS libraries lib/slicc/{slicc-writev,slicc-linkat,slicc-uname,slicc-env}.js.
slicc-env.js (since 6.0.9-27) gives a program only the environment the
kernel passes: none of Emscripten's placeholders (USER/LOGNAME=web_user,
HOME=/home/web_user, PATH=/, PWD=/), so under env -i they are unset.
Opt out with SLICC_EMCC_PLAIN=1 for plain web glue.
-sMAIN_MODULE=1 and -sLINKABLE=1 links (since 6.0.9-30, homescoop#1678)
wrap every input in --whole-archive, so all of libc.a is linked, and
every slicc override of a libc function would be a duplicate symbol. Those
links take lib/slicc/linkable/*.o instead: the same six objects compiled
with -fPIC (a main module needs it), with kill, poll, pselect,
nanosleep, setuid, setgid, seteuid, setegid, setreuid,
setregid, setresuid, setresgid, tcgetpgrp and tcsetpgrp renamed
out (-D<name>=slicc_linkable_<name>, so the shim's own calls inside an
object still reach its version), and without the override archives. Those links
get libc's versions of these functions and musl's realpath (ELOOP at 40
symlink follows): no kernel signals through kill, no kernel poll,
pselect or credentials, and tcgetpgrp/tcsetpgrp answer 0 and
EINVAL on a terminal. Everything else in the slicc defaults (spawn, exec,
the signal mask and raise, uname, the environment, the kernel's process
groups and sessions) stays. -sMAIN_MODULE=2 and -sSIDE_MODULE are not
affected: the first links like any executable, the second gets no slicc
defaults. Every -sMAIN_MODULE link needs the -fPIC system libraries in sysroot/lib/wasm32-emscripten/pic; emscripten-cache
ships them from 6.0.9-7 (homescoop#1699), so these links need nothing built
and work from a read-only global install. With emscripten-cache 6.0.9-6
(wasm-emscripten 6.0.9-30) emcc tried to build them into the package and
failed with PermissionError.
lib/slicc is compiled from homescoop's slicc shims (shims/slicc,
Apache-2.0); THIRD-PARTY-NOTICES.md carries that licence.
lib/slicc/slicc_main_envp.o (make: -Dmain=slicc_tool_main) and
lib/slicc/slicc_fork.o + lib/slicc/slicc-fork.js (needs ASYNCIFY) are
shipped but not auto-linked. Fork opt-in:
emcc … $SLICC_EM_LIBDIR/slicc_fork.o \
--js-library $SLICC_EM_LIBDIR/slicc-fork.js \
-sASYNCIFY -sASYNCIFY_STACK_SIZE=1048576 \
-sEXPORTED_RUNTIME_METHODS=FS,ENV,callMain,sliccRunMain,sliccForkChild
Root $PKG/emcc (cmake toolchain) and $PKG/bin/emcc are the same self-sufficient launcher.
CACHE model
EM_CACHE=$XDG_CACHE_HOME/emscriptenor~/.cache/emscripten(writable, user-owned)- On first use,
bin/em-ensure-cachecreates$EM_CACHE/sysrootas a symlink into@ai-ecoverse/emscripten-cache/sysroot— no 100MB copy - It also copies
stamps/sysroot_install.stampas a real file into$EM_CACHEso emcc never reinstalls headers through the symlink js_output/andsymbol_lists/warm in the user dir (surviveipkreinstall)FROZEN_CACHE = Falseso warm caches work;--clear-cacheskips a symlinked sysroot and the stamp- Override with
EM_CACHEif needed
SLICC patches (September + homescoop)
- Wasm-realm CLI link defaults — see above; optional
SLICC_VFS_PRE_JSonly when set (node-realm) - Configure patch — autoconf/conftest skips
NODERAWFSunder CLI defaults ac_cv_buildseeding —emconfiguresetsac_cv_build=x86_64-pc-linux-gnu(and host/target) soconfig.guessis not required in-realm- Closure unsupported —
-sCLOSURE=1/--closurefail with: Closure Compiler is not available in SLICC's Emscripten package - Cache erase — will not delete a symlinked
sysroot execvpe—tools/utils.pypassesos.environso emconfigure/emmakekeepCC=emcc
Package-only patches (package-patches/, since 6.0.9-27)
On top of the shared patches/ (which scripts/emscripten-tree.sh also
applies for emscripten-cache, llvm-emscripten, wasm-clang, wasm-binaryen and
cmake), this package ships five patches from package-patches/ and one from
package-patches/libc/. They are kept
out of patches/ so they don't change the published glue of those packages
without their own release:
-
0014, the symlink bound (homescoop#611):
FS.lookupPathallows 40 symlink follows, as Linux does, and the 41st isELOOP. Before it, a path of exactly 40 follows wasELOOPin every program this emcc linked. -
0015,
slicc-env.jsin the default link (homescoop#1531), ashomescoop_em_cli_ldflagshas it. -
0016,
libslicc_realpath.ain the default link (homescoop#1631, #1664): the default link addslib/slicc/libslicc_realpath.a, an archive whose one member,slicc_realpath.o, is musl'srealpath.cwith libc patch 0017, compiled with libc's flags. It comes after the program's own inputs and beforelibc.a, so the linker pulls it only whenrealpathis still undefined, and thenlibc.a's ownrealpathmember is never pulled in; emscripten-cache's prebuiltlibc.astays as it is. A program that defines its ownrealpath(in its sources, an object or an archive or-lon its command line) keeps it, as it did before 6.0.9-28. 6.0.9-28 addedslicc_realpath.oas a plain object, which is always linked, so such a program gotduplicate symbol: realpath(#1664).SLICC_EMCC_PLAIN=1links skip the archive and get musl's bound. -
0018, the override archives and
-sMAIN_MODULE=1(homescoop#1678): the default link addslib/slicc/libslicc_jobs.abesidelibslicc_realpath.a, and for-sMAIN_MODULE=1and-sLINKABLElinks takeslib/slicc/linkable/<obj>for each default object and leaves both archives out.slicc_jobs.ois compiled withtcgetpgrpandtcsetpgrprenamed out;libslicc_jobs.ahas one member per override,slicc_tcgetpgrp.oandslicc_tcsetpgrp.o, eachslicc_jobs.cwith every other global renamed, so the linker pulls each only while its symbol is undefined, and a program's owntcsetpgrpwins. libc'stcsetpgrpis not enough: it goes through emscripten'sTIOCSPGRPioctl, which isEINVALon every terminal and never reaches the kernel. build.sh fails if a member defines a global the base object also defines, or if alinkableobject still defines one of libc's. -
0019, the other override archives (homescoop#1724): the default link also adds
lib/slicc/libslicc_signals.a,libslicc_select.aandlibslicc_libc_gaps.a.slicc_signals.o,slicc_select.oandslicc_libc_gaps.oare compiled with the 12 libc functions they override renamed<object>_<name>(slicc_signals_kill,slicc_select_poll,slicc_libc_gaps_setuid, …). Each archive member,slicc_<name>.o, is a one-line function<name>that calls that renamed definition, so the linker pulls a member only while the program leaves<name>undefined, and a program that defines its ownkill,poll,pselect,nanosleeporset*idlinks and runs its own; until 6.0.9-30 those 12 were strong definitions in the always-linked objects, so it gotduplicate symbol. The members depend only on the always-linked objects, never on each other, so a program's ownpollleaves the sliccpselectin place. The shims' own calls (slicc_exec.ocallskill) go through the symbol, so they reach the program's version when it has one. build.sh fails if a plain object still defines an override, or a member defines anything but its own symbol. -
libc/0017, musl's realpath bound (homescoop#1631): musl stops at
++cnt == SYMLOOP_MAX, sorealpath()of a path of exactly 40 symlink follows wasELOOPeven after 0014 fixedopen()andstat(). 0017 makes itcnt++ == SYMLOOP_MAX: 40 follows resolve and the 41st isELOOP, as in glibc.
build.sh applies 0014-0016, 0018 and 0019 to the shipped tree and to the tree it links its
PRESTAGE programs with. package-patches/libc/ is applied to the shipped
tree only, so the PRESTAGE host tree builds the same upstream libc.a that
emscripten-cache ships; PRESTAGE then checks open, stat and realpath at 40
and 41 follows (absolute and relative chains) with the default link, and
that without libslicc_realpath.a realpath is still ELOOP at 40. wasm-clang, wasm-binaryen and cmake pick them up at
their own next release, or a later wave folds them into patches/.
Since 6.0.9-29 PRESTAGE also links programs that define their own
realpath: in the program's source, in a later object, in an archive and
through -l. Each must link with the default link and run its own
realpath. It also checks that 6.0.9-28's shape, slicc_realpath.o as a plain object
beside a program's own realpath, still gives duplicate symbol: realpath
(homescoop#1664).
Since 6.0.9-30 PRESTAGE links jobs.c (realpath, uname, tcgetpgrp(0) and
tcsetpgrp(0, getpid())) with the default link (tcgetpgrp self tcsetpgrp 0 ok; without libslicc_jobs.a it is libc's 0 and EINVAL), with
-sMAIN_MODULE=1 and with -sLINKABLE=1, and runs each; the last two must
answer libc's 0 and EINVAL and export slicc_linkable_kill, which the
default link's wasm must not have. It links own-tc.c, which defines its
own tcsetpgrp, and runs its own. 6.0.9-29's lib/slicc (slicc_jobs.o
with both overrides, no linkable/) must give duplicate symbol: tcsetpgrp
for own-tc.c and fail the -sMAIN_MODULE=1 and -sLINKABLE=1 links
(homescoop#1678).
Since 6.0.9-31 PRESTAGE links own-libc.c, which calls all 12 of kill,
poll, pselect, nanosleep and the 8 set*id, with a --pre-js
stand-in kernel, and prints for each whether the slicc, libc or the
program's own version answered. The default link must print slicc for
all 12 (and libc for all 12 without the three archives). Defining any
one of them in the program's source, or poll and nanosleep in its own
-l archive, must link and print own for those and slicc for the rest.
With 6.0.9-30's plain objects each of the 12 gives duplicate symbol: <name> (homescoop#1724).
priv.c checks that the privilege drop really happens: as root, with a
stand-in kernel that keeps credentials, setgid(5), setresuid(6, 0, 6)
and setuid(5) must change what getgid, getresuid, getuid and
geteuid report. Without libslicc_libc_gaps.a, libc's stubs return -1
and leave every id at 0, so the row fails.
Commands
emcc, em++, emar, emranlib, emcmake, emconfigure, emmake, embuilder, em-config, emscan-deps
emcc hello.c -o hello.js # runnable in the wasm realm
emcmake cmake -S . -B build
emconfigure ./configure --host=wasm32-unknown-emscripten