@ai-ecoverse/slicc-kernel 1.100.0 → 1.100.2

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
@@ -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`), 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`.
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, and a forked child's thread-locals (`errno` included) where the parent left them: the kernel reruns the module's `__wasm_init_tls` at the parent's TLS base (and puts back the block it overwrites), which also sets the internal globals that hold thread-local addresses (a position-independent program's, a side module's, and those of a variable in the `global-dynamic` TLS model), 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`).
@@ -298,6 +298,7 @@ On top of preview1 a WASI program gets:
298
298
  - **Waiting:**
299
299
  - `how` 0 blocks; 1 tries (`EAGAIN`); 2 waits until `deadline_ns` on `clock` (0 `CLOCK_REALTIME`, as `sem_timedwait`; 1 `CLOCK_MONOTONIC`, as `sem_clockwait`).
300
300
  - A timed wait gives `ETIMEDOUT`, never early, also when the deadline has already passed. A negative deadline is `EINVAL`.
301
+ - A wait stopped by job control (`SIGSTOP`, `^Z`) goes on after `SIGCONT`, and a timed one still ends at its deadline: the time spent stopped counts, and the wait isn't restarted with the whole timeout.
301
302
  - A wait blocks only its own thread, and a signal handler ends it with `EINTR`.
302
303
  - **Posting:** `sem_post` gives the unit to the longest-waiting thread, or adds it to the value; at the maximum it's `EOVERFLOW`.
303
304
  - **Lifetime:**
@@ -824,7 +825,7 @@ A character device (`0666`), there in every `users` mode, through which programs
824
825
 
825
826
  ### `/proc`
826
827
 
827
- Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `/proc/self` is a link to the `/proc/<pid>` of the process that reads it, as on Linux (`readlink /proc/self` is its pid, the one it was exec'd under included). They come from the kernel's process table when they are opened, so a terminal sees the processes of every client. A process that exits while a program reads the `/proc` directory doesn't fail or cut short the listing: its pid is still listed, and opening its files fails with `ENOENT`, which `ps`, `pgrep` and `pidof` skip, as on Linux; an exec'd program shows under the pid its parent knows. `/proc/uptime`, `/proc/loadavg`, `/proc/stat` and `/proc/meminfo` are there too, and `/proc/mounts`. `/proc/version` names the kernel, Linux-shaped, with the slicc-kernel version that built it: `Linux version 6.0.0-slicc (slicc-kernel <version>) #1 SMP PREEMPT_DYNAMIC`. `/proc/sys/kernel/osrelease` is `6.0.0-slicc` and `/proc/sys/kernel/ostype` is `Linux`. The version is the one npm publishes, set when the package is built (`0.0.0-dev` when run from the sources). A JS program reads it as `ctx.kernel.version`. A process with no parent in the kernel (one a page, a client or the Node entry started) has parent 1, as children of init do, in `getppid()`, `/proc/<pid>/stat` and `ps` alike, and so does a process whose parent has ended, waited for or not (`getppid()` asks the kernel each time; a shell's `$PPID` keeps the value it started with, as on Linux). Pids, parents, process groups, sessions, command lines, terminals, start and boot times are real, and so are the credentials in `status` (`Uid:`, `Gid:`, `Groups:`; a process with a tag, see `kernel.run`, also has `Tag:\t<tag>` after `Gid:`, shown to root and to the same uid only, with newlines, tabs and carriage returns written `\n`, `\t` and `\r`) and each process's memory: the size of its wasm memory (`VSZ` and `RSS` alike, as wasm has no paging), which `/proc/meminfo` counts as used. CPU times are measured per process, its threads and the images it exec'd included: time a worker spends waiting for the kernel's answer is system time (at most 20 ms of each call; the rest of a longer one, such as a write into a full pipe, counts as blocked), time it spends blocked (a read, a `poll` or `select` that waits, a sleep, `wait`, a futex, while stopped) is neither, from the moment the call actually starts to wait (a call that could wait but answers at once, such as a `select` with a zero timeout or a ready descriptor, a `waitpid` with `WNOHANG`, or a futex wait whose value has already changed, is system time), and the rest is user time; a process's times stop when it ends, a call still in flight then adds nothing, so a loop that never calls the kernel shows as user time while it runs; they show in `/proc/<pid>/stat`, `ps` and `ps()`, and the times of the children a process has waited for (theirs and their own waited-for children's, as POSIX counts them) show as its `cutime` and `cstime` in `/proc/<pid>/stat`; a stopped process is `T` there, in `/proc/<pid>/status` (`T (stopped)`) and in `ps()`; a WASI or WASIX program's `CLOCK_PROCESS_CPUTIME_ID` and `CLOCK_THREAD_CPUTIME_ID` (`clock_time_get`) count only the time its thread runs, its own waits and blocked kernel calls left out (a sleep is not CPU time), per thread for now, so a threaded program's process clock is its calling thread's; and `/proc/stat` counts every process of the kernel, the reaped ones included, and `/proc/loadavg` averages the processes not blocked, sampled every 5 seconds. A JS program's time isn't measured yet. `MemTotal` (from `navigator.deviceMemory`, else 4 GiB) is a placeholder. A process that isn't root sees only its own user's processes (see [Users](#users)). WASI and WASIX programs see the same `/proc`, read-only (opening a file in it for writing, or linking one of its files elsewhere, fails with `EROFS`), from every thread, also through a symlink that points into it (where every change, `mkdir`, `rename`, `unlink`, `chmod`, a hard link from it or into it, and so on, fails too: with `EROFS` when the root file system has a physical `/proc`, which stays hidden and untouched, and otherwise with the `ENOENT` of the missing directory, since that check is only paid for when a physical `/proc` exists; with one, a symlink whose target passes `..` after a file or a missing name, such as `/f/../proc`, fails with `ENOTDIR` or `ENOENT`, as on Linux), and `/` lists it, so a WASI program gets a preopen for it; `/proc/self` is the program's own pid there too (the pid an exec'd program took over). JS programs read the same `/proc` through `ctx.fs` and `ctx.open` (see [JS programs](#js-programs)). Neither has `/proc/<pid>/fd` yet.
828
+ Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `/proc/self` is a link to the `/proc/<pid>` of the process that reads it, as on Linux (`readlink /proc/self` is its pid, the one it was exec'd under included). They come from the kernel's process table when they are opened, so a terminal sees the processes of every client. A process that exits while a program reads the `/proc` directory doesn't fail or cut short the listing: its pid is still listed, and opening its files fails with `ENOENT`, which `ps`, `pgrep` and `pidof` skip, as on Linux; an exec'd program shows under the pid its parent knows. `/proc/uptime`, `/proc/loadavg`, `/proc/stat` and `/proc/meminfo` are there too, and `/proc/mounts`. `/proc/version` names the kernel, Linux-shaped, with the slicc-kernel version that built it: `Linux version 6.0.0-slicc (slicc-kernel <version>) #1 SMP PREEMPT_DYNAMIC`. `/proc/sys/kernel/osrelease` is `6.0.0-slicc` and `/proc/sys/kernel/ostype` is `Linux`. The version is the one npm publishes, set when the package is built (`0.0.0-dev` when run from the sources). A JS program reads it as `ctx.kernel.version`. A process with no parent in the kernel (one a page, a client or the Node entry started) has parent 1, as children of init do, in `getppid()`, `/proc/<pid>/stat` and `ps` alike, and so does a process whose parent has ended, waited for or not (`getppid()` asks the kernel each time; a shell's `$PPID` keeps the value it started with, as on Linux). Pids, parents, process groups, sessions, command lines, terminals, start and boot times are real, and so are the credentials in `status` (`Uid:`, `Gid:`, `Groups:`; a process with a tag, see `kernel.run`, also has `Tag:\t<tag>` after `Gid:`, shown to root and to the same uid only, with newlines, tabs and carriage returns written `\n`, `\t` and `\r`) and each process's memory: the size of its wasm memory (`VSZ` and `RSS` alike, as wasm has no paging), which `/proc/meminfo` counts as used. CPU times are measured per process, its threads and the images it exec'd included: time a worker spends waiting for the kernel's answer is system time (at most 20 ms of each call; the rest of a longer one, such as a write into a full pipe, counts as blocked), time it spends blocked (a read, a `poll` or `select` that waits, a sleep, `wait`, a futex, while stopped) is neither, from the moment the call actually starts to wait (a call that could wait but answers at once, such as a `select` with a zero timeout or a ready descriptor, a `waitpid` with `WNOHANG`, or a futex wait whose value has already changed, is system time), and the rest is user time; a process's times stop when it ends, a call still in flight then adds nothing, so a loop that never calls the kernel shows as user time while it runs; they show in `/proc/<pid>/stat`, `ps` and `ps()`, and the times of the children a process has waited for (theirs and their own waited-for children's, as POSIX counts them) show as its `cutime` and `cstime` in `/proc/<pid>/stat`; a stopped process is `T` there, in `/proc/<pid>/status` (`T (stopped)`) and in `ps()`; a WASI or WASIX program's `CLOCK_PROCESS_CPUTIME_ID` and `CLOCK_THREAD_CPUTIME_ID` (`clock_time_get`) count only the time its thread runs, its own waits and blocked kernel calls left out (a sleep is not CPU time), per thread for now, so a threaded program's process clock is its calling thread's; and `/proc/stat` counts every process of the kernel, the reaped ones included, and `/proc/loadavg` averages the processes not blocked, sampled every 5 seconds. A JS program's time isn't measured yet. `MemTotal` (from `navigator.deviceMemory`, else 4 GiB) is a placeholder. A process that isn't root sees only its own user's processes (see [Users](#users)). WASI and WASIX programs see the same `/proc`, read-only (opening a file in it for writing, or linking one of its files elsewhere, fails with `EROFS`), from every thread, also through a symlink that points into it (where every change, `mkdir`, `rename`, `unlink`, `chmod`, a hard link from it or into it, and so on, fails too: with `EROFS` when the root file system has a physical `/proc`, which stays hidden and untouched, and otherwise with the `ENOENT` of the missing directory, since that check is only paid for when a physical `/proc` exists; with one, a symlink whose target passes `..` after a file or a missing name, such as `/f/../proc`, fails with `ENOTDIR` or `ENOENT`, as on Linux), and `/` lists it, so a WASI program gets a preopen for it; `/proc/self` is the program's own pid there too (the pid an exec'd program took over). WASI and WASIX programs also have `/proc/<pid>/task/<tid>/stat` for each of a process's threads (the main thread is tid 1), whose state is `S` while the thread is blocked in the kernel (a `sem_wait`, a blocking read, a sleep, a futex) and `R` otherwise (`T` and `Z` as for the process). JS programs read the same `/proc` through `ctx.fs` and `ctx.open` (see [JS programs](#js-programs)). Neither has `/proc/<pid>/fd` yet.
828
829
 
829
830
  The kernel writes `/etc/passwd` and `/etc/group` when it starts (see [Users](#users)). While the root has none of them, or no `/etc/mtab`, reading them gets per-process ones: root is uid 0 and the realm user, uid 1000, is named after `USER`, or `web_user` (Emscripten's default) without one, so it matches `$USER`, with `HOME` as its home, so `id`, `whoami`, `ls -l` and `ps` show names. Nothing is written to OPFS, and a real file there wins.
830
831
 
@@ -897,7 +898,7 @@ Releases are cut by semantic-release on every push to `main`, in two jobs, and o
897
898
 
898
899
  - **plan:** works out the version, the previous tag, the commit and the base (`base.mjs`). The base is the newest earlier release whose `cert-review` commit status is `success`, or, until there is one, the release `base.json` names (the cert lane's last line). CI never reads the npm `certified` dist-tag.
899
900
  - **delta** (`delta.mjs`): lists the pull requests merged between the two tags (their merge commits, plus `pr` for a planted branch). Each gets its conventional-commit kind (`fix`, `feat`, `perf` and `revert` are behaviour changes) and the integration test files and titles it adds or changes. The titles are read from the pull request's patch: the added `test(…)` lines and the hunk headers git puts above changed lines. Escapes are decoded as JavaScript does, and a title git cut short, or a template literal, matches as a prefix.
900
- - **suite** (`suite.mjs`, `reporter.mjs`) runs on the bench pool, without secrets. It takes the tag's tree with its own dependencies and Chromium, and replaces `dist/` with the **published npm tarball** (for a planted branch, its own `npm pack`).
901
+ - **suite** (`suite.mjs`, `reporter.mjs`) runs on the bench pool, without secrets. It takes the tag's tree with its own dependencies and Chromium, and replaces `dist/` with the **published npm tarball** (for a planted branch, its own `npm pack`). The test command runs in a session of its own (`detached`), and `suite.mjs` notes a `SIGINT` (`ignored` in its summary) instead of dying of it. That way a stray signal to the step's process group can't cut the run short, while a `SIGTERM` (a real cancel) still ends it.
901
902
  - The release job runs the release's whole `test/integration/` suite, then the delta's test files again on the **previous release's dist** (the NEGATIVE).
902
903
  - The base job runs the base release's own suite on its own published dist.
903
904
  - Each run uses the tree's own `npm test` command, with a reporter that records every test and subtest with its status and error.
@@ -1000,16 +1001,22 @@ Each side prints `pattern` (a static array's damage), `low` (bytes of low memory
1000
1001
 
1001
1002
  **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`.
1002
1003
 
1004
+ ### `forktls`
1005
+
1006
+ Two builds of one C program (`forktls.c` with `tlsvars.c`), a position-independent one (`bin/forktls-pie.wasm`, `WASIXCC_PIC=yes`) and a static one (`bin/forktls-static.wasm`), plus a side module with a thread-local (`tlsside.c`, `lib/libtlsside.so`), for `test/integration/fork-tls.test.mjs` (#728). Before and after a fork, parent and child call `close` on a descriptor that isn't open and print `errno` (read directly and through a pointer), then bump one thread-local in the `local-exec` TLS model and one in `global-dynamic` (`tls_model` attributes, since wasixcc drops `-ftls-model`), and the side module's, when the program is given its path to `dlopen`. A position-independent program reaches `errno` through an internal global the linker sets with the TLS base, as a `global-dynamic` variable does in either build; a forked child that didn't set it again read `errno` as 0 and those variables at the wrong address.
1007
+
1008
+ **Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with homescoop's pinned `wasixcc` 0.4.7 and binaryen's `wasm-opt` 133 on `PATH` (the same toolchain rebuilds `bin/piefork.wasm` byte for byte). `bin/forktls-pie.wasm` is 816,066 bytes, sha256 `c5ed6b06036f4d3c42c660fc13ca1a995d647f11021829f2169567574254fd9e`; `bin/forktls-static.wasm` is 45,564 bytes, sha256 `5df019d5f2a4432132c8b7c6f5992d18ecf9d8e4c4be21351acd59c46c3ca48d`; `lib/libtlsside.so` is 736 bytes, sha256 `ecd32e561c051576a99e87caee5e1aca5238c4055e2e551065fa10fe7476d0b5`.
1009
+
1003
1010
  ### `semtest`
1004
1011
 
1005
1012
  A WASIX C program for `test/integration/semaphore.test.mjs` (#717). It calls the `slicc.sem_*` imports directly (`import_module("slicc")`), so the rows don't wait for the libc side. `semtest MODE` runs one of:
1006
1013
  - `handoff`, `inherit`, `spawn` (a spawned and an exec'd image open the semaphore by name; exec drops the handles);
1007
- - `try`, `timed`, `eintr`;
1014
+ - `try`, `timed`, `eintr`, `stopped` (a forked child's 300 ms timed wait is stopped for 200 ms and continued, and reports whether it ended at the deadline);
1008
1015
  - `threads` (FIFO, one thread blocked at a time);
1009
1016
  - `errors`, `unlink`, `owners setup|user`;
1010
1017
  - `killed` (a waiter killed before posts loses no unit), `exited`, `cap`, `handles`.
1011
1018
 
1012
- **Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 (legacy exceptions, `-pthread`) and binaryen's `wasm-opt` (version 132, `--asyncify` for `proc_fork`) on `PATH`. `bin/semtest.wasm` is 59,187 bytes, sha256 `c2091c18d799881721bb8e0e2fc7269732d52d6cd545f4f57266e8e8864c9ee6`.
1019
+ **Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 (legacy exceptions, `-pthread`) and binaryen's `wasm-opt` (version 132, `--asyncify` for `proc_fork`) on `PATH`. `bin/semtest.wasm` is 68,438 bytes, sha256 `af3d38d87b63fa219bae90b1225376882ba20ace423c5ce24ef57935e80e2fde`.
1013
1020
 
1014
1021
  ### `wlocktest`
1015
1022
 
@@ -4821,7 +4821,7 @@ async function runTagged(l, self, requested, tag) {
4821
4821
  }
4822
4822
  function processes(l, self) {
4823
4823
  const viewer = credOf(self);
4824
- return l.list(viewer).map(({ tid: _, cred = viewer, umask: _umask, ...entry }) => ({
4824
+ return l.list(viewer).map(({ tid: _, cred = viewer, umask: _umask, threads: _threads, ...entry }) => ({
4825
4825
  ...entry,
4826
4826
  rss: entry.memory,
4827
4827
  uid: cred.euid,
@@ -11593,6 +11593,13 @@ var ProcessClocks = class {
11593
11593
  running() {
11594
11594
  return this.enabled ? this.all().filter((c) => c.running()).length : 0;
11595
11595
  }
11596
+ states() {
11597
+ const state = (c) => c.running() ? "R" : "S";
11598
+ return [
11599
+ { tid: 1, state: state(this.main) },
11600
+ ...[...this.threads].map(([tid, c]) => ({ tid, state: state(c) }))
11601
+ ];
11602
+ }
11596
11603
  timed(clock, dispatch2) {
11597
11604
  const c = clock ?? this.main;
11598
11605
  const block = () => c.block();
@@ -12107,7 +12114,7 @@ var PATH_OPS = new Set(SYNC_FS_OPS);
12107
12114
  var EARLY_WHOLE_FILE = 64 * 1024;
12108
12115
 
12109
12116
  // src/version.ts
12110
- var KERNEL_VERSION = true ? "1.100.0" : "0.0.0-dev";
12117
+ var KERNEL_VERSION = true ? "1.100.2" : "0.0.0-dev";
12111
12118
  var OS_RELEASE = "6.0.0-slicc";
12112
12119
  function procVersion() {
12113
12120
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -12167,11 +12174,11 @@ function pages(bytes) {
12167
12174
  function ticks(ms) {
12168
12175
  return Math.max(0, Math.floor(ms * HZ / 1e3));
12169
12176
  }
12170
- function pidStat(info, boot) {
12177
+ function pidStat(info, boot, state = info.state) {
12171
12178
  const fields = [
12172
12179
  info.pid,
12173
12180
  `(${commOf(info)})`,
12174
- info.state,
12181
+ state,
12175
12182
  info.ppid,
12176
12183
  info.pgid,
12177
12184
  info.sid,
@@ -12209,8 +12216,19 @@ function pidStat(info, boot) {
12209
12216
  `;
12210
12217
  }
12211
12218
  var sigMask = (mask = 0) => (mask >>> 1).toString(16).padStart(16, "0");
12212
- var STATES = { S: "S (sleeping)", T: "T (stopped)", Z: "Z (zombie)" };
12219
+ var STATES = {
12220
+ S: "S (sleeping)",
12221
+ T: "T (stopped)",
12222
+ Z: "Z (zombie)"
12223
+ };
12213
12224
  var tagLine = (tag) => `Tag: ${tag.replaceAll("\n", "\\n").replaceAll(" ", "\\t").replaceAll("\r", "\\r")}`;
12225
+ function taskStat(info, tid, boot) {
12226
+ const thread = (info.threads ?? [{ tid: 1, state: info.state }]).find((t) => t.tid === tid);
12227
+ return thread && pidStat({ ...info, pid: tid }, boot, thread.state);
12228
+ }
12229
+ function taskIds(info) {
12230
+ return (info.threads ?? [{ tid: 1 }]).map((t) => t.tid);
12231
+ }
12214
12232
  function pidStatus(info) {
12215
12233
  const state = STATES[info.state];
12216
12234
  const c = info.cred ?? ROOT;
@@ -12388,8 +12406,10 @@ var ProcTree = class {
12388
12406
  content: () => systemFile(top, this.listing(), Date.now(), this.memory)
12389
12407
  };
12390
12408
  }
12391
- if (!/^[1-9]\d*$/.test(top) || rest2.length > 0 || !this.alive(Number(top))) return void 0;
12392
- if (leaf === void 0) return { kind: "dir", names: () => [...PID_NAMES] };
12409
+ if (!/^[1-9]\d*$/.test(top) || !this.alive(Number(top))) return void 0;
12410
+ if (leaf === "task") return this.task(Number(top), rest2);
12411
+ if (rest2.length > 0) return void 0;
12412
+ if (leaf === void 0) return { kind: "dir", names: () => [...PID_NAMES, "task"] };
12393
12413
  if (!PID_NAMES.includes(leaf)) return void 0;
12394
12414
  return {
12395
12415
  kind: "file",
@@ -12401,6 +12421,28 @@ var ProcTree = class {
12401
12421
  }
12402
12422
  };
12403
12423
  }
12424
+ info(pid) {
12425
+ return this.listing().processes.find((p) => p.pid === pid);
12426
+ }
12427
+ task(pid, rest2) {
12428
+ const [tid, leaf, ...more] = rest2;
12429
+ const tids = () => taskIds(this.info(pid));
12430
+ if (tid === void 0) return { kind: "dir", names: () => tids().map(String) };
12431
+ if (!/^[1-9]\d*$/.test(tid) || more.length > 0 || !tids().includes(Number(tid)))
12432
+ return void 0;
12433
+ if (leaf === void 0) return { kind: "dir", names: () => ["stat"] };
12434
+ if (leaf !== "stat") return void 0;
12435
+ return {
12436
+ kind: "file",
12437
+ content: () => {
12438
+ const listing = this.listing();
12439
+ const info = listing.processes.find((p) => p.pid === pid);
12440
+ const line = info && taskStat(info, Number(tid), listing.boot);
12441
+ if (line === void 0) throw syncError("ENOENT", `/proc/${pid}/task/${tid}/stat`);
12442
+ return line;
12443
+ }
12444
+ };
12445
+ }
12404
12446
  parts(path, follow2) {
12405
12447
  const parts2 = path.split("/").filter((p) => p !== "").slice(1);
12406
12448
  if (parts2[0] === "self" && (follow2 || parts2.length > 1)) parts2[0] = String(this.pid);
@@ -14547,6 +14589,13 @@ var SEM_NAMES_MAX = 4096;
14547
14589
  var SEM_HANDLES_MAX = 256;
14548
14590
  var NAME_MAX = 251;
14549
14591
  var TIMER_MAX_MS = 2147483647;
14592
+ var deadlines = /* @__PURE__ */ new WeakMap();
14593
+ function timeLeft(req, timeoutMs) {
14594
+ const now = performance.now();
14595
+ const deadline = deadlines.get(req) ?? now + timeoutMs;
14596
+ deadlines.set(req, deadline);
14597
+ return Math.max(0, deadline - now);
14598
+ }
14550
14599
  var SEM_OPS = [
14551
14600
  "sem-open",
14552
14601
  "sem-close",
@@ -14733,8 +14782,9 @@ async function semSyscall(req, proc) {
14733
14782
  const sem = handles.get(req.handle);
14734
14783
  if (sem.take()) return ok2();
14735
14784
  if (req.how === "try") throw new KernelError("EAGAIN");
14736
- if (req.how === "timed" && req.timeoutMs === 0) throw new KernelError("ETIMEDOUT");
14737
- const got = await sem.wait(proc.blocking(), req.how === "block" ? -1 : req.timeoutMs);
14785
+ const timeoutMs = req.how === "block" ? -1 : timeLeft(req, req.timeoutMs);
14786
+ if (timeoutMs === 0) throw new KernelError("ETIMEDOUT");
14787
+ const got = await sem.wait(proc.blocking(), timeoutMs);
14738
14788
  if (!got) throw new KernelError("ETIMEDOUT");
14739
14789
  return ok2();
14740
14790
  }
@@ -17388,8 +17438,14 @@ function spawnWasmProcess(opts) {
17388
17438
  umask: () => process.umask,
17389
17439
  cred: () => copyCred(process.cred),
17390
17440
  writesBack: () => opts.program.abi === "wasi" && !done4,
17441
+ ...clockViews(clocks)
17442
+ };
17443
+ }
17444
+ function clockViews(clocks) {
17445
+ return {
17391
17446
  times: () => clocks.times(),
17392
- running: () => clocks.running()
17447
+ running: () => clocks.running(),
17448
+ threads: () => clocks.states()
17393
17449
  };
17394
17450
  }
17395
17451
 
@@ -21808,6 +21864,7 @@ var Launcher = class {
21808
21864
  ...described2,
21809
21865
  started: (this.described.get(root.pid) ?? described2).started,
21810
21866
  state: this.stateOf(member.pid),
21867
+ ...this.threadsOf(member.pid),
21811
21868
  memory: this.processes.get(member.pid)?.memory() ?? 0,
21812
21869
  ...this.timesOf(member, members),
21813
21870
  ...this.childrenOf(root.pid),
@@ -21819,6 +21876,10 @@ var Launcher = class {
21819
21876
  }
21820
21877
  return listed;
21821
21878
  }
21879
+ threadsOf(pid) {
21880
+ const threads = this.stateOf(pid) === "S" ? this.processes.get(pid)?.threads?.() : void 0;
21881
+ return threads ? { threads } : {};
21882
+ }
21822
21883
  sudo(pid, call, signal) {
21823
21884
  const caller = {
21824
21885
  cred: this.credOf(pid),