From 4931b8dc93ee4a99a415dffab03d400e95d1a90a Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 8 Jan 2022 02:02:01 +0100 Subject: [PATCH] stage2: @errorName sema+llvm --- src/Air.zig | 6 ++- src/Liveness.zig | 1 + src/Sema.zig | 13 ++++- src/arch/aarch64/CodeGen.zig | 11 ++++ src/arch/arm/CodeGen.zig | 11 ++++ src/arch/riscv64/CodeGen.zig | 11 ++++ src/arch/x86_64/CodeGen.zig | 11 ++++ src/codegen/c.zig | 17 +++++++ src/codegen/llvm.zig | 92 ++++++++++++++++++++++++++++++++++ src/print_air.zig | 1 + test/behavior.zig | 1 + test/behavior/error_llvm.zig | 24 +++++++++ test/behavior/error_stage1.zig | 21 -------- 13 files changed, 197 insertions(+), 23 deletions(-) create mode 100644 test/behavior/error_llvm.zig diff --git a/src/Air.zig b/src/Air.zig index 08e11716cc9caed28d1089bc06d06849a15a0fe8..2e910f9c9a28f997e8a9c4a4aca43e32aa0a6b34 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 f3c194123d510d981db6cbc284c5909628c578cc..39de37d4b8f58c3060eb0181fafbac37e2f2267d 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 9ecac34c7778cdd58d9acdba94b6a8e14390f41c..bcda9e73ea074272e793d4cc3686d33338258f86 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 1a7105da3151a0c926f2f635544023d73e06d767..ecfb6de7f0ce52c524f95df187ba99de94cb1d05 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 b24ec3fa9b8453ea9eac816d7ae03686b43fd213..cbd75cc192527a8e99fe86b310f1853438036d47 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 ddb87c465143adcb0960f4743a6524c440b56b33..c878f672a30cd82058a0d96edc0708568180a07e 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 28c7cdc5ce32f3d37f001d9824c061c30822f5cc..76f48e5b9963e016e9a07e07dcda3bd90dd487b5 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 922e1d9c3ebaf96b68d1717af410f7bf69349a26..e85ca6c7054443e7c70007f6f25afda3d82c29f3 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 98b906741d081f5a92de8f4ed25d37d7eea3c08b..4a3ac80b705e738f12843a107c00d53c4d56799a 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 cc4acfa27967dea22057ee88fce6bbf9a1e78d85..3101c109cfc0cf9794028835fcf3f5e1d91a4dc8 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 0d4cdcfb8768d08111876df3d4bc8a2b0bf6edda..74e76b70a1b20e2df39305f4b3e6513fb3dad3dd 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 0000000000000000000000000000000000000000..edebd5f629bfe1f45af5b7c3e7fd7383bf9b4f03 --- /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 5980b231568894143ae33e0b0f177f5978dc4d30..2e4a6facf0adea5ab381572288c372842b8b9e89 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(); -- 2.54.0