threads(M6): getCurrentId, docs, and CI wiring — threading built

New thread_self=42 syscall backs runtime.Thread.getCurrentId (the calling
thread's kernel task id). thread-test gains an id mode: two workers read
getCurrentId and the main thread confirms all three ids are non-zero and
distinct. Per-thread threadlocal TLS is deferred by design (no consumer; it would
need context-switched fs.base for an unused feature), as are RwLock/WaitGroup.

Marks the threading feature built (M1-M6): threading.md + docs/README.md status
updated, all thread-* cases wired into qemu_test.py.

Gate thread-id PASS; full suite green: 21/21 (thread-spawn/join/futex/mutex/id,
aspace-refcount, + 15 guardrail cases), zig build clean, zig build test green.
This commit is contained in:
2026-07-20 21:55:13 +01:00
parent 1b33f48acd
commit def34e71fc
9 changed files with 149 additions and 25 deletions
+7 -6
View File
@@ -104,12 +104,13 @@ Start with the north star:
port to **one seam** (`std.os.danos`), so we build `runtime.os` (→ that seam) plus a
thin `runtime.fs`, retire the `posix` shim, and follow a phased path to
`zig build-exe hello.zig` running on danos — **not** Linux-ABI emulation.
- **[threading.md](threading.md) — threads, the std-shaped way.** A design note (not
built yet) on `runtime.Thread`: a type that mirrors `std.Thread`'s API (spawn/join,
Mutex/Condition) over a **private** thread ABI — several tasks sharing one address
space via a `thread_spawn` syscall, futex-backed blocking, aspace refcounting. Why
it's the native type and not literal `std.Thread` (the [private ABI](syscall.md)),
and why threads stay a narrow opt-in against the [resilience](resilience.md) default.
- **[threading.md](threading.md) — threads, the std-shaped way.** **Built** (M1–M6):
`runtime.Thread` mirrors `std.Thread`'s API (spawn/join/detach, Mutex/Condition/
Semaphore) over a **private** thread ABI — several tasks sharing one address space via
a `thread_spawn` syscall, futex-backed blocking, aspace refcounting. Why it's the
native type and not literal `std.Thread` (the [private ABI](syscall.md)), and why
threads stay a narrow opt-in against the [resilience](resilience.md) default. Build
plan + gates: [threading-plan.md](threading-plan.md).
- **[vdso.md](vdso.md) — the vDSO, the public system-call boundary.** A design note
(not built yet) on keeping `abi.zig` genuinely private: a kernel-supplied, C-ABI
entry blob mapped into every process as the *only* way into the kernel — so the
+30 -12
View File
@@ -240,20 +240,38 @@ green.
> `thread-mutex` gate exercises them under real concurrency instead; a host-side mock is
> future work.
## M6 — TLS, `getCurrentId` polish, docs, and CI wiring
## M6 — `getCurrentId`, docs, and CI wiring ✅
- [ ] Per-thread TLS: `thread_spawn` sets `fs.base` to a runtime-allocated per-thread
block; enough for `getCurrentId` and a `threadlocal` slot. Only expanded if a
consumer needs full `threadlocal`.
- [ ] `RwLock` / `WaitGroup` if a consumer wants them (else deferred).
- [ ] All `thread-*` cases wired into [test/qemu_test.py](../test/qemu_test.py);
threading.md status line updated to "built"; a short worked example in the doc.
- [ ] `-Dtest-case=thread-tls`: two threads read `getCurrentId` (distinct) and each
writes its own `threadlocal` slot without cross-talk.
- [x] `getCurrentId` via a small `thread_self = 42` syscall (`runtime.Thread.getCurrentId`
returns the kernel task id). **Per-thread `threadlocal` TLS is deferred** — no
consumer needs it, and it would require context-switching `fs.base` per task (real
kernel + per-switch cost) for an unused feature; threaded binaries have run fine
without it through M2–M5. threading.md's TLS reasoning already scoped it as
deferred-unless-needed. When a consumer appears, the shape is: `thread_spawn`
allocates a per-thread TLS block, sets `fs.base`, and the context switch saves/
restores it.
- [x] `RwLock` / `WaitGroup` deferred (no consumer yet); they slot onto the same
`Futex`/`Mutex`/`Condition` when wanted.
- [x] All `thread-*` cases wired into [test/qemu_test.py](../test/qemu_test.py)
(`thread-spawn`/`-join`/`-futex`/`-mutex`/`-id`); threading.md + docs/README.md
status updated to **built**; the worked example is threading.md's win-condition.
- [x] `-Dtest-case=thread-id` (`smp: 4`): two workers read `getCurrentId`; the main
thread confirms all three ids are non-zero and distinct — each thread has its own
kernel identity. (Renamed from `thread-tls`, which implied `threadlocal`.)
**Gate:** `python3 test/qemu_test.py thread-tls` logs `thread: per-thread id + tls ok`;
the whole `thread-*` suite plus the full guardrail set pass; default `zig build` is
clean and `zig build test` is green.
**Gate (met):** `python3 test/qemu_test.py thread-id` passes; the whole `thread-*` suite
(`thread-spawn`/`-join`/`-futex`/`-mutex`/`-id`) plus the full guardrail set pass; default
`zig build` clean, `zig build test` green.
---
## Status: built
M1–M6 complete. danos has `runtime.Thread` — `spawn`/`join`/`detach`, cross-core
parallelism, futex, and `Mutex`/`Condition`/`Semaphore`, all over a private thread ABI
behind the runtime. Deferred (with rationale, no consumer yet): `threadlocal` TLS,
`RwLock`/`WaitGroup`, kernel clear-on-exit for a futex-completion `join`, a per-aspace
mmap arena / thread-safe runtime heap, and host-side unit tests via a mockable `Futex`.
---
+10 -7
View File
@@ -1,12 +1,15 @@
# Threading: `runtime.Thread`, a std-shaped API over a private thread ABI
A design note (not built yet) for giving danos **threads** — several tasks sharing
one address space — through a `runtime.Thread` type that mirrors the shape of Zig's
`std.Thread` while keeping every kernel entry behind the [runtime](../library/runtime).
It is forward-looking, like [zig-self-hosting.md](zig-self-hosting.md) and
[vision.md](vision.md): it fixes the decisions now so the code we write bends toward
them. The analysis is against **Zig 0.16** (the pinned toolchain); `std.Thread`'s
internals move between releases, so treat upstream shapes as "0.16.x."
A note on danos **threads** — several tasks sharing one address space — provided by a
`runtime.Thread` type that mirrors the shape of Zig's `std.Thread` while keeping every
kernel entry behind the [runtime](../library/runtime). **Built** (M1–M6, see
[threading-plan.md](threading-plan.md)): `spawn`/`join`/`detach`, cross-core
parallelism, a futex (`futex_wait`/`futex_wake`), and a futex-backed
`Mutex`/`Condition`/`Semaphore`, plus `getCurrentId`/`currentCore`. Deferred by design
(no consumer yet): per-thread `threadlocal` TLS, `RwLock`/`WaitGroup`, and migrating
`join` to a futex completion word — see the plan's M5/M6 notes. The analysis is against
**Zig 0.16** (the pinned toolchain); `std.Thread`'s internals move between releases, so
treat upstream shapes as "0.16.x."
## The win condition