threads(M10): per-thread fs.base — the TLS thread-pointer mechanism
Each thread gets its own x86_64 thread pointer (FS base) for user-space TLS. Task.fs_base is restored on every context switch only when it changes (same conditional-load discipline as CR3; architecture.setFsBase -> wrmsr IA32_FS_BASE). New set_thread_pointer=44 syscall sets the caller's fs_base and loads it now. The kernel never touches FS, so no swapgs complication. The runtime lays a small per-thread TLS block at the top of each thread's stack (self-pointer at %fs:0 + scratch) and the thread trampoline calls set_thread_pointer before any user code — so every spawned thread has a private, switch-stable thread pointer, reclaimed with the stack. thread-test tls mode: two threads write unique markers to their own %fs:8 and, after both wrote, read back — a shared fs.base would clobber one (cross-talk). Deferred: the Zig threadlocal *compiler* layer (ELF variant-II PT_TLS + linker sections + template copy) — high-uncertainty, no consumer today; this lands the load-bearing per-thread fs.base it builds on. See docs/threading-plan.md M10. Gate thread-tls PASS (3x); full guardrail 25/25; build + host tests clean.
This commit is contained in:
@@ -357,6 +357,59 @@ fn runAllocMode() void {
|
||||
write("thread-alloc: ok\n"); // the M7 verdict marker
|
||||
}
|
||||
|
||||
// --- M10: tls mode (per-thread fs.base storage) -----------------------------
|
||||
|
||||
fn writeTlsSlot(value: u64) void {
|
||||
asm volatile ("movq %[v], %%fs:8"
|
||||
:
|
||||
: [v] "r" (value),
|
||||
: .{ .memory = true });
|
||||
}
|
||||
|
||||
fn readTlsSlot() u64 {
|
||||
return asm volatile ("movq %%fs:8, %[out]"
|
||||
: [out] "=r" (-> u64),
|
||||
:
|
||||
: .{ .memory = true });
|
||||
}
|
||||
|
||||
var tls_written = std.atomic.Value(u32).init(0);
|
||||
var tls_ok = std.atomic.Value(u32).init(0);
|
||||
|
||||
fn tlsWorker(marker: u64) void {
|
||||
writeTlsSlot(marker);
|
||||
_ = tls_written.fetchAdd(1, .release);
|
||||
// Wait until both threads have written their own slot. If fs.base were shared, the
|
||||
// second write would clobber the first, and the read below would return the wrong
|
||||
// marker — cross-talk. 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();
|
||||
}
|
||||
if (readTlsSlot() == marker and runtime.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 {
|
||||
write("thread-tls: FAIL spawn\n");
|
||||
return;
|
||||
};
|
||||
const t1 = runtime.Thread.spawn(.{}, tlsWorker, .{@as(u64, 0xBBBB_0000)}) catch {
|
||||
write("thread-tls: FAIL spawn\n");
|
||||
return;
|
||||
};
|
||||
t0.join();
|
||||
t1.join();
|
||||
if (tls_ok.load(.acquire) == 2) {
|
||||
write("thread-tls: ok\n"); // the M10 verdict marker
|
||||
} else {
|
||||
write("thread-tls: FAIL cross-talk (fs.base not per-thread)\n");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main(init: runtime.process.Init) void {
|
||||
const mode = init.arguments.get(1) orelse "spawn";
|
||||
if (std.mem.eql(u8, mode, "join")) {
|
||||
@@ -369,6 +422,8 @@ pub fn main(init: runtime.process.Init) void {
|
||||
runIdMode();
|
||||
} else if (std.mem.eql(u8, mode, "alloc")) {
|
||||
runAllocMode();
|
||||
} else if (std.mem.eql(u8, mode, "tls")) {
|
||||
runTlsMode();
|
||||
} else {
|
||||
runSpawnMode();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user