diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 88293c845e..5ca01ca7e7 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -6,6 +6,7 @@ const c = @import("c.zig"); const ir = @import("ir.zig"); const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; +const Scope = @import("scope.zig").Scope; const event = std.event; const assert = std.debug.assert; const DW = std.dwarf; @@ -156,7 +157,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) llvm_fn_type, ) orelse return error.OutOfMemory; - const want_fn_safety = fn_val.block_scope.safety.get(ofile.comp); + const want_fn_safety = fn_val.block_scope.?.safety.get(ofile.comp); if (want_fn_safety and ofile.comp.haveLibC()) { try addLLVMFnAttr(ofile, llvm_fn, "sspstrong"); try addLLVMFnAttrStr(ofile, llvm_fn, "stack-protector-buffer-size", "4"); @@ -227,9 +228,86 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) // TODO set up error return tracing // TODO allocate temporary stack values - // TODO create debug variable declarations for variables and allocate all local variables + + const var_list = fn_type.non_key.Normal.variable_list.toSliceConst(); + // create debug variable declarations for variables and allocate all local variables + for (var_list) |var_scope, i| { + const var_type = switch (var_scope.data) { + Scope.Var.Data.Const => unreachable, + Scope.Var.Data.Param => |param| param.typ, + }; + // if (!type_has_bits(var->value->type)) { + // continue; + // } + // if (ir_get_var_is_comptime(var)) + // continue; + // if (type_requires_comptime(var->value->type)) + // continue; + // if (var->src_arg_index == SIZE_MAX) { + // var->value_ref = build_alloca(g, var->value->type, buf_ptr(&var->name), var->align_bytes); + + // var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope), + // buf_ptr(&var->name), import->di_file, (unsigned)(var->decl_node->line + 1), + // var->value->type->di_type, !g->strip_debug_symbols, 0); + + // } else { + // it's a parameter + // assert(var->gen_arg_index != SIZE_MAX); + // TypeTableEntry *gen_type; + // FnGenParamInfo *gen_info = &fn_table_entry->type_entry->data.fn.gen_param_info[var->src_arg_index]; + + if (var_type.handleIsPtr()) { + // if (gen_info->is_byval) { + // gen_type = var->value->type; + // } else { + // gen_type = gen_info->type; + // } + var_scope.data.Param.llvm_value = llvm.GetParam(llvm_fn, @intCast(c_uint, i)); + } else { + // gen_type = var->value->type; + var_scope.data.Param.llvm_value = try renderAlloca(ofile, var_type, var_scope.name, Type.Pointer.Align.Abi); + } + // if (var->decl_node) { + // var->di_loc_var = ZigLLVMCreateParameterVariable(g->dbuilder, get_di_scope(g, var->parent_scope), + // buf_ptr(&var->name), import->di_file, + // (unsigned)(var->decl_node->line + 1), + // gen_type->di_type, !g->strip_debug_symbols, 0, (unsigned)(var->gen_arg_index + 1)); + // } + + // } + } + // TODO finishing error return trace setup. we have to do this after all the allocas. - // TODO create debug variable declarations for parameters + + // create debug variable declarations for parameters + // rely on the first variables in the variable_list being parameters. + //size_t next_var_i = 0; + for (fn_type.key.data.Normal.params) |param, i| { + //FnGenParamInfo *info = &fn_table_entry->type_entry->data.fn.gen_param_info[param_i]; + //if (info->gen_index == SIZE_MAX) + // continue; + const scope_var = var_list[i]; + //assert(variable->src_arg_index != SIZE_MAX); + //next_var_i += 1; + //assert(variable); + //assert(variable->value_ref); + + if (!param.typ.handleIsPtr()) { + //clear_debug_source_node(g); + const llvm_param = llvm.GetParam(llvm_fn, @intCast(c_uint, i)); + _ = renderStoreUntyped( + ofile, + llvm_param, + scope_var.data.Param.llvm_value, + Type.Pointer.Align.Abi, + Type.Pointer.Vol.Non, + ); + } + + //if (variable->decl_node) { + // gen_var_debug_decl(g, variable); + //} + } for (code.basic_block_list.toSlice()) |current_block| { llvm.PositionBuilderAtEnd(ofile.builder, current_block.llvm_block); @@ -294,3 +372,79 @@ fn addLLVMFnAttrStr(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []cons fn addLLVMFnAttrInt(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8, attr_val: u64) !void { return addLLVMAttrInt(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name, attr_val); } + +fn renderLoadUntyped( + ofile: *ObjectFile, + ptr: llvm.ValueRef, + alignment: Type.Pointer.Align, + vol: Type.Pointer.Vol, + name: [*]const u8, +) !llvm.ValueRef { + const result = llvm.BuildLoad(ofile.builder, ptr, name) orelse return error.OutOfMemory; + switch (vol) { + Type.Pointer.Vol.Non => {}, + Type.Pointer.Vol.Volatile => llvm.SetVolatile(result, 1), + } + llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm.GetElementType(llvm.TypeOf(ptr)))); + return result; +} + +fn renderLoad(ofile: *ObjectFile, ptr: llvm.ValueRef, ptr_type: *Type.Pointer, name: [*]const u8) !llvm.ValueRef { + return renderLoadUntyped(ofile, ptr, ptr_type.key.alignment, ptr_type.key.vol, name); +} + +pub fn getHandleValue(ofile: *ObjectFile, ptr: llvm.ValueRef, ptr_type: *Type.Pointer) !?llvm.ValueRef { + const child_type = ptr_type.key.child_type; + if (!child_type.hasBits()) { + return null; + } + if (child_type.handleIsPtr()) { + return ptr; + } + return try renderLoad(ofile, ptr, ptr_type, c""); +} + +pub fn renderStoreUntyped( + ofile: *ObjectFile, + value: llvm.ValueRef, + ptr: llvm.ValueRef, + alignment: Type.Pointer.Align, + vol: Type.Pointer.Vol, +) !llvm.ValueRef { + const result = llvm.BuildStore(ofile.builder, value, ptr) orelse return error.OutOfMemory; + switch (vol) { + Type.Pointer.Vol.Non => {}, + Type.Pointer.Vol.Volatile => llvm.SetVolatile(result, 1), + } + llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm.TypeOf(value))); + return result; +} + +pub fn renderStore( + ofile: *ObjectFile, + value: llvm.ValueRef, + ptr: llvm.ValueRef, + ptr_type: *Type.Pointer, +) !llvm.ValueRef { + return renderStoreUntyped(ofile, value, ptr, ptr_type.key.alignment, ptr_type.key.vol); +} + +pub fn renderAlloca( + ofile: *ObjectFile, + var_type: *Type, + name: []const u8, + alignment: Type.Pointer.Align, +) !llvm.ValueRef { + const llvm_var_type = try var_type.getLlvmType(ofile.arena, ofile.context); + const name_with_null = try std.cstr.addNullByte(ofile.arena, name); + const result = llvm.BuildAlloca(ofile.builder, llvm_var_type, name_with_null.ptr) orelse return error.OutOfMemory; + llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm_var_type)); + return result; +} + +pub fn resolveAlign(ofile: *ObjectFile, alignment: Type.Pointer.Align, llvm_type: llvm.TypeRef) u32 { + return switch (alignment) { + Type.Pointer.Align.Abi => return llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, llvm_type), + Type.Pointer.Align.Override => |a| a, + }; +} diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 8d41e2439b..1564abfbd3 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -1165,49 +1165,47 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { symbol_name_consumed = true; // Define local parameter variables - //for (size_t i = 0; i < fn_type_id->param_count; i += 1) { - // FnTypeParamInfo *param_info = &fn_type_id->param_info[i]; - // AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i); - // Buf *param_name; - // bool is_var_args = param_decl_node && param_decl_node->data.param_decl.is_var_args; - // if (param_decl_node && !is_var_args) { - // param_name = param_decl_node->data.param_decl.name; - // } else { - // param_name = buf_sprintf("arg%" ZIG_PRI_usize "", i); - // } - // if (param_name == nullptr) { - // continue; - // } + const root_scope = fn_decl.base.findRootScope(); + for (fn_type.key.data.Normal.params) |param, i| { + //AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i); + const param_decl = @fieldParentPtr(ast.Node.ParamDecl, "base", fn_decl.fn_proto.params.at(i).*); + const name_token = param_decl.name_token orelse { + try comp.addCompileError(root_scope, Span{ + .first = param_decl.firstToken(), + .last = param_decl.type_node.firstToken(), + }, "missing parameter name"); + return error.SemanticAnalysisFailed; + }; + const param_name = root_scope.tree.tokenSlice(name_token); - // TypeTableEntry *param_type = param_info->type; - // bool is_noalias = param_info->is_noalias; + // if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) { + // add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter")); + // } - // if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) { - // add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter")); - // } + // TODO check for shadowing - // VariableTableEntry *var = add_variable(g, param_decl_node, fn_table_entry->child_scope, - // param_name, true, create_const_runtime(param_type), nullptr); - // var->src_arg_index = i; - // fn_table_entry->child_scope = var->child_scope; - // var->shadowable = var->shadowable || is_var_args; + const var_scope = try Scope.Var.createParam( + comp, + fn_val.child_scope, + param_name, + ¶m_decl.base, + i, + param.typ, + ); + fn_val.child_scope = &var_scope.base; - // if (type_has_bits(param_type)) { - // fn_table_entry->variable_list.append(var); - // } - - // if (fn_type->data.fn.gen_param_info) { - // var->gen_arg_index = fn_type->data.fn.gen_param_info[i].gen_index; - // } - //} + try fn_type.non_key.Normal.variable_list.append(var_scope); + } const analyzed_code = try await (async comp.genAndAnalyzeCode( - &fndef_scope.base, + fn_val.child_scope, body_node, fn_type.key.data.Normal.return_type, ) catch unreachable); errdefer analyzed_code.destroy(comp.gpa()); + assert(fn_val.block_scope != null); + // Kick off rendering to LLVM module, but it doesn't block the fn decl // analysis from being complete. try comp.prelink_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code); @@ -1263,7 +1261,7 @@ async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.Fn const key = Type.Fn.Key{ .alignment = null, .data = Type.Fn.Key.Data{ - .Normal = Type.Fn.Normal{ + .Normal = Type.Fn.Key.Normal{ .return_type = return_type, .params = params.toOwnedSlice(), .is_var_args = false, // TODO diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 45355bbf2c..619cd4f330 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -10,8 +10,10 @@ const assert = std.debug.assert; const Token = std.zig.Token; const Span = @import("errmsg.zig").Span; const llvm = @import("llvm.zig"); -const ObjectFile = @import("codegen.zig").ObjectFile; +const codegen = @import("codegen.zig"); +const ObjectFile = codegen.ObjectFile; const Decl = @import("decl.zig").Decl; +const mem = std.mem; pub const LVal = enum { None, @@ -122,6 +124,8 @@ pub const Inst = struct { Id.Br => return @fieldParentPtr(Br, "base", base).analyze(ira), Id.AddImplicitReturnType => return @fieldParentPtr(AddImplicitReturnType, "base", base).analyze(ira), Id.PtrType => return await (async @fieldParentPtr(PtrType, "base", base).analyze(ira) catch unreachable), + Id.VarPtr => return await (async @fieldParentPtr(VarPtr, "base", base).analyze(ira) catch unreachable), + Id.LoadPtr => return await (async @fieldParentPtr(LoadPtr, "base", base).analyze(ira) catch unreachable), } } @@ -130,6 +134,8 @@ pub const Inst = struct { Id.Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val), Id.Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val), Id.Call => return @fieldParentPtr(Call, "base", base).render(ofile, fn_val), + Id.VarPtr => return @fieldParentPtr(VarPtr, "base", base).render(ofile, fn_val), + Id.LoadPtr => return @fieldParentPtr(LoadPtr, "base", base).render(ofile, fn_val), Id.DeclRef => unreachable, Id.PtrType => unreachable, Id.Ref => @panic("TODO"), @@ -248,6 +254,8 @@ pub const Inst = struct { Call, DeclRef, PtrType, + VarPtr, + LoadPtr, }; pub const Call = struct { @@ -491,6 +499,133 @@ pub const Inst = struct { } }; + pub const VarPtr = struct { + base: Inst, + params: Params, + + const Params = struct { + var_scope: *Scope.Var, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const VarPtr) void { + std.debug.warn("{}", inst.params.var_scope.name); + } + + pub fn hasSideEffects(inst: *const VarPtr) bool { + return false; + } + + pub async fn analyze(self: *const VarPtr, ira: *Analyze) !*Inst { + switch (self.params.var_scope.data) { + Scope.Var.Data.Const => @panic("TODO"), + Scope.Var.Data.Param => |param| { + const new_inst = try ira.irb.build( + Inst.VarPtr, + self.base.scope, + self.base.span, + Inst.VarPtr.Params{ .var_scope = self.params.var_scope }, + ); + const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{ + .child_type = param.typ, + .mut = Type.Pointer.Mut.Const, + .vol = Type.Pointer.Vol.Non, + .size = Type.Pointer.Size.One, + .alignment = Type.Pointer.Align.Abi, + }) catch unreachable); + new_inst.val = IrVal{ .KnownType = &ptr_type.base }; + return new_inst; + }, + } + } + + pub fn render(self: *VarPtr, ofile: *ObjectFile, fn_val: *Value.Fn) llvm.ValueRef { + switch (self.params.var_scope.data) { + Scope.Var.Data.Const => unreachable, // turned into Inst.Const in analyze pass + Scope.Var.Data.Param => |param| return param.llvm_value, + } + } + }; + + pub const LoadPtr = struct { + base: Inst, + params: Params, + + const Params = struct { + target: *Inst, + }; + + const ir_val_init = IrVal.Init.Unknown; + + pub fn dump(inst: *const LoadPtr) void {} + + pub fn hasSideEffects(inst: *const LoadPtr) bool { + return false; + } + + pub async fn analyze(self: *const LoadPtr, ira: *Analyze) !*Inst { + const target = try self.params.target.getAsParam(); + const target_type = target.getKnownType(); + if (target_type.id != Type.Id.Pointer) { + try ira.addCompileError(self.base.span, "dereference of non pointer type '{}'", target_type.name); + return error.SemanticAnalysisFailed; + } + const ptr_type = @fieldParentPtr(Type.Pointer, "base", target_type); + // if (instr_is_comptime(ptr)) { + // if (ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst || + // ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar) + // { + // ConstExprValue *pointee = const_ptr_pointee(ira->codegen, &ptr->value); + // if (pointee->special != ConstValSpecialRuntime) { + // IrInstruction *result = ir_create_const(&ira->new_irb, source_instruction->scope, + // source_instruction->source_node, child_type); + // copy_const_val(&result->value, pointee, ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst); + // result->value.type = child_type; + // return result; + // } + // } + // } + const new_inst = try ira.irb.build( + Inst.LoadPtr, + self.base.scope, + self.base.span, + Inst.LoadPtr.Params{ .target = target }, + ); + new_inst.val = IrVal{ .KnownType = ptr_type.key.child_type }; + return new_inst; + } + + pub fn render(self: *LoadPtr, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef { + const child_type = self.base.getKnownType(); + if (!child_type.hasBits()) { + return null; + } + const ptr = self.params.target.llvm_value.?; + const ptr_type = self.params.target.getKnownType().cast(Type.Pointer).?; + + return try codegen.getHandleValue(ofile, ptr, ptr_type); + + //uint32_t unaligned_bit_count = ptr_type->data.pointer.unaligned_bit_count; + //if (unaligned_bit_count == 0) + // return get_handle_value(g, ptr, child_type, ptr_type); + + //bool big_endian = g->is_big_endian; + + //assert(!handle_is_ptr(child_type)); + //LLVMValueRef containing_int = gen_load(g, ptr, ptr_type, ""); + + //uint32_t bit_offset = ptr_type->data.pointer.bit_offset; + //uint32_t host_bit_count = LLVMGetIntTypeWidth(LLVMTypeOf(containing_int)); + //uint32_t shift_amt = big_endian ? host_bit_count - bit_offset - unaligned_bit_count : bit_offset; + + //LLVMValueRef shift_amt_val = LLVMConstInt(LLVMTypeOf(containing_int), shift_amt, false); + //LLVMValueRef shifted_value = LLVMBuildLShr(g->builder, containing_int, shift_amt_val, ""); + + //return LLVMBuildTrunc(g->builder, shifted_value, child_type->type_ref, ""); + } + }; + pub const PtrType = struct { base: Inst, params: Params, @@ -1160,6 +1295,7 @@ pub const Builder = struct { Scope.Id.Block, Scope.Id.Defer, Scope.Id.DeferExpr, + Scope.Id.Var, => scope = scope.parent.?, } } @@ -1261,8 +1397,8 @@ pub const Builder = struct { var child_scope = outer_block_scope; if (parent_scope.findFnDef()) |fndef_scope| { - if (fndef_scope.fn_val.child_scope == parent_scope) { - fndef_scope.fn_val.block_scope = block_scope; + if (fndef_scope.fn_val.?.block_scope == null) { + fndef_scope.fn_val.?.block_scope = block_scope; } } @@ -1492,20 +1628,23 @@ pub const Builder = struct { error.OutOfMemory => return error.OutOfMemory, } - //VariableTableEntry *var = find_variable(irb->codegen, scope, variable_name); - //if (var) { - // IrInstruction *var_ptr = ir_build_var_ptr(irb, scope, node, var); - // if (lval == LValPtr) - // return var_ptr; - // else - // return ir_build_load_ptr(irb, scope, node, var_ptr); - //} - - if (await (async irb.findDecl(scope, name) catch unreachable)) |decl| { - return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{ - .decl = decl, - .lval = lval, - }); + switch (await (async irb.findIdent(scope, name) catch unreachable)) { + Ident.Decl => |decl| { + return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{ + .decl = decl, + .lval = lval, + }); + }, + Ident.VarScope => |var_scope| { + const var_ptr = try irb.build(Inst.VarPtr, scope, src_span, Inst.VarPtr.Params{ .var_scope = var_scope }); + switch (lval) { + LVal.Ptr => return var_ptr, + LVal.None => { + return irb.build(Inst.LoadPtr, scope, src_span, Inst.LoadPtr.Params{ .target = var_ptr }); + }, + } + }, + Ident.NotFound => {}, } //if (node->owner->any_imports_failed) { @@ -1546,6 +1685,7 @@ pub const Builder = struct { Scope.Id.Block, Scope.Id.Decls, Scope.Id.Root, + Scope.Id.Var, => scope = scope.parent orelse break, Scope.Id.DeferExpr => unreachable, @@ -1596,6 +1736,7 @@ pub const Builder = struct { Scope.Id.CompTime, Scope.Id.Block, + Scope.Id.Var, => scope = scope.parent orelse return is_noreturn, Scope.Id.DeferExpr => unreachable, @@ -1674,8 +1815,10 @@ pub const Builder = struct { Type.Pointer.Size, LVal, *Decl, + *Scope.Var, => {}, - // it's ok to add more types here, just make sure any instructions are ref'd appropriately + // it's ok to add more types here, just make sure that + // any instructions and basic blocks are ref'd appropriately else => @compileError("unrecognized type in Params: " ++ @typeName(FieldType)), } } @@ -1773,18 +1916,30 @@ pub const Builder = struct { //// the above blocks are rendered by ir_gen after the rest of codegen } - async fn findDecl(irb: *Builder, scope: *Scope, name: []const u8) ?*Decl { + const Ident = union(enum) { + NotFound, + Decl: *Decl, + VarScope: *Scope.Var, + }; + + async fn findIdent(irb: *Builder, scope: *Scope, name: []const u8) Ident { var s = scope; while (true) { switch (s.id) { + Scope.Id.Root => return Ident.NotFound, Scope.Id.Decls => { const decls = @fieldParentPtr(Scope.Decls, "base", s); const table = await (async decls.getTableReadOnly() catch unreachable); if (table.get(name)) |entry| { - return entry.value; + return Ident{ .Decl = entry.value }; + } + }, + Scope.Id.Var => { + const var_scope = @fieldParentPtr(Scope.Var, "base", s); + if (mem.eql(u8, var_scope.name, name)) { + return Ident{ .VarScope = var_scope }; } }, - Scope.Id.Root => return null, else => {}, } s = s.parent.?; diff --git a/src-self-hosted/llvm.zig b/src-self-hosted/llvm.zig index 8bb45ac616..778d3fae07 100644 --- a/src-self-hosted/llvm.zig +++ b/src-self-hosted/llvm.zig @@ -30,6 +30,7 @@ pub const AddGlobal = c.LLVMAddGlobal; pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag; pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag; pub const ArrayType = c.LLVMArrayType; +pub const BuildLoad = c.LLVMBuildLoad; pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation; pub const ConstAllOnes = c.LLVMConstAllOnes; pub const ConstArray = c.LLVMConstArray; @@ -95,13 +96,25 @@ pub const SetInitializer = c.LLVMSetInitializer; pub const SetLinkage = c.LLVMSetLinkage; pub const SetTarget = c.LLVMSetTarget; pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr; +pub const SetVolatile = c.LLVMSetVolatile; pub const StructTypeInContext = c.LLVMStructTypeInContext; pub const TokenTypeInContext = c.LLVMTokenTypeInContext; -pub const TypeOf = c.LLVMTypeOf; pub const VoidTypeInContext = c.LLVMVoidTypeInContext; pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext; pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext; +pub const GetElementType = LLVMGetElementType; +extern fn LLVMGetElementType(Ty: TypeRef) TypeRef; + +pub const TypeOf = LLVMTypeOf; +extern fn LLVMTypeOf(Val: ValueRef) TypeRef; + +pub const BuildStore = LLVMBuildStore; +extern fn LLVMBuildStore(arg0: BuilderRef, Val: ValueRef, Ptr: ValueRef) ?ValueRef; + +pub const BuildAlloca = LLVMBuildAlloca; +extern fn LLVMBuildAlloca(arg0: BuilderRef, Ty: TypeRef, Name: ?[*]const u8) ?ValueRef; + pub const ConstInBoundsGEP = LLVMConstInBoundsGEP; pub extern fn LLVMConstInBoundsGEP(ConstantVal: ValueRef, ConstantIndices: [*]ValueRef, NumIndices: c_uint) ?ValueRef; diff --git a/src-self-hosted/scope.zig b/src-self-hosted/scope.zig index 7a41083f44..a38e765c6e 100644 --- a/src-self-hosted/scope.zig +++ b/src-self-hosted/scope.zig @@ -6,23 +6,26 @@ const Compilation = @import("compilation.zig").Compilation; const mem = std.mem; const ast = std.zig.ast; const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; const ir = @import("ir.zig"); const Span = @import("errmsg.zig").Span; const assert = std.debug.assert; const event = std.event; +const llvm = @import("llvm.zig"); pub const Scope = struct { id: Id, parent: ?*Scope, - ref_count: usize, + ref_count: std.atomic.Int(usize), + /// Thread-safe pub fn ref(base: *Scope) void { - base.ref_count += 1; + _ = base.ref_count.incr(); } + /// Thread-safe pub fn deref(base: *Scope, comp: *Compilation) void { - base.ref_count -= 1; - if (base.ref_count == 0) { + if (base.ref_count.decr() == 1) { if (base.parent) |parent| parent.deref(comp); switch (base.id) { Id.Root => @fieldParentPtr(Root, "base", base).destroy(comp), @@ -32,6 +35,7 @@ pub const Scope = struct { Id.CompTime => @fieldParentPtr(CompTime, "base", base).destroy(comp), Id.Defer => @fieldParentPtr(Defer, "base", base).destroy(comp), Id.DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(comp), + Id.Var => @fieldParentPtr(Var, "base", base).destroy(comp), } } } @@ -49,15 +53,15 @@ pub const Scope = struct { var scope = base; while (true) { switch (scope.id) { - Id.FnDef => return @fieldParentPtr(FnDef, "base", base), - Id.Decls => return null, + Id.FnDef => return @fieldParentPtr(FnDef, "base", scope), + Id.Root, Id.Decls => return null, Id.Block, Id.Defer, Id.DeferExpr, Id.CompTime, - Id.Root, - => scope = scope.parent orelse return null, + Id.Var, + => scope = scope.parent.?, } } } @@ -66,7 +70,7 @@ pub const Scope = struct { var scope = base; while (true) { switch (scope.id) { - Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", base), + Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", scope), Id.FnDef, Id.Decls, @@ -76,11 +80,21 @@ pub const Scope = struct { Id.Defer, Id.CompTime, Id.Root, + Id.Var, => scope = scope.parent orelse return null, } } } + fn init(base: *Scope, id: Id, parent: *Scope) void { + base.* = Scope{ + .id = id, + .parent = parent, + .ref_count = std.atomic.Int(usize).init(1), + }; + parent.ref(); + } + pub const Id = enum { Root, Decls, @@ -89,6 +103,7 @@ pub const Scope = struct { CompTime, Defer, DeferExpr, + Var, }; pub const Root = struct { @@ -100,16 +115,16 @@ pub const Scope = struct { /// Takes ownership of realpath /// Takes ownership of tree, will deinit and destroy when done. pub fn create(comp: *Compilation, tree: *ast.Tree, realpath: []u8) !*Root { - const self = try comp.gpa().create(Root{ + const self = try comp.gpa().createOne(Root); + self.* = Root{ .base = Scope{ .id = Id.Root, .parent = null, - .ref_count = 1, + .ref_count = std.atomic.Int(usize).init(1), }, .tree = tree, .realpath = realpath, - }); - errdefer comp.gpa().destroy(self); + }; return self; } @@ -137,16 +152,13 @@ pub const Scope = struct { /// Creates a Decls scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope) !*Decls { - const self = try comp.gpa().create(Decls{ - .base = Scope{ - .id = Id.Decls, - .parent = parent, - .ref_count = 1, - }, + const self = try comp.gpa().createOne(Decls); + self.* = Decls{ + .base = undefined, .table = event.Locked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())), .name_future = event.Future(void).init(comp.loop), - }); - parent.ref(); + }; + self.base.init(Id.Decls, parent); return self; } @@ -199,21 +211,16 @@ pub const Scope = struct { /// Creates a Block scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope) !*Block { - const self = try comp.gpa().create(Block{ - .base = Scope{ - .id = Id.Block, - .parent = parent, - .ref_count = 1, - }, + const self = try comp.gpa().createOne(Block); + self.* = Block{ + .base = undefined, .incoming_values = undefined, .incoming_blocks = undefined, .end_block = undefined, .is_comptime = undefined, .safety = Safety.Auto, - }); - errdefer comp.gpa().destroy(self); - - parent.ref(); + }; + self.base.init(Id.Block, parent); return self; } @@ -226,22 +233,17 @@ pub const Scope = struct { base: Scope, /// This reference is not counted so that the scope can get destroyed with the function - fn_val: *Value.Fn, + fn_val: ?*Value.Fn, /// Creates a FnDef scope with 1 reference /// Must set the fn_val later pub fn create(comp: *Compilation, parent: *Scope) !*FnDef { - const self = try comp.gpa().create(FnDef{ - .base = Scope{ - .id = Id.FnDef, - .parent = parent, - .ref_count = 1, - }, - .fn_val = undefined, - }); - - parent.ref(); - + const self = try comp.gpa().createOne(FnDef); + self.* = FnDef{ + .base = undefined, + .fn_val = null, + }; + self.base.init(Id.FnDef, parent); return self; } @@ -255,15 +257,9 @@ pub const Scope = struct { /// Creates a CompTime scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope) !*CompTime { - const self = try comp.gpa().create(CompTime{ - .base = Scope{ - .id = Id.CompTime, - .parent = parent, - .ref_count = 1, - }, - }); - - parent.ref(); + const self = try comp.gpa().createOne(CompTime); + self.* = CompTime{ .base = undefined }; + self.base.init(Id.CompTime, parent); return self; } @@ -289,20 +285,14 @@ pub const Scope = struct { kind: Kind, defer_expr_scope: *DeferExpr, ) !*Defer { - const self = try comp.gpa().create(Defer{ - .base = Scope{ - .id = Id.Defer, - .parent = parent, - .ref_count = 1, - }, + const self = try comp.gpa().createOne(Defer); + self.* = Defer{ + .base = undefined, .defer_expr_scope = defer_expr_scope, .kind = kind, - }); - errdefer comp.gpa().destroy(self); - + }; + self.base.init(Id.Defer, parent); defer_expr_scope.base.ref(); - - parent.ref(); return self; } @@ -319,18 +309,13 @@ pub const Scope = struct { /// Creates a DeferExpr scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope, expr_node: *ast.Node) !*DeferExpr { - const self = try comp.gpa().create(DeferExpr{ - .base = Scope{ - .id = Id.DeferExpr, - .parent = parent, - .ref_count = 1, - }, + const self = try comp.gpa().createOne(DeferExpr); + self.* = DeferExpr{ + .base = undefined, .expr_node = expr_node, .reported_err = false, - }); - errdefer comp.gpa().destroy(self); - - parent.ref(); + }; + self.base.init(Id.DeferExpr, parent); return self; } @@ -338,4 +323,74 @@ pub const Scope = struct { comp.gpa().destroy(self); } }; + + pub const Var = struct { + base: Scope, + name: []const u8, + src_node: *ast.Node, + data: Data, + + pub const Data = union(enum) { + Param: Param, + Const: *Value, + }; + + pub const Param = struct { + index: usize, + typ: *Type, + llvm_value: llvm.ValueRef, + }; + + pub fn createParam( + comp: *Compilation, + parent: *Scope, + name: []const u8, + src_node: *ast.Node, + param_index: usize, + param_type: *Type, + ) !*Var { + const self = try create(comp, parent, name, src_node); + self.data = Data{ + .Param = Param{ + .index = param_index, + .typ = param_type, + .llvm_value = undefined, + }, + }; + return self; + } + + pub fn createConst( + comp: *Compilation, + parent: *Scope, + name: []const u8, + src_node: *ast.Node, + value: *Value, + ) !*Var { + const self = try create(comp, parent, name, src_node); + self.data = Data{ .Const = value }; + value.ref(); + return self; + } + + fn create(comp: *Compilation, parent: *Scope, name: []const u8, src_node: *ast.Node) !*Var { + const self = try comp.gpa().createOne(Var); + self.* = Var{ + .base = undefined, + .name = name, + .src_node = src_node, + .data = undefined, + }; + self.base.init(Id.Var, parent); + return self; + } + + pub fn destroy(self: *Var, comp: *Compilation) void { + switch (self.data) { + Data.Param => {}, + Data.Const => |value| value.deref(comp), + } + comp.gpa().destroy(self); + } + }; }; diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 3b57260447..6783130fc7 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -141,9 +141,13 @@ pub const Type = struct { Id.Promise, => return true, + Id.Pointer => { + const ptr_type = @fieldParentPtr(Pointer, "base", base); + return ptr_type.key.child_type.hasBits(); + }, + Id.ErrorSet => @panic("TODO"), Id.Enum => @panic("TODO"), - Id.Pointer => @panic("TODO"), Id.Struct => @panic("TODO"), Id.Array => @panic("TODO"), Id.Optional => @panic("TODO"), @@ -222,29 +226,65 @@ pub const Type = struct { pub const Fn = struct { base: Type, key: Key, + non_key: NonKey, garbage_node: std.atomic.Stack(*Fn).Node, + pub const Kind = enum { + Normal, + Generic, + }; + + pub const NonKey = union { + Normal: Normal, + Generic: void, + + pub const Normal = struct { + variable_list: std.ArrayList(*Scope.Var), + }; + }; + pub const Key = struct { data: Data, alignment: ?u32, - pub const Data = union(enum) { + pub const Data = union(Kind) { Generic: Generic, Normal: Normal, }; + pub const Normal = struct { + params: []Param, + return_type: *Type, + is_var_args: bool, + cc: CallingConvention, + }; + + pub const Generic = struct { + param_count: usize, + cc: CC, + + pub const CC = union(CallingConvention) { + Auto, + C, + Cold, + Naked, + Stdcall, + Async: *Type, // allocator type + }; + }; + pub fn hash(self: *const Key) u32 { var result: u32 = 0; result +%= hashAny(self.alignment, 0); switch (self.data) { - Data.Generic => |generic| { + Kind.Generic => |generic| { result +%= hashAny(generic.param_count, 1); switch (generic.cc) { CallingConvention.Async => |allocator_type| result +%= hashAny(allocator_type, 2), else => result +%= hashAny(CallingConvention(generic.cc), 3), } }, - Data.Normal => |normal| { + Kind.Normal => |normal| { result +%= hashAny(normal.return_type, 4); result +%= hashAny(normal.is_var_args, 5); result +%= hashAny(normal.cc, 6); @@ -264,7 +304,7 @@ pub const Type = struct { } if (@TagType(Data)(self.data) != @TagType(Data)(other.data)) return false; switch (self.data) { - Data.Generic => |*self_generic| { + Kind.Generic => |*self_generic| { const other_generic = &other.data.Generic; if (self_generic.param_count != other_generic.param_count) return false; if (CallingConvention(self_generic.cc) != CallingConvention(other_generic.cc)) return false; @@ -276,7 +316,7 @@ pub const Type = struct { else => {}, } }, - Data.Normal => |*self_normal| { + Kind.Normal => |*self_normal| { const other_normal = &other.data.Normal; if (self_normal.cc != other_normal.cc) return false; if (self_normal.is_var_args != other_normal.is_var_args) return false; @@ -293,13 +333,13 @@ pub const Type = struct { pub fn deref(key: Key, comp: *Compilation) void { switch (key.data) { - Key.Data.Generic => |generic| { + Kind.Generic => |generic| { switch (generic.cc) { CallingConvention.Async => |allocator_type| allocator_type.base.deref(comp), else => {}, } }, - Key.Data.Normal => |normal| { + Kind.Normal => |normal| { normal.return_type.base.deref(comp); for (normal.params) |param| { param.typ.base.deref(comp); @@ -310,13 +350,13 @@ pub const Type = struct { pub fn ref(key: Key) void { switch (key.data) { - Key.Data.Generic => |generic| { + Kind.Generic => |generic| { switch (generic.cc) { CallingConvention.Async => |allocator_type| allocator_type.base.ref(), else => {}, } }, - Key.Data.Normal => |normal| { + Kind.Normal => |normal| { normal.return_type.base.ref(); for (normal.params) |param| { param.typ.base.ref(); @@ -326,27 +366,6 @@ pub const Type = struct { } }; - pub const Normal = struct { - params: []Param, - return_type: *Type, - is_var_args: bool, - cc: CallingConvention, - }; - - pub const Generic = struct { - param_count: usize, - cc: CC, - - pub const CC = union(CallingConvention) { - Auto, - C, - Cold, - Naked, - Stdcall, - Async: *Type, // allocator type - }; - }; - pub const CallingConvention = enum { Auto, C, @@ -374,8 +393,8 @@ pub const Type = struct { pub fn paramCount(self: *Fn) usize { return switch (self.key.data) { - Key.Data.Generic => |generic| generic.param_count, - Key.Data.Normal => |normal| normal.params.len, + Kind.Generic => |generic| generic.param_count, + Kind.Normal => |normal| normal.params.len, }; } @@ -394,11 +413,13 @@ pub const Type = struct { key.ref(); errdefer key.deref(comp); - const self = try comp.gpa().create(Fn{ + const self = try comp.gpa().createOne(Fn); + self.* = Fn{ .base = undefined, .key = key, + .non_key = undefined, .garbage_node = undefined, - }); + }; errdefer comp.gpa().destroy(self); var name_buf = try std.Buffer.initSize(comp.gpa(), 0); @@ -407,7 +428,8 @@ pub const Type = struct { const name_stream = &std.io.BufferOutStream.init(&name_buf).stream; switch (key.data) { - Key.Data.Generic => |generic| { + Kind.Generic => |generic| { + self.non_key = NonKey{ .Generic = {} }; switch (generic.cc) { CallingConvention.Async => |async_allocator_type| { try name_stream.print("async<{}> ", async_allocator_type.name); @@ -429,7 +451,10 @@ pub const Type = struct { } try name_stream.write(" var"); }, - Key.Data.Normal => |normal| { + Kind.Normal => |normal| { + self.non_key = NonKey{ + .Normal = NonKey.Normal{ .variable_list = std.ArrayList(*Scope.Var).init(comp.gpa()) }, + }; const cc_str = ccFnTypeStr(normal.cc); try name_stream.print("{}fn(", cc_str); for (normal.params) |param, i| { @@ -462,6 +487,12 @@ pub const Type = struct { pub fn destroy(self: *Fn, comp: *Compilation) void { self.key.deref(comp); + switch (self.key.data) { + Kind.Generic => {}, + Kind.Normal => { + self.non_key.Normal.variable_list.deinit(); + }, + } comp.gpa().destroy(self); } diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 2005e3c119..e6dca4eff7 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -60,7 +60,7 @@ pub const Value = struct { pub fn getLlvmConst(base: *Value, ofile: *ObjectFile) (error{OutOfMemory}!?llvm.ValueRef) { switch (base.id) { Id.Type => unreachable, - Id.Fn => @panic("TODO"), + 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), @@ -180,7 +180,7 @@ pub const Value = struct { child_scope: *Scope, /// parent is child_scope - block_scope: *Scope.Block, + block_scope: ?*Scope.Block, /// Path to the object file that contains this function containing_object: Buffer, @@ -205,7 +205,7 @@ pub const Value = struct { }, .fndef_scope = fndef_scope, .child_scope = &fndef_scope.base, - .block_scope = undefined, + .block_scope = null, .symbol_name = symbol_name, .containing_object = Buffer.initNull(comp.gpa()), .link_set_node = link_set_node, @@ -231,6 +231,22 @@ pub const Value = struct { 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 {