diff options
| -rw-r--r-- | doc/0/0-value.md | 40 | ||||
| -rw-r--r-- | html/style.css | 4 | ||||
| -rw-r--r-- | src/main.zig | 21 | ||||
| -rw-r--r-- | src/zisp/gc/ListPool.zig | 25 | ||||
| -rw-r--r-- | src/zisp/value.zig | 14 | ||||
| -rw-r--r-- | src/zisp/value/list.zig | 33 |
6 files changed, 99 insertions, 38 deletions
diff --git a/doc/0/0-value.md b/doc/0/0-value.md index 16e1a58..050d003 100644 --- a/doc/0/0-value.md +++ b/doc/0/0-value.md @@ -186,10 +186,42 @@ Forbidden Value #4, Positive Infinity, is avoided thanks to the fact that heap pointers always have a non-zero heap-type tag. (See further above.) The first four categories simply mirror those of the previous 51-bit range, but -mark the values as being constants rather than code to evaluate. - -The remaining four categories are somewhat similar to VM instructions. - +mark the values as being constants rather than code to evaluate. This way, we +can inject direct data pointers into the AST without needing to worry about the +data being confused for code to evaluate, and without needing the `(quote ...)` +wrapper anymore. + +The remaining four categories could be seen as instructions for a tree-walking +virtual machine executing Zisp code. + +### Local reference + +Local variables, regardless of whether they are function parameters, variables +closed over lexically, or explicit local declarations, all use a single flat +"locals" array at run-time. References are then optimized into direct indexes +into this array. The actual index value is the lowest 16 bits, with the other +32 bits being reserved for other purposes. + +### Expression pointers + +The final three pointer types are derivatives of list pointers, using three low +tag bits indicating the count of elements making up the expression. + +A pointer to a constant function-call expression indicates that the destination +is an array whose first element is a raw, unpacked, untagged pointer to a Zisp +function object; the remaining elements need to be evaluated to produce the +arguments to the function. As a further optimization trick, the first element +may actually be an integer up to 255, that indicates dispatch to a built-in VM +operation acting as a function. + +In a variable function-call expression, the first element needs evaluation to +produce a function pointer: It could be a local variable reference, one of the +expression pointer types, or else a raw pointer into a module exports table. + +A special-form or macro-call expression is similar to a constant function-call +except that the arguments are passed as context-wrapped source code objects. +The first element can be an integer up to 255, dispatching to a VM built-in; +otherwise, it must be a pointer to a macro function. <!-- diff --git a/html/style.css b/html/style.css index 1e55997..d68a338 100644 --- a/html/style.css +++ b/html/style.css @@ -55,10 +55,10 @@ td, th { @media (prefers-color-scheme: dark) { body { background: black; - color: #999; + color: #ccc; } table, td, th { - border-color: #999; + border-color: #ccc; } a { color: #00aa00; diff --git a/src/main.zig b/src/main.zig index e601020..d08f9a1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,21 +1,25 @@ const std = @import("std"); const zisp = @import("zisp"); +const gc = zisp.gc; +const io = zisp.io; +const value = zisp.value; + pub fn main() !u8 { const alloc = std.heap.smp_allocator; - const io = std.Io.Threaded.global_single_threaded.io(); + const gst_io = std.Io.Threaded.global_single_threaded.io(); var stdin_buffer: [4096]u8 = undefined; - var stdin_reader = std.Io.File.stdin().reader(io, &stdin_buffer); + var stdin_reader = std.Io.File.stdin().reader(gst_io, &stdin_buffer); const reader = &stdin_reader.interface; var stdout_buffer: [4096]u8 = undefined; - var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); + var stdout_writer = std.Io.File.stdout().writer(gst_io, &stdout_buffer); const writer = &stdout_writer.interface; - try zisp.gc.init(); + try gc.init(); - var p = try zisp.io.Parser.init(alloc); + var p = try io.Parser.init(alloc); defer p.deinit(); while (true) { const datum = p.run(reader) catch { @@ -30,16 +34,17 @@ pub fn main() !u8 { std.debug.print(format, .{ err, pos, unread }); return 1; }; - if (datum.eq(zisp.value.eof)) { + if (datum.eq(value.eof)) { return 0; } try writer.writeAll("0x"); try writer.printInt(datum.bits, 16, .lower, .{}); try writer.writeAll(": "); - try zisp.io.print(writer, datum); + try io.print(writer, datum); try writer.writeAll("\n"); try writer.flush(); - if (datum.isList()) try zisp.value.list.free(datum); + // To test list array free / reuse: + if (datum.isList()) try value.list.free(p.alloc, p.list_pool, datum); } } diff --git a/src/zisp/gc/ListPool.zig b/src/zisp/gc/ListPool.zig index a843ae5..0735582 100644 --- a/src/zisp/gc/ListPool.zig +++ b/src/zisp/gc/ListPool.zig @@ -21,11 +21,8 @@ const SegStack = seg_stack.SegStack; const Self = @This(); -/// How many Value elements fit in a block. A good value should be 512, since -/// each Value is 8 bytes: 512 * 8 = 4 KiB -const block_value_cap = 512; - -const Block = [block_value_cap]Value; +// 4 KiB blocks fit 512 Value elements, which should be good. +const Block = [4096 / @sizeOf(Value)]Value; alloc: Alloc, @@ -65,6 +62,10 @@ fn newBlock(self: *Self) !void { self.current_index = 0; } +fn curBlockPtr(self: *Self) [*]Value { + return @ptrCast(&self.current_block[self.current_index]); +} + fn popFree(self: *Self, len: u3) ?[*]Value { return self.free_lists[len - 1].pop(self.alloc); } @@ -78,17 +79,27 @@ pub fn allocVals(self: *Self, len: u3) ![*]Value { if (self.popFree(len)) |ptr| return ptr; - if (len > block_value_cap - self.current_index) { + if (len > self.current_block.len - self.current_index) { try self.newBlock(); } defer self.current_index += len; - return @ptrCast(&self.current_block[self.current_index]); + return self.curBlockPtr(); } pub fn freeVals(self: *Self, len: u3, ptr: [*]Value) !void { std.debug.assert(len != 0); + // Is it simply the last allocation? If so, just undo it. + if (self.current_index >= len) { + const ptr_val = @intFromPtr(ptr); + const last_ptr_val = @intFromPtr(self.curBlockPtr() - len); + if (ptr_val == last_ptr_val) { + self.current_index -= len; + return; + } + } + try self.pushFree(len, ptr); } diff --git a/src/zisp/value.zig b/src/zisp/value.zig index 4ce6cd2..2ae0524 100644 --- a/src/zisp/value.zig +++ b/src/zisp/value.zig @@ -269,16 +269,16 @@ pub const Value = packed union { } /// Checks for a pointer and returns the value and tag separately, or null - /// if this isn't a pointer or null. Could be useful for a dispatch table. - pub fn getPtrAny(v: Value) ?struct { Zptr, HeapType } { + /// if this isn't a pointer at all. Could be useful for a dispatch table. + /// Note that unlike getPtr(), this can actually return a null pointer as + /// such, not conflating it with the "not a heap pointer" case. + pub fn getPtrAny(v: Value) ?struct { ?Zptr, HeapType } { const hi16_bits: u16 = @intCast(v.bits >> 48); if (hi16_bits != hi16_tag_ptr) return null; - const ptr_val: u48 = @intCast(v.bits << 20 >> 16); - if (ptr_val == 0) return null; - - const ht_val: u4 = @intCast(v.bits << 16 >> 60); - return .{ @ptrFromInt(ptr_val), @enumFromInt(ht_val) }; + const ht: u4 = @truncate(hi16_bits); + const pval: u48 = @intCast(v.bits << 20 >> 16); + return .{ @ptrFromInt(pval), @enumFromInt(ht) }; } /// Checks if the value is a list pointer. diff --git a/src/zisp/value/list.zig b/src/zisp/value/list.zig index 52c665e..a78d274 100644 --- a/src/zisp/value/list.zig +++ b/src/zisp/value/list.zig @@ -38,9 +38,15 @@ pub fn new(alloc: Alloc, pool: ?*ListPool, vals: []const Value) !Value { return .{ .list = .{ .len_tagged_ptr = len_tagged_ptr } }; } -// TODO: this is just a test -pub fn free(list: Value) !void { - try gc.mainListPool().freeList(list); +pub fn free(alloc: Alloc, pool: ?*ListPool, list: Value) !void { + const len = getLenTag(list); + const ptr = getValPtr(list); + if (len == 0 or pool == null) { + const full_len = findFullLen(list); + alloc.free(ptr[0..full_len]); + } else { + try pool.?.freeVals(len, ptr); + } } pub fn getLenTag(v: Value) u3 { @@ -51,19 +57,26 @@ pub fn getValPtr(v: Value) [*]Value { return @ptrFromInt(v.bits & 0x0000fffffffffff8); } +pub fn findFullLen(v: Value) usize { + std.debug.assert(getLenTag(v) == 0); + const ptr: [*]u64 = @ptrCast(getValPtr(v)); + var i: usize = 8; + while (true) : (i += 8) { + const vals: @Vector(8, u64) = (ptr + i)[0..8].*; + const mask: @Vector(8, u64) = @splat(value.none.bits); + const results: u8 = @bitCast(vals == mask); + if (results != 0) return i + @clz(results); + } +} + // Zisp API pub fn pred(v: Value) Value { return value.boole.pack(check(v)); } -pub fn getLen(v: Value) Value { +pub fn getLength(v: Value) Value { assert(v); - var len = getLenTag(v); - if (len == 0) { - const ptr = getValPtr(v); - len = 8; - while (ptr[len].bits != value.none.bits) len += 1; - } + const len = findFullLen(v); return value.fixnum.pack(@intCast(len)); } |
