stage2: @errorName sema+llvm

This commit is contained in:
Robin Voetter 2022-01-08 02:02:01 +01:00 committed by Andrew Kelley
parent 3f586781b6
commit 4931b8dc93
13 changed files with 197 additions and 23 deletions

View File

@ -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);

View File

@ -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 });

View File

@ -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 {

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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",

View File

@ -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) {

View File

@ -156,6 +156,7 @@ const Writer = struct {
.ret,
.ret_load,
.tag_name,
.error_name,
=> try w.writeUnOp(s, inst),
.breakpoint,

View File

@ -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");

View File

@ -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);
}

View File

@ -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();