stage2: implement sema for @errorToInt and @intToError

This commit is contained in:
jacob gw 2021-03-26 17:54:41 -04:00 committed by Andrew Kelley
parent f80f8a7a78
commit 0005b34637
10 changed files with 177 additions and 7 deletions

View File

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

View File

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

View File

@ -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.*;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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