diff options
Diffstat (limited to 'src-self-hosted/value.zig')
| -rw-r--r-- | src-self-hosted/value.zig | 581 |
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); + } + }; +}; |
