Commit 4931b8dc93

Robin Voetter <robin@voetter.nl>
2022-01-08 02:02:01
stage2: @errorName sema+llvm
1 parent 3f58678
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);
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);
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);
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);
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",
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) {
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);
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 });
src/print_air.zig
@@ -156,6 +156,7 @@ const Writer = struct {
             .ret,
             .ret_load,
             .tag_name,
+            .error_name,
             => try w.writeUnOp(s, inst),
 
             .breakpoint,
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 {
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);
+}
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();
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");