@ai-ecoverse/slicc-kernel 1.100.0 → 1.100.1

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`).
@@ -824,7 +824,7 @@ A character device (`0666`), there in every `users` mode, through which programs
824
824
 
825
825
  ### `/proc`
826
826
 
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.
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). 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
828
 
829
829
  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
830
 
@@ -1000,6 +1000,12 @@ Each side prints `pattern` (a static array's damage), `low` (bytes of low memory
1000
1000
 
1001
1001
  **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
1002
 
1003
+ ### `forktls`
1004
+
1005
+ 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.
1006
+
1007
+ **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`.
1008
+
1003
1009
  ### `semtest`
1004
1010
 
1005
1011
  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:
@@ -1009,7 +1015,7 @@ A WASIX C program for `test/integration/semaphore.test.mjs` (#717). It calls the
1009
1015
  - `errors`, `unlink`, `owners setup|user`;
1010
1016
  - `killed` (a waiter killed before posts loses no unit), `exited`, `cap`, `handles`.
1011
1017
 
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`.
1018
+ **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,078 bytes, sha256 `44178ed41ec8499c3c562b559b961438db135ca0914cc38c12316b8ab2d4e3f1`.
1013
1019
 
1014
1020
  ### `wlocktest`
1015
1021
 
@@ -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.1" : "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);
@@ -17388,8 +17430,14 @@ function spawnWasmProcess(opts) {
17388
17430
  umask: () => process.umask,
17389
17431
  cred: () => copyCred(process.cred),
17390
17432
  writesBack: () => opts.program.abi === "wasi" && !done4,
17433
+ ...clockViews(clocks)
17434
+ };
17435
+ }
17436
+ function clockViews(clocks) {
17437
+ return {
17391
17438
  times: () => clocks.times(),
17392
- running: () => clocks.running()
17439
+ running: () => clocks.running(),
17440
+ threads: () => clocks.states()
17393
17441
  };
17394
17442
  }
17395
17443
 
@@ -21808,6 +21856,7 @@ var Launcher = class {
21808
21856
  ...described2,
21809
21857
  started: (this.described.get(root.pid) ?? described2).started,
21810
21858
  state: this.stateOf(member.pid),
21859
+ ...this.threadsOf(member.pid),
21811
21860
  memory: this.processes.get(member.pid)?.memory() ?? 0,
21812
21861
  ...this.timesOf(member, members),
21813
21862
  ...this.childrenOf(root.pid),
@@ -21819,6 +21868,10 @@ var Launcher = class {
21819
21868
  }
21820
21869
  return listed;
21821
21870
  }
21871
+ threadsOf(pid) {
21872
+ const threads = this.stateOf(pid) === "S" ? this.processes.get(pid)?.threads?.() : void 0;
21873
+ return threads ? { threads } : {};
21874
+ }
21822
21875
  sudo(pid, call, signal) {
21823
21876
  const caller = {
21824
21877
  cred: this.credOf(pid),