diff --git a/src/AstGen.zig b/src/AstGen.zig index 82f606e7dc..b01834ab79 100644 --- a/src/AstGen.zig +++ b/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, diff --git a/src/Compilation.zig b/src/Compilation.zig index 30fcdefc99..e7dded68cd 100644 --- a/src/Compilation.zig +++ b/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; diff --git a/src/Module.zig b/src/Module.zig index ec2f685fa7..e3400e3166 100644 --- a/src/Module.zig +++ b/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.*; } diff --git a/src/Sema.zig b/src/Sema.zig index 0c3215d2a2..cb788afda1 100644 --- a/src/Sema.zig +++ b/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 { diff --git a/src/codegen.zig b/src/codegen.zig index 8c18c6777b..4142b562b3 100644 --- a/src/codegen.zig +++ b/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; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 02d44f53c3..6e68a43607 100644 --- a/src/codegen/c.zig +++ b/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(); diff --git a/src/ir.zig b/src/ir.zig index 496ea83bc3..630da1eefc 100644 --- a/src/ir.zig +++ b/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, diff --git a/src/link/C.zig b/src/link/C.zig index 440f52c49f..eed2d0b213 100644 --- a/src/link/C.zig +++ b/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'); } diff --git a/src/zir.zig b/src/zir.zig index b0a52d6beb..f09e928d0f 100644 --- a/src/zir.zig +++ b/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, diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 62f6aaf09f..3a97989582 100644 --- a/test/stage2/cbe.zig +++ b/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);