diff options
| author | Taylan Kammer <taylan.kammer@gmail.com> | 2026-08-16 10:01:12 +0200 |
|---|---|---|
| committer | Taylan Kammer <taylan.kammer@gmail.com> | 2026-08-16 10:01:12 +0200 |
| commit | e26e81aa33dc1ecc2f26de26b1d420d288a28493 (patch) | |
| tree | d0c906f3b6d504870a171f57709c6e912a3fc9c4 /src | |
| parent | 0e073b94c90b78ebe156adb27ec4456e86935d42 (diff) | |
Implemented meta allocator.
Diffstat (limited to 'src')
| -rw-r--r-- | src/zisp/gc.zig | 37 | ||||
| -rw-r--r-- | src/zisp/gc/IstrPool.zig | 6 | ||||
| -rw-r--r-- | src/zisp/gc/ListPool.zig | 10 | ||||
| -rw-r--r-- | src/zisp/gc/meta_alloc.zig | 360 | ||||
| -rw-r--r-- | src/zisp/io/Parser.zig | 17 | ||||
| -rw-r--r-- | src/zisp/lib.zig | 1 | ||||
| -rw-r--r-- | src/zisp/util/seg_stack.zig | 2 | ||||
| -rw-r--r-- | src/zisp/value.zig | 17 | ||||
| -rw-r--r-- | src/zisp/value/hptr.zig (renamed from src/zisp/value/ptr.zig) | 0 | ||||
| -rw-r--r-- | src/zisp/value/istr.zig | 30 |
10 files changed, 411 insertions, 69 deletions
diff --git a/src/zisp/gc.zig b/src/zisp/gc.zig index 9cb66d1..6efa8fe 100644 --- a/src/zisp/gc.zig +++ b/src/zisp/gc.zig @@ -8,42 +8,33 @@ const value = @import("value.zig"); pub const ListPool = @import("gc/ListPool.zig"); pub const IstrPool = @import("gc/IstrPool.zig"); +const Value = value.Value; const HeapPtr = value.HeapPtr; const HeapType = value.HeapType; -var main_alloc: Alloc = undefined; +const i32max = std.math.maxInt(i32); -const u32max = std.math.maxInt(u32); +var heap: *[2 * i32max]u64 = undefined; -var list_heap: *[u32max]Value = undefined; -var main_heap: *[u32max]u64 = undefined; -var istr_heap: *[u32max]u8 = undefined; +var watermark: i32 = 0; + +threadlocal var tl_watermark: i32 = 0; var main_list_pool: ListPool = undefined; var main_istr_pool: IstrPool = undefined; -pub fn listPtrFromIdx(idx: u32) [*]Value { - return &list_heap[idx]; -} - -pub fn heapPtrFromIdx(comptime typ: HeapType, idx: u32) typ.PtrType() { - return @ptrCast(&main_heap[idx]); +pub fn ptrFromIdx(idx: u32) [*]Value { + return &heap[idx]; } -pub fn istrPtrFromIdx(idx: u32) [*]const u8 { - return &istr_heap[idx]; +pub fn idxFromPtr(ptr: [*]Value) u32 { + return @intCast(ptr - heap); } -pub fn listIdxFromPtr(ptr: [*]Value) u32 { - return @intCast(ptr - list_heap); -} +// TODO: Actually allocate within the boundaries of the various heaps -pub fn heapIdxFromPtr(ptr: HeapPtr) u32 { - return @intCast(ptr - main_heap); -} - -pub fn istrIdxFromPtr(ptr: [*]const u8) u32 { - return @intCast(ptr - istr_heap); +pub fn allocIstr(len: u8) [*]const u8 { + return @ptrCast(main_alloc.alloc(u8, len).ptr); } pub fn allocHeap(comptime typ: HeapType, len: usize) !typ.PtrType() { @@ -76,8 +67,6 @@ pub fn init() !void { main_alloc = std.heap.smp_allocator; - //list_heap_start = ...; - main_list_pool = try ListPool.init(main_alloc); main_istr_pool = try IstrPool.init(main_alloc); } diff --git a/src/zisp/gc/IstrPool.zig b/src/zisp/gc/IstrPool.zig index bbf6e63..81b4632 100644 --- a/src/zisp/gc/IstrPool.zig +++ b/src/zisp/gc/IstrPool.zig @@ -11,6 +11,7 @@ const std = @import("std"); const Alloc = std.mem.Allocator; +const gc = @import("../gc.zig"); const value = @import("../value.zig"); const Value = value.Value; @@ -130,8 +131,9 @@ fn getOrPutOrNew(self: *Self, sptr: [*]const u8, len: u8, put: ?Value) !Value { fn newIstr(self: *Self, sptr: [*]const u8, len: u8) !Value { // TODO: Alloc manually in slabs, like ListPool FlexiBlock. - const ptr = try self.alloc.alloc(len); - const idx = gc.istrIdxFromPtr(ptr); + const s = try self.alloc.alloc(u8, len); + @memcpy(s, sptr[0..len]); + const idx = gc.istrIdxFromPtr(s.ptr); return .{ .istr = .{ .idx = idx, .len = len } }; } diff --git a/src/zisp/gc/ListPool.zig b/src/zisp/gc/ListPool.zig index d63a6f8..127456a 100644 --- a/src/zisp/gc/ListPool.zig +++ b/src/zisp/gc/ListPool.zig @@ -117,7 +117,7 @@ const Block = [block_size]Value; // should be multiples of 2 (and even 4) so we could divide them to make them // fit in a u8 each, fitting the whole map in a cache line, but then we'd need // to multiply them (shift-left) on extraction, so whatever. -const chunk_real_idx_map: [39]u16 = undefined; +var chunk_real_idx_map: [39]u16 = undefined; comptime { // zig fmt: off for ( 0..30) |i| chunk_real_idx_map[i] = ( i * 12 + 0 * 16); @@ -137,7 +137,7 @@ const chunk_empty_big: u64 = (2 ^ 1 - 1) << 38; const array_block_chunk_size = 32; const array_block_max_index = block_size / array_block_chunk_size; -const FreeListNode = packed union(u64) { +const FreeListNode = union { bits: u64, vals_ptr: [*]Value, prev_ptr: *FreeListNode, @@ -190,12 +190,12 @@ pub fn deinit(self: *Self) void { self.alloc.destroy(self.cur_flexi_block); } -fn pushFree(list: *FreeListNode, len: u8, ptr: [*]Value) void { +fn pushFree(list: *FreeListNode, ptr: [*]Value) void { ptr[0].bits = list.bits; list.vals_ptr = ptr; } -fn popFree(list: *FreeListNode, len: u8) ?[*]Value { +fn popFree(list: *FreeListNode) ?[*]Value { if (list.bits == 0) return null; const ptr = list.vals_ptr; list.* = list.prev_ptr.*; @@ -215,7 +215,7 @@ fn newChunkBlock(self: *Self) !void { // Put unused chunks in free lists: var empty = self.cur_chunk_empty; while (empty != 0) { - const idx = @ctz(chunks); + const idx = @ctz(empty); if (idx < 30) { pushFree(&self.chunk_free_lists[0], curChunkBlockPtr(idx)); empty &= ~@shlExact(@as(u64, 1), @intCast(idx)); diff --git a/src/zisp/gc/meta_alloc.zig b/src/zisp/gc/meta_alloc.zig new file mode 100644 index 0000000..a600f3f --- /dev/null +++ b/src/zisp/gc/meta_alloc.zig @@ -0,0 +1,360 @@ +// +// = Meta Allocator = +// +// Please read the full documentation of the allocation strategy to understand +// what's going on here at a high level: /doc/0/A-memory.html +// +// That said, a few quick implementation notes follow. +// +// == Slabs and their watermarks == +// +// We use an i32 for slab watermarks so we can simply let it overflow on the +// last valid allocation; the next allocation attempt will then see that it's +// negative, and panic. +// +// It's actually stored as a u32 anyway, because Zig is annoying. +// +// The watermark is counted in 8-byte units. If you do the math, you'll notice +// that this means the slabs are limited to 16 GiB each (2^31 units because i32; +// 8 bytes each) for a 256 GiB total, since we have 16 size classes. +// +// Using 8-byte units means free conversion to byte-based addresses thanks to +// instructions like LEA or the ARM barrel shifter. Using size class sized +// units would require additional shift instructions and seems unnecessary. +// +// The global slab watermark of a size class is bumped in chunks, reserving a +// number of slots for the thread that performed the bump. This way, threads +// don't need to perform any atomic operations for most allocations. +// +// == Free-lists and free slot cache arrays == +// +// For each size class there's a global free-list that uses the freed memory +// slots themselves as intrusive linked lists with an inline array, so we can +// directly store a chunk of indexes. Concretely, nodes of this linked list +// have the following format: +// +// { next_head_idx: u32, idx_count: u32, idx_array: [idx_count]u32 } +// +// But with some padding to make idx_array 128-bit aligned for SIMD. +// +// So, it's not a typical free-list where each node simply points to the next +// entry; rather, each node is the first of a chunk of N entries, where the +// remaining entries of the chunk are inlined as an array into the first. +// +// Each thread also has a small static thread-local array of indexes of most +// recently freed slots. This makes alloc/free extremely efficient in common +// cases. We fill or empty half of this array from the global free-list when +// it's empty or full, respectively. This forms the above mentioned chunks in +// the global free-list. +// +// == Monomorphization (or lack thereof) over size class values == +// +// Given that we have a small-ish static number of size classes, we could make +// alloc and free generic and be monomorphized, having various per size class +// constants baked into each. However, this doesn't seem worth it, as these +// constants are efficiently calculated in a small handful of instructions. +// Further, we need a dynamic version anyway for when the size class is only +// known at run-time. +// +// The compiler may still decide to emit specialized versions of alloc/free +// based on statically known size class parameters, but we don't force it. +// + +const std = @import("std"); + +/// Size classes. +const SIZES: [16]comptime_int = .{ + // Important: Ensure the smallest size class is inherently immune to false + // sharing, and no smaller than: 16 + RESERVE_MAX / 2 * @sizeOf(u32) bytes. + // That's 80 bytes assuming RESERVE_MAX = 32. + 128, + 256, + 512, + 1024, + 1024 * 2, + 1024 * 4, + 1024 * 8, + 1024 * 16, + 1024 * 32, + 1024 * 64, + 1024 * 128, + 1024 * 256, + 1024 * 512, + 1024 * 1024, + 1024 * 1024 * 2, + 1024 * 1024 * 4, +}; + +/// Gets the index [0,15] of a size class, from the size value, returning a +/// value greater than 15 if the size is beyond the largest size class. +inline fn getSizeClassIndex(size: usize) u8 { + const min = @as(u8, SIZES[0]); + return @ctz(size >> @ctz(min)); +} + +/// Size of each slab, in bytes. +const SLAB_SIZE: usize = std.math.maxInt(i32) * 8; + +/// Pointer to start of all 16 slabs as a contiguous vmem block. +var slabs: [*]u8 = undefined; + +/// Free-list head pointer with ABA counter. +const FlHead = packed struct(u64) { + // Putting aba first leads to slightly better codegen. + aba: u32, + idx: u32, +}; + +/// First invalid value for our 31-bit indexes; used as a NULL, since index +/// value 0 is actually valid. +const INVAL_IDX: u32 = 0x8000_0000; + +/// Global shared metadata per slab / size class. +const SlabInfo = struct { + /// Force cache-line size alignment to prevent false sharing. + _: void align(std.atomic.cache_line) = undefined, + /// Head of global free-list for the size class, with ABA counter. + free_list: FlHead = .{ .aba = 0, .idx = INVAL_IDX }, + /// Global slab watermark: Start address of unused vmem, as 8-byte index. + watermark: u32 = 0, +}; + +/// Global shared metadata per slab / size class. +var slab_infos: [SIZES.len]SlabInfo = @splat(.{}); + +/// Maximum number of slots reserved for a thread. +const RESERVE_MAX = 32; + +/// Maps indexes [0,15] to { 32, 16, 8, 4 } in steps of 4, because we want more +/// thread-local reserved slots for smaller size classes. So, for example, the +/// size classes from 128 to 1K will use 32 thread-local reserved slots, while +/// classes 512K to 4M will use only 4 reserved slots. +inline fn reserveCountForSizeClassIndex(sci: u8) u8 { + return @as(u8, RESERVE_MAX) >> @intCast(sci / 4); +} + +/// Thread-local metadata per slab / size class. +const TlSlabInfo = struct { + /// Force cache-line size alignment to prevent false sharing. + _: void align(std.atomic.cache_line) = undefined, + /// Start point of memory reserved for this thread. + wm_lo: u32 = 0, + /// End point of memory reserved for this thread. + wm_hi: u32 = 0, + /// Current count of entries in free slot cache. + fc_count: u32 = 0, + /// Free slot cache; aligned to 16 bytes for SIMD. + fc: [RESERVE_MAX]u32 align(16) = undefined, +}; + +/// Thread-local metadata per slab / size class. +threadlocal var tl_slab_infos: [SIZES.len]TlSlabInfo = @splat(.{}); + +/// Wrapper around std.posix.mmap(). +fn mmap(size: usize) []u8 { + return std.posix.mmap( + null, + size, + .{ .READ = true, .WRITE = true }, + .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, + -1, + 0, + ) catch @panic("Syscall mmap() failed."); +} + +/// Must call this once to initialize the slabs. +pub fn init() void { + slabs = mmap(SIZES.len * SLAB_SIZE).ptr; +} + +/// Allocate a slot of the given size, which must be a power of two and greater +/// than or equal to the smallest size class. If it's greater than the largest +/// size class, this will fall back to an mmap() syscall. +pub fn alloc(size: usize) []u8 { + // Can't allocate size that isn't power of two. + std.debug.assert(@popCount(size) == 1); + + // Can't allocate below smallest size class. + std.debug.assert(size >= SIZES[0]); + + const sci = getSizeClassIndex(size); + if (sci < SIZES.len) { + @branchHint(.likely); + return alloc_size_class(sci, @intCast(size)); + } else { + return mmap(size); + } +} + +fn alloc_size_class(sci: u8, size: u32) []u8 { + std.debug.assert(sci < SIZES.len); + + const tl = &tl_slab_infos[sci]; + const slab = slabs + sci * SLAB_SIZE; + + // Reuse from thread-local free cache. + if (tl.fc_count > 0) { + tl.fc_count -= 1; + const ptr = slab + tl.fc[tl.fc_count] * 8; + return ptr[0..size]; + } + + // Use part of the reserved memory for this thread. + if (tl.wm_lo < tl.wm_hi) { + const ptr = slab + tl.wm_lo * 8; + tl.wm_lo += size; + return ptr[0..size]; + } + + const info = &slab_infos[sci]; + const res_n = reserveCountForSizeClassIndex(sci); + + // Try to transfer a chunk from the global free-list. + if (alloc_from_fl(slab, info, tl)) |p| return p[0..size]; + + return alloc_fresh(slab, size, res_n * size, info, tl); +} + +fn alloc_from_fl( + slab: [*]u8, + info: *SlabInfo, + tl: *TlSlabInfo, +) ?[*]u8 { + var fl_head = @atomicLoad(FlHead, &info.free_list, .acquire); + // Checking for < INVAL_IDX is optimal; it'll just test the sign bit. + while (fl_head.idx < INVAL_IDX) { + const ptr = slab + fl_head.idx * 8; + const arr: [*]u32 = @ptrCast(@alignCast(ptr)); + + const next_head = @atomicLoad(u32, &arr[0], .unordered); + if (@cmpxchgWeak( + FlHead, + &info.free_list, + fl_head, + .{ .aba = fl_head.aba +% 1, .idx = next_head }, + .release, + .acquire, + )) |head| { + fl_head = head; + std.atomic.spinLoopHint(); + continue; + } + + tl.fc_count = arr[1]; + // We can safely copy more than needed since we set fc_count; this way + // it should compile neatly into SIMD instructions. + const arr_u128: [*]u128 = @ptrCast(@alignCast(arr + 4)); + const fc_u128: [*]u128 = @ptrCast(&tl.fc); + for (0..RESERVE_MAX / 2 / 4) |i| fc_u128[i] = arr_u128[i]; + + return ptr; + } + return null; +} + +fn alloc_fresh( + slab: [*]u8, + size: u32, + bump: u32, + info: *SlabInfo, + tl: *TlSlabInfo, +) []u8 { + // Have to use fresh memory from the top of the slab; bump it by a chunk, + // atomically, to reserve memory for this thread. + const old_wm = @atomicRmw(u32, &info.watermark, .Add, bump, .monotonic); + const new_wm = old_wm + bump; + + // Check only if the old WM was already overflown; chunk size is such that + // it's always safe to bump by a chunk if the old WM was still valid. + if (old_wm >= INVAL_IDX) { + std.debug.panic("Exhausted slab for size class: {}", .{size}); + } + + const ptr = slab + old_wm * 8; + tl.wm_lo = old_wm + size; + tl.wm_hi = new_wm; + return ptr[0..size]; +} + +/// Free memory that was returned by alloc(). +pub fn free(slot: []u8) void { + // Can only happen if the slice doesn't originate from our alloc(). + std.debug.assert(slot.len >= SIZES[0]); + + const sci = getSizeClassIndex(slot.len); + if (sci < SIZES.len) { + @branchHint(.likely); + free_size_class(sci, slot); + } else { + std.posix.munmap(@alignCast(slot)); + } +} + +fn free_size_class(sci: u8, slot: []u8) void { + std.debug.assert(sci < SIZES.len); + + const tl = &tl_slab_infos[sci]; + const slab = slabs + sci * SLAB_SIZE; + const res_n = reserveCountForSizeClassIndex(sci); + + tl.fc[tl.fc_count] = @intCast((slot.ptr - slab) / 8); + tl.fc_count += 1; + + // Is the local free slot cache saturated? + if (tl.fc_count == res_n) { + const info = &slab_infos[sci]; + flush_fc(slab, info, tl); + } +} + +fn flush_fc(slab: [*]u8, info: *SlabInfo, tl: *TlSlabInfo) void { + var fl_head = @atomicLoad(FlHead, &info.free_list, .acquire); + while (true) { + const prev = fl_head.idx; + + // fc_count is always even here and can be divided in equal halves. + const half = tl.fc_count / 2; + + const idx = tl.fc[half]; + const ptr = slab + idx * 8; + const arr: [*]u32 = @ptrCast(@alignCast(ptr)); + + @atomicStore(u32, &arr[0], prev, .unordered); + @atomicStore(u32, &arr[1], half, .unordered); + // Safe to copy more than needed; should compile into SIMD ops if we + // treat it as a u128 array. + const arr_u128: [*]u128 = @ptrCast(@alignCast(arr + 4)); + const fc_u128: [*]u128 = @ptrCast(&tl.fc); + const start = half / 4; + for (0..RESERVE_MAX / 2 / 4) |i| { + @atomicStore(u128, &arr_u128[i], fc_u128[start + i], .unordered); + } + if (@cmpxchgWeak( + FlHead, + &info.free_list, + fl_head, + .{ .aba = fl_head.aba +% 1, .idx = idx }, + .release, + .acquire, + )) |head| { + fl_head = head; + std.atomic.spinLoopHint(); + continue; + } + tl.fc_count = half; + + return; + } +} + +export fn alloc_(size: usize) [*]u8 { + return alloc(size).ptr; +} + +export fn free_(ptr: [*]u8, len: usize) void { + return free(ptr[0..len]); +} + +export fn init_() void { + init(); +} diff --git a/src/zisp/io/Parser.zig b/src/zisp/io/Parser.zig index b1db437..0a64698 100644 --- a/src/zisp/io/Parser.zig +++ b/src/zisp/io/Parser.zig @@ -45,10 +45,9 @@ const lib = @import("../lib.zig"); const value = @import("../value.zig"); const ListPool = gc.ListPool; -const IstrSet = gc.IstrSet; +const IstrPool = gc.IstrPool; const Decoder = io.Decoder; const Value = value.Value; -const IstrPtr = value.istr.IstrPtr; const Parser = @This(); @@ -98,7 +97,7 @@ pub const Context = struct { }; list_pool: ?*ListPool, -istr_set: ?*IstrSet, +istr_pool: ?*IstrPool, alloc: Alloc, ctx_stack: List(Context), str_chars: List(u8), @@ -113,15 +112,15 @@ err_msg: []const u8 = undefined, pub fn init() !Parser { const list_pool = gc.mainListPool(); - const istr_set = gc.mainIstrSet(); + const istr_pool = gc.mainIstrPool(); const alloc = gc.mainAlloc(); const decoder = io.mainDecoder(); - return initCustom(list_pool, istr_set, alloc, 16, 512, 32, decoder); + return initCustom(list_pool, istr_pool, alloc, 16, 512, 32, decoder); } pub fn initCustom( list_pool: ?*ListPool, - istr_set: ?*IstrSet, + istr_pool: ?*IstrPool, alloc: Alloc, init_ctx_stack_cap: usize, init_str_chars_cap: usize, @@ -134,7 +133,7 @@ pub fn initCustom( const lst_cap = @max(2, init_list_elts_cap); return .{ .list_pool = list_pool, - .istr_set = istr_set, + .istr_pool = istr_pool, .alloc = alloc, .ctx_stack = try .initCapacity(alloc, ctx_cap), .str_chars = try .initCapacity(alloc, str_cap), @@ -229,8 +228,8 @@ fn getCharsAsString(p: *Parser) !Value { } } -fn getIstr(p: *Parser, s: []const u8) !IstrPtr { - if (p.istr_set) |set| { +fn getIstr(p: *Parser, s: []const u8) !Value { + if (p.istr_pool) |set| { return try value.istr.getOrNewInSet(set, s); } else { return try value.istr.new(p.alloc, s); diff --git a/src/zisp/lib.zig b/src/zisp/lib.zig index 75c52ea..7752110 100644 --- a/src/zisp/lib.zig +++ b/src/zisp/lib.zig @@ -1,2 +1 @@ pub const list = @import("lib/list.zig"); -pub const seg_stack = @import("lib/seg_stack.zig"); diff --git a/src/zisp/util/seg_stack.zig b/src/zisp/util/seg_stack.zig index 4abb6ef..2794657 100644 --- a/src/zisp/util/seg_stack.zig +++ b/src/zisp/util/seg_stack.zig @@ -11,7 +11,7 @@ pub fn SegStack(T: type, seg_max_bytes: comptime_int) type { // correct segment size (in element count) based on that. const seg_size = (seg_max_bytes - @sizeOf(usize)) / @sizeOf(T); - const IdxType = std.math.IntFittingRange(seg_size); + const IdxType = std.math.IntFittingRange(0, seg_size); comptime { if (seg_size < 4) { diff --git a/src/zisp/value.zig b/src/zisp/value.zig index f075b35..aff05fe 100644 --- a/src/zisp/value.zig +++ b/src/zisp/value.zig @@ -101,8 +101,8 @@ pub const HeapType = enum(u8) { pub fn of(ptr: anytype) HeapType { return switch (@TypeOf(ptr)) { - PairPtr => .pair, - ArrayHeader => .array, + pair.PairPtr => .pair, + array.ArrayPtr => .array, else => @panic("Unknown heap pointer."), }; } @@ -316,12 +316,17 @@ pub const Value = packed union { /// Check for small rat (rational number). pub fn isSrat(v: Value) bool { - return v.srat._hi == hi15_srat; + return v.srat._hi == hi16_srat; } - /// Placeholder: Check for (undefined). - pub fn isNdef(v: Value) bool { - return v.ndef._hi == hi15_ndef; + /// Placeholder: Check for (undefined 1). + pub fn isNdf1(v: Value) bool { + return v.ndf1._hi == hi16_ndf1; + } + + /// Placeholder: Check for (undefined 2). + pub fn isNdf2(v: Value) bool { + return v.ndf2._hi == hi16_ndf2; } /// Check for rune. diff --git a/src/zisp/value/ptr.zig b/src/zisp/value/hptr.zig index 8a3ca18..8a3ca18 100644 --- a/src/zisp/value/ptr.zig +++ b/src/zisp/value/hptr.zig diff --git a/src/zisp/value/istr.zig b/src/zisp/value/istr.zig index 528280a..c0928ef 100644 --- a/src/zisp/value/istr.zig +++ b/src/zisp/value/istr.zig @@ -38,35 +38,24 @@ fn assertValidIstr(s: []const u8) void { } } -pub fn new(alloc: Alloc, s: []const u8) !IstrPtr { - const algn = std.mem.Alignment.of(IstrPtr); - const size = @sizeOf(IstrHead) + s.len; - const istr: IstrPtr = @ptrCast(try alloc.alignedAlloc(u8, algn, size)); - istr.putStr(s); - return istr; +pub fn new(s: []const u8) !Value { + const istr = gc.allocIstr(s.len); + @memcpy(istr) } -pub fn getOrNew(s: []const u8) !IstrPtr { +pub fn getOrNew(s: []const u8) !Value { return getOrNewInPool(gc.mainIstrPool(), s); } -pub fn getOrNewInPool(pool: *IstrPool, s: []const u8) !IstrPtr { +pub fn getOrNewInPool(pool: *IstrPool, s: []const u8) !Value { assertValidIstr(s); return try pool.getOrNew(s); } -pub fn pack(istr: IstrPtr) Value { - const ptr = @intFromPtr(istr); - return .{ .istr = .{ .ptr = @intCast(ptr) } }; -} - -pub fn unpack(v: Value) IstrPtr { - return @ptrFromInt(v.istr.ptr); -} - pub fn getBytes(v: Value) []const u8 { assert(v); - return unpack(v).bytes(); + const ptr = gc.istrPtrFromIdx(v.istr.idx); + return ptr[0..v.istr.len]; } // Zisp API @@ -77,8 +66,7 @@ pub fn pred(v: Value) Value { pub fn getLen(v: Value) Value { assert(v); - const istr: IstrPtr = @ptrFromInt(v.istr.ptr); - return value.fixnum.pack(istr.len); + return value.fixnum.pack(v.istr.len); } // TODO: Zisp representation of IstrPool & ability to intern in given IstrPool @@ -86,5 +74,5 @@ pub fn getLen(v: Value) Value { pub fn intern(v: Value) Value { const istr = assert(v); const pool = gc.mainIstrPool(); - return pack(pool.getOrPut(istr)); + return pool.getOrPut(istr); } |
