summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/0/0-value.md40
-rw-r--r--html/style.css4
-rw-r--r--src/main.zig21
-rw-r--r--src/zisp/gc/ListPool.zig25
-rw-r--r--src/zisp/value.zig14
-rw-r--r--src/zisp/value/list.zig33
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));
}