const std = @import("std"); const Value = @import("value.zig").Value; const assert = std.debug.assert; /// This is the raw data, with no bookkeeping, no memory awareness, no de-duplication. /// It's important for this struct to be small. /// It is not copyable since it may contain references to its inner data. /// Types are not de-duplicated, which helps with multi-threading since it obviates the requirement /// of obtaining a lock on a global type table, as well as making the /// garbage collection bookkeeping simpler. /// This union takes advantage of the fact that the first page of memory /// is unmapped, giving us 4096 possible enum tags that have no payload. pub const Type = extern union { /// If the tag value is less than Tag.no_payload_count, then no pointer /// dereference is needed. tag_if_small_enough: usize, ptr_otherwise: *Payload, pub fn zigTypeTag(self: Type) std.builtin.TypeId { switch (self.tag()) { .int_u8, .int_usize => return .Int, .array_u8, .array_u8_sentinel_0 => return .Array, .single_const_pointer => return .Pointer, } } pub fn initTag(comptime small_tag: Tag) Type { comptime assert(@enumToInt(small_tag) < Tag.no_payload_count); return .{ .tag_if_small_enough = @enumToInt(small_tag) }; } pub fn initPayload(payload: *Payload) Type { assert(@enumToInt(payload.tag) >= Tag.no_payload_count); return .{ .ptr_otherwise = payload }; } pub fn tag(self: Type) Tag { if (self.tag_if_small_enough < Tag.no_payload_count) { return @intToEnum(Tag, @intCast(@TagType(Tag), self.tag_if_small_enough)); } else { return self.ptr_otherwise.tag; } } pub fn format( self: Type, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: var, ) !void { comptime assert(fmt.len == 0); var ty = self; while (true) { switch (ty.tag()) { .no_return => return out_stream.writeAll("noreturn"), .int_comptime => return out_stream.writeAll("comptime_int"), .int_u8 => return out_stream.writeAll("u8"), .int_usize => return out_stream.writeAll("usize"), .array_u8_sentinel_0 => { const payload = @fieldParentPtr(Payload.Array_u8_Sentinel0, "base", ty.ptr_otherwise); return out_stream.print("[{}:0]u8", .{payload.len}); }, .array => { const payload = @fieldParentPtr(Payload.Array, "base", ty.ptr_otherwise); try out_stream.print("[{}]", .{payload.len}); ty = payload.elem_type; continue; }, .single_const_pointer => { const payload = @fieldParentPtr(Payload.SingleConstPointer, "base", ty.ptr_otherwise); try out_stream.writeAll("*const "); ty = payload.pointee_type; continue; }, } unreachable; } } /// This enum does not directly correspond to `std.builtin.TypeId` because /// it has extra enum tags in it, as a way of using less memory. For example, /// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types /// but with different alignment values, in this data structure they are represented /// with different enum tags, because the the former requires more payload data than the latter. /// See `zigTypeTag` for the function that corresponds to `std.builtin.TypeId`. pub const Tag = enum { // The first section of this enum are tags that require no payload. no_return, int_comptime, int_u8, int_usize, // See last_no_payload_tag below. // After this, the tag requires a payload. array_u8_sentinel_0, array, single_const_pointer, pub const last_no_payload_tag = Tag.int_usize; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; }; pub const Payload = struct { tag: Tag, pub const Array_u8_Sentinel0 = struct { base: Payload = Payload{ .tag = .array_u8_sentinel_0 }, len: u64, }; pub const Array = struct { base: Payload = Payload{ .tag = .array }, elem_type: Type, len: u64, }; pub const SingleConstPointer = struct { base: Payload = Payload{ .tag = .single_const_pointer }, pointee_type: Type, }; }; };