diff --git a/src/Air.zig b/src/Air.zig index 08e11716cc..2e910f9c9a 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -501,6 +501,10 @@ pub const Inst = struct { /// Uses the `un_op` field. tag_name, + /// Given an error value, return the error name. Result type is always `[:0] const u8`. + /// Uses the `un_op` field. + error_name, + pub fn fromCmpOp(op: std.math.CompareOperator) Tag { return switch (op) { .lt => .cmp_lt, @@ -816,7 +820,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .bool_to_int => return Type.initTag(.u1), - .tag_name => return Type.initTag(.const_slice_u8_sentinel_0), + .tag_name, .error_name => return Type.initTag(.const_slice_u8_sentinel_0), .call => { const callee_ty = air.typeOf(datas[inst].pl_op.operand); diff --git a/src/Liveness.zig b/src/Liveness.zig index f3c194123d..39de37d4b8 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -334,6 +334,7 @@ fn analyzeInst( .ret, .ret_load, .tag_name, + .error_name, => { const operand = inst_datas[inst].un_op; return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index 9ecac34c77..bcda9e73ea 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10478,7 +10478,18 @@ fn zirBoolToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A fn zirErrorName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirErrorName", .{}); + _ = src; + const operand = sema.resolveInst(inst_data.operand); + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + + if (try sema.resolveDefinedValue(block, operand_src, operand)) |val| { + const bytes = val.castTag(.@"error").?.data.name; + return sema.addStrLit(block, bytes); + } + + // Similar to zirTagName, we have special AIR instruction for the error name in case an optimimzation pass + // might be able to resolve the result at compile time. + return block.addUnOp(.error_name, operand); } fn zirUnaryMath(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 1a7105da31..ecfb6de7f0 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -592,6 +592,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .ctz => try self.airCtz(inst), .popcount => try self.airPopcount(inst), .tag_name => try self.airTagName(inst), + .error_name => try self.airErrorName(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -2557,6 +2558,16 @@ fn airTagName(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } +fn airErrorName(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else { + _ = operand; + return self.fail("TODO implement airErrorName for aarch64", .{}); + }; + return self.finishAir(inst, result, .{ un_op, .none, .none }); +} + fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { // First section of indexes correspond to a set number of constant values. const ref_int = @enumToInt(inst); diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index b24ec3fa9b..cbd75cc192 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -590,6 +590,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .ctz => try self.airCtz(inst), .popcount => try self.airPopcount(inst), .tag_name => try self.airTagName(inst), + .error_name => try self.airErrorName(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -3682,6 +3683,16 @@ fn airTagName(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } +fn airErrorName(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else { + _ = operand; + return self.fail("TODO implement airErrorName for arm", .{}); + }; + return self.finishAir(inst, result, .{ un_op, .none, .none }); +} + fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { // First section of indexes correspond to a set number of constant values. const ref_int = @enumToInt(inst); diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index ddb87c4651..c878f672a3 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -571,6 +571,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .ctz => try self.airCtz(inst), .popcount => try self.airPopcount(inst), .tag_name => try self.airTagName(inst), + .error_name => try self.airErrorName(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -2056,6 +2057,16 @@ fn airTagName(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } +fn airErrorName(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else { + _ = operand; + return self.fail("TODO implement airErrorName for riscv64", .{}); + }; + return self.finishAir(inst, result, .{ un_op, .none, .none }); +} + fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { // First section of indexes correspond to a set number of constant values. const ref_int = @enumToInt(inst); diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 28c7cdc5ce..76f48e5b99 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -635,6 +635,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .ctz => try self.airCtz(inst), .popcount => try self.airPopcount(inst), .tag_name => try self.airTagName(inst), + .error_name, => try self.airErrorName(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -3649,6 +3650,16 @@ fn airTagName(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } +fn airErrorName(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else { + _ = operand; + return self.fail("TODO implement airErrorName for x86_64", .{}); + }; + return self.finishAir(inst, result, .{ un_op, .none, .none }); +} + fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { // First section of indexes correspond to a set number of constant values. const ref_int = @enumToInt(inst); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 922e1d9c3e..e85ca6c705 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1244,6 +1244,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .ctz => try airBuiltinCall(f, inst, "ctz"), .popcount => try airBuiltinCall(f, inst, "popcount"), .tag_name => try airTagName(f, inst), + .error_name => try airErrorName(f, inst), .int_to_float, .float_to_int, @@ -2998,6 +2999,22 @@ fn airTagName(f: *Function, inst: Air.Inst.Index) !CValue { //return local; } +fn airErrorName(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; + + const un_op = f.air.instructions.items(.data)[inst].un_op; + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + const operand = try f.resolveInst(un_op); + const local = try f.allocLocal(inst_ty, .Const); + + try writer.writeAll(" = "); + + _ = operand; + _ = local; + return f.fail("TODO: C backend: implement airErrorName", .{}); +} + fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { return switch (order) { .Unordered => "memory_order_relaxed", diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 98b906741d..4a3ac80b70 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -181,6 +181,9 @@ pub const Object = struct { /// The backing memory for `type_map`. Periodically garbage collected after flush(). /// The code for doing the periodical GC is not yet implemented. type_map_arena: std.heap.ArenaAllocator, + /// The LLVM global table which holds the names corresponding to Zig errors. Note that the values + /// are not added until flushModule, when all errors in the compilation are known. + error_name_table: ?*const llvm.Value, pub const TypeMap = std.HashMapUnmanaged( Type, @@ -269,6 +272,7 @@ pub const Object = struct { .decl_map = .{}, .type_map = .{}, .type_map_arena = std.heap.ArenaAllocator.init(gpa), + .error_name_table = null, }; } @@ -298,7 +302,60 @@ pub const Object = struct { return slice.ptr; } + fn genErrorNameTable(self: *Object, comp: *Compilation) !void { + // If self.error_name_table is null, there was no instruction that actually referenced the error table. + const error_name_table_ptr_global = self.error_name_table orelse return; + + const mod = comp.bin_file.options.module.?; + const target = mod.getTarget(); + + const llvm_ptr_ty = self.context.intType(8).pointerType(0); // TODO: Address space + const llvm_usize_ty = self.context.intType(target.cpu.arch.ptrBitWidth()); + const type_fields = [_]*const llvm.Type{ + llvm_ptr_ty, + llvm_usize_ty, + }; + const llvm_slice_ty = self.context.structType(&type_fields, type_fields.len, .False); + const slice_ty = Type.initTag(.const_slice_u8_sentinel_0); + const slice_alignment = slice_ty.abiAlignment(target); + + const error_name_list = mod.error_name_list.items; + const llvm_errors = try comp.gpa.alloc(*const llvm.Value, error_name_list.len); + defer comp.gpa.free(llvm_errors); + + llvm_errors[0] = llvm_slice_ty.getUndef(); + for (llvm_errors[1..]) |*llvm_error, i| { + const name = error_name_list[1..][i]; + const str_init = self.context.constString(name.ptr, @intCast(c_uint, name.len), .False); + const str_global = self.llvm_module.addGlobal(str_init.typeOf(), ""); + str_global.setInitializer(str_init); + str_global.setLinkage(.Private); + str_global.setGlobalConstant(.True); + str_global.setUnnamedAddr(.True); + str_global.setAlignment(1); + + const slice_fields = [_]*const llvm.Value{ + str_global.constBitCast(llvm_ptr_ty), + llvm_usize_ty.constInt(name.len, .False), + }; + llvm_error.* = llvm_slice_ty.constNamedStruct(&slice_fields, slice_fields.len); + } + + const error_name_table_init = llvm_slice_ty.constArray(llvm_errors.ptr, @intCast(c_uint, error_name_list.len)); + + const error_name_table_global = self.llvm_module.addGlobal(error_name_table_init.typeOf(), ""); + error_name_table_global.setInitializer(error_name_table_init); + error_name_table_global.setLinkage(.Private); + error_name_table_global.setGlobalConstant(.True); + error_name_table_global.setUnnamedAddr(.True); + error_name_table_global.setAlignment(slice_alignment); // TODO: Dont hardcode + + const error_name_table_ptr = error_name_table_global.constBitCast(llvm_slice_ty.pointerType(0)); // TODO: Address space + error_name_table_ptr_global.setInitializer(error_name_table_ptr); + } + pub fn flushModule(self: *Object, comp: *Compilation) !void { + try self.genErrorNameTable(comp); if (comp.verbose_llvm_ir) { self.llvm_module.dump(); } @@ -2031,6 +2088,7 @@ pub const FuncGen = struct { .ctz => try self.airClzCtz(inst, "cttz"), .popcount => try self.airPopCount(inst, "ctpop"), .tag_name => try self.airTagName(inst), + .error_name => try self.airErrorName(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -4279,6 +4337,40 @@ pub const FuncGen = struct { return fn_val; } + fn airErrorName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + + const error_name_table_ptr = try self.getErrorNameTable(); + const error_name_table = self.builder.buildLoad(error_name_table_ptr, ""); + const indices = [_]*const llvm.Value{operand}; + const error_name_ptr = self.builder.buildInBoundsGEP(error_name_table, &indices, indices.len, ""); + return self.builder.buildLoad(error_name_ptr, ""); + } + + fn getErrorNameTable(self: *FuncGen) !*const llvm.Value { + if (self.dg.object.error_name_table) |table| { + return table; + } + + const slice_ty = Type.initTag(.const_slice_u8_sentinel_0); + const slice_alignment = slice_ty.abiAlignment(self.dg.module.getTarget()); + const llvm_slice_ty = try self.dg.llvmType(slice_ty); + const llvm_slice_ptr_ty = llvm_slice_ty.pointerType(0); // TODO: Address space + + const error_name_table_global = self.dg.object.llvm_module.addGlobal(llvm_slice_ptr_ty, "__zig_err_name_table"); + error_name_table_global.setInitializer(llvm_slice_ptr_ty.getUndef()); + error_name_table_global.setLinkage(.Private); + error_name_table_global.setGlobalConstant(.True); + error_name_table_global.setUnnamedAddr(.True); + error_name_table_global.setAlignment(slice_alignment); + + self.dg.object.error_name_table = error_name_table_global; + return error_name_table_global; + } + /// Assumes the optional is not pointer-like and payload has bits. fn optIsNonNull(self: *FuncGen, opt_handle: *const llvm.Value, is_by_ref: bool) *const llvm.Value { if (is_by_ref) { diff --git a/src/print_air.zig b/src/print_air.zig index cc4acfa279..3101c109cf 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -156,6 +156,7 @@ const Writer = struct { .ret, .ret_load, .tag_name, + .error_name, => try w.writeUnOp(s, inst), .breakpoint, diff --git a/test/behavior.zig b/test/behavior.zig index 0d4cdcfb87..74e76b70a1 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -90,6 +90,7 @@ test { _ = @import("behavior/bugs/9584.zig"); _ = @import("behavior/cast_llvm.zig"); _ = @import("behavior/enum_llvm.zig"); + _ = @import("behavior/error_llvm.zig"); _ = @import("behavior/eval.zig"); _ = @import("behavior/floatop.zig"); _ = @import("behavior/fn.zig"); diff --git a/test/behavior/error_llvm.zig b/test/behavior/error_llvm.zig new file mode 100644 index 0000000000..edebd5f629 --- /dev/null +++ b/test/behavior/error_llvm.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const expect = std.testing.expect; +const mem = std.mem; + +fn gimmeItBroke() anyerror { + return error.ItBroke; +} + +test "@errorName" { + try expect(mem.eql(u8, @errorName(error.AnError), "AnError")); + try expect(mem.eql(u8, @errorName(error.ALongerErrorName), "ALongerErrorName")); + try expect(mem.eql(u8, @errorName(gimmeItBroke()), "ItBroke")); +} + +test "@errorName sentinel length matches slice length" { + const name = testBuiltinErrorName(error.FooBar); + const length: usize = 6; + try expect(length == std.mem.indexOfSentinel(u8, 0, name.ptr)); + try expect(length == name.len); +} + +pub fn testBuiltinErrorName(err: anyerror) [:0]const u8 { + return @errorName(err); +} diff --git a/test/behavior/error_stage1.zig b/test/behavior/error_stage1.zig index 5980b23156..2e4a6facf0 100644 --- a/test/behavior/error_stage1.zig +++ b/test/behavior/error_stage1.zig @@ -4,27 +4,6 @@ const expectError = std.testing.expectError; const expectEqual = std.testing.expectEqual; const mem = std.mem; -fn gimmeItBroke() anyerror { - return error.ItBroke; -} - -test "@errorName" { - try expect(mem.eql(u8, @errorName(error.AnError), "AnError")); - try expect(mem.eql(u8, @errorName(error.ALongerErrorName), "ALongerErrorName")); - try expect(mem.eql(u8, @errorName(gimmeItBroke()), "ItBroke")); -} - -test "@errorName sentinel length matches slice length" { - const name = testBuiltinErrorName(error.FooBar); - const length: usize = 6; - try expectEqual(length, std.mem.indexOfSentinel(u8, 0, name.ptr)); - try expectEqual(length, name.len); -} - -pub fn testBuiltinErrorName(err: anyerror) [:0]const u8 { - return @errorName(err); -} - test "error union type " { try testErrorUnionType(); comptime try testErrorUnionType();