aboutsummaryrefslogtreecommitdiff
path: root/src-self-hosted/value.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src-self-hosted/value.zig')
-rw-r--r--src-self-hosted/value.zig581
1 files changed, 581 insertions, 0 deletions
diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig
new file mode 100644
index 0000000000..e6dca4eff7
--- /dev/null
+++ b/src-self-hosted/value.zig
@@ -0,0 +1,581 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const Scope = @import("scope.zig").Scope;
+const Compilation = @import("compilation.zig").Compilation;
+const ObjectFile = @import("codegen.zig").ObjectFile;
+const llvm = @import("llvm.zig");
+const Buffer = std.Buffer;
+const assert = std.debug.assert;
+
+/// Values are ref-counted, heap-allocated, and copy-on-write
+/// If there is only 1 ref then write need not copy
+pub const Value = struct {
+ id: Id,
+ typ: *Type,
+ ref_count: std.atomic.Int(usize),
+
+ /// Thread-safe
+ pub fn ref(base: *Value) void {
+ _ = base.ref_count.incr();
+ }
+
+ /// Thread-safe
+ pub fn deref(base: *Value, comp: *Compilation) void {
+ if (base.ref_count.decr() == 1) {
+ base.typ.base.deref(comp);
+ switch (base.id) {
+ Id.Type => @fieldParentPtr(Type, "base", base).destroy(comp),
+ Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(comp),
+ Id.FnProto => @fieldParentPtr(FnProto, "base", base).destroy(comp),
+ Id.Void => @fieldParentPtr(Void, "base", base).destroy(comp),
+ Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(comp),
+ Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(comp),
+ Id.Ptr => @fieldParentPtr(Ptr, "base", base).destroy(comp),
+ Id.Int => @fieldParentPtr(Int, "base", base).destroy(comp),
+ Id.Array => @fieldParentPtr(Array, "base", base).destroy(comp),
+ }
+ }
+ }
+
+ pub fn setType(base: *Value, new_type: *Type, comp: *Compilation) void {
+ base.typ.base.deref(comp);
+ new_type.base.ref();
+ base.typ = new_type;
+ }
+
+ pub fn getRef(base: *Value) *Value {
+ base.ref();
+ return base;
+ }
+
+ pub fn cast(base: *Value, comptime T: type) ?*T {
+ if (base.id != @field(Id, @typeName(T))) return null;
+ return @fieldParentPtr(T, "base", base);
+ }
+
+ pub fn dump(base: *const Value) void {
+ std.debug.warn("{}", @tagName(base.id));
+ }
+
+ pub fn getLlvmConst(base: *Value, ofile: *ObjectFile) (error{OutOfMemory}!?llvm.ValueRef) {
+ switch (base.id) {
+ Id.Type => unreachable,
+ Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmConst(ofile),
+ Id.FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile),
+ Id.Void => return null,
+ Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile),
+ Id.NoReturn => unreachable,
+ Id.Ptr => return @fieldParentPtr(Ptr, "base", base).getLlvmConst(ofile),
+ Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmConst(ofile),
+ Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmConst(ofile),
+ }
+ }
+
+ pub fn derefAndCopy(self: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) {
+ if (self.ref_count.get() == 1) {
+ // ( ͡° ͜ʖ ͡°)
+ return self;
+ }
+
+ assert(self.ref_count.decr() != 1);
+ return self.copy(comp);
+ }
+
+ pub fn copy(base: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) {
+ switch (base.id) {
+ Id.Type => unreachable,
+ Id.Fn => unreachable,
+ Id.FnProto => unreachable,
+ Id.Void => unreachable,
+ Id.Bool => unreachable,
+ Id.NoReturn => unreachable,
+ Id.Ptr => unreachable,
+ Id.Array => unreachable,
+ Id.Int => return &(try @fieldParentPtr(Int, "base", base).copy(comp)).base,
+ }
+ }
+
+ pub const Parent = union(enum) {
+ None,
+ BaseStruct: BaseStruct,
+ BaseArray: BaseArray,
+ BaseUnion: *Value,
+ BaseScalar: *Value,
+
+ pub const BaseStruct = struct {
+ val: *Value,
+ field_index: usize,
+ };
+
+ pub const BaseArray = struct {
+ val: *Value,
+ elem_index: usize,
+ };
+ };
+
+ pub const Id = enum {
+ Type,
+ Fn,
+ Void,
+ Bool,
+ NoReturn,
+ Array,
+ Ptr,
+ Int,
+ FnProto,
+ };
+
+ pub const Type = @import("type.zig").Type;
+
+ pub const FnProto = struct {
+ base: Value,
+
+ /// The main external name that is used in the .o file.
+ /// TODO https://github.com/ziglang/zig/issues/265
+ symbol_name: Buffer,
+
+ pub fn create(comp: *Compilation, fn_type: *Type.Fn, symbol_name: Buffer) !*FnProto {
+ const self = try comp.gpa().create(FnProto{
+ .base = Value{
+ .id = Value.Id.FnProto,
+ .typ = &fn_type.base,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .symbol_name = symbol_name,
+ });
+ fn_type.base.base.ref();
+ return self;
+ }
+
+ pub fn destroy(self: *FnProto, comp: *Compilation) void {
+ self.symbol_name.deinit();
+ comp.gpa().destroy(self);
+ }
+
+ pub fn getLlvmConst(self: *FnProto, ofile: *ObjectFile) !?llvm.ValueRef {
+ const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
+ const llvm_fn = llvm.AddFunction(
+ ofile.module,
+ self.symbol_name.ptr(),
+ llvm_fn_type,
+ ) orelse return error.OutOfMemory;
+
+ // TODO port more logic from codegen.cpp:fn_llvm_value
+
+ return llvm_fn;
+ }
+ };
+
+ pub const Fn = struct {
+ base: Value,
+
+ /// The main external name that is used in the .o file.
+ /// TODO https://github.com/ziglang/zig/issues/265
+ symbol_name: Buffer,
+
+ /// parent should be the top level decls or container decls
+ fndef_scope: *Scope.FnDef,
+
+ /// parent is scope for last parameter
+ child_scope: *Scope,
+
+ /// parent is child_scope
+ block_scope: ?*Scope.Block,
+
+ /// Path to the object file that contains this function
+ containing_object: Buffer,
+
+ link_set_node: *std.LinkedList(?*Value.Fn).Node,
+
+ /// Creates a Fn value with 1 ref
+ /// Takes ownership of symbol_name
+ pub fn create(comp: *Compilation, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: Buffer) !*Fn {
+ const link_set_node = try comp.gpa().create(Compilation.FnLinkSet.Node{
+ .data = null,
+ .next = undefined,
+ .prev = undefined,
+ });
+ errdefer comp.gpa().destroy(link_set_node);
+
+ const self = try comp.gpa().create(Fn{
+ .base = Value{
+ .id = Value.Id.Fn,
+ .typ = &fn_type.base,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .fndef_scope = fndef_scope,
+ .child_scope = &fndef_scope.base,
+ .block_scope = null,
+ .symbol_name = symbol_name,
+ .containing_object = Buffer.initNull(comp.gpa()),
+ .link_set_node = link_set_node,
+ });
+ fn_type.base.base.ref();
+ fndef_scope.fn_val = self;
+ fndef_scope.base.ref();
+ return self;
+ }
+
+ pub fn destroy(self: *Fn, comp: *Compilation) void {
+ // remove with a tombstone so that we do not have to grab a lock
+ if (self.link_set_node.data != null) {
+ // it's now the job of the link step to find this tombstone and
+ // deallocate it.
+ self.link_set_node.data = null;
+ } else {
+ comp.gpa().destroy(self.link_set_node);
+ }
+
+ self.containing_object.deinit();
+ self.fndef_scope.base.deref(comp);
+ self.symbol_name.deinit();
+ comp.gpa().destroy(self);
+ }
+
+ /// We know that the function definition will end up in an .o file somewhere.
+ /// Here, all we have to do is generate a global prototype.
+ /// TODO cache the prototype per ObjectFile
+ pub fn getLlvmConst(self: *Fn, ofile: *ObjectFile) !?llvm.ValueRef {
+ const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
+ const llvm_fn = llvm.AddFunction(
+ ofile.module,
+ self.symbol_name.ptr(),
+ llvm_fn_type,
+ ) orelse return error.OutOfMemory;
+
+ // TODO port more logic from codegen.cpp:fn_llvm_value
+
+ return llvm_fn;
+ }
+ };
+
+ pub const Void = struct {
+ base: Value,
+
+ pub fn get(comp: *Compilation) *Void {
+ comp.void_value.base.ref();
+ return comp.void_value;
+ }
+
+ pub fn destroy(self: *Void, comp: *Compilation) void {
+ comp.gpa().destroy(self);
+ }
+ };
+
+ pub const Bool = struct {
+ base: Value,
+ x: bool,
+
+ pub fn get(comp: *Compilation, x: bool) *Bool {
+ if (x) {
+ comp.true_value.base.ref();
+ return comp.true_value;
+ } else {
+ comp.false_value.base.ref();
+ return comp.false_value;
+ }
+ }
+
+ pub fn destroy(self: *Bool, comp: *Compilation) void {
+ comp.gpa().destroy(self);
+ }
+
+ pub fn getLlvmConst(self: *Bool, ofile: *ObjectFile) ?llvm.ValueRef {
+ const llvm_type = llvm.Int1TypeInContext(ofile.context);
+ if (self.x) {
+ return llvm.ConstAllOnes(llvm_type);
+ } else {
+ return llvm.ConstNull(llvm_type);
+ }
+ }
+ };
+
+ pub const NoReturn = struct {
+ base: Value,
+
+ pub fn get(comp: *Compilation) *NoReturn {
+ comp.noreturn_value.base.ref();
+ return comp.noreturn_value;
+ }
+
+ pub fn destroy(self: *NoReturn, comp: *Compilation) void {
+ comp.gpa().destroy(self);
+ }
+ };
+
+ pub const Ptr = struct {
+ base: Value,
+ special: Special,
+ mut: Mut,
+
+ pub const Mut = enum {
+ CompTimeConst,
+ CompTimeVar,
+ RunTime,
+ };
+
+ pub const Special = union(enum) {
+ Scalar: *Value,
+ BaseArray: BaseArray,
+ BaseStruct: BaseStruct,
+ HardCodedAddr: u64,
+ Discard,
+ };
+
+ pub const BaseArray = struct {
+ val: *Value,
+ elem_index: usize,
+ };
+
+ pub const BaseStruct = struct {
+ val: *Value,
+ field_index: usize,
+ };
+
+ pub async fn createArrayElemPtr(
+ comp: *Compilation,
+ array_val: *Array,
+ mut: Type.Pointer.Mut,
+ size: Type.Pointer.Size,
+ elem_index: usize,
+ ) !*Ptr {
+ array_val.base.ref();
+ errdefer array_val.base.deref(comp);
+
+ const elem_type = array_val.base.typ.cast(Type.Array).?.key.elem_type;
+ const ptr_type = try await (async Type.Pointer.get(comp, Type.Pointer.Key{
+ .child_type = elem_type,
+ .mut = mut,
+ .vol = Type.Pointer.Vol.Non,
+ .size = size,
+ .alignment = Type.Pointer.Align.Abi,
+ }) catch unreachable);
+ var ptr_type_consumed = false;
+ errdefer if (!ptr_type_consumed) ptr_type.base.base.deref(comp);
+
+ const self = try comp.gpa().create(Value.Ptr{
+ .base = Value{
+ .id = Value.Id.Ptr,
+ .typ = &ptr_type.base,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .special = Special{
+ .BaseArray = BaseArray{
+ .val = &array_val.base,
+ .elem_index = 0,
+ },
+ },
+ .mut = Mut.CompTimeConst,
+ });
+ ptr_type_consumed = true;
+ errdefer comp.gpa().destroy(self);
+
+ return self;
+ }
+
+ pub fn destroy(self: *Ptr, comp: *Compilation) void {
+ comp.gpa().destroy(self);
+ }
+
+ pub fn getLlvmConst(self: *Ptr, ofile: *ObjectFile) !?llvm.ValueRef {
+ const llvm_type = self.base.typ.getLlvmType(ofile.arena, ofile.context);
+ // TODO carefully port the logic from codegen.cpp:gen_const_val_ptr
+ switch (self.special) {
+ Special.Scalar => |scalar| @panic("TODO"),
+ Special.BaseArray => |base_array| {
+ // TODO put this in one .o file only, and after that, generate extern references to it
+ const array_llvm_value = (try base_array.val.getLlvmConst(ofile)).?;
+ const ptr_bit_count = ofile.comp.target_ptr_bits;
+ const usize_llvm_type = llvm.IntTypeInContext(ofile.context, ptr_bit_count) orelse return error.OutOfMemory;
+ const indices = []llvm.ValueRef{
+ llvm.ConstNull(usize_llvm_type) orelse return error.OutOfMemory,
+ llvm.ConstInt(usize_llvm_type, base_array.elem_index, 0) orelse return error.OutOfMemory,
+ };
+ return llvm.ConstInBoundsGEP(
+ array_llvm_value,
+ &indices,
+ @intCast(c_uint, indices.len),
+ ) orelse return error.OutOfMemory;
+ },
+ Special.BaseStruct => |base_struct| @panic("TODO"),
+ Special.HardCodedAddr => |addr| @panic("TODO"),
+ Special.Discard => unreachable,
+ }
+ }
+ };
+
+ pub const Array = struct {
+ base: Value,
+ special: Special,
+
+ pub const Special = union(enum) {
+ Undefined,
+ OwnedBuffer: []u8,
+ Explicit: Data,
+ };
+
+ pub const Data = struct {
+ parent: Parent,
+ elements: []*Value,
+ };
+
+ /// Takes ownership of buffer
+ pub async fn createOwnedBuffer(comp: *Compilation, buffer: []u8) !*Array {
+ const u8_type = Type.Int.get_u8(comp);
+ defer u8_type.base.base.deref(comp);
+
+ const array_type = try await (async Type.Array.get(comp, Type.Array.Key{
+ .elem_type = &u8_type.base,
+ .len = buffer.len,
+ }) catch unreachable);
+ errdefer array_type.base.base.deref(comp);
+
+ const self = try comp.gpa().create(Value.Array{
+ .base = Value{
+ .id = Value.Id.Array,
+ .typ = &array_type.base,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .special = Special{ .OwnedBuffer = buffer },
+ });
+ errdefer comp.gpa().destroy(self);
+
+ return self;
+ }
+
+ pub fn destroy(self: *Array, comp: *Compilation) void {
+ switch (self.special) {
+ Special.Undefined => {},
+ Special.OwnedBuffer => |buf| {
+ comp.gpa().free(buf);
+ },
+ Special.Explicit => {},
+ }
+ comp.gpa().destroy(self);
+ }
+
+ pub fn getLlvmConst(self: *Array, ofile: *ObjectFile) !?llvm.ValueRef {
+ switch (self.special) {
+ Special.Undefined => {
+ const llvm_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
+ return llvm.GetUndef(llvm_type);
+ },
+ Special.OwnedBuffer => |buf| {
+ const dont_null_terminate = 1;
+ const llvm_str_init = llvm.ConstStringInContext(
+ ofile.context,
+ buf.ptr,
+ @intCast(c_uint, buf.len),
+ dont_null_terminate,
+ ) orelse return error.OutOfMemory;
+ const str_init_type = llvm.TypeOf(llvm_str_init);
+ const global = llvm.AddGlobal(ofile.module, str_init_type, c"") orelse return error.OutOfMemory;
+ llvm.SetInitializer(global, llvm_str_init);
+ llvm.SetLinkage(global, llvm.PrivateLinkage);
+ llvm.SetGlobalConstant(global, 1);
+ llvm.SetUnnamedAddr(global, 1);
+ llvm.SetAlignment(global, llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, str_init_type));
+ return global;
+ },
+ Special.Explicit => @panic("TODO"),
+ }
+
+ //{
+ // uint64_t len = type_entry->data.array.len;
+ // if (const_val->data.x_array.special == ConstArraySpecialUndef) {
+ // return LLVMGetUndef(type_entry->type_ref);
+ // }
+
+ // LLVMValueRef *values = allocate<LLVMValueRef>(len);
+ // LLVMTypeRef element_type_ref = type_entry->data.array.child_type->type_ref;
+ // bool make_unnamed_struct = false;
+ // for (uint64_t i = 0; i < len; i += 1) {
+ // ConstExprValue *elem_value = &const_val->data.x_array.s_none.elements[i];
+ // LLVMValueRef val = gen_const_val(g, elem_value, "");
+ // values[i] = val;
+ // make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(elem_value->type, val);
+ // }
+ // if (make_unnamed_struct) {
+ // return LLVMConstStruct(values, len, true);
+ // } else {
+ // return LLVMConstArray(element_type_ref, values, (unsigned)len);
+ // }
+ //}
+ }
+ };
+
+ pub const Int = struct {
+ base: Value,
+ big_int: std.math.big.Int,
+
+ pub fn createFromString(comp: *Compilation, typ: *Type, base: u8, value: []const u8) !*Int {
+ const self = try comp.gpa().create(Value.Int{
+ .base = Value{
+ .id = Value.Id.Int,
+ .typ = typ,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .big_int = undefined,
+ });
+ typ.base.ref();
+ errdefer comp.gpa().destroy(self);
+
+ self.big_int = try std.math.big.Int.init(comp.gpa());
+ errdefer self.big_int.deinit();
+
+ try self.big_int.setString(base, value);
+
+ return self;
+ }
+
+ pub fn getLlvmConst(self: *Int, ofile: *ObjectFile) !?llvm.ValueRef {
+ switch (self.base.typ.id) {
+ Type.Id.Int => {
+ const type_ref = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
+ if (self.big_int.len == 0) {
+ return llvm.ConstNull(type_ref);
+ }
+ const unsigned_val = if (self.big_int.len == 1) blk: {
+ break :blk llvm.ConstInt(type_ref, self.big_int.limbs[0], @boolToInt(false));
+ } else if (@sizeOf(std.math.big.Limb) == @sizeOf(u64)) blk: {
+ break :blk llvm.ConstIntOfArbitraryPrecision(
+ type_ref,
+ @intCast(c_uint, self.big_int.len),
+ @ptrCast([*]u64, self.big_int.limbs.ptr),
+ );
+ } else {
+ @compileError("std.math.Big.Int.Limb size does not match LLVM");
+ };
+ return if (self.big_int.positive) unsigned_val else llvm.ConstNeg(unsigned_val);
+ },
+ Type.Id.ComptimeInt => unreachable,
+ else => unreachable,
+ }
+ }
+
+ pub fn copy(old: *Int, comp: *Compilation) !*Int {
+ old.base.typ.base.ref();
+ errdefer old.base.typ.base.deref(comp);
+
+ const new = try comp.gpa().create(Value.Int{
+ .base = Value{
+ .id = Value.Id.Int,
+ .typ = old.base.typ,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .big_int = undefined,
+ });
+ errdefer comp.gpa().destroy(new);
+
+ new.big_int = try old.big_int.clone();
+ errdefer new.big_int.deinit();
+
+ return new;
+ }
+
+ pub fn destroy(self: *Int, comp: *Compilation) void {
+ self.big_int.deinit();
+ comp.gpa().destroy(self);
+ }
+ };
+};