diff options
| -rw-r--r-- | html/index.md | 4 | ||||
| -rw-r--r-- | src/libzisp.zig | 131 | ||||
| -rw-r--r-- | src/libzisp/value.zig | 34 | ||||
| -rw-r--r-- | src/libzisp/value/boole.zig | 28 | ||||
| -rw-r--r-- | src/libzisp/value/char.zig | 4 | ||||
| -rw-r--r-- | src/libzisp/value/eof.zig | 27 | ||||
| -rw-r--r-- | src/libzisp/value/fixnum.zig | 2 | ||||
| -rw-r--r-- | src/libzisp/value/misc.zig | 8 | ||||
| -rw-r--r-- | src/libzisp/value/nil.zig | 27 | ||||
| -rw-r--r-- | src/libzisp/value/ptr.zig | 34 | ||||
| -rw-r--r-- | src/libzisp/value/sstr.zig | 64 |
11 files changed, 306 insertions, 57 deletions
diff --git a/html/index.md b/html/index.md index e369e56..37565f1 100644 --- a/html/index.md +++ b/html/index.md @@ -22,3 +22,7 @@ ramblings of a madman. * [Object-oriented programming](notes/oop.html) * [Equality and equivalence semantics](notes/equal.html) * [NaN-packing](notes/nan.html) + +Temporary source repo before I set up my own git server: + +[Zisp on GitHub](https://github.com/TaylanUB/zisp/) diff --git a/src/libzisp.zig b/src/libzisp.zig index 542b84c..c7ead3b 100644 --- a/src/libzisp.zig +++ b/src/libzisp.zig @@ -5,7 +5,7 @@ const std = @import("std"); const builtin = @import("builtin"); const testing = std.testing; -const value = @import("libzisp/value.zig"); +pub const value = @import("libzisp/value.zig"); test "double" { const d1: f64 = 0.123456789; @@ -18,7 +18,7 @@ test "double" { try std.testing.expect(value.double.check(v1)); try std.testing.expect(value.double.check(v2)); try std.testing.expect(value.double.check(v3)); - try std.testing.expect(result == d1 + d2); + try std.testing.expectEqual(d1 + d2, result); } test "fixnum" { @@ -32,20 +32,121 @@ test "fixnum" { try std.testing.expect(value.fixnum.check(v1)); try std.testing.expect(value.fixnum.check(v2)); try std.testing.expect(value.fixnum.check(v3)); - try std.testing.expect(result == int1 + int2); + try std.testing.expectEqual(int1 + int2, result); } test "ptr" { - const ptr1 = value.ptr.pack(@ptrFromInt(256), value.ptr.Tag.string); - try std.testing.expect(value.ptr.check(ptr1)); - try std.testing.expect(value.ptr.checkZisp(ptr1)); - try std.testing.expect(value.ptr.checkNormal(ptr1)); - - const ptr2 = value.ptr.makeWeak(ptr1); - try std.testing.expect(value.ptr.check(ptr2)); - try std.testing.expect(value.ptr.checkZisp(ptr2)); - try std.testing.expect(value.ptr.checkWeak(ptr2)); - - // Make sure ptr1 wasn't modified - try std.testing.expect(value.ptr.checkNormal(ptr1)); + const p: *anyopaque = @ptrFromInt(256); + + const p1 = value.ptr.pack(p, value.ptr.Tag.string); + try std.testing.expect(value.ptr.check(p1)); + try std.testing.expect(value.ptr.checkZisp(p1)); + try std.testing.expect(value.ptr.checkNormal(p1)); + const p1v, const p1t = value.ptr.unpack(p1); + try std.testing.expectEqual(p, p1v); + try std.testing.expectEqual(value.ptr.Tag.string, p1t); + + const p2 = value.ptr.makeWeak(p1); + try std.testing.expect(value.ptr.check(p2)); + try std.testing.expect(value.ptr.checkZisp(p2)); + try std.testing.expect(value.ptr.checkWeak(p2)); + const p2v, const p2t = value.ptr.unpack(p1); + try std.testing.expectEqual(p, p2v); + try std.testing.expectEqual(value.ptr.Tag.string, p2t); +} + +test "sstr" { + const impls = .{ + .{ value.sstr.pack, value.sstr.unpack }, + // .{ value.sstr.pack1, value.sstr.unpack1 }, + // .{ value.sstr.pack2, value.sstr.unpack2 }, + // .{ value.sstr.pack3, value.sstr.unpack3 }, + // .{ value.sstr.pack4, value.sstr.unpack4 }, + }; + + inline for (impls, 0..) |impl, i| { + const pack, const unpack = impl; + + const ss1 = pack("1"); + const ss2 = pack("123"); + const ss3 = pack("123456"); + + const s1, const l1 = unpack(ss1); + const s2, const l2 = unpack(ss2); + const s3, const l3 = unpack(ss3); + + try std.testing.expect(value.sstr.check(ss1)); + try std.testing.expect(value.sstr.check(ss2)); + try std.testing.expect(value.sstr.check(ss3)); + + try std.testing.expectEqual(1, l1); + try std.testing.expectEqualStrings("1", s1[0..l1]); + + try std.testing.expectEqual(3, l2); + try std.testing.expectEqualStrings("123", s2[0..l2]); + + try std.testing.expectEqual(6, l3); + try std.testing.expectEqualStrings("123456", s3[0..l3]); + + var timer = try std.time.Timer.start(); + var ns: f64 = undefined; + var secs: f64 = undefined; + + const iters = 1; + if (iters > 1) { + for (0..iters) |_i| { + _ = _i; + std.mem.doNotOptimizeAway(pack("1")); + std.mem.doNotOptimizeAway(pack("123")); + std.mem.doNotOptimizeAway(pack("123456")); + } + + ns = @floatFromInt(timer.lap()); + secs = ns / 1_000_000_000; + + std.debug.print("pack{}: {d:.3}s\t", .{ i, secs }); + + for (0..iters) |_i| { + _ = _i; + std.mem.doNotOptimizeAway(unpack(ss1)); + std.mem.doNotOptimizeAway(unpack(ss2)); + std.mem.doNotOptimizeAway(unpack(ss3)); + } + + ns = @floatFromInt(timer.lap()); + secs = ns / 1_000_000_000; + + std.debug.print("unpack{}: {d:.3}s\n", .{ i, secs }); + } + } +} + +test "char" { + const c1 = value.char.pack('\x00'); + try std.testing.expect(value.char.check(c1)); + try std.testing.expectEqual('\x00', value.char.unpack(c1)); + + const c2 = value.char.pack('😀'); + try std.testing.expect(value.char.check(c2)); + try std.testing.expectEqual('😀', value.char.unpack(c2)); +} + +test "misc" { + const f = value.boole.pack(false); + try std.testing.expect(value.boole.check(f)); + try std.testing.expectEqual(false, value.boole.unpack(f)); + try std.testing.expect(value.boole.unpack(value.boole.pred(f))); + + const t = value.boole.pack(true); + try std.testing.expect(value.boole.check(t)); + try std.testing.expectEqual(true, value.boole.unpack(t)); + try std.testing.expect(value.boole.unpack(value.boole.pred(t))); + + const nil = value.nil.get(); + try std.testing.expect(value.nil.check(nil)); + try std.testing.expect(value.boole.unpack(value.nil.pred(nil))); + + const eof = value.eof.get(); + try std.testing.expect(value.eof.check(eof)); + try std.testing.expect(value.boole.unpack(value.eof.pred(eof))); } diff --git a/src/libzisp/value.zig b/src/libzisp/value.zig index 62807be..da67af7 100644 --- a/src/libzisp/value.zig +++ b/src/libzisp/value.zig @@ -100,8 +100,13 @@ // into 6 bytes or less will be stored as an immediate value, not requiring any // heap allocation or interning. (It's implicitly interned.) // -// There may still be uninterned strings on the heap that are just as short. -// Calling intern on them will return the equivalent small string. +// The null byte serves as a terminator and cannot appear in these strings; a +// string that short but actually containing a null byte will need to be heap +// allocated like other strings. +// +// There may also be uninterned strings on the heap that are also as short but +// ended up on the heap due to being uninterned. Calling intern on them will +// return the equivalent small string. // // Unicode code points need a maximum of 21 bits, yet we have 48 available. // This may be exploited for a future extension. @@ -110,10 +115,10 @@ // dozen singleton values (false, true, null, and so on). As such, this range // of bit patterns may be subdivided further in the future. // -// And on top of all that we still have two 50-bit ranges left! +// And on top of all that we still have a 48-bit and a 50-bit range left! // -// The forbidden value 4, Positive Infinity, is in one of the two undefined -// value ranges. +// The forbidden value 4, Positive Infinity, is in the 48-bit undefined value +// range starting with the 000 tag. // // Here's the original article explaining the strategy: @@ -133,8 +138,9 @@ pub const ptr = @import("value/ptr.zig"); pub const sstr = @import("value/sstr.zig"); pub const char = @import("value/char.zig"); -pub const misc = @import("value/misc.zig"); pub const boole = @import("value/boole.zig"); +pub const nil = @import("value/nil.zig"); +pub const eof = @import("value/eof.zig"); /// To fill up the u11 exponent part of a NaN. const FILL = 0x7ff; @@ -142,6 +148,7 @@ const FILL = 0x7ff; /// Represents a Zisp value/object. pub const Value = packed union { double: f64, + bits: u64, nan: packed struct { rest: u51, @@ -178,7 +185,7 @@ pub const Value = packed union { sstr: packed struct { // packed struct cannot contain array value: u48, - tag: Tag = .str, + tag: Tag = .sstr, ptr: bool = false, _: u11 = FILL, fixnum: bool = false, @@ -186,7 +193,7 @@ pub const Value = packed union { char: packed struct { value: u48, - tag: u3 = 2, + tag: Tag = .char, ptr: bool = false, _: u11 = FILL, fixnum: bool = false, @@ -194,13 +201,13 @@ pub const Value = packed union { misc: packed struct { value: u48, - tag: u3 = 3, + tag: Tag = .misc, ptr: bool = false, _: u11 = FILL, fixnum: bool = false, }, - const Tag = enum(u3) { str = 1, char = 2, misc = 3 }; + const Tag = enum(u3) { sstr = 1, char = 2, misc = 3 }; const Self = @This(); @@ -209,13 +216,8 @@ pub const Value = packed union { std.debug.dumpHex(std.mem.asBytes(&self)); } - /// Checks for any IEEE 754 NaN. - pub fn isNan(self: Self) bool { - return self.nan.exp == FILL; - } - /// Checks for a Zisp value (non-double) packed into a NaN. pub fn isPacked(self: Self) bool { - return self.isNan() and self.nan.rest != 0; + return self.nan.exp == FILL and self.nan.rest != 0; } }; diff --git a/src/libzisp/value/boole.zig b/src/libzisp/value/boole.zig index d4fbd28..0af7e22 100644 --- a/src/libzisp/value/boole.zig +++ b/src/libzisp/value/boole.zig @@ -1,10 +1,34 @@ const Value = @import("../value.zig").Value; const misc = @import("misc.zig"); -// These can be accessed from either namespace. pub const f = misc.f; pub const t = misc.t; +// Zig API + +/// Checks if the value is a boole. +pub fn check(v: Value) bool { + return v.bits == f.bits or v.bits == t.bits; +} + +pub fn assert(v: Value) void { + if (!check(v)) { + v.dump(); + @panic("not bool"); + } +} + pub fn pack(b: bool) Value { - return if (b) f else t; + return if (b) t else f; +} + +pub fn unpack(v: Value) bool { + assert(v); + return v.bits == t.bits; +} + +// Zisp API + +pub fn pred(v: Value) Value { + return if (check(v)) t else f; } diff --git a/src/libzisp/value/char.zig b/src/libzisp/value/char.zig index 7034128..6a38f0d 100644 --- a/src/libzisp/value/char.zig +++ b/src/libzisp/value/char.zig @@ -15,10 +15,10 @@ pub fn assert(v: Value) void { } pub fn pack(c: u21) Value { - return .{ .char = .{c} }; + return .{ .char = .{ .value = c } }; } pub fn unpack(v: Value) u21 { assert(v); - return v.char.value; + return @truncate(v.char.value); } diff --git a/src/libzisp/value/eof.zig b/src/libzisp/value/eof.zig new file mode 100644 index 0000000..34ab35d --- /dev/null +++ b/src/libzisp/value/eof.zig @@ -0,0 +1,27 @@ +const Value = @import("../value.zig").Value; +const misc = @import("misc.zig"); + +pub const eof = misc.eof; + +// Zig API + +pub fn check(v: Value) bool { + return v.bits == eof.bits; +} + +pub fn assert(v: Value) void { + if (!check(v)) { + v.dump(); + @panic("not bool"); + } +} + +// Zisp API + +pub fn get() Value { + return eof; +} + +pub fn pred(v: Value) Value { + return if (check(v)) misc.t else misc.f; +} diff --git a/src/libzisp/value/fixnum.zig b/src/libzisp/value/fixnum.zig index 60b4239..6d26a9c 100644 --- a/src/libzisp/value/fixnum.zig +++ b/src/libzisp/value/fixnum.zig @@ -21,7 +21,7 @@ pub fn assert(v: Value) void { const fixnum_min = std.math.minInt(i52) + 1; const fixnum_max = std.math.maxInt(i52) - 1; -fn isValidRange(int: i64) bool { +pub fn isValidRange(int: i64) bool { return fixnum_min < int and int < fixnum_max; } diff --git a/src/libzisp/value/misc.zig b/src/libzisp/value/misc.zig index 2570644..793c60e 100644 --- a/src/libzisp/value/misc.zig +++ b/src/libzisp/value/misc.zig @@ -1,6 +1,6 @@ const Value = @import("../value.zig").Value; -pub const f = Value{ .misc = .{0} }; -pub const t = Value{ .misc = .{1} }; -pub const nil = Value{ .misc = .{2} }; -pub const eof = Value{ .misc = .{3} }; +pub const f = Value{ .misc = .{ .value = 0 } }; +pub const t = Value{ .misc = .{ .value = 1 } }; +pub const nil = Value{ .misc = .{ .value = 2 } }; +pub const eof = Value{ .misc = .{ .value = 3 } }; diff --git a/src/libzisp/value/nil.zig b/src/libzisp/value/nil.zig new file mode 100644 index 0000000..1b1a51e --- /dev/null +++ b/src/libzisp/value/nil.zig @@ -0,0 +1,27 @@ +const Value = @import("../value.zig").Value; +const misc = @import("misc.zig"); + +pub const nil = misc.nil; + +// Zig API + +pub fn check(v: Value) bool { + return v.bits == nil.bits; +} + +pub fn assert(v: Value) void { + if (!check(v)) { + v.dump(); + @panic("not bool"); + } +} + +// Zisp API + +pub fn get() Value { + return nil; +} + +pub fn pred(v: Value) Value { + return if (check(v)) misc.t else misc.f; +} diff --git a/src/libzisp/value/ptr.zig b/src/libzisp/value/ptr.zig index 4bf92b6..6a3a6c4 100644 --- a/src/libzisp/value/ptr.zig +++ b/src/libzisp/value/ptr.zig @@ -88,15 +88,15 @@ pub fn packWeak(ptr: *anyopaque, tag: Tag) Value { } // Unpacks weak as well; no need for a separate fn. -pub fn unpack(v: Value) struct { ptr: *anyopaque, tag: Tag } { +pub fn unpack(v: Value) PtrAndTag { assertZisp(v); - return untagPtr(v.ptr.value.tagged); + return untagPtr(v.ptr.value); } // Weak pointers may be null. pub fn isNull(v: Value) bool { assertWeak(v); - const ptr, _ = untagPtr(v.ptr.value.tagged); + const ptr, _ = untagPtr(v.ptr.value); return @intFromPtr(ptr) == 0; } @@ -106,36 +106,38 @@ pub fn tagPtr(ptr: *anyopaque, tag: Tag) u49 { return untagged << 1 | @intFromEnum(tag); } -pub fn untagPtr(tagged: 49) struct { ptr: *anyopaque, tag: Tag } { +pub const PtrAndTag = struct { *anyopaque, Tag }; + +pub fn untagPtr(tagged: u49) PtrAndTag { const untagged: u49 = tagged >> 1 & 0xfffffffffff0; const ptr: *anyopaque = @ptrFromInt(untagged); const int: u4 = @truncate(tagged); const tag: Tag = @enumFromInt(int); - return .{ .ptr = ptr, .tag = tag }; + return .{ ptr, tag }; } pub const Tag = enum(u4) { - /// 1. Strings / Symbols + /// 0. Strings / Symbols string, - /// 2. Bignums / Ratnums + /// 1. Bignums / Ratnums number, - /// 3. Pairs ([2]Value) + /// 2. Pairs ([2]Value) pair, - /// 4. Vector, bytevector, etc. + /// 3. Vector, bytevector, etc. array, - /// 5. Ordered hash table + /// 4. Ordered hash table table, - /// 6. String buffer + /// 5. String buffer text, - /// 7. Class, interface, etc. + /// 6. Class, interface, etc. role, - /// 8. Instance, basically + /// 7. Instance, basically actor, - /// 9. I/O Port + /// 8. I/O Port port, - /// 10. Procedure + /// 9. Procedure proc, - /// 11. Continuation + /// 10. Continuation cont, /// Other other = 15, diff --git a/src/libzisp/value/sstr.zig b/src/libzisp/value/sstr.zig index 3c0755d..c2b2859 100644 --- a/src/libzisp/value/sstr.zig +++ b/src/libzisp/value/sstr.zig @@ -1,3 +1,65 @@ +const std = @import("std"); + const Value = @import("../value.zig").Value; -// stub +// Zig API + +pub fn check(v: Value) bool { + return v.isPacked() and + !v.sstr.fixnum and + !v.sstr.ptr and + v.sstr.tag == .sstr; +} + +pub fn assert(v: Value) void { + if (!check(v)) { + v.dump(); + @panic("not sstr"); + } +} + +// For now, ignore encoding, just treat it as []u8. + +pub fn isValidSstr(s: []const u8) bool { + if (s.len > 6) { + return false; + } + for (s) |c| { + if (c == 0) { + return false; + } + } + return true; +} + +fn assertValidSstr(s: []const u8) void { + if (!isValidSstr(s)) { + std.debug.print("invalid sstr: {s}", .{s}); + @panic("invalid sstr"); + } +} + +// Different ways of doing the following have been tested, including manual +// shifting and bit masking, but memcpy always wins easily according to our +// micro-benchmarks, both under ReleaseSafe and under ReleaseFast. + +pub fn pack(s: []const u8) Value { + assertValidSstr(s); + var v = Value{ .sstr = .{ .value = 0 } }; + const dest: [*]u8 = @ptrCast(&v.sstr.value); + @memcpy(dest, s); + return v; +} + +// It's tempting to inline for here to eliminate the if statement or prevent +// need of @truncate but all alternatives were a little slower. + +pub fn unpack(v: Value) struct { [6]u8, u3 } { + var s: [6]u8 = undefined; + const src: *const [6]u8 = @ptrCast(&v.sstr.value); + @memcpy(&s, src); + for (0..6) |i| { + if (s[i] == 0) return .{ s, @truncate(i) }; + } + return .{ s, 6 }; +} |
