zig/src-self-hosted/value.zig

581 lines
19 KiB
Zig

const std = @import("std");
const Type = @import("type.zig").Type;
const log2 = std.math.log2;
const assert = std.debug.assert;
const BigInt = std.math.big.Int;
const Target = std.Target;
const Allocator = std.mem.Allocator;
/// This is the raw data, with no bookkeeping, no memory awareness,
/// no de-duplication, and no type system awareness.
/// It's important for this struct to be small.
/// 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 Value = 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 const Tag = enum {
// The first section of this enum are tags that require no payload.
u8_type,
i8_type,
isize_type,
usize_type,
c_short_type,
c_ushort_type,
c_int_type,
c_uint_type,
c_long_type,
c_ulong_type,
c_longlong_type,
c_ulonglong_type,
c_longdouble_type,
f16_type,
f32_type,
f64_type,
f128_type,
c_void_type,
bool_type,
void_type,
type_type,
anyerror_type,
comptime_int_type,
comptime_float_type,
noreturn_type,
fn_naked_noreturn_no_args_type,
single_const_pointer_to_comptime_int_type,
const_slice_u8_type,
zero,
void_value,
noreturn_value,
bool_true,
bool_false, // See last_no_payload_tag below.
// After this, the tag requires a payload.
ty,
int_u64,
int_i64,
int_big,
function,
ref,
ref_val,
bytes,
pub const last_no_payload_tag = Tag.bool_false;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
};
pub fn initTag(comptime small_tag: Tag) Value {
comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
return .{ .tag_if_small_enough = @enumToInt(small_tag) };
}
pub fn initPayload(payload: *Payload) Value {
assert(@enumToInt(payload.tag) >= Tag.no_payload_count);
return .{ .ptr_otherwise = payload };
}
pub fn tag(self: Value) 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 cast(self: Value, comptime T: type) ?*T {
if (self.tag_if_small_enough < Tag.no_payload_count)
return null;
const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag;
if (self.ptr_otherwise.tag != expected_tag)
return null;
return @fieldParentPtr(T, "base", self.ptr_otherwise);
}
pub fn format(
self: Value,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
out_stream: var,
) !void {
comptime assert(fmt.len == 0);
var val = self;
while (true) switch (val.tag()) {
.u8_type => return out_stream.writeAll("u8"),
.i8_type => return out_stream.writeAll("i8"),
.isize_type => return out_stream.writeAll("isize"),
.usize_type => return out_stream.writeAll("usize"),
.c_short_type => return out_stream.writeAll("c_short"),
.c_ushort_type => return out_stream.writeAll("c_ushort"),
.c_int_type => return out_stream.writeAll("c_int"),
.c_uint_type => return out_stream.writeAll("c_uint"),
.c_long_type => return out_stream.writeAll("c_long"),
.c_ulong_type => return out_stream.writeAll("c_ulong"),
.c_longlong_type => return out_stream.writeAll("c_longlong"),
.c_ulonglong_type => return out_stream.writeAll("c_ulonglong"),
.c_longdouble_type => return out_stream.writeAll("c_longdouble"),
.f16_type => return out_stream.writeAll("f16"),
.f32_type => return out_stream.writeAll("f32"),
.f64_type => return out_stream.writeAll("f64"),
.f128_type => return out_stream.writeAll("f128"),
.c_void_type => return out_stream.writeAll("c_void"),
.bool_type => return out_stream.writeAll("bool"),
.void_type => return out_stream.writeAll("void"),
.type_type => return out_stream.writeAll("type"),
.anyerror_type => return out_stream.writeAll("anyerror"),
.comptime_int_type => return out_stream.writeAll("comptime_int"),
.comptime_float_type => return out_stream.writeAll("comptime_float"),
.noreturn_type => return out_stream.writeAll("noreturn"),
.fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
.single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
.const_slice_u8_type => return out_stream.writeAll("[]const u8"),
.zero => return out_stream.writeAll("0"),
.void_value => return out_stream.writeAll("{}"),
.noreturn_value => return out_stream.writeAll("unreachable"),
.bool_true => return out_stream.writeAll("true"),
.bool_false => return out_stream.writeAll("false"),
.ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream),
.int_u64 => return std.fmt.formatIntValue(val.cast(Payload.Int_u64).?.int, "", options, out_stream),
.int_i64 => return std.fmt.formatIntValue(val.cast(Payload.Int_i64).?.int, "", options, out_stream),
.int_big => return out_stream.print("{}", .{val.cast(Payload.IntBig).?.big_int}),
.function => return out_stream.writeAll("(function)"),
.ref => return out_stream.writeAll("(ref)"),
.ref_val => {
try out_stream.writeAll("*const ");
val = val.cast(Payload.RefVal).?.val;
continue;
},
.bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream),
};
}
/// Asserts that the value is representable as an array of bytes.
/// Copies the value into a freshly allocated slice of memory, which is owned by the caller.
pub fn toAllocatedBytes(self: Value, allocator: *Allocator) Allocator.Error![]u8 {
if (self.cast(Payload.Bytes)) |bytes| {
return std.mem.dupe(allocator, u8, bytes.data);
}
unreachable;
}
/// Asserts that the value is representable as a type.
pub fn toType(self: Value) Type {
return switch (self.tag()) {
.ty => self.cast(Payload.Ty).?.ty,
.u8_type => Type.initTag(.@"u8"),
.i8_type => Type.initTag(.@"i8"),
.isize_type => Type.initTag(.@"isize"),
.usize_type => Type.initTag(.@"usize"),
.c_short_type => Type.initTag(.@"c_short"),
.c_ushort_type => Type.initTag(.@"c_ushort"),
.c_int_type => Type.initTag(.@"c_int"),
.c_uint_type => Type.initTag(.@"c_uint"),
.c_long_type => Type.initTag(.@"c_long"),
.c_ulong_type => Type.initTag(.@"c_ulong"),
.c_longlong_type => Type.initTag(.@"c_longlong"),
.c_ulonglong_type => Type.initTag(.@"c_ulonglong"),
.c_longdouble_type => Type.initTag(.@"c_longdouble"),
.f16_type => Type.initTag(.@"f16"),
.f32_type => Type.initTag(.@"f32"),
.f64_type => Type.initTag(.@"f64"),
.f128_type => Type.initTag(.@"f128"),
.c_void_type => Type.initTag(.@"c_void"),
.bool_type => Type.initTag(.@"bool"),
.void_type => Type.initTag(.@"void"),
.type_type => Type.initTag(.@"type"),
.anyerror_type => Type.initTag(.@"anyerror"),
.comptime_int_type => Type.initTag(.@"comptime_int"),
.comptime_float_type => Type.initTag(.@"comptime_float"),
.noreturn_type => Type.initTag(.@"noreturn"),
.fn_naked_noreturn_no_args_type => Type.initTag(.fn_naked_noreturn_no_args),
.single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int),
.const_slice_u8_type => Type.initTag(.const_slice_u8),
.zero,
.void_value,
.noreturn_value,
.bool_true,
.bool_false,
.int_u64,
.int_i64,
.int_big,
.function,
.ref,
.ref_val,
.bytes,
=> unreachable,
};
}
/// Asserts the value is an integer.
pub fn toBigInt(self: Value, allocator: *Allocator) Allocator.Error!BigInt {
switch (self.tag()) {
.ty,
.u8_type,
.i8_type,
.isize_type,
.usize_type,
.c_short_type,
.c_ushort_type,
.c_int_type,
.c_uint_type,
.c_long_type,
.c_ulong_type,
.c_longlong_type,
.c_ulonglong_type,
.c_longdouble_type,
.f16_type,
.f32_type,
.f64_type,
.f128_type,
.c_void_type,
.bool_type,
.void_type,
.type_type,
.anyerror_type,
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.fn_naked_noreturn_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.void_value,
.noreturn_value,
.bool_true,
.bool_false,
.function,
.ref,
.ref_val,
.bytes,
=> unreachable,
.zero => return BigInt.initSet(allocator, 0),
.int_u64 => return BigInt.initSet(allocator, self.cast(Payload.Int_u64).?.int),
.int_i64 => return BigInt.initSet(allocator, self.cast(Payload.Int_i64).?.int),
.int_big => return self.cast(Payload.IntBig).?.big_int,
}
}
/// Asserts the value is an integer and it fits in a u64
pub fn toUnsignedInt(self: Value) u64 {
switch (self.tag()) {
.ty,
.u8_type,
.i8_type,
.isize_type,
.usize_type,
.c_short_type,
.c_ushort_type,
.c_int_type,
.c_uint_type,
.c_long_type,
.c_ulong_type,
.c_longlong_type,
.c_ulonglong_type,
.c_longdouble_type,
.f16_type,
.f32_type,
.f64_type,
.f128_type,
.c_void_type,
.bool_type,
.void_type,
.type_type,
.anyerror_type,
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.fn_naked_noreturn_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.void_value,
.noreturn_value,
.bool_true,
.bool_false,
.function,
.ref,
.ref_val,
.bytes,
=> unreachable,
.zero => return 0,
.int_u64 => return self.cast(Payload.Int_u64).?.int,
.int_i64 => return @intCast(u64, self.cast(Payload.Int_u64).?.int),
.int_big => return self.cast(Payload.IntBig).?.big_int.to(u64) catch unreachable,
}
}
/// Asserts the value is an integer, and the destination type is ComptimeInt or Int.
pub fn intFitsInType(self: Value, ty: Type, target: Target) bool {
switch (self.tag()) {
.ty,
.u8_type,
.i8_type,
.isize_type,
.usize_type,
.c_short_type,
.c_ushort_type,
.c_int_type,
.c_uint_type,
.c_long_type,
.c_ulong_type,
.c_longlong_type,
.c_ulonglong_type,
.c_longdouble_type,
.f16_type,
.f32_type,
.f64_type,
.f128_type,
.c_void_type,
.bool_type,
.void_type,
.type_type,
.anyerror_type,
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.fn_naked_noreturn_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.void_value,
.noreturn_value,
.bool_true,
.bool_false,
.function,
.ref,
.ref_val,
.bytes,
=> unreachable,
.zero => return true,
.int_u64 => switch (ty.zigTypeTag()) {
.Int => {
const x = self.cast(Payload.Int_u64).?.int;
if (x == 0) return true;
const info = ty.intInfo(target);
const needed_bits = std.math.log2(x) + 1 + @boolToInt(info.signed);
return info.bits >= needed_bits;
},
.ComptimeInt => return true,
else => unreachable,
},
.int_i64 => switch (ty.zigTypeTag()) {
.Int => {
const x = self.cast(Payload.Int_i64).?.int;
if (x == 0) return true;
const info = ty.intInfo(target);
if (!info.signed and x < 0)
return false;
@panic("TODO implement i64 intFitsInType");
},
.ComptimeInt => return true,
else => unreachable,
},
.int_big => switch (ty.zigTypeTag()) {
.Int => {
const info = ty.intInfo(target);
return self.cast(Payload.IntBig).?.big_int.fitsInTwosComp(info.signed, info.bits);
},
.ComptimeInt => return true,
else => unreachable,
},
}
}
/// Asserts the value is a pointer and dereferences it.
pub fn pointerDeref(self: Value) Value {
switch (self.tag()) {
.ty,
.u8_type,
.i8_type,
.isize_type,
.usize_type,
.c_short_type,
.c_ushort_type,
.c_int_type,
.c_uint_type,
.c_long_type,
.c_ulong_type,
.c_longlong_type,
.c_ulonglong_type,
.c_longdouble_type,
.f16_type,
.f32_type,
.f64_type,
.f128_type,
.c_void_type,
.bool_type,
.void_type,
.type_type,
.anyerror_type,
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.fn_naked_noreturn_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.zero,
.void_value,
.noreturn_value,
.bool_true,
.bool_false,
.function,
.int_u64,
.int_i64,
.int_big,
.bytes,
=> unreachable,
.ref => return self.cast(Payload.Ref).?.cell.contents,
.ref_val => return self.cast(Payload.RefVal).?.val,
}
}
/// Asserts the value is a single-item pointer to an array, or an array,
/// or an unknown-length pointer, and returns the element value at the index.
pub fn elemValueAt(self: Value, allocator: *Allocator, index: usize) Allocator.Error!Value {
switch (self.tag()) {
.ty,
.u8_type,
.i8_type,
.isize_type,
.usize_type,
.c_short_type,
.c_ushort_type,
.c_int_type,
.c_uint_type,
.c_long_type,
.c_ulong_type,
.c_longlong_type,
.c_ulonglong_type,
.c_longdouble_type,
.f16_type,
.f32_type,
.f64_type,
.f128_type,
.c_void_type,
.bool_type,
.void_type,
.type_type,
.anyerror_type,
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.fn_naked_noreturn_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.zero,
.void_value,
.noreturn_value,
.bool_true,
.bool_false,
.function,
.int_u64,
.int_i64,
.int_big,
=> unreachable,
.ref => @panic("TODO figure out how MemoryCell works"),
.ref_val => @panic("TODO figure out how MemoryCell works"),
.bytes => {
const int_payload = try allocator.create(Value.Payload.Int_u64);
int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] };
return Value.initPayload(&int_payload.base);
},
}
}
/// This type is not copyable since it may contain pointers to its inner data.
pub const Payload = struct {
tag: Tag,
pub const Int_u64 = struct {
base: Payload = Payload{ .tag = .int_u64 },
int: u64,
};
pub const Int_i64 = struct {
base: Payload = Payload{ .tag = .int_i64 },
int: i64,
};
pub const IntBig = struct {
base: Payload = Payload{ .tag = .int_big },
big_int: BigInt,
};
pub const Function = struct {
base: Payload = Payload{ .tag = .function },
/// Index into the `fns` array of the `ir.Module`
index: usize,
};
pub const ArraySentinel0_u8_Type = struct {
base: Payload = Payload{ .tag = .array_sentinel_0_u8_type },
len: u64,
};
pub const SingleConstPtrType = struct {
base: Payload = Payload{ .tag = .single_const_ptr_type },
elem_type: *Type,
};
pub const Ref = struct {
base: Payload = Payload{ .tag = .ref },
cell: *MemoryCell,
};
pub const RefVal = struct {
base: Payload = Payload{ .tag = .ref_val },
val: Value,
};
pub const Bytes = struct {
base: Payload = Payload{ .tag = .bytes },
data: []const u8,
};
pub const Ty = struct {
base: Payload = Payload{ .tag = .ty },
ty: Type,
};
};
};
/// This is the heart of resource management of the Zig compiler. The Zig compiler uses
/// stop-the-world mark-and-sweep garbage collection during compilation to manage the resources
/// associated with evaluating compile-time code and semantic analysis. Each `MemoryCell` represents
/// a root.
pub const MemoryCell = struct {
parent: Parent,
contents: Value,
pub const Parent = union(enum) {
none,
struct_field: struct {
struct_base: *MemoryCell,
field_index: usize,
},
array_elem: struct {
array_base: *MemoryCell,
elem_index: usize,
},
union_field: *MemoryCell,
err_union_code: *MemoryCell,
err_union_payload: *MemoryCell,
optional_payload: *MemoryCell,
optional_flag: *MemoryCell,
};
};