From b2682237dbe90306b569cb36914f8823cd7b0431 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Mar 2021 22:19:28 -0700 Subject: stage2: get Module and Sema compiling again There are some `@panic("TODO")` in there but I'm trying to get the branch to the point where collaborators can jump in. Next is to repair the seam between LazySrcLoc and codegen's expected absolute file offsets. --- src/codegen/c.zig | 32 ++++++++++++++++---------------- src/codegen/wasm.zig | 19 +++++++++---------- 2 files changed, 25 insertions(+), 26 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index af8d2d272d..dc5c29716b 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -14,6 +14,7 @@ const TypedValue = @import("../TypedValue.zig"); const C = link.File.C; const Decl = Module.Decl; const trace = @import("../tracy.zig").trace; +const LazySrcLoc = Module.LazySrcLoc; const Mutability = enum { Const, Mut }; @@ -145,11 +146,10 @@ pub const DeclGen = struct { error_msg: ?*Module.ErrorMsg, typedefs: TypedefMap, - fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { - dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{ - .file_scope = dg.decl.getFileScope(), - .byte_offset = src, - }, format, args); + fn fail(dg: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { + @setCold(true); + const src_loc = src.toSrcLocWithDecl(dg.decl); + dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, src_loc, format, args); return error.AnalysisFail; } @@ -160,7 +160,7 @@ pub const DeclGen = struct { val: Value, ) error{ OutOfMemory, AnalysisFail }!void { if (val.isUndef()) { - return dg.fail(dg.decl.src(), "TODO: C backend: properly handle undefined in all cases (with debug safety?)", .{}); + return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: properly handle undefined in all cases (with debug safety?)", .{}); } switch (t.zigTypeTag()) { .Int => { @@ -193,7 +193,7 @@ pub const DeclGen = struct { try writer.print("{s}", .{decl.name}); }, else => |e| return dg.fail( - dg.decl.src(), + .{ .node_offset = 0 }, "TODO: C backend: implement Pointer value {s}", .{@tagName(e)}, ), @@ -276,7 +276,7 @@ pub const DeclGen = struct { try writer.writeAll(", .error = 0 }"); } }, - else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ + else => |e| return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement value {s}", .{ @tagName(e), }), } @@ -350,7 +350,7 @@ pub const DeclGen = struct { break; } } else { - return dg.fail(dg.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{}); + return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement integer types larger than 128 bits", .{}); } }, else => unreachable, @@ -358,7 +358,7 @@ pub const DeclGen = struct { }, .Pointer => { if (t.isSlice()) { - return dg.fail(dg.decl.src(), "TODO: C backend: implement slices", .{}); + return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement slices", .{}); } else { try dg.renderType(w, t.elemType()); try w.writeAll(" *"); @@ -431,7 +431,7 @@ pub const DeclGen = struct { dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered }); }, .Null, .Undefined => unreachable, // must be const or comptime - else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ + else => |e| return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type {s}", .{ @tagName(e), }), } @@ -575,7 +575,7 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .unwrap_errunion_err_ptr => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err_ptr).?), .wrap_errunion_payload => try genWrapErrUnionPay(o, inst.castTag(.wrap_errunion_payload).?), .wrap_errunion_err => try genWrapErrUnionErr(o, inst.castTag(.wrap_errunion_err).?), - else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), + else => |e| return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for {}", .{e}), }; switch (result_value) { .none => {}, @@ -756,7 +756,7 @@ fn genCall(o: *Object, inst: *Inst.Call) !CValue { try writer.writeAll(");\n"); return result_local; } else { - return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement function pointers", .{}); + return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement function pointers", .{}); } } @@ -913,13 +913,13 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { try o.writeCValue(writer, arg_c_value); try writer.writeAll(";\n"); } else { - return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{}); + return o.dg.fail(.{ .node_offset = 0 }, "TODO non-explicit inline asm regs", .{}); } } const volatile_string: []const u8 = if (as.is_volatile) "volatile " else ""; try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source }); if (as.output) |_| { - return o.dg.fail(o.dg.decl.src(), "TODO inline asm output", .{}); + return o.dg.fail(.{ .node_offset = 0 }, "TODO inline asm output", .{}); } if (as.inputs.len > 0) { if (as.output == null) { @@ -945,7 +945,7 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { if (as.base.isUnused()) return CValue.none; - return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{}); + return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: inline asm expression result used", .{}); } fn genIsNull(o: *Object, inst: *Inst.UnOp) !CValue { diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 34e0b2f9b5..3a4837f673 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -14,6 +14,7 @@ const Type = @import("../type.zig").Type; const Value = @import("../value.zig").Value; const Compilation = @import("../Compilation.zig"); const AnyMCValue = @import("../codegen.zig").AnyMCValue; +const LazySrcLoc = Module.LazySrcLoc; /// Wasm Value, created when generating an instruction const WValue = union(enum) { @@ -70,11 +71,9 @@ pub const Context = struct { } /// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig - fn fail(self: *Context, src: usize, comptime fmt: []const u8, args: anytype) InnerError { - self.err_msg = try Module.ErrorMsg.create(self.gpa, .{ - .file_scope = self.decl.getFileScope(), - .byte_offset = src, - }, fmt, args); + fn fail(self: *Context, src: LazySrcLoc, comptime fmt: []const u8, args: anytype) InnerError { + const src_loc = src.toSrcLocWithDecl(self.decl); + self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, fmt, args); return error.CodegenFail; } @@ -91,7 +90,7 @@ pub const Context = struct { } /// Using a given `Type`, returns the corresponding wasm value type - fn genValtype(self: *Context, src: usize, ty: Type) InnerError!u8 { + fn genValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!u8 { return switch (ty.tag()) { .f32 => wasm.valtype(.f32), .f64 => wasm.valtype(.f64), @@ -104,7 +103,7 @@ pub const Context = struct { /// Using a given `Type`, returns the corresponding wasm value type /// Differently from `genValtype` this also allows `void` to create a block /// with no return type - fn genBlockType(self: *Context, src: usize, ty: Type) InnerError!u8 { + fn genBlockType(self: *Context, src: LazySrcLoc, ty: Type) InnerError!u8 { return switch (ty.tag()) { .void, .noreturn => wasm.block_empty, else => self.genValtype(src, ty), @@ -139,7 +138,7 @@ pub const Context = struct { ty.fnParamTypes(params); for (params) |param_type| { // Can we maybe get the source index of each param? - const val_type = try self.genValtype(self.decl.src(), param_type); + const val_type = try self.genValtype(.{ .node_offset = 0 }, param_type); try writer.writeByte(val_type); } } @@ -151,7 +150,7 @@ pub const Context = struct { else => |ret_type| { try leb.writeULEB128(writer, @as(u32, 1)); // Can we maybe get the source index of the return type? - const val_type = try self.genValtype(self.decl.src(), return_type); + const val_type = try self.genValtype(.{ .node_offset = 0 }, return_type); try writer.writeByte(val_type); }, } @@ -168,7 +167,7 @@ pub const Context = struct { const mod_fn = blk: { if (tv.val.castTag(.function)) |func| break :blk func.data; if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions - return self.fail(self.decl.src(), "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()}); + return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()}); }; // Reserve space to write the size after generating the code as well as space for locals count -- cgit v1.2.3 From bd2154da3d90daa4520ee7ef69dac42f9049ed92 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Mar 2021 22:48:28 -0700 Subject: stage2: the code is compiling again (with a lot of things commented out) --- src/Module.zig | 24 ++++++++++++++++++------ src/Sema.zig | 4 +++- src/codegen.zig | 14 +++++++++----- src/codegen/c.zig | 2 +- src/ir.zig | 16 +++++++++++++++- 5 files changed, 46 insertions(+), 14 deletions(-) (limited to 'src/codegen') diff --git a/src/Module.zig b/src/Module.zig index 4eef536fcd..9a0134c797 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -923,6 +923,20 @@ pub const Scope = struct { try block.instructions.append(block.sema.gpa, &inst.base); return &inst.base; } + + pub fn addDbgStmt(block: *Scope.Block, src: LazySrcLoc, abs_byte_off: u32) !*ir.Inst { + const inst = try block.sema.arena.create(ir.Inst.DbgStmt); + inst.* = .{ + .base = .{ + .tag = .dbg_stmt, + .ty = Type.initTag(.void), + .src = src, + }, + .byte_offset = abs_byte_off, + }; + try block.instructions.append(block.sema.gpa, &inst.base); + return &inst.base; + } }; /// This is a temporary structure; references to it are valid only @@ -1330,14 +1344,12 @@ pub const SrcLoc = struct { .byte_abs => |byte_index| return byte_index, .token_abs => |tok_index| { - const file_scope = src_loc.container.file_scope; - const tree = try mod.getAstTree(file_scope); + const tree = src_loc.container.file_scope.base.tree(); const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; }, .node_abs => |node_index| { - const file_scope = src_loc.container.file_scope; - const tree = try mod.getAstTree(file_scope); + const tree = src_loc.container.file_scope.base.tree(); const token_starts = tree.tokens.items(.start); const tok_index = tree.firstToken(node_index); return token_starts[tok_index]; @@ -1349,14 +1361,14 @@ pub const SrcLoc = struct { .token_offset => |tok_off| { const decl = src_loc.container.decl; const tok_index = decl.srcToken() + tok_off; - const tree = try mod.getAstTree(decl.container.file_scope); + const tree = src_loc.container.file_scope.base.tree(); const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; }, .node_offset => |node_off| { const decl = src_loc.container.decl; const node_index = decl.srcNode() + node_off; - const tree = try mod.getAstTree(decl.container.file_scope); + const tree = src_loc.container.file_scope.base.tree(); const tok_index = tree.firstToken(node_index); const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; diff --git a/src/Sema.zig b/src/Sema.zig index 88aa82eaec..ed3b441e61 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1003,7 +1003,9 @@ fn zirDbgStmtNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerE const src_node = sema.code.instructions.items(.data)[inst].node; const src: LazySrcLoc = .{ .node_offset = src_node }; - return block.addNoOp(src, Type.initTag(.void), .dbg_stmt); + const src_loc = src.toSrcLoc(&block.base); + const abs_byte_off = try src_loc.byteOffset(sema.mod); + return block.addDbgStmt(src, abs_byte_off); } fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { diff --git a/src/codegen.zig b/src/codegen.zig index 45d66b2767..6649226426 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -792,8 +792,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - fn dbgAdvancePCAndLine(self: *Self, src: usize) InnerError!void { - self.prev_di_src = src; + fn dbgAdvancePCAndLine(self: *Self, abs_byte_off: usize) InnerError!void { + self.prev_di_src = abs_byte_off; self.prev_di_pc = self.code.items.len; switch (self.debug_output) { .dwarf => |dbg_out| { @@ -801,7 +801,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // lookup table, and changing ir.Inst from storing byte offset to token. Currently // this involves scanning over the source code for newlines // (but only from the previous byte offset to the new one). - const delta_line = std.zig.lineDelta(self.source, self.prev_di_src, src); + const delta_line = std.zig.lineDelta(self.source, self.prev_di_src, abs_byte_off); const delta_pc = self.code.items.len - self.prev_di_pc; // TODO Look into using the DWARF special opcodes to compress this data. It lets you emit // single-byte opcodes that add different numbers to both the PC and the line number @@ -2315,8 +2315,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - fn genDbgStmt(self: *Self, inst: *ir.Inst.NoOp) !MCValue { - try self.dbgAdvancePCAndLine(inst.base.src); + fn genDbgStmt(self: *Self, inst: *ir.Inst.DbgStmt) !MCValue { + // TODO when reworking tzir memory layout, rework source locations here as + // well to be more efficient, as well as support inlined function calls correctly. + // For now we convert LazySrcLoc to absolute byte offset, to match what the + // existing codegen code expects. + try self.dbgAdvancePCAndLine(inst.byte_offset); assert(inst.base.isUnused()); return MCValue.dead; } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index dc5c29716b..02d44f53c3 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -760,7 +760,7 @@ fn genCall(o: *Object, inst: *Inst.Call) !CValue { } } -fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue { +fn genDbgStmt(o: *Object, inst: *Inst.DbgStmt) !CValue { // TODO emit #line directive here with line number and filename return CValue.none; } diff --git a/src/ir.zig b/src/ir.zig index 9a96f4bcb1..bbcd30d620 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -138,7 +138,6 @@ pub const Inst = struct { .retvoid, .unreach, .breakpoint, - .dbg_stmt, => NoOp, .ref, @@ -198,6 +197,7 @@ pub const Inst = struct { .loop => Loop, .varptr => VarPtr, .switchbr => SwitchBr, + .dbg_stmt => DbgStmt, }; } @@ -584,6 +584,20 @@ pub const Inst = struct { return (self.deaths + self.else_index)[0..self.else_deaths]; } }; + + pub const DbgStmt = struct { + pub const base_tag = Tag.dbg_stmt; + + base: Inst, + byte_offset: u32, + + pub fn operandCount(self: *const DbgStmt) usize { + return 0; + } + pub fn getOperand(self: *const DbgStmt, index: usize) ?*Inst { + return null; + } + }; }; pub const Body = struct { -- cgit v1.2.3 From c50397c2682846635d1ed116fcd5fe98e8f08c81 Mon Sep 17 00:00:00 2001 From: jacob gw Date: Fri, 19 Mar 2021 08:51:09 -0400 Subject: llvm backend: use new srcloc this allows to compile with ninja --- src/Module.zig | 13 ++++++++----- src/codegen/llvm.zig | 27 ++++++++++++++++----------- 2 files changed, 24 insertions(+), 16 deletions(-) (limited to 'src/codegen') diff --git a/src/Module.zig b/src/Module.zig index cef594e557..def4e725a4 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2337,11 +2337,14 @@ fn astgenAndSemaVarDecl( }; defer gen_scope.instructions.deinit(mod.gpa); - const init_result_loc: astgen.ResultLoc = if (var_decl.ast.type_node != 0) .{ - .ty = try astgen.expr(mod, &gen_scope.base, .{ - .ty = @enumToInt(zir.Const.type_type), - }, var_decl.ast.type_node), - } else .none; + const init_result_loc: astgen.ResultLoc = if (var_decl.ast.type_node != 0) + .{ + .ty = try astgen.expr(mod, &gen_scope.base, .{ + .ty = @enumToInt(zir.Const.type_type), + }, var_decl.ast.type_node), + } + else + .none; const init_inst = try astgen.comptimeExpr( mod, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 7233dbdd07..d0ba4b8986 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -15,6 +15,9 @@ const Inst = ir.Inst; const Value = @import("../value.zig").Value; const Type = @import("../type.zig").Type; +const LazySrcLoc = Module.LazySrcLoc; +const SrcLoc = Module.SrcLoc; + pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { const llvm_arch = switch (target.cpu.arch) { .arm => "arm", @@ -158,6 +161,10 @@ pub const LLVMIRModule = struct { // TODO: The fields below should really move into a different struct, // because they are only valid when generating a function + /// TODO: this should not be undefined since it should be in another per-decl struct + /// Curent decl we are analysing. Stored to get source locations from relative info + decl: *Module.Decl = undefined, + /// This stores the LLVM values used in a function, such that they can be /// referred to in other instructions. This table is cleared before every function is generated. /// TODO: Change this to a stack of Branch. Currently we store all the values from all the blocks @@ -342,9 +349,9 @@ pub const LLVMIRModule = struct { fn gen(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void { const typed_value = decl.typed_value.most_recent.typed_value; - const src = decl.src(); - self.src_loc = decl.srcLoc(); + self.decl = decl; + const src = self.src_loc.lazy; log.debug("gen: {s} type: {}, value: {}", .{ decl.name, typed_value.ty, typed_value.val }); @@ -765,7 +772,7 @@ pub const LLVMIRModule = struct { return self.fail(inst.src, "TODO implement global llvm values (or the value is not in the func_inst_table table)", .{}); } - fn genTypedValue(self: *LLVMIRModule, src: usize, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + fn genTypedValue(self: *LLVMIRModule, src: LazySrcLoc, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { const llvm_type = try self.getLLVMType(tv.ty, src); if (tv.val.isUndef()) @@ -852,7 +859,7 @@ pub const LLVMIRModule = struct { } } - fn getLLVMType(self: *LLVMIRModule, t: Type, src: usize) error{ OutOfMemory, CodegenFail }!*const llvm.Type { + fn getLLVMType(self: *LLVMIRModule, t: Type, src: LazySrcLoc) error{ OutOfMemory, CodegenFail }!*const llvm.Type { switch (t.zigTypeTag()) { .Void => return self.context.voidType(), .NoReturn => return self.context.voidType(), @@ -891,7 +898,7 @@ pub const LLVMIRModule = struct { } } - fn resolveGlobalDecl(self: *LLVMIRModule, decl: *Module.Decl, src: usize) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + fn resolveGlobalDecl(self: *LLVMIRModule, decl: *Module.Decl, src: LazySrcLoc) error{ OutOfMemory, CodegenFail }!*const llvm.Value { // TODO: do we want to store this in our own datastructure? if (self.llvm_module.getNamedGlobal(decl.name)) |val| return val; @@ -910,7 +917,7 @@ pub const LLVMIRModule = struct { } /// If the llvm function does not exist, create it - fn resolveLLVMFunction(self: *LLVMIRModule, func: *Module.Decl, src: usize) !*const llvm.Value { + fn resolveLLVMFunction(self: *LLVMIRModule, func: *Module.Decl, src: LazySrcLoc) !*const llvm.Value { // TODO: do we want to store this in our own datastructure? if (self.llvm_module.getNamedFunction(func.name)) |llvm_fn| return llvm_fn; @@ -958,13 +965,11 @@ pub const LLVMIRModule = struct { self.addAttr(val, std.math.maxInt(llvm.AttributeIndex), attr_name); } - pub fn fail(self: *LLVMIRModule, src: usize, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { + pub fn fail(self: *LLVMIRModule, src: LazySrcLoc, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { @setCold(true); assert(self.err_msg == null); - self.err_msg = try Module.ErrorMsg.create(self.gpa, .{ - .file_scope = self.src_loc.file_scope, - .byte_offset = src, - }, format, args); + const src_loc = src.toSrcLocWithDecl(self.decl); + self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, format, args); return error.CodegenFail; } }; -- cgit v1.2.3 From a710368054096889385562addaed2d16f0705332 Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Mon, 8 Mar 2021 00:09:03 +0100 Subject: stage2: restructure LLVM backend The LLVM backend is now structured into 3 different structs, namely Object, DeclGen and FuncGen. Object represents an object that is generated by the LLVM backend. DeclGen is responsible for generating a decl and FuncGen is responsible for generating llvm instructions from tzir in a function. --- src/codegen/llvm.zig | 743 +++++++++++++++++++++++++++------------------------ src/link/Coff.zig | 18 +- src/link/Elf.zig | 24 +- 3 files changed, 408 insertions(+), 377 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d0ba4b8986..06f70db665 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -16,7 +16,6 @@ const Value = @import("../value.zig").Value; const Type = @import("../type.zig").Type; const LazySrcLoc = Module.LazySrcLoc; -const SrcLoc = Module.SrcLoc; pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { const llvm_arch = switch (target.cpu.arch) { @@ -146,83 +145,42 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { return std.fmt.allocPrintZ(allocator, "{s}-unknown-{s}-{s}", .{ llvm_arch, llvm_os, llvm_abi }); } -pub const LLVMIRModule = struct { - module: *Module, +pub const Object = struct { llvm_module: *const llvm.Module, context: *const llvm.Context, target_machine: *const llvm.TargetMachine, - builder: *const llvm.Builder, - - object_path: []const u8, - - gpa: *Allocator, - err_msg: ?*Module.ErrorMsg = null, - - // TODO: The fields below should really move into a different struct, - // because they are only valid when generating a function - - /// TODO: this should not be undefined since it should be in another per-decl struct - /// Curent decl we are analysing. Stored to get source locations from relative info - decl: *Module.Decl = undefined, - - /// This stores the LLVM values used in a function, such that they can be - /// referred to in other instructions. This table is cleared before every function is generated. - /// TODO: Change this to a stack of Branch. Currently we store all the values from all the blocks - /// in here, however if a block ends, the instructions can be thrown away. - func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value) = .{}, - - /// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction. - args: []*const llvm.Value = &[_]*const llvm.Value{}, - arg_index: usize = 0, - - entry_block: *const llvm.BasicBlock = undefined, - /// This fields stores the last alloca instruction, such that we can append more alloca instructions - /// to the top of the function. - latest_alloca_inst: ?*const llvm.Value = null, - - llvm_func: *const llvm.Value = undefined, - - /// This data structure is used to implement breaking to blocks. - blocks: std.AutoHashMapUnmanaged(*Inst.Block, struct { - parent_bb: *const llvm.BasicBlock, - break_bbs: *BreakBasicBlocks, - break_vals: *BreakValues, - }) = .{}, - - src_loc: Module.SrcLoc, - - const BreakBasicBlocks = std.ArrayListUnmanaged(*const llvm.BasicBlock); - const BreakValues = std.ArrayListUnmanaged(*const llvm.Value); + object_pathZ: [:0]const u8, - pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*LLVMIRModule { - const self = try allocator.create(LLVMIRModule); + pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Object { + const self = try allocator.create(Object); errdefer allocator.destroy(self); - const gpa = options.module.?.gpa; - - const obj_basename = try std.zig.binNameAlloc(gpa, .{ + const obj_basename = try std.zig.binNameAlloc(allocator, .{ .root_name = options.root_name, .target = options.target, .output_mode = .Obj, }); - defer gpa.free(obj_basename); + defer allocator.free(obj_basename); const o_directory = options.module.?.zig_cache_artifact_directory; - const object_path = try o_directory.join(gpa, &[_][]const u8{obj_basename}); - errdefer gpa.free(object_path); + const object_path = try o_directory.join(allocator, &[_][]const u8{obj_basename}); + defer allocator.free(object_path); + + const object_pathZ = try allocator.dupeZ(u8, object_path); + errdefer allocator.free(object_pathZ); const context = llvm.Context.create(); errdefer context.dispose(); initializeLLVMTargets(); - const root_nameZ = try gpa.dupeZ(u8, options.root_name); - defer gpa.free(root_nameZ); + const root_nameZ = try allocator.dupeZ(u8, options.root_name); + defer allocator.free(root_nameZ); const llvm_module = llvm.Module.createWithName(root_nameZ.ptr, context); errdefer llvm_module.dispose(); - const llvm_target_triple = try targetTriple(gpa, options.target); - defer gpa.free(llvm_target_triple); + const llvm_target_triple = try targetTriple(allocator, options.target); + defer allocator.free(llvm_target_triple); var error_message: [*:0]const u8 = undefined; var target: *const llvm.Target = undefined; @@ -257,34 +215,21 @@ pub const LLVMIRModule = struct { ); errdefer target_machine.dispose(); - const builder = context.createBuilder(); - errdefer builder.dispose(); - self.* = .{ - .module = options.module.?, .llvm_module = llvm_module, .context = context, .target_machine = target_machine, - .builder = builder, - .object_path = object_path, - .gpa = gpa, - // TODO move this field into a struct that is only instantiated per gen() call - .src_loc = undefined, + .object_pathZ = object_pathZ, }; return self; } - pub fn deinit(self: *LLVMIRModule, allocator: *Allocator) void { - self.builder.dispose(); + pub fn deinit(self: *Object, allocator: *Allocator) void { self.target_machine.dispose(); self.llvm_module.dispose(); self.context.dispose(); - self.func_inst_table.deinit(self.gpa); - self.gpa.free(self.object_path); - - self.blocks.deinit(self.gpa); - + allocator.free(self.object_pathZ); allocator.destroy(self); } @@ -296,7 +241,7 @@ pub const LLVMIRModule = struct { llvm.initializeAllAsmParsers(); } - pub fn flushModule(self: *LLVMIRModule, comp: *Compilation) !void { + pub fn flushModule(self: *Object, comp: *Compilation) !void { if (comp.verbose_llvm_ir) { const dump = self.llvm_module.printToString(); defer llvm.disposeMessage(dump); @@ -317,13 +262,10 @@ pub const LLVMIRModule = struct { } } - const object_pathZ = try self.gpa.dupeZ(u8, self.object_path); - defer self.gpa.free(object_pathZ); - var error_message: [*:0]const u8 = undefined; if (self.target_machine.emitToFile( self.llvm_module, - object_pathZ.ptr, + self.object_pathZ.ptr, .ObjectFile, &error_message, ).toBool()) { @@ -335,23 +277,55 @@ pub const LLVMIRModule = struct { } } - pub fn updateDecl(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void { - self.gen(module, decl) catch |err| switch (err) { + pub fn updateDecl(self: *Object, module: *Module, decl: *Module.Decl) !void { + var dg: DeclGen = .{ + .object = self, + .module = module, + .decl = decl, + .err_msg = null, + .gpa = module.gpa, + }; + dg.genDecl() catch |err| switch (err) { error.CodegenFail => { decl.analysis = .codegen_failure; - try module.failed_decls.put(module.gpa, decl, self.err_msg.?); - self.err_msg = null; + try module.failed_decls.put(module.gpa, decl, dg.err_msg.?); + dg.err_msg = null; return; }, else => |e| return e, }; } +}; + +pub const DeclGen = struct { + object: *Object, + module: *Module, + decl: *Module.Decl, + err_msg: ?*Module.ErrorMsg, + + gpa: *Allocator, + + fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { + @setCold(true); + assert(self.err_msg == null); + const src_loc = src.toSrcLocWithDecl(self.decl); + self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, format, args); + return error.CodegenFail; + } + + fn llvmModule(self: *DeclGen) *const llvm.Module { + return self.object.llvm_module; + } + + fn context(self: *DeclGen) *const llvm.Context { + return self.object.context; + } - fn gen(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void { + fn genDecl(self: *DeclGen) !void { + const decl = self.decl; const typed_value = decl.typed_value.most_recent.typed_value; - self.src_loc = decl.srcLoc(); - self.decl = decl; - const src = self.src_loc.lazy; + + const src = decl.srcLoc().lazy; log.debug("gen: {s} type: {}, value: {}", .{ decl.name, typed_value.ty, typed_value.val }); @@ -363,16 +337,10 @@ pub const LLVMIRModule = struct { // This gets the LLVM values from the function and stores them in `self.args`. const fn_param_len = func.owner_decl.typed_value.most_recent.typed_value.ty.fnParamLen(); var args = try self.gpa.alloc(*const llvm.Value, fn_param_len); - defer self.gpa.free(args); for (args) |*arg, i| { arg.* = llvm.getParam(llvm_func, @intCast(c_uint, i)); } - self.args = args; - self.arg_index = 0; - - // Make sure no other LLVM values from other functions can be referenced - self.func_inst_table.clearRetainingCapacity(); // We remove all the basic blocks of a function to support incremental // compilation! @@ -381,12 +349,25 @@ pub const LLVMIRModule = struct { bb.deleteBasicBlock(); } - self.entry_block = self.context.appendBasicBlock(llvm_func, "Entry"); - self.builder.positionBuilderAtEnd(self.entry_block); - self.latest_alloca_inst = null; - self.llvm_func = llvm_func; + const builder = self.context().createBuilder(); + + const entry_block = self.context().appendBasicBlock(llvm_func, "Entry"); + builder.positionBuilderAtEnd(entry_block); + + var fg: FuncGen = .{ + .dg = self, + .builder = builder, + .args = args, + .arg_index = 0, + .func_inst_table = .{}, + .entry_block = entry_block, + .latest_alloca_inst = null, + .llvm_func = llvm_func, + .blocks = .{}, + }; + defer fg.deinit(); - try self.genBody(func.body); + try fg.genBody(func.body); } else if (typed_value.val.castTag(.extern_fn)) |extern_fn| { _ = try self.resolveLLVMFunction(extern_fn.data, src); } else { @@ -394,7 +375,267 @@ pub const LLVMIRModule = struct { } } - fn genBody(self: *LLVMIRModule, body: ir.Body) error{ OutOfMemory, CodegenFail }!void { + /// If the llvm function does not exist, create it + fn resolveLLVMFunction(self: *DeclGen, func: *Module.Decl, src: LazySrcLoc) !*const llvm.Value { + // TODO: do we want to store this in our own datastructure? + if (self.llvmModule().getNamedFunction(func.name)) |llvm_fn| return llvm_fn; + + const zig_fn_type = func.typed_value.most_recent.typed_value.ty; + const return_type = zig_fn_type.fnReturnType(); + + const fn_param_len = zig_fn_type.fnParamLen(); + + const fn_param_types = try self.gpa.alloc(Type, fn_param_len); + defer self.gpa.free(fn_param_types); + zig_fn_type.fnParamTypes(fn_param_types); + + const llvm_param = try self.gpa.alloc(*const llvm.Type, fn_param_len); + defer self.gpa.free(llvm_param); + + for (fn_param_types) |fn_param, i| { + llvm_param[i] = try self.getLLVMType(fn_param, src); + } + + const fn_type = llvm.Type.functionType( + try self.getLLVMType(return_type, src), + if (fn_param_len == 0) null else llvm_param.ptr, + @intCast(c_uint, fn_param_len), + .False, + ); + const llvm_fn = self.llvmModule().addFunction(func.name, fn_type); + + if (return_type.tag() == .noreturn) { + self.addFnAttr(llvm_fn, "noreturn"); + } + + return llvm_fn; + } + + fn resolveGlobalDecl(self: *DeclGen, decl: *Module.Decl, src: LazySrcLoc) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + // TODO: do we want to store this in our own datastructure? + if (self.llvmModule().getNamedGlobal(decl.name)) |val| return val; + + const typed_value = decl.typed_value.most_recent.typed_value; + + // TODO: remove this redundant `getLLVMType`, it is also called in `genTypedValue`. + const llvm_type = try self.getLLVMType(typed_value.ty, src); + const val = try self.genTypedValue(src, typed_value, null); + const global = self.llvmModule().addGlobal(llvm_type, decl.name); + llvm.setInitializer(global, val); + + // TODO ask the Decl if it is const + // https://github.com/ziglang/zig/issues/7582 + + return global; + } + + fn getLLVMType(self: *DeclGen, t: Type, src: LazySrcLoc) error{ OutOfMemory, CodegenFail }!*const llvm.Type { + switch (t.zigTypeTag()) { + .Void => return self.context().voidType(), + .NoReturn => return self.context().voidType(), + .Int => { + const info = t.intInfo(self.module.getTarget()); + return self.context().intType(info.bits); + }, + .Bool => return self.context().intType(1), + .Pointer => { + if (t.isSlice()) { + return self.fail(src, "TODO: LLVM backend: implement slices", .{}); + } else { + const elem_type = try self.getLLVMType(t.elemType(), src); + return elem_type.pointerType(0); + } + }, + .Array => { + const elem_type = try self.getLLVMType(t.elemType(), src); + return elem_type.arrayType(@intCast(c_uint, t.abiSize(self.module.getTarget()))); + }, + .Optional => { + if (!t.isPtrLikeOptional()) { + var buf: Type.Payload.ElemType = undefined; + const child_type = t.optionalChild(&buf); + + var optional_types: [2]*const llvm.Type = .{ + try self.getLLVMType(child_type, src), + self.context().intType(1), + }; + return self.context().structType(&optional_types, 2, .False); + } else { + return self.fail(src, "TODO implement optional pointers as actual pointers", .{}); + } + }, + else => return self.fail(src, "TODO implement getLLVMType for type '{}'", .{t}), + } + } + + // TODO: figure out a way to remove the FuncGen argument + fn genTypedValue(self: *DeclGen, src: LazySrcLoc, tv: TypedValue, fg: ?*FuncGen) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + const llvm_type = try self.getLLVMType(tv.ty, src); + + if (tv.val.isUndef()) + return llvm_type.getUndef(); + + switch (tv.ty.zigTypeTag()) { + .Bool => return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(), + .Int => { + var bigint_space: Value.BigIntSpace = undefined; + const bigint = tv.val.toBigInt(&bigint_space); + + if (bigint.eqZero()) return llvm_type.constNull(); + + if (bigint.limbs.len != 1) { + return self.fail(src, "TODO implement bigger bigint", .{}); + } + const llvm_int = llvm_type.constInt(bigint.limbs[0], .False); + if (!bigint.positive) { + return llvm.constNeg(llvm_int); + } + return llvm_int; + }, + .Pointer => switch (tv.val.tag()) { + .decl_ref => { + const decl = tv.val.castTag(.decl_ref).?.data; + const val = try self.resolveGlobalDecl(decl, src); + + const usize_type = try self.getLLVMType(Type.initTag(.usize), src); + + // TODO: second index should be the index into the memory! + var indices: [2]*const llvm.Value = .{ + usize_type.constNull(), + usize_type.constNull(), + }; + + // TODO: consider using buildInBoundsGEP2 for opaque pointers + return fg.?.builder.buildInBoundsGEP(val, &indices, 2, ""); + }, + .ref_val => { + const elem_value = tv.val.castTag(.ref_val).?.data; + const elem_type = tv.ty.castPointer().?.data; + const alloca = fg.?.buildAlloca(try self.getLLVMType(elem_type, src)); + _ = fg.?.builder.buildStore(try self.genTypedValue(src, .{ .ty = elem_type, .val = elem_value }, fg), alloca); + return alloca; + }, + else => return self.fail(src, "TODO implement const of pointer type '{}'", .{tv.ty}), + }, + .Array => { + if (tv.val.castTag(.bytes)) |payload| { + const zero_sentinel = if (tv.ty.sentinel()) |sentinel| blk: { + if (sentinel.tag() == .zero) break :blk true; + return self.fail(src, "TODO handle other sentinel values", .{}); + } else false; + + return self.context().constString(payload.data.ptr, @intCast(c_uint, payload.data.len), llvm.Bool.fromBool(!zero_sentinel)); + } else { + return self.fail(src, "TODO handle more array values", .{}); + } + }, + .Optional => { + if (!tv.ty.isPtrLikeOptional()) { + var buf: Type.Payload.ElemType = undefined; + const child_type = tv.ty.optionalChild(&buf); + const llvm_child_type = try self.getLLVMType(child_type, src); + + if (tv.val.tag() == .null_value) { + var optional_values: [2]*const llvm.Value = .{ + llvm_child_type.constNull(), + self.context().intType(1).constNull(), + }; + return self.context().constStruct(&optional_values, 2, .False); + } else { + var optional_values: [2]*const llvm.Value = .{ + try self.genTypedValue(src, .{ .ty = child_type, .val = tv.val }, fg), + self.context().intType(1).constAllOnes(), + }; + return self.context().constStruct(&optional_values, 2, .False); + } + } else { + return self.fail(src, "TODO implement const of optional pointer", .{}); + } + }, + else => return self.fail(src, "TODO implement const of type '{}'", .{tv.ty}), + } + } + + // Helper functions + fn addAttr(self: *DeclGen, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void { + const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len); + assert(kind_id != 0); + const llvm_attr = self.context().createEnumAttribute(kind_id, 0); + val.addAttributeAtIndex(index, llvm_attr); + } + + fn addFnAttr(self: *DeclGen, val: *const llvm.Value, attr_name: []const u8) void { + // TODO: improve this API, `addAttr(-1, attr_name)` + self.addAttr(val, std.math.maxInt(llvm.AttributeIndex), attr_name); + } +}; + +pub const FuncGen = struct { + dg: *DeclGen, + + builder: *const llvm.Builder, + + /// This stores the LLVM values used in a function, such that they can be + /// referred to in other instructions. This table is cleared before every function is generated. + /// TODO: Change this to a stack of Branch. Currently we store all the values from all the blocks + /// in here, however if a block ends, the instructions can be thrown away. + func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value), + + /// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction. + args: []*const llvm.Value, + arg_index: usize, + + entry_block: *const llvm.BasicBlock, + /// This fields stores the last alloca instruction, such that we can append more alloca instructions + /// to the top of the function. + latest_alloca_inst: ?*const llvm.Value, + + llvm_func: *const llvm.Value, + + /// This data structure is used to implement breaking to blocks. + blocks: std.AutoHashMapUnmanaged(*Inst.Block, struct { + parent_bb: *const llvm.BasicBlock, + break_bbs: *BreakBasicBlocks, + break_vals: *BreakValues, + }), + + const BreakBasicBlocks = std.ArrayListUnmanaged(*const llvm.BasicBlock); + const BreakValues = std.ArrayListUnmanaged(*const llvm.Value); + + fn deinit(self: *FuncGen) void { + self.builder.dispose(); + self.func_inst_table.deinit(self.gpa()); + self.gpa().free(self.args); + self.blocks.deinit(self.gpa()); + } + + fn fail(self: *FuncGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { + @setCold(true); + return self.dg.fail(src, format, args); + } + + fn llvmModule(self: *FuncGen) *const llvm.Module { + return self.dg.object.llvm_module; + } + + fn context(self: *FuncGen) *const llvm.Context { + return self.dg.object.context; + } + + fn gpa(self: *FuncGen) *Allocator { + return self.dg.gpa; + } + + fn resolveInst(self: *FuncGen, inst: *ir.Inst) !*const llvm.Value { + if (inst.value()) |val| { + return self.dg.genTypedValue(inst.src, .{ .ty = inst.ty, .val = val }, self); + } + if (self.func_inst_table.get(inst)) |value| return value; + + return self.fail(inst.src, "TODO implement global llvm values (or the value is not in the func_inst_table table)", .{}); + } + + fn genBody(self: *FuncGen, body: ir.Body) error{ OutOfMemory, CodegenFail }!void { for (body.instructions) |inst| { const opt_value = switch (inst.tag) { .add => try self.genAdd(inst.castTag(.add).?), @@ -434,11 +675,11 @@ pub const LLVMIRModule = struct { }, else => |tag| return self.fail(inst.src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}), }; - if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa, inst, val); + if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa(), inst, val); } } - fn genCall(self: *LLVMIRModule, inst: *Inst.Call) !?*const llvm.Value { + fn genCall(self: *FuncGen, inst: *Inst.Call) !?*const llvm.Value { if (inst.func.value()) |func_value| { const fn_decl = if (func_value.castTag(.extern_fn)) |extern_fn| extern_fn.data @@ -448,12 +689,12 @@ pub const LLVMIRModule = struct { unreachable; const zig_fn_type = fn_decl.typed_value.most_recent.typed_value.ty; - const llvm_fn = try self.resolveLLVMFunction(fn_decl, inst.base.src); + const llvm_fn = try self.dg.resolveLLVMFunction(fn_decl, inst.base.src); const num_args = inst.args.len; - const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, num_args); - defer self.gpa.free(llvm_param_vals); + const llvm_param_vals = try self.gpa().alloc(*const llvm.Value, num_args); + defer self.gpa().free(llvm_param_vals); for (inst.args) |arg, i| { llvm_param_vals[i] = try self.resolveInst(arg); @@ -482,17 +723,17 @@ pub const LLVMIRModule = struct { } } - fn genRetVoid(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.Value { + fn genRetVoid(self: *FuncGen, inst: *Inst.NoOp) ?*const llvm.Value { _ = self.builder.buildRetVoid(); return null; } - fn genRet(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + fn genRet(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value { _ = self.builder.buildRet(try self.resolveInst(inst.operand)); return null; } - fn genCmp(self: *LLVMIRModule, inst: *Inst.BinOp, op: math.CompareOperator) !?*const llvm.Value { + fn genCmp(self: *FuncGen, inst: *Inst.BinOp, op: math.CompareOperator) !?*const llvm.Value { const lhs = try self.resolveInst(inst.lhs); const rhs = try self.resolveInst(inst.rhs); @@ -513,21 +754,21 @@ pub const LLVMIRModule = struct { return self.builder.buildICmp(operation, lhs, rhs, ""); } - fn genBlock(self: *LLVMIRModule, inst: *Inst.Block) !?*const llvm.Value { - const parent_bb = self.context.createBasicBlock("Block"); + fn genBlock(self: *FuncGen, inst: *Inst.Block) !?*const llvm.Value { + const parent_bb = self.context().createBasicBlock("Block"); // 5 breaks to a block seems like a reasonable default. - var break_bbs = try BreakBasicBlocks.initCapacity(self.gpa, 5); - var break_vals = try BreakValues.initCapacity(self.gpa, 5); - try self.blocks.putNoClobber(self.gpa, inst, .{ + var break_bbs = try BreakBasicBlocks.initCapacity(self.gpa(), 5); + var break_vals = try BreakValues.initCapacity(self.gpa(), 5); + try self.blocks.putNoClobber(self.gpa(), inst, .{ .parent_bb = parent_bb, .break_bbs = &break_bbs, .break_vals = &break_vals, }); defer { self.blocks.removeAssertDiscard(inst); - break_bbs.deinit(self.gpa); - break_vals.deinit(self.gpa); + break_bbs.deinit(self.gpa()); + break_vals.deinit(self.gpa()); } try self.genBody(inst.body); @@ -538,7 +779,7 @@ pub const LLVMIRModule = struct { // If the block does not return a value, we dont have to create a phi node. if (!inst.base.ty.hasCodeGenBits()) return null; - const phi_node = self.builder.buildPhi(try self.getLLVMType(inst.base.ty, inst.base.src), ""); + const phi_node = self.builder.buildPhi(try self.dg.getLLVMType(inst.base.ty, inst.base.src), ""); phi_node.addIncoming( break_vals.items.ptr, break_bbs.items.ptr, @@ -547,7 +788,7 @@ pub const LLVMIRModule = struct { return phi_node; } - fn genBr(self: *LLVMIRModule, inst: *Inst.Br) !?*const llvm.Value { + fn genBr(self: *FuncGen, inst: *Inst.Br) !?*const llvm.Value { var block = self.blocks.get(inst.block).?; // If the break doesn't break a value, then we don't have to add @@ -560,25 +801,25 @@ pub const LLVMIRModule = struct { // For the phi node, we need the basic blocks and the values of the // break instructions. - try block.break_bbs.append(self.gpa, self.builder.getInsertBlock()); - try block.break_vals.append(self.gpa, val); + try block.break_bbs.append(self.gpa(), self.builder.getInsertBlock()); + try block.break_vals.append(self.gpa(), val); _ = self.builder.buildBr(block.parent_bb); } return null; } - fn genBrVoid(self: *LLVMIRModule, inst: *Inst.BrVoid) !?*const llvm.Value { + fn genBrVoid(self: *FuncGen, inst: *Inst.BrVoid) !?*const llvm.Value { var block = self.blocks.get(inst.block).?; _ = self.builder.buildBr(block.parent_bb); return null; } - fn genCondBr(self: *LLVMIRModule, inst: *Inst.CondBr) !?*const llvm.Value { + fn genCondBr(self: *FuncGen, inst: *Inst.CondBr) !?*const llvm.Value { const condition_value = try self.resolveInst(inst.condition); - const then_block = self.context.appendBasicBlock(self.llvm_func, "Then"); - const else_block = self.context.appendBasicBlock(self.llvm_func, "Else"); + const then_block = self.context().appendBasicBlock(self.llvm_func, "Then"); + const else_block = self.context().appendBasicBlock(self.llvm_func, "Else"); { const prev_block = self.builder.getInsertBlock(); defer self.builder.positionBuilderAtEnd(prev_block); @@ -593,8 +834,8 @@ pub const LLVMIRModule = struct { return null; } - fn genLoop(self: *LLVMIRModule, inst: *Inst.Loop) !?*const llvm.Value { - const loop_block = self.context.appendBasicBlock(self.llvm_func, "Loop"); + fn genLoop(self: *FuncGen, inst: *Inst.Loop) !?*const llvm.Value { + const loop_block = self.context().appendBasicBlock(self.llvm_func, "Loop"); _ = self.builder.buildBr(loop_block); self.builder.positionBuilderAtEnd(loop_block); @@ -604,20 +845,20 @@ pub const LLVMIRModule = struct { return null; } - fn genNot(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + fn genNot(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value { return self.builder.buildNot(try self.resolveInst(inst.operand), ""); } - fn genUnreach(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.Value { + fn genUnreach(self: *FuncGen, inst: *Inst.NoOp) ?*const llvm.Value { _ = self.builder.buildUnreachable(); return null; } - fn genIsNonNull(self: *LLVMIRModule, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value { + fn genIsNonNull(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value { const operand = try self.resolveInst(inst.operand); if (operand_is_ptr) { - const index_type = self.context.intType(32); + const index_type = self.context().intType(32); var indices: [2]*const llvm.Value = .{ index_type.constNull(), @@ -630,15 +871,15 @@ pub const LLVMIRModule = struct { } } - fn genIsNull(self: *LLVMIRModule, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value { + fn genIsNull(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value { return self.builder.buildNot((try self.genIsNonNull(inst, operand_is_ptr)).?, ""); } - fn genOptionalPayload(self: *LLVMIRModule, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value { + fn genOptionalPayload(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value { const operand = try self.resolveInst(inst.operand); if (operand_is_ptr) { - const index_type = self.context.intType(32); + const index_type = self.context().intType(32); var indices: [2]*const llvm.Value = .{ index_type.constNull(), @@ -651,7 +892,7 @@ pub const LLVMIRModule = struct { } } - fn genAdd(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { + fn genAdd(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value { const lhs = try self.resolveInst(inst.lhs); const rhs = try self.resolveInst(inst.rhs); @@ -664,7 +905,7 @@ pub const LLVMIRModule = struct { self.builder.buildNUWAdd(lhs, rhs, ""); } - fn genSub(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { + fn genSub(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value { const lhs = try self.resolveInst(inst.lhs); const rhs = try self.resolveInst(inst.rhs); @@ -677,44 +918,44 @@ pub const LLVMIRModule = struct { self.builder.buildNUWSub(lhs, rhs, ""); } - fn genIntCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + fn genIntCast(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value { const val = try self.resolveInst(inst.operand); const signed = inst.base.ty.isSignedInt(); // TODO: Should we use intcast here or just a simple bitcast? // LLVM does truncation vs bitcast (+signed extension) in the intcast depending on the sizes - return self.builder.buildIntCast2(val, try self.getLLVMType(inst.base.ty, inst.base.src), llvm.Bool.fromBool(signed), ""); + return self.builder.buildIntCast2(val, try self.dg.getLLVMType(inst.base.ty, inst.base.src), llvm.Bool.fromBool(signed), ""); } - fn genBitCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + fn genBitCast(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value { const val = try self.resolveInst(inst.operand); - const dest_type = try self.getLLVMType(inst.base.ty, inst.base.src); + const dest_type = try self.dg.getLLVMType(inst.base.ty, inst.base.src); return self.builder.buildBitCast(val, dest_type, ""); } - fn genArg(self: *LLVMIRModule, inst: *Inst.Arg) !?*const llvm.Value { + fn genArg(self: *FuncGen, inst: *Inst.Arg) !?*const llvm.Value { const arg_val = self.args[self.arg_index]; self.arg_index += 1; - const ptr_val = self.buildAlloca(try self.getLLVMType(inst.base.ty, inst.base.src)); + const ptr_val = self.buildAlloca(try self.dg.getLLVMType(inst.base.ty, inst.base.src)); _ = self.builder.buildStore(arg_val, ptr_val); return self.builder.buildLoad(ptr_val, ""); } - fn genAlloc(self: *LLVMIRModule, inst: *Inst.NoOp) !?*const llvm.Value { + fn genAlloc(self: *FuncGen, inst: *Inst.NoOp) !?*const llvm.Value { // buildAlloca expects the pointee type, not the pointer type, so assert that // a Payload.PointerSimple is passed to the alloc instruction. const pointee_type = inst.base.ty.castPointer().?.data; // TODO: figure out a way to get the name of the var decl. // TODO: set alignment and volatile - return self.buildAlloca(try self.getLLVMType(pointee_type, inst.base.src)); + return self.buildAlloca(try self.dg.getLLVMType(pointee_type, inst.base.src)); } /// Use this instead of builder.buildAlloca, because this function makes sure to /// put the alloca instruction at the top of the function! - fn buildAlloca(self: *LLVMIRModule, t: *const llvm.Type) *const llvm.Value { + fn buildAlloca(self: *FuncGen, t: *const llvm.Type) *const llvm.Value { const prev_block = self.builder.getInsertBlock(); defer self.builder.positionBuilderAtEnd(prev_block); @@ -736,240 +977,30 @@ pub const LLVMIRModule = struct { return val; } - fn genStore(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { + fn genStore(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value { const val = try self.resolveInst(inst.rhs); const ptr = try self.resolveInst(inst.lhs); _ = self.builder.buildStore(val, ptr); return null; } - fn genLoad(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + fn genLoad(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value { const ptr_val = try self.resolveInst(inst.operand); return self.builder.buildLoad(ptr_val, ""); } - fn genBreakpoint(self: *LLVMIRModule, inst: *Inst.NoOp) !?*const llvm.Value { + fn genBreakpoint(self: *FuncGen, inst: *Inst.NoOp) !?*const llvm.Value { const llvn_fn = self.getIntrinsic("llvm.debugtrap"); _ = self.builder.buildCall(llvn_fn, null, 0, ""); return null; } - fn getIntrinsic(self: *LLVMIRModule, name: []const u8) *const llvm.Value { + fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value { const id = llvm.lookupIntrinsicID(name.ptr, name.len); assert(id != 0); // TODO: add support for overload intrinsics by passing the prefix of the intrinsic // to `lookupIntrinsicID` and then passing the correct types to // `getIntrinsicDeclaration` - return self.llvm_module.getIntrinsicDeclaration(id, null, 0); - } - - fn resolveInst(self: *LLVMIRModule, inst: *ir.Inst) !*const llvm.Value { - if (inst.value()) |val| { - return self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = val }); - } - if (self.func_inst_table.get(inst)) |value| return value; - - return self.fail(inst.src, "TODO implement global llvm values (or the value is not in the func_inst_table table)", .{}); - } - - fn genTypedValue(self: *LLVMIRModule, src: LazySrcLoc, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - const llvm_type = try self.getLLVMType(tv.ty, src); - - if (tv.val.isUndef()) - return llvm_type.getUndef(); - - switch (tv.ty.zigTypeTag()) { - .Bool => return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(), - .Int => { - var bigint_space: Value.BigIntSpace = undefined; - const bigint = tv.val.toBigInt(&bigint_space); - - if (bigint.eqZero()) return llvm_type.constNull(); - - if (bigint.limbs.len != 1) { - return self.fail(src, "TODO implement bigger bigint", .{}); - } - const llvm_int = llvm_type.constInt(bigint.limbs[0], .False); - if (!bigint.positive) { - return llvm.constNeg(llvm_int); - } - return llvm_int; - }, - .Pointer => switch (tv.val.tag()) { - .decl_ref => { - const decl = tv.val.castTag(.decl_ref).?.data; - const val = try self.resolveGlobalDecl(decl, src); - - const usize_type = try self.getLLVMType(Type.initTag(.usize), src); - - // TODO: second index should be the index into the memory! - var indices: [2]*const llvm.Value = .{ - usize_type.constNull(), - usize_type.constNull(), - }; - - // TODO: consider using buildInBoundsGEP2 for opaque pointers - return self.builder.buildInBoundsGEP(val, &indices, 2, ""); - }, - .ref_val => { - const elem_value = tv.val.castTag(.ref_val).?.data; - const elem_type = tv.ty.castPointer().?.data; - const alloca = self.buildAlloca(try self.getLLVMType(elem_type, src)); - _ = self.builder.buildStore(try self.genTypedValue(src, .{ .ty = elem_type, .val = elem_value }), alloca); - return alloca; - }, - else => return self.fail(src, "TODO implement const of pointer type '{}'", .{tv.ty}), - }, - .Array => { - if (tv.val.castTag(.bytes)) |payload| { - const zero_sentinel = if (tv.ty.sentinel()) |sentinel| blk: { - if (sentinel.tag() == .zero) break :blk true; - return self.fail(src, "TODO handle other sentinel values", .{}); - } else false; - - return self.context.constString(payload.data.ptr, @intCast(c_uint, payload.data.len), llvm.Bool.fromBool(!zero_sentinel)); - } else { - return self.fail(src, "TODO handle more array values", .{}); - } - }, - .Optional => { - if (!tv.ty.isPtrLikeOptional()) { - var buf: Type.Payload.ElemType = undefined; - const child_type = tv.ty.optionalChild(&buf); - const llvm_child_type = try self.getLLVMType(child_type, src); - - if (tv.val.tag() == .null_value) { - var optional_values: [2]*const llvm.Value = .{ - llvm_child_type.constNull(), - self.context.intType(1).constNull(), - }; - return self.context.constStruct(&optional_values, 2, .False); - } else { - var optional_values: [2]*const llvm.Value = .{ - try self.genTypedValue(src, .{ .ty = child_type, .val = tv.val }), - self.context.intType(1).constAllOnes(), - }; - return self.context.constStruct(&optional_values, 2, .False); - } - } else { - return self.fail(src, "TODO implement const of optional pointer", .{}); - } - }, - else => return self.fail(src, "TODO implement const of type '{}'", .{tv.ty}), - } - } - - fn getLLVMType(self: *LLVMIRModule, t: Type, src: LazySrcLoc) error{ OutOfMemory, CodegenFail }!*const llvm.Type { - switch (t.zigTypeTag()) { - .Void => return self.context.voidType(), - .NoReturn => return self.context.voidType(), - .Int => { - const info = t.intInfo(self.module.getTarget()); - return self.context.intType(info.bits); - }, - .Bool => return self.context.intType(1), - .Pointer => { - if (t.isSlice()) { - return self.fail(src, "TODO: LLVM backend: implement slices", .{}); - } else { - const elem_type = try self.getLLVMType(t.elemType(), src); - return elem_type.pointerType(0); - } - }, - .Array => { - const elem_type = try self.getLLVMType(t.elemType(), src); - return elem_type.arrayType(@intCast(c_uint, t.abiSize(self.module.getTarget()))); - }, - .Optional => { - if (!t.isPtrLikeOptional()) { - var buf: Type.Payload.ElemType = undefined; - const child_type = t.optionalChild(&buf); - - var optional_types: [2]*const llvm.Type = .{ - try self.getLLVMType(child_type, src), - self.context.intType(1), - }; - return self.context.structType(&optional_types, 2, .False); - } else { - return self.fail(src, "TODO implement optional pointers as actual pointers", .{}); - } - }, - else => return self.fail(src, "TODO implement getLLVMType for type '{}'", .{t}), - } - } - - fn resolveGlobalDecl(self: *LLVMIRModule, decl: *Module.Decl, src: LazySrcLoc) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - // TODO: do we want to store this in our own datastructure? - if (self.llvm_module.getNamedGlobal(decl.name)) |val| return val; - - const typed_value = decl.typed_value.most_recent.typed_value; - - // TODO: remove this redundant `getLLVMType`, it is also called in `genTypedValue`. - const llvm_type = try self.getLLVMType(typed_value.ty, src); - const val = try self.genTypedValue(src, typed_value); - const global = self.llvm_module.addGlobal(llvm_type, decl.name); - llvm.setInitializer(global, val); - - // TODO ask the Decl if it is const - // https://github.com/ziglang/zig/issues/7582 - - return global; - } - - /// If the llvm function does not exist, create it - fn resolveLLVMFunction(self: *LLVMIRModule, func: *Module.Decl, src: LazySrcLoc) !*const llvm.Value { - // TODO: do we want to store this in our own datastructure? - if (self.llvm_module.getNamedFunction(func.name)) |llvm_fn| return llvm_fn; - - const zig_fn_type = func.typed_value.most_recent.typed_value.ty; - const return_type = zig_fn_type.fnReturnType(); - - const fn_param_len = zig_fn_type.fnParamLen(); - - const fn_param_types = try self.gpa.alloc(Type, fn_param_len); - defer self.gpa.free(fn_param_types); - zig_fn_type.fnParamTypes(fn_param_types); - - const llvm_param = try self.gpa.alloc(*const llvm.Type, fn_param_len); - defer self.gpa.free(llvm_param); - - for (fn_param_types) |fn_param, i| { - llvm_param[i] = try self.getLLVMType(fn_param, src); - } - - const fn_type = llvm.Type.functionType( - try self.getLLVMType(return_type, src), - if (fn_param_len == 0) null else llvm_param.ptr, - @intCast(c_uint, fn_param_len), - .False, - ); - const llvm_fn = self.llvm_module.addFunction(func.name, fn_type); - - if (return_type.tag() == .noreturn) { - self.addFnAttr(llvm_fn, "noreturn"); - } - - return llvm_fn; - } - - // Helper functions - fn addAttr(self: LLVMIRModule, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void { - const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len); - assert(kind_id != 0); - const llvm_attr = self.context.createEnumAttribute(kind_id, 0); - val.addAttributeAtIndex(index, llvm_attr); - } - - fn addFnAttr(self: *LLVMIRModule, val: *const llvm.Value, attr_name: []const u8) void { - // TODO: improve this API, `addAttr(-1, attr_name)` - self.addAttr(val, std.math.maxInt(llvm.AttributeIndex), attr_name); - } - - pub fn fail(self: *LLVMIRModule, src: LazySrcLoc, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { - @setCold(true); - assert(self.err_msg == null); - const src_loc = src.toSrcLocWithDecl(self.decl); - self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, format, args); - return error.CodegenFail; + return self.llvmModule().getIntrinsicDeclaration(id, null, 0); } }; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 308164ac95..33c37f8efe 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -34,7 +34,7 @@ pub const base_tag: link.File.Tag = .coff; const msdos_stub = @embedFile("msdos-stub.bin"); /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. -llvm_ir_module: ?*llvm_backend.LLVMIRModule = null, +llvm_object: ?*llvm_backend.Object = null, base: link.File, ptr_width: PtrWidth, @@ -129,7 +129,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio const self = try createEmpty(allocator, options); errdefer self.base.destroy(); - self.llvm_ir_module = try llvm_backend.LLVMIRModule.create(allocator, sub_path, options); + self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options); return self; } @@ -413,7 +413,7 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Coff { } pub fn allocateDeclIndexes(self: *Coff, decl: *Module.Decl) !void { - if (self.llvm_ir_module) |_| return; + if (self.llvm_object) |_| return; try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); @@ -660,7 +660,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { defer tracy.end(); if (build_options.have_llvm) - if (self.llvm_ir_module) |llvm_ir_module| return try llvm_ir_module.updateDecl(module, decl); + if (self.llvm_object) |llvm_object| return try llvm_object.updateDecl(module, decl); const typed_value = decl.typed_value.most_recent.typed_value; if (typed_value.val.tag() == .extern_fn) { @@ -720,7 +720,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { } pub fn freeDecl(self: *Coff, decl: *Module.Decl) void { - if (self.llvm_ir_module) |_| return; + if (self.llvm_object) |_| return; // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. self.freeTextBlock(&decl.link.coff); @@ -728,7 +728,7 @@ pub fn freeDecl(self: *Coff, decl: *Module.Decl) void { } pub fn updateDeclExports(self: *Coff, module: *Module, decl: *Module.Decl, exports: []const *Module.Export) !void { - if (self.llvm_ir_module) |_| return; + if (self.llvm_object) |_| return; for (exports) |exp| { if (exp.options.section) |section_name| { @@ -771,7 +771,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation) !void { defer tracy.end(); if (build_options.have_llvm) - if (self.llvm_ir_module) |llvm_ir_module| return try llvm_ir_module.flushModule(comp); + if (self.llvm_object) |llvm_object| return try llvm_object.flushModule(comp); if (self.text_section_size_dirty) { // Write the new raw size in the .text header @@ -1308,7 +1308,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { } pub fn getDeclVAddr(self: *Coff, decl: *const Module.Decl) u64 { - assert(self.llvm_ir_module == null); + assert(self.llvm_object == null); return self.text_section_virtual_address + decl.link.coff.text_offset; } @@ -1318,7 +1318,7 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v pub fn deinit(self: *Coff) void { if (build_options.have_llvm) - if (self.llvm_ir_module) |ir_module| ir_module.deinit(self.base.allocator); + if (self.llvm_object) |ir_module| ir_module.deinit(self.base.allocator); self.text_block_free_list.deinit(self.base.allocator); self.offset_table.deinit(self.base.allocator); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 2f89e26c3b..3d074f4043 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -35,7 +35,7 @@ base: File, ptr_width: PtrWidth, /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. -llvm_ir_module: ?*llvm_backend.LLVMIRModule = null, +llvm_object: ?*llvm_backend.Object = null, /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. /// Same order as in the file. @@ -232,7 +232,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio const self = try createEmpty(allocator, options); errdefer self.base.destroy(); - self.llvm_ir_module = try llvm_backend.LLVMIRModule.create(allocator, sub_path, options); + self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options); return self; } @@ -299,7 +299,7 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Elf { pub fn deinit(self: *Elf) void { if (build_options.have_llvm) - if (self.llvm_ir_module) |ir_module| + if (self.llvm_object) |ir_module| ir_module.deinit(self.base.allocator); self.sections.deinit(self.base.allocator); @@ -318,7 +318,7 @@ pub fn deinit(self: *Elf) void { } pub fn getDeclVAddr(self: *Elf, decl: *const Module.Decl) u64 { - assert(self.llvm_ir_module == null); + assert(self.llvm_object == null); assert(decl.link.elf.local_sym_index != 0); return self.local_symbols.items[decl.link.elf.local_sym_index].st_value; } @@ -438,7 +438,7 @@ fn updateString(self: *Elf, old_str_off: u32, new_name: []const u8) !u32 { } pub fn populateMissingMetadata(self: *Elf) !void { - assert(self.llvm_ir_module == null); + assert(self.llvm_object == null); const small_ptr = switch (self.ptr_width) { .p32 => true, @@ -745,7 +745,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { defer tracy.end(); if (build_options.have_llvm) - if (self.llvm_ir_module) |llvm_ir_module| return try llvm_ir_module.flushModule(comp); + if (self.llvm_object) |llvm_object| return try llvm_object.flushModule(comp); // TODO This linker code currently assumes there is only 1 compilation unit and it corresponds to the // Zig source code. @@ -2111,7 +2111,7 @@ fn allocateTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, al } pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { - if (self.llvm_ir_module) |_| return; + if (self.llvm_object) |_| return; if (decl.link.elf.local_sym_index != 0) return; @@ -2149,7 +2149,7 @@ pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { } pub fn freeDecl(self: *Elf, decl: *Module.Decl) void { - if (self.llvm_ir_module) |_| return; + if (self.llvm_object) |_| return; // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. self.freeTextBlock(&decl.link.elf); @@ -2189,7 +2189,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { defer tracy.end(); if (build_options.have_llvm) - if (self.llvm_ir_module) |llvm_ir_module| return try llvm_ir_module.updateDecl(module, decl); + if (self.llvm_object) |llvm_object| return try llvm_object.updateDecl(module, decl); const typed_value = decl.typed_value.most_recent.typed_value; if (typed_value.val.tag() == .extern_fn) { @@ -2673,7 +2673,7 @@ pub fn updateDeclExports( decl: *Module.Decl, exports: []const *Module.Export, ) !void { - if (self.llvm_ir_module) |_| return; + if (self.llvm_object) |_| return; const tracy = trace(@src()); defer tracy.end(); @@ -2748,7 +2748,7 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec const tracy = trace(@src()); defer tracy.end(); - if (self.llvm_ir_module) |_| return; + if (self.llvm_object) |_| return; const tree = decl.container.file_scope.tree; const node_tags = tree.nodes.items(.tag); @@ -2773,7 +2773,7 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec } pub fn deleteExport(self: *Elf, exp: Export) void { - if (self.llvm_ir_module) |_| return; + if (self.llvm_object) |_| return; const sym_index = exp.sym_index orelse return; self.global_symbol_free_list.append(self.base.allocator, sym_index) catch {}; -- cgit v1.2.3 From d73b0473a1df98703d5c742d59e57e225cfa7ba4 Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Tue, 23 Mar 2021 14:23:51 +0100 Subject: stage2: rename fail to todo in LLVM backend This way we don't have to pass src to every function and we can simply use the first node as the lazy source location for all the todo errors. --- src/codegen/llvm.zig | 98 +++++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 50 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 06f70db665..e78bd8d103 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -305,11 +305,11 @@ pub const DeclGen = struct { gpa: *Allocator, - fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { + fn todo(self: *DeclGen, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { @setCold(true); assert(self.err_msg == null); - const src_loc = src.toSrcLocWithDecl(self.decl); - self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, format, args); + const src_loc = @as(LazySrcLoc, .{ .node_offset = 0 }).toSrcLocWithDecl(self.decl); + self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, "TODO (LLVM): " ++ format, args); return error.CodegenFail; } @@ -325,14 +325,12 @@ pub const DeclGen = struct { const decl = self.decl; const typed_value = decl.typed_value.most_recent.typed_value; - const src = decl.srcLoc().lazy; - log.debug("gen: {s} type: {}, value: {}", .{ decl.name, typed_value.ty, typed_value.val }); if (typed_value.val.castTag(.function)) |func_payload| { const func = func_payload.data; - const llvm_func = try self.resolveLLVMFunction(func.owner_decl, src); + const llvm_func = try self.resolveLLVMFunction(func.owner_decl); // This gets the LLVM values from the function and stores them in `self.args`. const fn_param_len = func.owner_decl.typed_value.most_recent.typed_value.ty.fnParamLen(); @@ -369,14 +367,14 @@ pub const DeclGen = struct { try fg.genBody(func.body); } else if (typed_value.val.castTag(.extern_fn)) |extern_fn| { - _ = try self.resolveLLVMFunction(extern_fn.data, src); + _ = try self.resolveLLVMFunction(extern_fn.data); } else { - _ = try self.resolveGlobalDecl(decl, src); + _ = try self.resolveGlobalDecl(decl); } } /// If the llvm function does not exist, create it - fn resolveLLVMFunction(self: *DeclGen, func: *Module.Decl, src: LazySrcLoc) !*const llvm.Value { + fn resolveLLVMFunction(self: *DeclGen, func: *Module.Decl) !*const llvm.Value { // TODO: do we want to store this in our own datastructure? if (self.llvmModule().getNamedFunction(func.name)) |llvm_fn| return llvm_fn; @@ -393,11 +391,11 @@ pub const DeclGen = struct { defer self.gpa.free(llvm_param); for (fn_param_types) |fn_param, i| { - llvm_param[i] = try self.getLLVMType(fn_param, src); + llvm_param[i] = try self.getLLVMType(fn_param); } const fn_type = llvm.Type.functionType( - try self.getLLVMType(return_type, src), + try self.getLLVMType(return_type), if (fn_param_len == 0) null else llvm_param.ptr, @intCast(c_uint, fn_param_len), .False, @@ -411,15 +409,15 @@ pub const DeclGen = struct { return llvm_fn; } - fn resolveGlobalDecl(self: *DeclGen, decl: *Module.Decl, src: LazySrcLoc) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + fn resolveGlobalDecl(self: *DeclGen, decl: *Module.Decl) error{ OutOfMemory, CodegenFail }!*const llvm.Value { // TODO: do we want to store this in our own datastructure? if (self.llvmModule().getNamedGlobal(decl.name)) |val| return val; const typed_value = decl.typed_value.most_recent.typed_value; // TODO: remove this redundant `getLLVMType`, it is also called in `genTypedValue`. - const llvm_type = try self.getLLVMType(typed_value.ty, src); - const val = try self.genTypedValue(src, typed_value, null); + const llvm_type = try self.getLLVMType(typed_value.ty); + const val = try self.genTypedValue(typed_value, null); const global = self.llvmModule().addGlobal(llvm_type, decl.name); llvm.setInitializer(global, val); @@ -429,7 +427,7 @@ pub const DeclGen = struct { return global; } - fn getLLVMType(self: *DeclGen, t: Type, src: LazySrcLoc) error{ OutOfMemory, CodegenFail }!*const llvm.Type { + fn getLLVMType(self: *DeclGen, t: Type) error{ OutOfMemory, CodegenFail }!*const llvm.Type { switch (t.zigTypeTag()) { .Void => return self.context().voidType(), .NoReturn => return self.context().voidType(), @@ -440,14 +438,14 @@ pub const DeclGen = struct { .Bool => return self.context().intType(1), .Pointer => { if (t.isSlice()) { - return self.fail(src, "TODO: LLVM backend: implement slices", .{}); + return self.todo("implement slices", .{}); } else { - const elem_type = try self.getLLVMType(t.elemType(), src); + const elem_type = try self.getLLVMType(t.elemType()); return elem_type.pointerType(0); } }, .Array => { - const elem_type = try self.getLLVMType(t.elemType(), src); + const elem_type = try self.getLLVMType(t.elemType()); return elem_type.arrayType(@intCast(c_uint, t.abiSize(self.module.getTarget()))); }, .Optional => { @@ -456,21 +454,21 @@ pub const DeclGen = struct { const child_type = t.optionalChild(&buf); var optional_types: [2]*const llvm.Type = .{ - try self.getLLVMType(child_type, src), + try self.getLLVMType(child_type), self.context().intType(1), }; return self.context().structType(&optional_types, 2, .False); } else { - return self.fail(src, "TODO implement optional pointers as actual pointers", .{}); + return self.todo("implement optional pointers as actual pointers", .{}); } }, - else => return self.fail(src, "TODO implement getLLVMType for type '{}'", .{t}), + else => return self.todo("implement getLLVMType for type '{}'", .{t}), } } // TODO: figure out a way to remove the FuncGen argument - fn genTypedValue(self: *DeclGen, src: LazySrcLoc, tv: TypedValue, fg: ?*FuncGen) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - const llvm_type = try self.getLLVMType(tv.ty, src); + fn genTypedValue(self: *DeclGen, tv: TypedValue, fg: ?*FuncGen) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + const llvm_type = try self.getLLVMType(tv.ty); if (tv.val.isUndef()) return llvm_type.getUndef(); @@ -484,7 +482,7 @@ pub const DeclGen = struct { if (bigint.eqZero()) return llvm_type.constNull(); if (bigint.limbs.len != 1) { - return self.fail(src, "TODO implement bigger bigint", .{}); + return self.todo("implement bigger bigint", .{}); } const llvm_int = llvm_type.constInt(bigint.limbs[0], .False); if (!bigint.positive) { @@ -495,9 +493,9 @@ pub const DeclGen = struct { .Pointer => switch (tv.val.tag()) { .decl_ref => { const decl = tv.val.castTag(.decl_ref).?.data; - const val = try self.resolveGlobalDecl(decl, src); + const val = try self.resolveGlobalDecl(decl); - const usize_type = try self.getLLVMType(Type.initTag(.usize), src); + const usize_type = try self.getLLVMType(Type.initTag(.usize)); // TODO: second index should be the index into the memory! var indices: [2]*const llvm.Value = .{ @@ -511,29 +509,29 @@ pub const DeclGen = struct { .ref_val => { const elem_value = tv.val.castTag(.ref_val).?.data; const elem_type = tv.ty.castPointer().?.data; - const alloca = fg.?.buildAlloca(try self.getLLVMType(elem_type, src)); - _ = fg.?.builder.buildStore(try self.genTypedValue(src, .{ .ty = elem_type, .val = elem_value }, fg), alloca); + const alloca = fg.?.buildAlloca(try self.getLLVMType(elem_type)); + _ = fg.?.builder.buildStore(try self.genTypedValue(.{ .ty = elem_type, .val = elem_value }, fg), alloca); return alloca; }, - else => return self.fail(src, "TODO implement const of pointer type '{}'", .{tv.ty}), + else => return self.todo("implement const of pointer type '{}'", .{tv.ty}), }, .Array => { if (tv.val.castTag(.bytes)) |payload| { const zero_sentinel = if (tv.ty.sentinel()) |sentinel| blk: { if (sentinel.tag() == .zero) break :blk true; - return self.fail(src, "TODO handle other sentinel values", .{}); + return self.todo("handle other sentinel values", .{}); } else false; return self.context().constString(payload.data.ptr, @intCast(c_uint, payload.data.len), llvm.Bool.fromBool(!zero_sentinel)); } else { - return self.fail(src, "TODO handle more array values", .{}); + return self.todo("handle more array values", .{}); } }, .Optional => { if (!tv.ty.isPtrLikeOptional()) { var buf: Type.Payload.ElemType = undefined; const child_type = tv.ty.optionalChild(&buf); - const llvm_child_type = try self.getLLVMType(child_type, src); + const llvm_child_type = try self.getLLVMType(child_type); if (tv.val.tag() == .null_value) { var optional_values: [2]*const llvm.Value = .{ @@ -543,16 +541,16 @@ pub const DeclGen = struct { return self.context().constStruct(&optional_values, 2, .False); } else { var optional_values: [2]*const llvm.Value = .{ - try self.genTypedValue(src, .{ .ty = child_type, .val = tv.val }, fg), + try self.genTypedValue(.{ .ty = child_type, .val = tv.val }, fg), self.context().intType(1).constAllOnes(), }; return self.context().constStruct(&optional_values, 2, .False); } } else { - return self.fail(src, "TODO implement const of optional pointer", .{}); + return self.todo("implement const of optional pointer", .{}); } }, - else => return self.fail(src, "TODO implement const of type '{}'", .{tv.ty}), + else => return self.todo("implement const of type '{}'", .{tv.ty}), } } @@ -609,9 +607,9 @@ pub const FuncGen = struct { self.blocks.deinit(self.gpa()); } - fn fail(self: *FuncGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { + fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { @setCold(true); - return self.dg.fail(src, format, args); + return self.dg.todo(format, args); } fn llvmModule(self: *FuncGen) *const llvm.Module { @@ -628,11 +626,11 @@ pub const FuncGen = struct { fn resolveInst(self: *FuncGen, inst: *ir.Inst) !*const llvm.Value { if (inst.value()) |val| { - return self.dg.genTypedValue(inst.src, .{ .ty = inst.ty, .val = val }, self); + return self.dg.genTypedValue(.{ .ty = inst.ty, .val = val }, self); } if (self.func_inst_table.get(inst)) |value| return value; - return self.fail(inst.src, "TODO implement global llvm values (or the value is not in the func_inst_table table)", .{}); + return self.todo("implement global llvm values (or the value is not in the func_inst_table table)", .{}); } fn genBody(self: *FuncGen, body: ir.Body) error{ OutOfMemory, CodegenFail }!void { @@ -673,7 +671,7 @@ pub const FuncGen = struct { // TODO: implement debug info break :blk null; }, - else => |tag| return self.fail(inst.src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}), + else => |tag| return self.todo("implement TZIR instruction: {}", .{tag}), }; if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa(), inst, val); } @@ -689,7 +687,7 @@ pub const FuncGen = struct { unreachable; const zig_fn_type = fn_decl.typed_value.most_recent.typed_value.ty; - const llvm_fn = try self.dg.resolveLLVMFunction(fn_decl, inst.base.src); + const llvm_fn = try self.dg.resolveLLVMFunction(fn_decl); const num_args = inst.args.len; @@ -719,7 +717,7 @@ pub const FuncGen = struct { return call; } else { - return self.fail(inst.base.src, "TODO implement calling runtime known function pointer LLVM backend", .{}); + return self.todo("implement calling runtime known function pointer", .{}); } } @@ -739,7 +737,7 @@ pub const FuncGen = struct { if (!inst.base.ty.isInt()) if (inst.base.ty.tag() != .bool) - return self.fail(inst.base.src, "TODO implement 'genCmp' for type {}", .{inst.base.ty}); + return self.todo("implement 'genCmp' for type {}", .{inst.base.ty}); const is_signed = inst.base.ty.isSignedInt(); const operation = switch (op) { @@ -779,7 +777,7 @@ pub const FuncGen = struct { // If the block does not return a value, we dont have to create a phi node. if (!inst.base.ty.hasCodeGenBits()) return null; - const phi_node = self.builder.buildPhi(try self.dg.getLLVMType(inst.base.ty, inst.base.src), ""); + const phi_node = self.builder.buildPhi(try self.dg.getLLVMType(inst.base.ty), ""); phi_node.addIncoming( break_vals.items.ptr, break_bbs.items.ptr, @@ -897,7 +895,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(inst.rhs); if (!inst.base.ty.isInt()) - return self.fail(inst.base.src, "TODO implement 'genAdd' for type {}", .{inst.base.ty}); + return self.todo("implement 'genAdd' for type {}", .{inst.base.ty}); return if (inst.base.ty.isSignedInt()) self.builder.buildNSWAdd(lhs, rhs, "") @@ -910,7 +908,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(inst.rhs); if (!inst.base.ty.isInt()) - return self.fail(inst.base.src, "TODO implement 'genSub' for type {}", .{inst.base.ty}); + return self.todo("implement 'genSub' for type {}", .{inst.base.ty}); return if (inst.base.ty.isSignedInt()) self.builder.buildNSWSub(lhs, rhs, "") @@ -924,12 +922,12 @@ pub const FuncGen = struct { const signed = inst.base.ty.isSignedInt(); // TODO: Should we use intcast here or just a simple bitcast? // LLVM does truncation vs bitcast (+signed extension) in the intcast depending on the sizes - return self.builder.buildIntCast2(val, try self.dg.getLLVMType(inst.base.ty, inst.base.src), llvm.Bool.fromBool(signed), ""); + return self.builder.buildIntCast2(val, try self.dg.getLLVMType(inst.base.ty), llvm.Bool.fromBool(signed), ""); } fn genBitCast(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value { const val = try self.resolveInst(inst.operand); - const dest_type = try self.dg.getLLVMType(inst.base.ty, inst.base.src); + const dest_type = try self.dg.getLLVMType(inst.base.ty); return self.builder.buildBitCast(val, dest_type, ""); } @@ -938,7 +936,7 @@ pub const FuncGen = struct { const arg_val = self.args[self.arg_index]; self.arg_index += 1; - const ptr_val = self.buildAlloca(try self.dg.getLLVMType(inst.base.ty, inst.base.src)); + const ptr_val = self.buildAlloca(try self.dg.getLLVMType(inst.base.ty)); _ = self.builder.buildStore(arg_val, ptr_val); return self.builder.buildLoad(ptr_val, ""); } @@ -950,7 +948,7 @@ pub const FuncGen = struct { // TODO: figure out a way to get the name of the var decl. // TODO: set alignment and volatile - return self.buildAlloca(try self.dg.getLLVMType(pointee_type, inst.base.src)); + return self.buildAlloca(try self.dg.getLLVMType(pointee_type)); } /// Use this instead of builder.buildAlloca, because this function makes sure to -- cgit v1.2.3 From 982df37135a5ce2e5e93067d716f096ec614f3ef Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Tue, 23 Mar 2021 16:06:15 +0100 Subject: stage2: handle void value in genRet in LLVM backend --- src/codegen/llvm.zig | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index e78bd8d103..cd601debda 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -727,6 +727,11 @@ pub const FuncGen = struct { } fn genRet(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value { + if (!inst.operand.ty.hasCodeGenBits()) { + // TODO: in astgen these instructions should turn into `retvoid` instructions. + _ = self.builder.buildRetVoid(); + return null; + } _ = self.builder.buildRet(try self.resolveInst(inst.operand)); return null; } -- cgit v1.2.3 From 0005b346375f1fbe7bc42c22d658e3218bbd599d Mon Sep 17 00:00:00 2001 From: jacob gw Date: Fri, 26 Mar 2021 17:54:41 -0400 Subject: stage2: implement sema for @errorToInt and @intToError --- src/AstGen.zig | 14 ++++++++++-- src/Compilation.zig | 3 +++ src/Module.zig | 28 +++++++++++++++++++++--- src/Sema.zig | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/codegen.zig | 10 +++++++++ src/codegen/c.zig | 10 +++++++++ src/ir.zig | 10 +++++++++ src/link/C.zig | 3 +-- src/zir.zig | 8 +++++++ test/stage2/cbe.zig | 36 +++++++++++++++++++++++++++++++ 10 files changed, 177 insertions(+), 7 deletions(-) (limited to 'src/codegen') diff --git a/src/AstGen.zig b/src/AstGen.zig index 82f606e7dc..b01834ab79 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1237,6 +1237,8 @@ fn blockExprStmts( .bit_not, .error_set, .error_value, + .error_to_int, + .int_to_error, .slice_start, .slice_end, .slice_sentinel, @@ -3370,6 +3372,16 @@ fn builtinCall( const result = try gz.addUnNode(.import, target, node); return rvalue(gz, scope, rl, result, node); }, + .error_to_int => { + const target = try expr(gz, scope, .none, params[0]); + const result = try gz.addUnNode(.error_to_int, target, node); + return rvalue(gz, scope, rl, result, node); + }, + .int_to_error => { + const target = try expr(gz, scope, .{ .ty = .u16_type }, params[0]); + const result = try gz.addUnNode(.int_to_error, target, node); + return rvalue(gz, scope, rl, result, node); + }, .compile_error => { const target = try expr(gz, scope, .none, params[0]); const result = try gz.addUnNode(.compile_error, target, node); @@ -3439,7 +3451,6 @@ fn builtinCall( .enum_to_int, .error_name, .error_return_trace, - .error_to_int, .err_set_cast, .@"export", .fence, @@ -3448,7 +3459,6 @@ fn builtinCall( .has_decl, .has_field, .int_to_enum, - .int_to_error, .int_to_float, .int_to_ptr, .memcpy, diff --git a/src/Compilation.zig b/src/Compilation.zig index 30fcdefc99..e7dded68cd 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -941,6 +941,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { }; const module = try arena.create(Module); + errdefer module.deinit(); module.* = .{ .gpa = gpa, .comp = comp, @@ -948,7 +949,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .root_scope = root_scope, .zig_cache_artifact_directory = zig_cache_artifact_directory, .emit_h = options.emit_h, + .error_name_list = try std.ArrayListUnmanaged([]const u8).initCapacity(gpa, 1), }; + module.error_name_list.appendAssumeCapacity("(no error)"); break :blk module; } else blk: { if (options.emit_h != null) return error.NoZigModuleForCHeader; diff --git a/src/Module.zig b/src/Module.zig index ec2f685fa7..e3400e3166 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -80,6 +80,9 @@ deletion_set: ArrayListUnmanaged(*Decl) = .{}, /// Error tags and their values, tag names are duped with mod.gpa. global_error_set: std.StringHashMapUnmanaged(u16) = .{}, +/// error u16 -> []const u8 for fast lookups for @intToError at comptime +error_name_list: ArrayListUnmanaged([]const u8) = .{}, + /// Keys are fully qualified paths import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{}, @@ -1570,7 +1573,22 @@ pub const SrcLoc = struct { const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; }, - .node_offset_builtin_call_arg0 => @panic("TODO"), + .node_offset_builtin_call_arg0 => |node_off| { + const decl = src_loc.container.decl; + const tree = decl.container.file_scope.base.tree(); + const node_datas = tree.nodes.items(.data); + const node_tags = tree.nodes.items(.tag); + const node = decl.relativeToNodeIndex(node_off); + const param = switch (node_tags[node]) { + .builtin_call_two, .builtin_call_two_comma => node_datas[node].lhs, + .builtin_call, .builtin_call_comma => tree.extra_data[node_datas[node].lhs], + else => unreachable, + }; + const main_tokens = tree.nodes.items(.main_token); + const tok_index = main_tokens[param]; + const token_starts = tree.tokens.items(.start); + return token_starts[tok_index]; + }, .node_offset_builtin_call_arg1 => @panic("TODO"), .node_offset_builtin_call_argn => unreachable, // Handled specially in `Sema`. .node_offset_array_access_index => @panic("TODO"), @@ -1893,6 +1911,8 @@ pub fn deinit(mod: *Module) void { } mod.global_error_set.deinit(gpa); + mod.error_name_list.deinit(gpa); + for (mod.import_table.items()) |entry| { entry.value.destroy(gpa); } @@ -3346,10 +3366,12 @@ pub fn getErrorValue(mod: *Module, name: []const u8) !std.StringHashMapUnmanaged const gop = try mod.global_error_set.getOrPut(mod.gpa, name); if (gop.found_existing) return gop.entry.*; - errdefer mod.global_error_set.removeAssertDiscard(name); + errdefer mod.global_error_set.removeAssertDiscard(name); + try mod.error_name_list.ensureCapacity(mod.gpa, mod.error_name_list.items.len + 1); gop.entry.key = try mod.gpa.dupe(u8, name); - gop.entry.value = @intCast(u16, mod.global_error_set.count() - 1); + gop.entry.value = @intCast(u16, mod.error_name_list.items.len); + mod.error_name_list.appendAssumeCapacity(gop.entry.key); return gop.entry.*; } diff --git a/src/Sema.zig b/src/Sema.zig index 0c3215d2a2..cb788afda1 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -177,6 +177,8 @@ pub fn analyzeBody( .error_set => try sema.zirErrorSet(block, inst), .error_union_type => try sema.zirErrorUnionType(block, inst), .error_value => try sema.zirErrorValue(block, inst), + .error_to_int => try sema.zirErrorToInt(block, inst), + .int_to_error => try sema.zirIntToError(block, inst), .field_ptr => try sema.zirFieldPtr(block, inst), .field_ptr_named => try sema.zirFieldPtrNamed(block, inst), .field_val => try sema.zirFieldVal(block, inst), @@ -1460,6 +1462,65 @@ fn zirErrorValue(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerEr }); } +fn zirErrorToInt(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { + const tracy = trace(@src()); + defer tracy.end(); + + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const op = try sema.resolveInst(inst_data.operand); + const op_coerced = try sema.coerce(block, Type.initTag(.anyerror), op, operand_src); + + if (op_coerced.value()) |val| { + const payload = try sema.arena.create(Value.Payload.U64); + payload.* = .{ + .base = .{ .tag = .int_u64 }, + .data = (try sema.mod.getErrorValue(val.castTag(.@"error").?.data.name)).value, + }; + return sema.mod.constInst(sema.arena, src, .{ + .ty = Type.initTag(.u16), + .val = Value.initPayload(&payload.base), + }); + } + + try sema.requireRuntimeBlock(block, src); + return block.addUnOp(src, Type.initTag(.u16), .error_to_int, op_coerced); +} + +fn zirIntToError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { + const tracy = trace(@src()); + defer tracy.end(); + + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + + const op = try sema.resolveInst(inst_data.operand); + + if (try sema.resolveDefinedValue(block, operand_src, op)) |value| { + const int = value.toUnsignedInt(); + if (int > sema.mod.global_error_set.count() or int == 0) + return sema.mod.fail(&block.base, operand_src, "integer value {d} represents no error", .{int}); + const payload = try sema.arena.create(Value.Payload.Error); + payload.* = .{ + .base = .{ .tag = .@"error" }, + .data = .{ .name = sema.mod.error_name_list.items[int] }, + }; + return sema.mod.constInst(sema.arena, src, .{ + .ty = Type.initTag(.anyerror), + .val = Value.initPayload(&payload.base), + }); + } + try sema.requireRuntimeBlock(block, src); + if (block.wantSafety()) { + return sema.mod.fail(&block.base, src, "TODO: get max errors in compilation", .{}); + // const is_gt_max = @panic("TODO get max errors in compilation"); + // try sema.addSafetyCheck(block, is_gt_max, .invalid_error_code); + } + return block.addUnOp(src, Type.initTag(.anyerror), .int_to_error, op); +} + fn zirMergeErrorSets(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); @@ -3242,6 +3303,7 @@ pub const PanicId = enum { unreach, unwrap_null, unwrap_errunion, + invalid_error_code, }; fn addSafetyCheck(sema: *Sema, parent_block: *Scope.Block, ok: *Inst, panic_id: PanicId) !void { diff --git a/src/codegen.zig b/src/codegen.zig index 8c18c6777b..4142b562b3 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -898,6 +898,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .is_null_ptr => return self.genIsNullPtr(inst.castTag(.is_null_ptr).?), .is_err => return self.genIsErr(inst.castTag(.is_err).?), .is_err_ptr => return self.genIsErrPtr(inst.castTag(.is_err_ptr).?), + .error_to_int => return self.genErrorToInt(inst.castTag(.error_to_int).?), + .int_to_error => return self.genIntToError(inst.castTag(.int_to_error).?), .load => return self.genLoad(inst.castTag(.load).?), .loop => return self.genLoop(inst.castTag(.loop).?), .not => return self.genNot(inst.castTag(.not).?), @@ -2557,6 +2559,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.fail(inst.base.src, "TODO load the operand and call genIsErr", .{}); } + fn genErrorToInt(self: *Self, inst: *ir.Inst.UnOp) !MCValue { + return self.resolveInst(inst.operand); + } + + fn genIntToError(self: *Self, inst: *ir.Inst.UnOp) !MCValue { + return self.resolveInst(inst.operand); + } + fn genLoop(self: *Self, inst: *ir.Inst.Loop) !MCValue { // A loop is a setup to be able to jump back to the beginning. const start_index = self.code.items.len; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 02d44f53c3..6e68a43607 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -569,6 +569,8 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload_ptr).?), .is_err => try genIsErr(o, inst.castTag(.is_err).?), .is_err_ptr => try genIsErr(o, inst.castTag(.is_err_ptr).?), + .error_to_int => try genErrorToInt(o, inst.castTag(.error_to_int).?), + .int_to_error => try genIntToError(o, inst.castTag(.int_to_error).?), .unwrap_errunion_payload => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload).?), .unwrap_errunion_err => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err).?), .unwrap_errunion_payload_ptr => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload_ptr).?), @@ -1072,6 +1074,14 @@ fn genIsErr(o: *Object, inst: *Inst.UnOp) !CValue { return local; } +fn genIntToError(o: *Object, inst: *Inst.UnOp) !CValue { + return o.resolveInst(inst.operand); +} + +fn genErrorToInt(o: *Object, inst: *Inst.UnOp) !CValue { + return o.resolveInst(inst.operand); +} + fn IndentWriter(comptime UnderlyingWriter: type) type { return struct { const Self = @This(); diff --git a/src/ir.zig b/src/ir.zig index 496ea83bc3..630da1eefc 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -92,6 +92,10 @@ pub const Inst = struct { is_err, /// *E!T => bool is_err_ptr, + /// E => u16 + error_to_int, + /// u16 => E + int_to_error, bool_and, bool_or, /// Read a value from a pointer. @@ -152,6 +156,8 @@ pub const Inst = struct { .is_null_ptr, .is_err, .is_err_ptr, + .int_to_error, + .error_to_int, .ptrtoint, .floatcast, .intcast, @@ -696,6 +702,8 @@ const DumpTzir = struct { .is_null_ptr, .is_err, .is_err_ptr, + .error_to_int, + .int_to_error, .ptrtoint, .floatcast, .intcast, @@ -817,6 +825,8 @@ const DumpTzir = struct { .is_null_ptr, .is_err, .is_err_ptr, + .error_to_int, + .int_to_error, .ptrtoint, .floatcast, .intcast, diff --git a/src/link/C.zig b/src/link/C.zig index 440f52c49f..eed2d0b213 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -185,8 +185,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { if (module.global_error_set.size == 0) break :render_errors; var it = module.global_error_set.iterator(); while (it.next()) |entry| { - // + 1 because 0 represents no error - try err_typedef_writer.print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value + 1 }); + try err_typedef_writer.print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value }); } try err_typedef_writer.writeByte('\n'); } diff --git a/src/zir.zig b/src/zir.zig index b0a52d6beb..f09e928d0f 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -365,6 +365,10 @@ pub const Inst = struct { /// Make an integer type out of signedness and bit count. /// Payload is `int_type` int_type, + /// Convert an error type to `u16` + error_to_int, + /// Convert a `u16` to `anyerror` + int_to_error, /// Return a boolean false if an optional is null. `x != null` /// Uses the `un_node` field. is_non_null, @@ -728,6 +732,8 @@ pub const Inst = struct { .err_union_payload_unsafe_ptr, .err_union_code, .err_union_code_ptr, + .error_to_int, + .int_to_error, .ptr_type, .ptr_type_simple, .ensure_err_payload_void, @@ -1414,6 +1420,8 @@ const Writer = struct { .err_union_payload_unsafe_ptr, .err_union_code, .err_union_code_ptr, + .int_to_error, + .error_to_int, .is_non_null, .is_null, .is_non_null_ptr, diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 62f6aaf09f..3a97989582 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -54,6 +54,42 @@ pub fn addCases(ctx: *TestContext) !void { , "Hello, world!" ++ std.cstr.line_sep); } + { + var case = ctx.exeFromCompiledC("@intToError", .{}); + + case.addCompareOutput( + \\pub export fn main() c_int { + \\ // comptime checks + \\ const a = error.A; + \\ const b = error.B; + \\ const c = @intToError(2); + \\ const d = @intToError(1); + \\ if (!(c == b)) unreachable; + \\ if (!(a == d)) unreachable; + \\ // runtime checks + \\ var x = error.A; + \\ var y = error.B; + \\ var z = @intToError(2); + \\ var f = @intToError(1); + \\ if (!(y == z)) unreachable; + \\ if (!(x == f)) unreachable; + \\ return 0; + \\} + , ""); + case.addError( + \\pub export fn main() c_int { + \\ const c = @intToError(0); + \\ return 0; + \\} + , &.{":2:27: error: integer value 0 represents no error"}); + case.addError( + \\pub export fn main() c_int { + \\ const c = @intToError(3); + \\ return 0; + \\} + , &.{":2:27: error: integer value 3 represents no error"}); + } + { var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64); -- cgit v1.2.3