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:
@@ -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)" {
|
||||
|
||||
Reference in New Issue
Block a user