@ai-ecoverse/slicc-kernel 1.48.3 → 1.49.0

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 CHANGED
@@ -187,6 +187,7 @@ On top of preview1 a WASI program gets:
187
187
 
188
188
  - **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; `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.
189
189
  - **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`.
190
+ - **Foreign calls** (libffi's WASIX backend, so Python's `ctypes`): `call_dynamic` calls a function through the program's indirect function table (a static module's exported `__indirect_function_table`, or the dynamic linker's), its arguments and results packed in memory one after another (`i32`/`f32` 4 bytes, `i64`/`f64` 8). With `strict` set, the buffers must fit the function exactly (else `EINVAL`); without it, missing arguments are zero and results that do not fit are left out. A trap or exception of the callee goes through to the caller. JS cannot ask a function for its type, so the kernel reads the types from the module files (the program's and each side module's, once, when a program first calls through `call_dynamic`): a table slot that is empty, past the end, holds a function of no module the kernel knows of, or takes or returns a `v128`, gives `EINVAL`. For closures (callbacks such as `CFUNCTYPE`), a dynamically linked program gets table slots from `closure_allocate`, handed out by the kernel so that the program's threads, each with its own copy of the table, never pick the same one, and `closure_free` gives them back for reuse; a static program gets `ENOTSUP`. `closure_prepare`, which puts a function into such a slot, is not there yet and answers `ENOSYS`. A `fork` or a `longjmp` (both through Asyncify) inside a function reached through `call_dynamic` cannot unwind through it.
190
191
  - **Job control:** `^Z`, `SIGTSTP`, `SIGTTIN` and `SIGTTOU` stop a WASIX program at once, unless it has a handler for them. A stopped program that is busy computing is stopped for job control (`jobs` shows it, `fg`, `bg` and `SIGCONT` work), but its worker keeps a core busy until its next system call, since a worker's running code cannot be frozen from outside. Agents should `kill` such a job rather than leave it stopped. For the same reason, a signal with a handler or a terminating default (`^C`, `SIGTERM`) reaches a program that is busy computing only at its next system call, so a CPU-bound loop may not end on `^C`; `kill -9` always ends it. A thread blocked in the kernel or waiting on a lock (`pthread_cond_wait`, a futex) is woken by a signal for it (a futex wait checks for signals every 50 ms, so each waiting thread wakes briefly that often), and `pthread_kill` runs the handler on the thread it names.
191
192
  - **Pipes** have no file type for WASI and WASIX programs' `fstat` (`st_mode` type 0), because WASI has no FIFO file type. A socket type, as kernels 1.47.3 to 1.48.1 gave them, made Ruby treat a `popen` pipe as a socket (`close_write` then never ends the child's input). The kernel's `slicc_fs.fd_mode` answers `S_IFIFO | 0600` (Linux numbering) for a pipe, and wasix-sysroot 2025.9.30-21 and later take the type from it, so programs built on it see a FIFO (`S_ISFIFO`); older libcs keep only the permission bits. Until then Python's asyncio refuses a pipe for a subprocess (`Pipe transport is only for pipes, sockets and character devices`). They are read and written as pipes; socket calls on them fail with `ENOTSOCK`, and `isatty()` is false. Emscripten programs, bash included, see a FIFO. The same holds for any kernel descriptor that is neither seekable nor a terminal or device, such as captured output.
192
193
  - **Sockets**: the WASIX socket calls (`sock_open`, `sock_pair` for an `AF_UNIX` stream pair, `bind`, `listen`, `connect`, `accept`, options, local and peer names) are the kernel's loopback sockets, and preview1's `sock_recv` / `sock_send` / `sock_shutdown` work on them and on inherited ones. Writing to a socket whose peer is gone fails with `EPIPE`, through `fd_write` as through `sock_send`, as if `MSG_NOSIGNAL` were set, since a WASI program cannot ignore `SIGPIPE`; a pipe or terminal whose reader is gone still ends the program with status 141. HTTP and HTTPS leave through the realm proxy that `https_proxy` names, as for any other program; `resolve` answers `/etc/hosts` names and literal addresses, and the [uplink](#uplink)'s names.
@@ -9410,6 +9410,8 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
9410
9410
  "proc-cred",
9411
9411
  "proc-setcred",
9412
9412
  "dl-log",
9413
+ "dl-closure",
9414
+ "dl-closure-free",
9413
9415
  "fd-renumber",
9414
9416
  "fd-promote",
9415
9417
  "fd-open-tty",
@@ -10026,6 +10028,11 @@ var WasmProcess = class {
10026
10028
  case "dl-log":
10027
10029
  if (req.append) this.dlLog.push(req.append);
10028
10030
  return { ok: true, kind: "json", json: this.dlLog.slice(req.from) };
10031
+ case "dl-closure":
10032
+ return { ok: true, kind: "json", json: this.allocateClosure(req.floor) };
10033
+ case "dl-closure-free":
10034
+ this.freeClosure(req.index);
10035
+ return { ok: true, kind: "void" };
10029
10036
  case "sig-mask":
10030
10037
  this.caught = req.caught;
10031
10038
  this.ignored = req.ignored;
@@ -10062,6 +10069,18 @@ var WasmProcess = class {
10062
10069
  }
10063
10070
  alarm;
10064
10071
  dlLog = [];
10072
+ closures = { next: 0, free: [], live: /* @__PURE__ */ new Set() };
10073
+ allocateClosure(floor) {
10074
+ const index = this.closures.free.pop() ?? Math.max(floor, this.closures.next);
10075
+ this.closures.next = Math.max(this.closures.next, index + 1);
10076
+ this.closures.live.add(index);
10077
+ this.dlLog.push({ kind: "closure", index });
10078
+ return index;
10079
+ }
10080
+ freeClosure(index) {
10081
+ if (!this.closures.live.delete(index)) throw new KernelError("EINVAL");
10082
+ this.closures.free.push(index);
10083
+ }
10065
10084
  asyncOps = new AsyncOps(
10066
10085
  (req, cancelled) => this.syscall(req, cancelled),
10067
10086
  () => this.options.onAsync?.()
@@ -12228,6 +12247,7 @@ var Launcher = class {
12228
12247
  module,
12229
12248
  ...memory ? { memory } : {},
12230
12249
  foreign,
12250
+ path: target.wasm,
12231
12251
  ...names ? { names } : {},
12232
12252
  ...imports !== void 0 ? { imports } : {},
12233
12253
  ...target.preopenRoot ? { preopenRoot: true } : {}