@ai-ecoverse/slicc-kernel 1.98.3 → 1.98.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/kernel-worker.js +1 -1
- package/dist/node-process-worker.js +27 -9
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +1 -1
- package/dist/process-worker.js +27 -9
- package/dist/process-worker.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -261,7 +261,7 @@ An empty path is `ENOENT` in every path call (`path_open`, `path_filestat_get`,
|
|
|
261
261
|
On top of preview1 a WASI program gets:
|
|
262
262
|
|
|
263
263
|
- **Threads**: `wasi.thread-spawn` (wasm32-wasip1-threads) and WASIX `thread_spawn_v2` start a worker per thread on the process's shared memory, sized from the module's own import (a 2 GiB maximum when the engine cannot reserve more). The threads share one descriptor table, and a file written, created, renamed or removed in one thread shows its new size and metadata to `stat` in every other thread at once, under any of its names (a symlink, a hard link, a new name after a rename in the same process), also when another thread renames a file over that name meanwhile, since the kernel reports which file an open or a change reached; this holds among the kernel's own processes, not against other tabs, `hostfs` writers or programs that aren't WASI; another process's changes show at close and after a `spawn` or `wait`, as with close-to-open caching, so a file another process renames while this one holds it open may show its old size under the new name until then; `exit` or a trap in any thread ends the process. At most 64 threads per process; `SLICC_WASM_THREADS` sets another cap, up to 256.
|
|
264
|
-
- **WASIX** (a module importing `wasix_32v1`): fork and setjmp/longjmp through Asyncify (the module is built with `wasm-opt --asyncify`), exec, `posix_spawn`, `waitpid` (with `WNOHANG`, and `WUNTRACED` for a child that stops; WASIX has no `WCONTINUED`), pipes, `dup2`, signal handlers and interval timers, terminal modes, and dynamic linking: a position-independent main module loads side modules with `dlopen` from `LD_LIBRARY_PATH`, its runtime path, `/lib`, `/usr/lib` and `/usr/local/lib`. Each thread has its own copy of the function table, so the kernel hands out its slots (a side module's range, a `dlsym`'d function, a closure): threads loading or looking up at the same time never get the same slot. As in glibc, one `dlopen` at a time runs in a process, constructors included: a `dlopen` from a library's constructor goes ahead in its own thread, and another thread's `dlopen` waits, then finds the library loaded and gets the same handle. A thread waiting there still ends or stops with its process, but a signal handler it has runs once that `dlopen` returns. The lock is let go when a `dlopen` fails (an exception, a trap or `pthread_exit` out of a constructor included) and when its thread ends; a forked child starts with it free. A forked child also gets the side modules its parent had loaded, at the same addresses and table slots, with the slot and closure counters and the parent's thread-local blocks: they are linked again in the child before it resumes, without running their constructors again, since their data is already in the copied memory. A side-module function on the stack when `fork` is called (a callback into the main module that forks) is not instrumented by Asyncify, so it runs again when the stack is rewound, in the parent and in the child, and libffi frames there can trap; side modules would need building with Asyncify for that, which the toolchain does not do yet. In such a program, `dlopen(NULL)`, and `dlopen` of the program's own file (also by a path that resolves to it through symlinks), give a handle for the program that is already running (no second copy is loaded); a program that isn't position-independent gets an error from `dlopen(NULL)`, as from any `dlopen`; `dlsym` on it searches the program, then the loaded libraries, and `dlclose` of it does nothing. A function already in the table (the program's own, or a library's) keeps its slot, so `dlsym(handle, "strlen") == strlen`.
|
|
264
|
+
- **WASIX** (a module importing `wasix_32v1`): fork and setjmp/longjmp through Asyncify (the module is built with `wasm-opt --asyncify`), with the unwound frames kept in the unused part of the program's stack, below its stack pointer (for a position-independent program, the stack the kernel's linker gives it), so a fork from deep in the stack leaves the program's data alone, exec, `posix_spawn`, `waitpid` (with `WNOHANG`, and `WUNTRACED` for a child that stops; WASIX has no `WCONTINUED`), pipes, `dup2`, signal handlers and interval timers, terminal modes, and dynamic linking: a position-independent main module loads side modules with `dlopen` from `LD_LIBRARY_PATH`, its runtime path, `/lib`, `/usr/lib` and `/usr/local/lib`. Each thread has its own copy of the function table, so the kernel hands out its slots (a side module's range, a `dlsym`'d function, a closure): threads loading or looking up at the same time never get the same slot. As in glibc, one `dlopen` at a time runs in a process, constructors included: a `dlopen` from a library's constructor goes ahead in its own thread, and another thread's `dlopen` waits, then finds the library loaded and gets the same handle. A thread waiting there still ends or stops with its process, but a signal handler it has runs once that `dlopen` returns. The lock is let go when a `dlopen` fails (an exception, a trap or `pthread_exit` out of a constructor included) and when its thread ends; a forked child starts with it free. A forked child also gets the side modules its parent had loaded, at the same addresses and table slots, with the slot and closure counters and the parent's thread-local blocks: they are linked again in the child before it resumes, without running their constructors again, since their data is already in the copied memory. A side-module function on the stack when `fork` is called (a callback into the main module that forks) is not instrumented by Asyncify, so it runs again when the stack is rewound, in the parent and in the child, and libffi frames there can trap; side modules would need building with Asyncify for that, which the toolchain does not do yet. In such a program, `dlopen(NULL)`, and `dlopen` of the program's own file (also by a path that resolves to it through symlinks), give a handle for the program that is already running (no second copy is loaded); a program that isn't position-independent gets an error from `dlopen(NULL)`, as from any `dlopen`; `dlsym` on it searches the program, then the loaded libraries, and `dlclose` of it does nothing. A function already in the table (the program's own, or a library's) keeps its slot, so `dlsym(handle, "strlen") == strlen`.
|
|
265
265
|
- **epoll and eventfd** (WASIX `epoll_create`, `epoll_ctl`, `epoll_wait` and `fd_event`, which tokio's reactor uses through the WASIX mio fork):
|
|
266
266
|
- **Where they live:** an epoll instance and an eventfd are kernel descriptors, so they survive `dup`, `fork` and `exec` (unless close-on-exec) and are shared by a process's threads.
|
|
267
267
|
- **What epoll watches:** sockets (listening, connecting, connected, loopback and uplink), pipes, terminals, `kernel.run`'s stdin, eventfds and other epoll instances (a cycle of instances is `ELOOP`).
|
|
@@ -919,6 +919,20 @@ WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh
|
|
|
919
919
|
- `wasixcc` (0.4.7, WASIX clang 21.1.2) comes from `PATH`, or is installed by `install-wasixcc.sh` as for `dlfork`.
|
|
920
920
|
- The build is reproducible: `bin/epolltest.wasm` is 78,799 bytes, sha256 `9ff8d336a261ebb9076b5b9c760e9ba50fa17053b1d66ab89cfe05395c8e1deb`.
|
|
921
921
|
|
|
922
|
+
### `piefork`
|
|
923
|
+
|
|
924
|
+
A position-independent C program, built like `dlfork` (wasixcc with `WASIXCC_PIC=yes` and legacy exceptions, against `@ai-ecoverse/wasix-sysroot` 2025.9.30-28, then `wasm-opt --asyncify` with `asyncify-imports@wasix_32v1.proc_fork`). It's the fixture of `test/integration/pie-fork.test.mjs` (#708).
|
|
925
|
+
|
|
926
|
+
What it does:
|
|
927
|
+
1. It recurses 300 frames, each holding stack buffers.
|
|
928
|
+
2. At the bottom it snapshots the first 64 KiB of memory, from address 16, where the linker puts its data, and forks.
|
|
929
|
+
3. Parent and child then compare that memory with the snapshot and scribble 64 KiB of stack.
|
|
930
|
+
4. On the way back up, each frame checks its own buffers.
|
|
931
|
+
|
|
932
|
+
Each side prints `pattern` (a static array's damage), `low` (bytes of low memory changed) and `frames` (damaged frames).
|
|
933
|
+
|
|
934
|
+
**Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 and binaryen's `wasm-opt` (version 132) on `PATH`. `bin/piefork.wasm` is 823,983 bytes, sha256 `0477598c71b02356145d1725548ee0f2b9e53ca0d43c7349f17acc2ff12b59f8`.
|
|
935
|
+
|
|
922
936
|
## License
|
|
923
937
|
|
|
924
938
|
Apache-2.0
|
package/dist/kernel-worker.js
CHANGED
|
@@ -12082,7 +12082,7 @@ var PATH_OPS = new Set(SYNC_FS_OPS);
|
|
|
12082
12082
|
var EARLY_WHOLE_FILE = 64 * 1024;
|
|
12083
12083
|
|
|
12084
12084
|
// src/version.ts
|
|
12085
|
-
var KERNEL_VERSION = true ? "1.98.
|
|
12085
|
+
var KERNEL_VERSION = true ? "1.98.4" : "0.0.0-dev";
|
|
12086
12086
|
var OS_RELEASE = "6.0.0-slicc";
|
|
12087
12087
|
function procVersion() {
|
|
12088
12088
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -4591,7 +4591,7 @@ var KERNEL_VERSION, OS_RELEASE;
|
|
|
4591
4591
|
var init_version = __esm({
|
|
4592
4592
|
"src/version.ts"() {
|
|
4593
4593
|
"use strict";
|
|
4594
|
-
KERNEL_VERSION = true ? "1.98.
|
|
4594
|
+
KERNEL_VERSION = true ? "1.98.4" : "0.0.0-dev";
|
|
4595
4595
|
OS_RELEASE = "6.0.0-slicc";
|
|
4596
4596
|
}
|
|
4597
4597
|
});
|
|
@@ -13276,16 +13276,18 @@ function mutableGlobals(exports) {
|
|
|
13276
13276
|
}
|
|
13277
13277
|
return out;
|
|
13278
13278
|
}
|
|
13279
|
-
var UNWINDING, REWINDING, AsyncifyDriver;
|
|
13279
|
+
var STACK_POINTER, UNWINDING, REWINDING, AsyncifyDriver;
|
|
13280
13280
|
var init_wasix_fork = __esm({
|
|
13281
13281
|
"src/process/wasi/wasix-fork.ts"() {
|
|
13282
13282
|
"use strict";
|
|
13283
13283
|
init_wasi_abi();
|
|
13284
|
+
STACK_POINTER = "__stack_pointer";
|
|
13284
13285
|
UNWINDING = 1;
|
|
13285
13286
|
REWINDING = 2;
|
|
13286
13287
|
AsyncifyDriver = class {
|
|
13287
13288
|
exports = {};
|
|
13288
13289
|
stackUpper = 0;
|
|
13290
|
+
linked;
|
|
13289
13291
|
pending;
|
|
13290
13292
|
answer = 0;
|
|
13291
13293
|
snapshots = /* @__PURE__ */ new Map();
|
|
@@ -13294,10 +13296,12 @@ var init_wasix_fork = __esm({
|
|
|
13294
13296
|
constructor(mem) {
|
|
13295
13297
|
this.mem = mem;
|
|
13296
13298
|
}
|
|
13299
|
+
useStack(pointer, low) {
|
|
13300
|
+
this.linked = { pointer, low };
|
|
13301
|
+
}
|
|
13297
13302
|
bind(exports) {
|
|
13298
13303
|
this.exports = exports;
|
|
13299
|
-
|
|
13300
|
-
this.stackUpper = sp instanceof WebAssembly.Global ? sp.value : 0;
|
|
13304
|
+
this.stackUpper = this.stackPointer();
|
|
13301
13305
|
}
|
|
13302
13306
|
get supported() {
|
|
13303
13307
|
return typeof this.exports.asyncify_start_unwind === "function";
|
|
@@ -13332,7 +13336,7 @@ var init_wasix_fork = __esm({
|
|
|
13332
13336
|
}
|
|
13333
13337
|
fork(fork) {
|
|
13334
13338
|
const sp = this.stackPointer();
|
|
13335
|
-
const globals =
|
|
13339
|
+
const globals = this.globals();
|
|
13336
13340
|
return this.unwind((data) => {
|
|
13337
13341
|
this.answer = fork(data, globals, sp);
|
|
13338
13342
|
});
|
|
@@ -13343,7 +13347,7 @@ var init_wasix_fork = __esm({
|
|
|
13343
13347
|
v.setBigUint64(snapPtr, BigInt(retPtr), true);
|
|
13344
13348
|
v.setBigUint64(snapPtr + 8, BigInt(id), true);
|
|
13345
13349
|
v.setBigUint64(snapPtr + 16, 0x51cc5117n, true);
|
|
13346
|
-
const globals =
|
|
13350
|
+
const globals = this.globals();
|
|
13347
13351
|
return this.unwind((data) => {
|
|
13348
13352
|
const end = this.mem.view().getUint32(data, true);
|
|
13349
13353
|
this.snapshots.set(id, { frames: this.mem.bytes(data + 8, end - data - 8).slice(), globals });
|
|
@@ -13361,11 +13365,24 @@ var init_wasix_fork = __esm({
|
|
|
13361
13365
|
this.answer = Number(val) || 1;
|
|
13362
13366
|
});
|
|
13363
13367
|
}
|
|
13368
|
+
stackGlobal() {
|
|
13369
|
+
const g = this.exports[STACK_POINTER];
|
|
13370
|
+
return g instanceof WebAssembly.Global ? g : this.linked?.pointer;
|
|
13371
|
+
}
|
|
13364
13372
|
stackPointer() {
|
|
13365
|
-
|
|
13366
|
-
|
|
13373
|
+
return this.stackGlobal()?.value ?? 0;
|
|
13374
|
+
}
|
|
13375
|
+
globals() {
|
|
13376
|
+
const out = mutableGlobals(this.exports);
|
|
13377
|
+
if (this.linked && !out.some(([name2]) => name2 === STACK_POINTER)) {
|
|
13378
|
+
out.push([STACK_POINTER, this.linked.pointer.value]);
|
|
13379
|
+
}
|
|
13380
|
+
return out;
|
|
13367
13381
|
}
|
|
13368
13382
|
dataPointer() {
|
|
13383
|
+
if (this.linked && !(this.exports[STACK_POINTER] instanceof WebAssembly.Global)) {
|
|
13384
|
+
return this.linked.low + 15 & ~15;
|
|
13385
|
+
}
|
|
13369
13386
|
const g = this.exports.__data_end;
|
|
13370
13387
|
const dataEnd = g instanceof WebAssembly.Global ? g.value : 0;
|
|
13371
13388
|
return dataEnd < this.stackUpper ? dataEnd + 15 & ~15 : 16;
|
|
@@ -13385,7 +13402,7 @@ var init_wasix_fork = __esm({
|
|
|
13385
13402
|
}
|
|
13386
13403
|
restoreGlobals(globals) {
|
|
13387
13404
|
for (const [name2, value2] of globals) {
|
|
13388
|
-
const g = this.exports[name2];
|
|
13405
|
+
const g = name2 === STACK_POINTER ? this.stackGlobal() : this.exports[name2];
|
|
13389
13406
|
if (g instanceof WebAssembly.Global) g.value = value2;
|
|
13390
13407
|
}
|
|
13391
13408
|
}
|
|
@@ -15135,6 +15152,7 @@ async function instantiate(host, module, memory, threads, {
|
|
|
15135
15152
|
}
|
|
15136
15153
|
}
|
|
15137
15154
|
if (sync) bindLinker(host, sync, instance, { thread, forked, path });
|
|
15155
|
+
if (sync && !thread) driver.useStack(sync.linker.stackPointer, sync.linker.stackLow);
|
|
15138
15156
|
return { instance, driver };
|
|
15139
15157
|
}
|
|
15140
15158
|
function bindLinker(host, sync, instance, at) {
|