Commit 0005b34637

jacob gw <jacoblevgw@gmail.com>
2021-03-26 22:54:41
stage2: implement sema for @errorToInt and @intToError
1 parent f80f8a7
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);