comptime VM: memory = arena of stable host allocations; Addr = real host pointer (Phase 4D.0)

Replace the growable ArrayList(u8) flat buffer (reallocs/MOVES on growth) with a
std.heap.ArenaAllocator. Each allocBytes is a separate arena allocation that never
moves and is freed wholesale on deinit -- no per-object free, no cap, no fixed
buffer. Addr is now the allocation's ABSOLUTE host pointer (@intFromPtr), not an
offset, so a flat-memory pointer and an FFI-returned host pointer are the same kind
of value -- the FFI bridge (4D.1) passes them to/from libc with zero translation and
no per-call pinning (the moving-buffer hazard is gone by construction).

readWord/writeWord/bytes deref the absolute pointer with a null-check bail (the
malformed-IR / null-deref safety contract). Dropped the offset-based upper-bounds
check (can't bound an absolute pointer; Frame.bad_ref still catches the dominant
malformed-IR vector) and the test-only mark/reset (arena has no reset-to-mark; the
VM never used them outside tests).

697/0 both gates + all unit tests (rewrote the two Machine tests). Pure refactor, no
comptime behavior change.
This commit is contained in:
agra
2026-06-18 17:51:49 +03:00
parent 1526d198e2
commit 625ba0fb27
3 changed files with 80 additions and 75 deletions

View File

@@ -1396,24 +1396,21 @@ test "comptime_vm: a malformed operand TYPE ref bails (refTy), not a panic" {
try std.testing.expectError(error.Unsupported, v.run(&fb.func, &.{}));
}
test "comptime_vm: hardened accessors return OutOfBounds, not a panic" {
test "comptime_vm: hardened accessors return OutOfBounds on null, not a panic" {
var m = vm.Machine.init(std.testing.allocator);
defer m.deinit();
const addr = m.allocBytes(8, 8);
try std.testing.expect(addr != vm.null_addr);
// Null address (reserved guard) → OutOfBounds on every accessor.
// Null address → OutOfBounds on every accessor (the malformed-IR / null-deref
// safety contract `tryEval` relies on — bail, never crash).
try std.testing.expectError(error.OutOfBounds, m.readWord(vm.null_addr, 8));
try std.testing.expectError(error.OutOfBounds, m.writeWord(vm.null_addr, 8, 0));
try std.testing.expectError(error.OutOfBounds, m.bytes(vm.null_addr, 4));
// Past the end of allocated memory → OutOfBounds.
const past = m.mark() + 64;
try std.testing.expectError(error.OutOfBounds, m.readWord(@intCast(past), 1));
try std.testing.expectError(error.OutOfBounds, m.bytes(@intCast(past), 1));
// Straddling the end (last valid byte + an oversized read) → OutOfBounds.
try std.testing.expectError(error.OutOfBounds, m.readWord(addr + 4, 8));
// An oversized scalar read (> 8 bytes) → OutOfBounds.
try std.testing.expectError(error.OutOfBounds, m.readWord(addr, 16));
// A zero-length view is always valid (no memory touched), even at null.
try std.testing.expectEqual(@as(usize, 0), (try m.bytes(vm.null_addr, 0)).len);
@@ -1438,20 +1435,22 @@ test "comptime_vm tryEval: deref of a null pointer bails (null, not a crash)" {
try std.testing.expect(vm.tryEval(alloc, &module, bad_id) == null);
}
test "comptime_vm: mark/reset reclaims the stack region" {
test "comptime_vm: arena allocations are aligned, non-null, and stable across grows" {
var m = vm.Machine.init(std.testing.allocator);
defer m.deinit();
_ = m.allocBytes(16, 8);
const top = m.mark();
const reclaimed = m.allocBytes(64, 8);
try std.testing.expect(m.mark() > top);
m.reset(top);
try std.testing.expectEqual(top, m.mark());
const a = m.allocBytes(16, 8);
try std.testing.expect(a != vm.null_addr);
try std.testing.expectEqual(@as(u64, 0), a % 8);
try m.writeWord(a, 8, 0xCAFEBABE);
// After reset the freed region is handed back out again (same address).
const reused = m.allocBytes(64, 8);
try std.testing.expectEqual(reclaimed, reused);
// A later (much larger) allocation must NOT move or clobber the first — the
// arena never relocates an existing allocation (the property the FFI bridge
// relies on).
const b = m.allocBytes(1 << 20, 16);
try std.testing.expect(b != vm.null_addr);
try std.testing.expectEqual(@as(u64, 0), b % 16);
try std.testing.expectEqual(@as(u64, 0xCAFEBABE), try m.readWord(a, 8));
}
test "comptime_vm: Frame register file round-trips (no stack reclaim)" {