C2: migrate consumers off the runtime shim to direct concern-module imports

Every user binary and the two device-logic library modules (pci, usb) now
`@import` the concern modules directly instead of aliasing through `runtime`:

  runtime.ipc/process/time/service/input/block/display  -> @import("<module>")
  runtime.device / runtime.device_manager               -> @import("driver")
  runtime.fs                                             -> @import("file-system")
  runtime.Thread                                         -> @import("thread").Thread
  runtime.system.{write,writeRecord,klog*}              -> logging.*
  runtime.system.{sleep,timerOnce,wallClock,clock}     -> time.*
  runtime.system.{spawn*,kill,exit,yield,processes,...}-> process.*
  runtime.system.{mmap,munmap,PROT_*}                  -> memory.*
  runtime.dma.* / runtime.shared_memory.* / runtime.allocator -> memory.*

Each consumer keeps its own alias name (e.g. `const device = @import("driver")`),
so call sites are unchanged and there are no collisions with local `driver`
variables. build.zig now injects the concern modules into every user binary via
`default_imports`; pci/usb module import lists were updated to match.

The `runtime` and `system` shims remain for one more step (root.zig still uses
runtime); they are deleted in C5. Nothing but root.zig imports `runtime` now.

Verified: zig build, zig build test, and 17 QEMU cases (smoke, device-manager,
logger, fat-mount, fat-mutations, usb-storage, usb-hid, display-native,
virtio-gpu, input, thread-spawn, thread-mutex, process-kill, shared-memory,
driver-restart, acpi-ps2, pci-scan).
This commit is contained in:
Daniel Samson
2026-07-22 23:28:34 +01:00
parent dded46726b
commit 23bcd77c58
37 changed files with 783 additions and 692 deletions
+56 -52
View File
@@ -2,7 +2,7 @@
//!
//! Two modes, chosen by argv[1] (default "spawn"):
//! spawn — M2: one worker writes a shared global; the main thread observes it, proving
//! `runtime.Thread.spawn` started a task in the **same** address space.
//! `Thread.spawn` started a task in the **same** address space.
//! join — M3: N workers each do K atomic increments on a shared counter and stamp the
//! core they ran on; the main thread `join`s all N and checks the total is
//! exactly N*K (every worker ran, join waited) and that >1 core was used
@@ -12,10 +12,14 @@
//! Built multi-threaded (`addThreadedUserBinary`) so atomics/shared reads are real.
const std = @import("std");
const runtime = @import("runtime");
const process = @import("process");
const time = @import("time");
const memory = @import("memory");
const logging = @import("logging");
const Thread = @import("thread").Thread;
fn write(comptime s: []const u8) void {
_ = runtime.system.write(s);
_ = logging.write(s);
}
// --- M2: spawn mode ---------------------------------------------------------
@@ -31,13 +35,13 @@ fn spawnWorker() void {
fn runSpawnMode() void {
write("thread-test: starting\n");
_ = runtime.Thread.spawn(.{}, spawnWorker, .{}) catch {
_ = Thread.spawn(.{}, spawnWorker, .{}) catch {
write("thread-test: FAIL spawn refused\n");
return;
};
var spins: usize = 0;
while (spawn_done.load(.acquire) == 0 and spins < 50_000_000) : (spins += 1) {
runtime.system.yield();
process.yield();
}
if (spawn_done.load(.acquire) == 1 and shared_value == sentinel) {
write("thread-test: child ran in shared address space ok\n");
@@ -64,7 +68,7 @@ fn joinWorker() void {
}
fn stampCore() void {
const core = runtime.Thread.currentCore();
const core = Thread.currentCore();
if (core < 32) _ = cores_seen.fetchOr(@as(u32, 1) << @intCast(core), .monotonic);
}
@@ -79,10 +83,10 @@ fn noopWorker() void {}
fn runJoinMode() void {
write("thread-test: join mode starting\n");
var threads: [worker_count]runtime.Thread = undefined;
var threads: [worker_count]Thread = undefined;
var spawned: u32 = 0;
while (spawned < worker_count) : (spawned += 1) {
threads[spawned] = runtime.Thread.spawn(.{}, joinWorker, .{}) catch break;
threads[spawned] = Thread.spawn(.{}, joinWorker, .{}) catch break;
}
if (spawned != worker_count) {
write("thread-test: FAIL could not spawn all workers\n");
@@ -102,14 +106,14 @@ fn runJoinMode() void {
}
// detach: the worker runs and we never join it.
const dt = runtime.Thread.spawn(.{}, detachWorker, .{}) catch {
const dt = Thread.spawn(.{}, detachWorker, .{}) catch {
write("thread-test: FAIL detach spawn refused\n");
return;
};
dt.detach();
var spins: usize = 0;
while (detach_done.load(.acquire) == 0 and spins < 50_000_000) : (spins += 1) {
runtime.system.yield();
process.yield();
}
if (detach_done.load(.acquire) != 1) {
write("thread-test: FAIL detached worker did not run\n");
@@ -121,7 +125,7 @@ fn runJoinMode() void {
// 16-slot handle table well before 40; here they all succeed.
var cycle: u32 = 0;
while (cycle < 40) : (cycle += 1) {
const th = runtime.Thread.spawn(.{}, noopWorker, .{}) catch {
const th = Thread.spawn(.{}, noopWorker, .{}) catch {
write("thread-test: FAIL spawn exhausted across join cycles (endpoint leak?)\n");
return;
};
@@ -133,7 +137,7 @@ fn runJoinMode() void {
// --- M4: futex mode ---------------------------------------------------------
const Futex = runtime.Thread.Futex;
const Futex = Thread.Futex;
var futex_word = std.atomic.Value(u32).init(0);
var waiter_parked = std.atomic.Value(u32).init(0);
@@ -151,16 +155,16 @@ fn futexWaiter() void {
fn runFutexMode() void {
write("thread-futex: starting\n");
const waiter = runtime.Thread.spawn(.{}, futexWaiter, .{}) catch {
const waiter = Thread.spawn(.{}, futexWaiter, .{}) catch {
write("thread-futex: FAIL spawn refused\n");
return;
};
// Let the waiter reach its wait, then give it a beat to actually park in-kernel.
var spins: usize = 0;
while (waiter_parked.load(.acquire) == 0 and spins < 50_000_000) : (spins += 1) {
runtime.system.yield();
process.yield();
}
runtime.system.sleep(50);
time.sleepMillis(50);
// The handshake: publish the value, then wake the parked waiter.
futex_word.store(1, .release);
@@ -182,8 +186,8 @@ fn runFutexMode() void {
// --- M5: mutex mode (bounded producer/consumer over Mutex + Condition) ------
const Mutex = runtime.Thread.Mutex;
const Condition = runtime.Thread.Condition;
const Mutex = Thread.Mutex;
const Condition = Thread.Condition;
const producers: u32 = 2;
const consumers: u32 = 2;
@@ -237,11 +241,11 @@ fn consumer() void {
fn runMutexMode() void {
write("thread-mutex: starting\n");
var threads: [producers + consumers]runtime.Thread = undefined;
var threads: [producers + consumers]Thread = undefined;
var n: usize = 0;
var p: u32 = 0;
while (p < producers) : (p += 1) {
threads[n] = runtime.Thread.spawn(.{}, producer, .{p * per_producer}) catch {
threads[n] = Thread.spawn(.{}, producer, .{p * per_producer}) catch {
write("thread-mutex: FAIL producer spawn\n");
return;
};
@@ -249,7 +253,7 @@ fn runMutexMode() void {
}
var c: u32 = 0;
while (c < consumers) : (c += 1) {
threads[n] = runtime.Thread.spawn(.{}, consumer, .{}) catch {
threads[n] = Thread.spawn(.{}, consumer, .{}) catch {
write("thread-mutex: FAIL consumer spawn\n");
return;
};
@@ -276,18 +280,18 @@ fn runMutexMode() void {
var worker_ids: [2]std.atomic.Value(u32) = .{ std.atomic.Value(u32).init(0), std.atomic.Value(u32).init(0) };
fn idWorker(slot: usize) void {
worker_ids[slot].store(runtime.Thread.getCurrentId(), .release);
worker_ids[slot].store(Thread.getCurrentId(), .release);
}
fn runIdMode() void {
write("thread-id: starting\n");
const main_id = runtime.Thread.getCurrentId();
const main_id = Thread.getCurrentId();
const t0 = runtime.Thread.spawn(.{}, idWorker, .{@as(usize, 0)}) catch {
const t0 = Thread.spawn(.{}, idWorker, .{@as(usize, 0)}) catch {
write("thread-id: FAIL spawn\n");
return;
};
const t1 = runtime.Thread.spawn(.{}, idWorker, .{@as(usize, 1)}) catch {
const t1 = Thread.spawn(.{}, idWorker, .{@as(usize, 1)}) catch {
write("thread-id: FAIL spawn\n");
return;
};
@@ -315,7 +319,7 @@ const allocs_per_thread: u32 = 500;
var allocs_clean = std.atomic.Value(u32).init(0);
fn allocWorker(seed: u32) void {
const gpa = runtime.allocator();
const gpa = memory.allocator();
var rng: u32 = seed | 1;
var round: u32 = 0;
while (round < allocs_per_thread) : (round += 1) {
@@ -338,10 +342,10 @@ fn allocWorker(seed: u32) void {
fn runAllocMode() void {
write("thread-alloc: starting\n");
var threads: [alloc_threads]runtime.Thread = undefined;
var threads: [alloc_threads]Thread = undefined;
var n: u32 = 0;
while (n < alloc_threads) : (n += 1) {
threads[n] = runtime.Thread.spawn(.{}, allocWorker, .{n +% 1}) catch {
threads[n] = Thread.spawn(.{}, allocWorker, .{n +% 1}) catch {
write("thread-alloc: FAIL spawn\n");
return;
};
@@ -384,20 +388,20 @@ fn tlsWorker(marker: u64) void {
// marker — cross-talk. A per-thread FS base keeps each thread's slot private.
var spins: usize = 0;
while (tls_written.load(.acquire) < 2 and spins < 50_000_000) : (spins += 1) {
runtime.system.yield();
process.yield();
}
if (readTlsSlot() == marker and runtime.Thread.getCurrentId() != 0) {
if (readTlsSlot() == marker and Thread.getCurrentId() != 0) {
_ = tls_ok.fetchAdd(1, .monotonic);
}
}
fn runTlsMode() void {
write("thread-tls: starting\n");
const t0 = runtime.Thread.spawn(.{}, tlsWorker, .{@as(u64, 0xAAAA_0000)}) catch {
const t0 = Thread.spawn(.{}, tlsWorker, .{@as(u64, 0xAAAA_0000)}) catch {
write("thread-tls: FAIL spawn\n");
return;
};
const t1 = runtime.Thread.spawn(.{}, tlsWorker, .{@as(u64, 0xBBBB_0000)}) catch {
const t1 = Thread.spawn(.{}, tlsWorker, .{@as(u64, 0xBBBB_0000)}) catch {
write("thread-tls: FAIL spawn\n");
return;
};
@@ -412,7 +416,7 @@ fn runTlsMode() void {
// --- M11: rwlock mode (readers/writers over an RwLock) ----------------------
const RwLock = runtime.Thread.RwLock;
const RwLock = Thread.RwLock;
var rwlock = RwLock{};
var rw_a: u64 = 0;
@@ -444,16 +448,16 @@ fn rwReader() void {
fn runRwlockMode() void {
write("thread-rwlock: starting\n");
var writers: [2]runtime.Thread = undefined;
var readers: [3]runtime.Thread = undefined;
var writers: [2]Thread = undefined;
var readers: [3]Thread = undefined;
for (&writers) |*w| {
w.* = runtime.Thread.spawn(.{}, rwWriter, .{}) catch {
w.* = Thread.spawn(.{}, rwWriter, .{}) catch {
write("thread-rwlock: FAIL spawn\n");
return;
};
}
for (&readers) |*r| {
r.* = runtime.Thread.spawn(.{}, rwReader, .{}) catch {
r.* = Thread.spawn(.{}, rwReader, .{}) catch {
write("thread-rwlock: FAIL spawn\n");
return;
};
@@ -484,11 +488,11 @@ fn faultingWorker() void {
/// main thread parks forever and never prints anything more.
fn runFaultWorkerMode() void {
write("thread-test: spawning faulting worker\n");
_ = runtime.Thread.spawn(.{}, faultingWorker, .{}) catch {
_ = Thread.spawn(.{}, faultingWorker, .{}) catch {
write("thread-test: FAIL spawn refused\n");
return;
};
while (true) runtime.system.yield();
while (true) process.yield();
}
fn spinningWorker() void {
@@ -498,27 +502,27 @@ fn spinningWorker() void {
/// spin-forever: a kill target. The worker spins without syscalls (the condemned
/// path); the main thread yields (the parked path).
fn runSpinForeverMode() void {
_ = runtime.Thread.spawn(.{}, spinningWorker, .{}) catch {
_ = Thread.spawn(.{}, spinningWorker, .{}) catch {
write("thread-test: FAIL spawn refused\n");
return;
};
write("thread-test: spinning\n");
while (true) runtime.system.yield();
while (true) process.yield();
}
fn exitingWorker() void {
write("thread-test: worker exiting the process\n");
runtime.system.exit(3); // exit from ANY thread is group death (.aborted)
process.exit(3); // exit from ANY thread is group death (.aborted)
}
/// exit-worker: a WORKER calls exit(3); the group must die with the leader's
/// reason reading .aborted.
fn runExitWorkerMode() void {
_ = runtime.Thread.spawn(.{}, exitingWorker, .{}) catch {
_ = Thread.spawn(.{}, exitingWorker, .{}) catch {
write("thread-test: FAIL spawn refused\n");
return;
};
while (true) runtime.system.yield();
while (true) process.yield();
}
var race_go = std.atomic.Value(u32).init(0);
@@ -531,7 +535,7 @@ fn racingWorker() void {
/// race: two members fault as near-simultaneously as user space can arrange —
/// the group-dying latch must make the two triggers count as one death.
fn runRaceMode() void {
_ = runtime.Thread.spawn(.{}, racingWorker, .{}) catch {
_ = Thread.spawn(.{}, racingWorker, .{}) catch {
write("thread-test: FAIL spawn refused\n");
return;
};
@@ -543,17 +547,17 @@ fn runRaceMode() void {
var leader_exit_done = std.atomic.Value(u32).init(0);
fn patientWorker() void {
while (leader_exit_done.load(.acquire) == 0) runtime.system.yield();
while (leader_exit_done.load(.acquire) == 0) process.yield();
}
/// leader-exit: the MAIN thread asks for thread_exit; the kernel must refuse
/// (-EPERM) and the worker must be entirely unaffected.
fn runLeaderExitMode() void {
const worker = runtime.Thread.spawn(.{}, patientWorker, .{}) catch {
const worker = Thread.spawn(.{}, patientWorker, .{}) catch {
write("thread-test: FAIL spawn refused\n");
return;
};
runtime.Thread.tryExitCurrent(); // refused: we are the leader
Thread.tryExitCurrent(); // refused: we are the leader
write("thread-test: leader thread_exit refused ok\n");
leader_exit_done.store(1, .release);
worker.join();
@@ -564,7 +568,7 @@ fn promptWorker() void {}
/// solo: regression — a WORKER's thread_exit stays per-thread; the sibling
/// (main) survives it.
fn runSoloMode() void {
const worker = runtime.Thread.spawn(.{}, promptWorker, .{}) catch {
const worker = Thread.spawn(.{}, promptWorker, .{}) catch {
write("thread-test: FAIL spawn refused\n");
return;
};
@@ -580,7 +584,7 @@ fn patternByte(i: usize) u8 {
}
fn shmWorker() void {
const region = runtime.shared_memory.create(shm_pattern_length) orelse {
const region = memory.sharedCreate(shm_pattern_length) orelse {
write("thread-shm: FAIL create refused\n");
return;
};
@@ -596,7 +600,7 @@ fn shmWorker() void {
/// then checks the mapping is intact — freed frames would have been reused and
/// scribbled on.
fn runShmWorkerMode() void {
const worker = runtime.Thread.spawn(.{}, shmWorker, .{}) catch {
const worker = Thread.spawn(.{}, shmWorker, .{}) catch {
write("thread-test: FAIL spawn refused\n");
return;
};
@@ -605,7 +609,7 @@ fn runShmWorkerMode() void {
if (base == 0) return; // the worker already printed the failure
var churn: usize = 0;
while (churn < 8) : (churn += 1) {
const noise = runtime.shared_memory.create(shm_pattern_length) orelse break;
const noise = memory.sharedCreate(shm_pattern_length) orelse break;
@memset(noise.ptr[0..shm_pattern_length], 0xFF);
}
const view: [*]const u8 = @ptrFromInt(base);
@@ -620,7 +624,7 @@ fn runShmWorkerMode() void {
}
}
pub fn main(init: runtime.process.Init) void {
pub fn main(init: process.Init) void {
const mode = init.arguments.get(1) orelse "spawn";
if (std.mem.eql(u8, mode, "join")) {
runJoinMode();