Commit 0005b34637
Changed files (10)
test
stage2
src/codegen/c.zig
@@ -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();
src/link/C.zig
@@ -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');
}
src/AstGen.zig
@@ -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,
src/codegen.zig
@@ -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;
src/Compilation.zig
@@ -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;
src/ir.zig
@@ -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,
src/Module.zig
@@ -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.*;
}
src/Sema.zig
@@ -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 {
src/zir.zig
@@ -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,
test/stage2/cbe.zig
@@ -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);