Commit 153c97ac9e

g-w1 <jacoblevgw@gmail.com>
2020-12-31 23:10:49
improve stage2 to allow catch at comptime:
* add error_union value tag. * add analyzeIsErr * add Value.isError * add TZIR wrap_errunion_payload and wrap_errunion_err for wrapping from T -> E!T and E -> E!T * add anlyzeInstUnwrapErrCode and analyzeInstUnwrapErr * add analyzeInstEnsureErrPayloadVoid: * add wrapErrorUnion * add comptime error comparison for tests * tests!
1 parent 7edb204
src/codegen.zig
@@ -909,7 +909,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .unreach => return MCValue{ .unreach = {} },
                 .optional_payload => return self.genOptionalPayload(inst.castTag(.optional_payload).?),
                 .optional_payload_ptr => return self.genOptionalPayloadPtr(inst.castTag(.optional_payload_ptr).?),
+                .unwrap_errunion_err => return self.genUnwrapErrErr(inst.castTag(.unwrap_errunion_err).?),
+                .unwrap_errunion_payload => return self.genUnwrapErrPayload(inst.castTag(.unwrap_errunion_payload).?),
+                .unwrap_errunion_err_ptr => return self.genUnwrapErrErrPtr(inst.castTag(.unwrap_errunion_err_ptr).?),
+                .unwrap_errunion_payload_ptr => return self.genUnwrapErrPayloadPtr(inst.castTag(.unwrap_errunion_payload_ptr).?),
                 .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?),
+                .wrap_errunion_payload => return self.genWrapErrUnionPayload(inst.castTag(.wrap_errunion_payload).?),
+                .wrap_errunion_err => return self.genWrapErrUnionErr(inst.castTag(.wrap_errunion_err).?),
                 .varptr => return self.genVarPtr(inst.castTag(.varptr).?),
                 .xor => return self.genXor(inst.castTag(.xor).?),
             }
@@ -1170,6 +1176,41 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
+        fn genUnwrapErrErr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+            // No side effects, so if it's unreferenced, do nothing.
+            if (inst.base.isUnused())
+                return MCValue.dead;
+            switch (arch) {
+                else => return self.fail(inst.base.src, "TODO implement unwrap error union error for {}", .{self.target.cpu.arch}),
+            }
+        }
+
+        fn genUnwrapErrPayload(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+            // No side effects, so if it's unreferenced, do nothing.
+            if (inst.base.isUnused())
+                return MCValue.dead;
+            switch (arch) {
+                else => return self.fail(inst.base.src, "TODO implement unwrap error union payload for {}", .{self.target.cpu.arch}),
+            }
+        }
+        // *(E!T) -> E
+        fn genUnwrapErrErrPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+            // No side effects, so if it's unreferenced, do nothing.
+            if (inst.base.isUnused())
+                return MCValue.dead;
+            switch (arch) {
+                else => return self.fail(inst.base.src, "TODO implement unwrap error union error ptr for {}", .{self.target.cpu.arch}),
+            }
+        }
+        // *(E!T) -> *T
+        fn genUnwrapErrPayloadPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+            // No side effects, so if it's unreferenced, do nothing.
+            if (inst.base.isUnused())
+                return MCValue.dead;
+            switch (arch) {
+                else => return self.fail(inst.base.src, "TODO implement unwrap error union payload ptr for {}", .{self.target.cpu.arch}),
+            }
+        }
         fn genWrapOptional(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
             const optional_ty = inst.base.ty;
 
@@ -1186,6 +1227,27 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
+        /// T to E!T
+        fn genWrapErrUnionPayload(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+            // No side effects, so if it's unreferenced, do nothing.
+            if (inst.base.isUnused())
+                return MCValue.dead;
+
+            switch (arch) {
+                else => return self.fail(inst.base.src, "TODO implement wrap errunion payload for {}", .{self.target.cpu.arch}),
+            }
+        }
+
+        /// E to E!T
+        fn genWrapErrUnionErr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+            // No side effects, so if it's unreferenced, do nothing.
+            if (inst.base.isUnused())
+                return MCValue.dead;
+
+            switch (arch) {
+                else => return self.fail(inst.base.src, "TODO implement wrap errunion error for {}", .{self.target.cpu.arch}),
+            }
+        }
         fn genVarPtr(self: *Self, inst: *ir.Inst.VarPtr) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
             if (inst.base.isUnused())
src/ir.zig
@@ -114,6 +114,18 @@ pub const Inst = struct {
         // *?T => *T
         optional_payload_ptr,
         wrap_optional,
+        /// E!T -> T
+        unwrap_errunion_payload,
+        /// E!T -> E
+        unwrap_errunion_err,
+        /// *(E!T) -> *T
+        unwrap_errunion_payload_ptr,
+        /// *(E!T) -> E
+        unwrap_errunion_err_ptr,
+        /// wrap from T to E!T
+        wrap_errunion_payload,
+        /// wrap from E to E!T
+        wrap_errunion_err,
         xor,
         switchbr,
 
@@ -143,6 +155,12 @@ pub const Inst = struct {
                 .optional_payload,
                 .optional_payload_ptr,
                 .wrap_optional,
+                .unwrap_errunion_payload,
+                .unwrap_errunion_err,
+                .unwrap_errunion_payload_ptr,
+                .unwrap_errunion_err_ptr,
+                .wrap_errunion_payload,
+                .wrap_errunion_err,
                 => UnOp,
 
                 .add,
src/Module.zig
@@ -2871,7 +2871,15 @@ pub fn analyzeIsNull(
 }
 
 pub fn analyzeIsErr(self: *Module, scope: *Scope, src: usize, operand: *Inst) InnerError!*Inst {
-    return self.fail(scope, src, "TODO implement analysis of iserr", .{});
+    const ot = operand.ty.zigTypeTag();
+    if (ot != .ErrorSet and ot != .ErrorUnion) return self.constBool(scope, src, false);
+    if (ot == .ErrorSet) return self.constBool(scope, src, true);
+    assert(ot == .ErrorUnion);
+    if (operand.value()) |err_union| {
+        return self.constBool(scope, src, err_union.getError() != null);
+    }
+    const b = try self.requireRuntimeBlock(scope, src);
+    return self.addUnOp(b, src, Type.initTag(.bool), .is_err, operand);
 }
 
 pub fn analyzeSlice(self: *Module, scope: *Scope, src: usize, array_ptr: *Inst, start: *Inst, end_opt: ?*Inst, sentinel_opt: ?*Inst) InnerError!*Inst {
@@ -3174,6 +3182,52 @@ fn wrapOptional(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*In
     return self.addUnOp(b, inst.src, dest_type, .wrap_optional, inst);
 }
 
+fn wrapErrorUnion(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
+    // TODO deal with inferred error sets
+    const err_union = dest_type.castTag(.error_union).?;
+    if (inst.value()) |val| {
+        const to_wrap = if (inst.ty.zigTypeTag() != .ErrorSet) blk: {
+            _ = try self.coerce(scope, err_union.data.payload, inst);
+            break :blk val;
+        } else switch (err_union.data.error_set.tag()) {
+            .anyerror => val,
+            .error_set_single => blk: {
+                const n = err_union.data.error_set.castTag(.error_set_single).?.data;
+                if (!mem.eql(u8, val.castTag(.@"error").?.data.name, n))
+                    return self.fail(scope, inst.src, "expected type '{}', found type '{}'", .{ err_union.data.error_set, inst.ty });
+                break :blk val;
+            },
+            .error_set => blk: {
+                const f = err_union.data.error_set.castTag(.error_set).?.data.typed_value.most_recent.typed_value.val.castTag(.error_set).?.data.fields;
+                if (f.get(val.castTag(.@"error").?.data.name) == null)
+                    return self.fail(scope, inst.src, "expected type '{}', found type '{}'", .{ err_union.data.error_set, inst.ty });
+                break :blk val;
+            },
+            else => unreachable,
+        };
+
+        return self.constInst(scope, inst.src, .{
+            .ty = dest_type,
+            // creating a SubValue for the error_union payload
+            .val = try Value.Tag.error_union.create(
+                scope.arena(),
+                to_wrap,
+            ),
+        });
+    }
+
+    const b = try self.requireRuntimeBlock(scope, inst.src);
+
+    // we are coercing from E to E!T
+    if (inst.ty.zigTypeTag() == .ErrorSet) {
+        var coerced = try self.coerce(scope, err_union.data.error_set, inst);
+        return self.addUnOp(b, inst.src, dest_type, .wrap_errunion_err, coerced);
+    } else {
+        var coerced = try self.coerce(scope, err_union.data.payload, inst);
+        return self.addUnOp(b, inst.src, dest_type, .wrap_errunion_payload, coerced);
+    }
+}
+
 fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type {
     const int_payload = try scope.arena().create(Type.Payload.Bits);
     int_payload.* = .{
@@ -3240,7 +3294,7 @@ pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Ty
     return chosen.ty;
 }
 
-pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
+pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) InnerError!*Inst {
     // If the types are the same, we can return the operand.
     if (dest_type.eql(inst.ty))
         return inst;
@@ -3274,6 +3328,11 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst
         }
     }
 
+    // T to E!T or E to E!T
+    if (dest_type.tag() == .error_union) {
+        return try self.wrapErrorUnion(scope, dest_type, inst);
+    }
+
     // Coercions where the source is a single pointer to an array.
     src_array_ptr: {
         if (!inst.ty.isSinglePointer()) break :src_array_ptr;
@@ -3352,7 +3411,7 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst
     return self.fail(scope, inst.src, "expected {}, found {}", .{ dest_type, inst.ty });
 }
 
-pub fn coerceNum(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !?*Inst {
+pub fn coerceNum(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) InnerError!?*Inst {
     const val = inst.value() orelse return null;
     const src_zig_tag = inst.ty.zigTypeTag();
     const dst_zig_tag = dest_type.zigTypeTag();
@@ -3843,6 +3902,7 @@ pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void {
 pub const PanicId = enum {
     unreach,
     unwrap_null,
+    unwrap_errunion,
 };
 
 pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic_id: PanicId) !void {
src/value.zig
@@ -102,6 +102,7 @@ pub const Value = extern union {
         enum_literal,
         error_set,
         @"error",
+        error_union,
         /// This is a special value that tracks a set of types that have been stored
         /// to an inferred allocation. It does not support any of the normal value queries.
         inferred_alloc,
@@ -174,6 +175,7 @@ pub const Value = extern union {
 
                 .ref_val,
                 .repeated,
+                .error_union,
                 => Payload.SubValue,
 
                 .bytes,
@@ -388,9 +390,17 @@ pub const Value = extern union {
                 return Value{ .ptr_otherwise = &new_payload.base };
             },
             .@"error" => return self.copyPayloadShallow(allocator, Payload.Error),
+            .error_union => {
+                const payload = self.castTag(.error_union).?;
+                const new_payload = try allocator.create(Payload.SubValue);
+                new_payload.* = .{
+                    .base = payload.base,
+                    .data = try payload.data.copy(allocator),
+                };
+                return Value{ .ptr_otherwise = &new_payload.base };
+            },
 
             .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet),
-
             .inferred_alloc => unreachable,
         }
     }
@@ -510,6 +520,8 @@ pub const Value = extern union {
                 return out_stream.writeAll("}");
             },
             .@"error" => return out_stream.print("error.{s}", .{val.castTag(.@"error").?.data.name}),
+            // TODO to print this it should be error{ Set, Items }!T(val), but we need the type for that
+            .error_union => return out_stream.print("error_union_val({})", .{val.castTag(.error_union).?.data}),
             .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"),
         };
     }
@@ -622,6 +634,7 @@ pub const Value = extern union {
             .float_128,
             .enum_literal,
             .@"error",
+            .error_union,
             .empty_struct_value,
             .inferred_alloc,
             => unreachable,
@@ -692,6 +705,7 @@ pub const Value = extern union {
             .empty_array,
             .enum_literal,
             .error_set,
+            .error_union,
             .@"error",
             .empty_struct_value,
             .inferred_alloc,
@@ -779,6 +793,7 @@ pub const Value = extern union {
             .enum_literal,
             .error_set,
             .@"error",
+            .error_union,
             .empty_struct_value,
             .inferred_alloc,
             => unreachable,
@@ -865,6 +880,7 @@ pub const Value = extern union {
             .enum_literal,
             .error_set,
             .@"error",
+            .error_union,
             .empty_struct_value,
             .inferred_alloc,
             => unreachable,
@@ -979,6 +995,7 @@ pub const Value = extern union {
             .enum_literal,
             .error_set,
             .@"error",
+            .error_union,
             .empty_struct_value,
             .inferred_alloc,
             => unreachable,
@@ -1069,6 +1086,7 @@ pub const Value = extern union {
             .enum_literal,
             .error_set,
             .@"error",
+            .error_union,
             .empty_struct_value,
             .inferred_alloc,
             => unreachable,
@@ -1228,6 +1246,7 @@ pub const Value = extern union {
             .enum_literal,
             .error_set,
             .@"error",
+            .error_union,
             .empty_struct_value,
             .inferred_alloc,
             => unreachable,
@@ -1305,6 +1324,7 @@ pub const Value = extern union {
             .enum_literal,
             .error_set,
             .@"error",
+            .error_union,
             .empty_struct_value,
             .inferred_alloc,
             => unreachable,
@@ -1543,7 +1563,10 @@ pub const Value = extern union {
                 hasher.update(payload.name);
                 std.hash.autoHash(&hasher, payload.value);
             },
-
+            .error_union => {
+                const payload = self.castTag(.error_union).?.data;
+                std.hash.autoHash(&hasher, payload.hash());
+            },
             .inferred_alloc => unreachable,
         }
         return hasher.final();
@@ -1621,6 +1644,7 @@ pub const Value = extern union {
             .enum_literal,
             .error_set,
             .@"error",
+            .error_union,
             .empty_struct_value,
             .inferred_alloc,
             => unreachable,
@@ -1707,6 +1731,7 @@ pub const Value = extern union {
             .enum_literal,
             .error_set,
             .@"error",
+            .error_union,
             .empty_struct_value,
             .inferred_alloc,
             => unreachable,
@@ -1810,6 +1835,7 @@ pub const Value = extern union {
             .enum_literal,
             .error_set,
             .@"error",
+            .error_union,
             .empty_struct_value,
             => false,
 
@@ -1820,6 +1846,93 @@ pub const Value = extern union {
         };
     }
 
+    /// Valid for all types. Asserts the value is not undefined and not unreachable.
+    pub fn getError(self: Value) ?[]const u8 {
+        return switch (self.tag()) {
+            .ty,
+            .int_type,
+            .u8_type,
+            .i8_type,
+            .u16_type,
+            .i16_type,
+            .u32_type,
+            .i32_type,
+            .u64_type,
+            .i64_type,
+            .usize_type,
+            .isize_type,
+            .c_short_type,
+            .c_ushort_type,
+            .c_int_type,
+            .c_uint_type,
+            .c_long_type,
+            .c_ulong_type,
+            .c_longlong_type,
+            .c_ulonglong_type,
+            .c_longdouble_type,
+            .f16_type,
+            .f32_type,
+            .f64_type,
+            .f128_type,
+            .c_void_type,
+            .bool_type,
+            .void_type,
+            .type_type,
+            .anyerror_type,
+            .comptime_int_type,
+            .comptime_float_type,
+            .noreturn_type,
+            .null_type,
+            .undefined_type,
+            .fn_noreturn_no_args_type,
+            .fn_void_no_args_type,
+            .fn_naked_noreturn_no_args_type,
+            .fn_ccc_void_no_args_type,
+            .single_const_pointer_to_comptime_int_type,
+            .const_slice_u8_type,
+            .enum_literal_type,
+            .anyframe_type,
+            .zero,
+            .one,
+            .null_value,
+            .empty_array,
+            .bool_true,
+            .bool_false,
+            .function,
+            .extern_fn,
+            .variable,
+            .int_u64,
+            .int_i64,
+            .int_big_positive,
+            .int_big_negative,
+            .ref_val,
+            .decl_ref,
+            .elem_ptr,
+            .bytes,
+            .repeated,
+            .float_16,
+            .float_32,
+            .float_64,
+            .float_128,
+            .void_value,
+            .enum_literal,
+            .error_set,
+            .empty_struct_value,
+            => null,
+
+            .error_union => {
+                const data = self.castTag(.error_union).?.data;
+                return if (data.tag() == .@"error")
+                    data.castTag(.@"error").?.data.name
+                else
+                    null;
+            },
+            .@"error" => self.castTag(.@"error").?.data.name,
+            .undef => unreachable,
+            .unreachable_value => unreachable,
+            .inferred_alloc => unreachable,
+        };
+    }
     /// Valid for all types. Asserts the value is not undefined.
     pub fn isFloat(self: Value) bool {
         return switch (self.tag()) {
@@ -1908,6 +2021,7 @@ pub const Value = extern union {
             .void_value,
             .enum_literal,
             .@"error",
+            .error_union,
             .empty_struct_value,
             .null_value,
             => false,
src/zir.zig
@@ -1622,6 +1622,12 @@ const DumpTzir = struct {
                 .optional_payload,
                 .optional_payload_ptr,
                 .wrap_optional,
+                .wrap_errunion_payload,
+                .wrap_errunion_err,
+                .unwrap_errunion_payload,
+                .unwrap_errunion_err,
+                .unwrap_errunion_payload_ptr,
+                .unwrap_errunion_err_ptr,
                 => {
                     const un_op = inst.cast(ir.Inst.UnOp).?;
                     try dtz.findConst(un_op.operand);
@@ -1733,6 +1739,12 @@ const DumpTzir = struct {
                 .optional_payload,
                 .optional_payload_ptr,
                 .wrap_optional,
+                .wrap_errunion_err,
+                .wrap_errunion_payload,
+                .unwrap_errunion_err,
+                .unwrap_errunion_payload,
+                .unwrap_errunion_payload_ptr,
+                .unwrap_errunion_err_ptr,
                 => {
                     const un_op = inst.cast(ir.Inst.UnOp).?;
                     const kinky = try dtz.writeInst(writer, un_op.operand);
src/zir_sema.zig
@@ -1263,34 +1263,124 @@ fn zirOptionalPayload(
 fn zirErrUnionPayload(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp, safety_check: bool) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    return mod.fail(scope, unwrap.base.src, "TODO implement zir_sema.zirErrUnionPayload", .{});
+
+    const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
+    if (operand.ty.zigTypeTag() != .ErrorUnion)
+        return mod.fail(scope, operand.src, "expected error union type, found '{}'", .{operand.ty});
+
+    if (operand.value()) |val| {
+        if (val.getError()) |name| {
+            return mod.fail(scope, unwrap.base.src, "caught unexpected error '{s}'", .{name});
+        }
+        const data = val.castTag(.error_union).?.data;
+        return mod.constInst(scope, unwrap.base.src, .{
+            .ty = operand.ty.castTag(.error_union).?.data.payload,
+            .val = data,
+        });
+    }
+    const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
+    if (safety_check and mod.wantSafety(scope)) {
+        const is_non_err = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .is_err, operand);
+        try mod.addSafetyCheck(b, is_non_err, .unwrap_errunion);
+    }
+    return mod.addUnOp(b, unwrap.base.src, operand.ty.castTag(.error_union).?.data.payload, .unwrap_errunion_payload, operand);
 }
 
 /// Pointer in, pointer out
 fn zirErrUnionPayloadPtr(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp, safety_check: bool) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    return mod.fail(scope, unwrap.base.src, "TODO implement zir_sema.zirErrUnionPayloadPtr", .{});
+
+    const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
+    assert(operand.ty.zigTypeTag() == .Pointer);
+
+    if (operand.ty.elemType().zigTypeTag() != .ErrorUnion)
+        return mod.fail(scope, unwrap.base.src, "expected error union type, found {}", .{operand.ty.elemType()});
+
+    const operand_pointer_ty = try mod.simplePtrType(scope, unwrap.base.src, operand.ty.elemType().castTag(.error_union).?.data.payload, !operand.ty.isConstPtr(), .One);
+
+    if (operand.value()) |pointer_val| {
+        const val = try pointer_val.pointerDeref(scope.arena());
+        if (val.getError()) |name| {
+            return mod.fail(scope, unwrap.base.src, "caught unexpected error '{s}'", .{name});
+        }
+        const data = val.castTag(.error_union).?.data;
+        // The same Value represents the pointer to the error union and the payload.
+        return mod.constInst(scope, unwrap.base.src, .{
+            .ty = operand_pointer_ty,
+            .val = try Value.Tag.ref_val.create(
+                scope.arena(),
+                data,
+            ),
+        });
+    }
+
+    const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
+    if (safety_check and mod.wantSafety(scope)) {
+        const is_non_err = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .is_err, operand);
+        try mod.addSafetyCheck(b, is_non_err, .unwrap_errunion);
+    }
+    return mod.addUnOp(b, unwrap.base.src, operand_pointer_ty, .unwrap_errunion_payload_ptr, operand);
 }
 
 /// Value in, value out
 fn zirErrUnionCode(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    return mod.fail(scope, unwrap.base.src, "TODO implement zir_sema.zirErrUnionCode", .{});
+
+    const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
+    if (operand.ty.zigTypeTag() != .ErrorUnion)
+        return mod.fail(scope, unwrap.base.src, "expected error union type, found '{}'", .{operand.ty});
+
+    if (operand.value()) |val| {
+        assert(val.getError() != null);
+        const data = val.castTag(.error_union).?.data;
+        return mod.constInst(scope, unwrap.base.src, .{
+            .ty = operand.ty.castTag(.error_union).?.data.error_set,
+            .val = data,
+        });
+    }
+
+    const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
+    return mod.addUnOp(b, unwrap.base.src, operand.ty.castTag(.error_union).?.data.payload, .unwrap_errunion_err, operand);
 }
 
 /// Pointer in, value out
 fn zirErrUnionCodePtr(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    return mod.fail(scope, unwrap.base.src, "TODO implement zir_sema.zirErrUnionCodePtr", .{});
+
+    const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
+    assert(operand.ty.zigTypeTag() == .Pointer);
+
+    if (operand.ty.elemType().zigTypeTag() != .ErrorUnion)
+        return mod.fail(scope, unwrap.base.src, "expected error union type, found {}", .{operand.ty.elemType()});
+
+    if (operand.value()) |pointer_val| {
+        const val = try pointer_val.pointerDeref(scope.arena());
+        assert(val.getError() != null);
+        const data = val.castTag(.error_union).?.data;
+        return mod.constInst(scope, unwrap.base.src, .{
+            .ty = operand.ty.elemType().castTag(.error_union).?.data.error_set,
+            .val = data,
+        });
+    }
+
+    const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
+    return mod.addUnOp(b, unwrap.base.src, operand.ty.castTag(.error_union).?.data.payload, .unwrap_errunion_err_ptr, operand);
 }
 
 fn zirEnsureErrPayloadVoid(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    return mod.fail(scope, unwrap.base.src, "TODO implement zirEnsureErrPayloadVoid", .{});
+
+    const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
+    if (operand.ty.zigTypeTag() != .ErrorUnion)
+        return mod.fail(scope, unwrap.base.src, "expected error union type, found '{}'", .{operand.ty});
+    if (operand.ty.castTag(.error_union).?.data.payload.zigTypeTag() != .Void) {
+        return mod.fail(scope, unwrap.base.src, "expression value is ignored", .{});
+    }
+    return mod.constVoid(scope, unwrap.base.src);
 }
 
 fn zirFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst {
@@ -2106,7 +2196,12 @@ fn zirCmp(
         if (!is_equality_cmp) {
             return mod.fail(scope, inst.base.src, "{s} operator not allowed for errors", .{@tagName(op)});
         }
-        return mod.fail(scope, inst.base.src, "TODO implement equality comparison between errors", .{});
+        if (rhs.value()) |rval| {
+            if (lhs.value()) |lval| {
+                return mod.constBool(scope, inst.base.src, (lval.castTag(.@"error").?.data.value == rval.castTag(.@"error").?.data.value) == (op == .eq));
+            }
+        }
+        return mod.fail(scope, inst.base.src, "TODO implement equality comparison between runtime errors", .{});
     } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) {
         // This operation allows any combination of integer and float types, regardless of the
         // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
test/stage2/test.zig
@@ -1397,7 +1397,6 @@ pub fn addCases(ctx: *TestContext) !void {
             "",
         );
     }
-
     {
         var case = ctx.exe("passing u0 to function", linux_x64);
         case.addCompareOutput(
@@ -1419,4 +1418,111 @@ pub fn addCases(ctx: *TestContext) !void {
             "",
         );
     }
+    {
+        var case = ctx.exe("catch at comptime", linux_x64);
+        case.addCompareOutput(
+            \\export fn _start() noreturn {
+            \\    const i: anyerror!u64 = 0;
+            \\    const caught = i catch 5;
+            \\    assert(caught == 0);
+            \\    exit();
+            \\}
+            \\fn assert(b: bool) void {
+            \\    if (!b) unreachable;
+            \\}
+            \\fn exit() noreturn {
+            \\    asm volatile ("syscall"
+            \\        :
+            \\        : [number] "{rax}" (231),
+            \\          [arg1] "{rdi}" (0)
+            \\        : "rcx", "r11", "memory"
+            \\    );
+            \\    unreachable;
+            \\}
+        ,
+            "",
+        );
+        case.addCompareOutput(
+            \\export fn _start() noreturn {
+            \\    const i: anyerror!u64 = error.B;
+            \\    const caught = i catch 5;
+            \\    assert(caught == 5);
+            \\    exit();
+            \\}
+            \\fn assert(b: bool) void {
+            \\    if (!b) unreachable;
+            \\}
+            \\fn exit() noreturn {
+            \\    asm volatile ("syscall"
+            \\        :
+            \\        : [number] "{rax}" (231),
+            \\          [arg1] "{rdi}" (0)
+            \\        : "rcx", "r11", "memory"
+            \\    );
+            \\    unreachable;
+            \\}
+        ,
+            "",
+        );
+        case.addCompareOutput(
+            \\export fn _start() noreturn {
+            \\    const a: anyerror!comptime_int = 42;
+            \\    const b: *const comptime_int = &(a catch unreachable);
+            \\    assert(b.* == 42);
+            \\
+            \\    exit();
+            \\}
+            \\fn assert(b: bool) void {
+            \\    if (!b) unreachable; // assertion failure
+            \\}
+            \\fn exit() noreturn {
+            \\    asm volatile ("syscall"
+            \\        :
+            \\        : [number] "{rax}" (231),
+            \\          [arg1] "{rdi}" (0)
+            \\        : "rcx", "r11", "memory"
+            \\    );
+            \\    unreachable;
+            \\}
+        , "");
+        case.addCompareOutput(
+            \\export fn _start() noreturn {
+            \\const a: anyerror!u32 = error.B;
+            \\_ = &(a catch |err| assert(err == error.B));
+            \\exit();
+            \\}
+            \\fn assert(b: bool) void {
+            \\    if (!b) unreachable;
+            \\}
+            \\fn exit() noreturn {
+            \\    asm volatile ("syscall"
+            \\        :
+            \\        : [number] "{rax}" (231),
+            \\          [arg1] "{rdi}" (0)
+            \\        : "rcx", "r11", "memory"
+            \\    );
+            \\    unreachable;
+            \\}
+        , "");
+        case.addCompareOutput(
+            \\export fn _start() noreturn {
+            \\    const a: anyerror!u32 = error.Bar;
+            \\    a catch |err| assert(err == error.Bar);
+            \\
+            \\    exit();
+            \\}
+            \\fn assert(b: bool) void {
+            \\    if (!b) unreachable;
+            \\}
+            \\fn exit() noreturn {
+            \\    asm volatile ("syscall"
+            \\        :
+            \\        : [number] "{rax}" (231),
+            \\          [arg1] "{rdi}" (0)
+            \\        : "rcx", "r11", "memory"
+            \\    );
+            \\    unreachable;
+            \\}
+        , "");
+    }
 }