threads(M7): thread-safe allocation (per-aspace mmap arena + locked heap)
Move the mmap/mmio grant-arena cursors off Task into the per-address-space object (scheduler aspace_refs, exposed via aspaceMmapNextPtr/aspaceDeviceMapNextPtr), so sibling threads in one address space hand out disjoint grants. systemMmap reserves a range under a brief lock then maps per page under a short-held lock (not the whole grant): the big lock runs with interrupts disabled, so pinning it across a multi-MiB memset+map froze other cores. Guard the runtime heap's rawAlloc/rawFree with a Thread.Mutex, gated on !single_threaded so ordinary binaries compile it out. thread-test gains an alloc mode: 4 threads x 500 alloc/fill/verify/free cycles; any overlap between concurrent allocations is caught by the pattern check. Also fix the affinity guardrail: its 3-billion-iteration busy-loop had codegen-dependent wall-time (adding a function to tests.zig swung it ~4s -> ~63s and timed it out). Reworked to wait on the wall clock instead. Gate thread-alloc PASS (3x); full guardrail 23/23 green; build + host tests clean.
This commit is contained in:
@@ -294,6 +294,55 @@ fn runIdMode() void {
|
||||
write("thread-id: ok\n"); // the M6 verdict marker
|
||||
}
|
||||
|
||||
// --- M7: alloc mode (concurrent heap allocation) ----------------------------
|
||||
|
||||
const alloc_threads: u32 = 4;
|
||||
const allocs_per_thread: u32 = 500;
|
||||
var allocs_clean = std.atomic.Value(u32).init(0);
|
||||
|
||||
fn allocWorker(seed: u32) void {
|
||||
const gpa = runtime.allocator();
|
||||
var rng: u32 = seed | 1;
|
||||
var round: u32 = 0;
|
||||
while (round < allocs_per_thread) : (round += 1) {
|
||||
rng = rng *% 1664525 +% 1013904223; // cheap LCG for varied sizes
|
||||
const size: usize = 16 + (rng % 4080); // 16..4095 bytes
|
||||
const buf = gpa.alloc(u8, size) catch return; // OOM: don't count this thread clean
|
||||
const pattern: u8 = @truncate(seed +% round);
|
||||
@memset(buf, pattern);
|
||||
// Nothing else should touch our block; if a concurrent allocation overlapped it,
|
||||
// one of us would read the other's pattern here.
|
||||
var ok = true;
|
||||
for (buf) |b| {
|
||||
if (b != pattern) ok = false;
|
||||
}
|
||||
gpa.free(buf);
|
||||
if (!ok) return; // corruption — leave without counting clean
|
||||
}
|
||||
_ = allocs_clean.fetchAdd(1, .monotonic);
|
||||
}
|
||||
|
||||
fn runAllocMode() void {
|
||||
write("thread-alloc: starting\n");
|
||||
var threads: [alloc_threads]runtime.Thread = undefined;
|
||||
var n: u32 = 0;
|
||||
while (n < alloc_threads) : (n += 1) {
|
||||
threads[n] = runtime.Thread.spawn(.{}, allocWorker, .{n +% 1}) catch {
|
||||
write("thread-alloc: FAIL spawn\n");
|
||||
return;
|
||||
};
|
||||
}
|
||||
for (threads[0..alloc_threads]) |t| t.join();
|
||||
|
||||
// Every thread must have completed all rounds with each block intact — proof the
|
||||
// shared heap and the per-aspace mmap arena are safe under concurrent allocation.
|
||||
if (allocs_clean.load(.acquire) != alloc_threads) {
|
||||
write("thread-alloc: FAIL corruption or OOM under concurrent allocation\n");
|
||||
return;
|
||||
}
|
||||
write("thread-alloc: ok\n"); // the M7 verdict marker
|
||||
}
|
||||
|
||||
pub fn main(init: runtime.process.Init) void {
|
||||
const mode = init.arguments.get(1) orelse "spawn";
|
||||
if (std.mem.eql(u8, mode, "join")) {
|
||||
@@ -304,6 +353,8 @@ pub fn main(init: runtime.process.Init) void {
|
||||
runMutexMode();
|
||||
} else if (std.mem.eql(u8, mode, "id")) {
|
||||
runIdMode();
|
||||
} else if (std.mem.eql(u8, mode, "alloc")) {
|
||||
runAllocMode();
|
||||
} else {
|
||||
runSpawnMode();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user