summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/0/index.md10
-rw-r--r--doc/index.md10
-rw-r--r--src/zisp/gc.zig37
-rw-r--r--src/zisp/gc/IstrPool.zig6
-rw-r--r--src/zisp/gc/ListPool.zig10
-rw-r--r--src/zisp/gc/meta_alloc.zig360
-rw-r--r--src/zisp/io/Parser.zig17
-rw-r--r--src/zisp/lib.zig1
-rw-r--r--src/zisp/util/seg_stack.zig2
-rw-r--r--src/zisp/value.zig17
-rw-r--r--src/zisp/value/hptr.zig (renamed from src/zisp/value/ptr.zig)0
-rw-r--r--src/zisp/value/istr.zig30
12 files changed, 427 insertions, 73 deletions
diff --git a/doc/0/index.md b/doc/0/index.md
index 844729c..da707d2 100644
--- a/doc/0/index.md
+++ b/doc/0/index.md
@@ -1,7 +1,7 @@
# Chapter 0: Genesis
This chapter explains the core value representation of Zisp, and goes
-through the processes of parsing, decoding, running, and optionally
+through the processes of parsing, decoding, running, and optionally,
compiling code.
0. [Value](0-value.html)
@@ -32,3 +32,11 @@ compiling code.
the compilation of other modules, with various options, yielding
static or dynamic object files. These may be loaded immediately,
replacing the previously uncompiled module code in memory.
+
+## Appendices
+
++ Appendix A: [Memory](A-memory.html)
+
+ Zisp foregoes use of a platform-provided heap allocator as much as
+ possible, in favor of an efficient heap allocator specialized for
+ the implementation details of Zisp.
diff --git a/doc/index.md b/doc/index.md
index 51b92fa..4087078 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -7,9 +7,9 @@ After all, Zisp is software, and its users are software developers; the easiest
way to explain *why* Zisp does certain things is often to explain *how* it does
them.
-That doesn't mean this manual will walk you through the source code line by
-line. Instead, consider this a documentation of the implementation at large,
-doubling as a language reference.
+That doesn't mean this manual will walk you through source code line by line.
+Instead, consider it a documentation of the implementation at large, doubling
+as a language reference.
## Table of Contents
@@ -21,6 +21,10 @@ doubling as a language reference.
3. [Execute](./0/3-execute.html)
4. [Compile](./0/4-compile.html)
+ Appendices:
+
+ + A. [Memory](./0/A-memory.html)
+
1. [Chapter 1: Taxonomy](./1/)
0. ...
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);
}