Commit ecc246efa2

Andrew Kelley <andrew@ziglang.org>
2021-01-19 03:17:23
stage2: rework ZIR/TZIR for optionals and error unions
* fix wrong pointer const-ness when unwrapping optionals * allow grouped expressions and orelse as lvalues * ZIR for unwrapping optionals: no redundant deref - add notes to please don't use rlWrapPtr, this function should be deleted * catch and orelse: better ZIR for non-lvalue: no redundant deref; operate entirely on values. lvalue case still works properly. - properly propagate the result location into the target expression * Test harness: better output when tests fail due to compile errors. * TZIR: add instruction variants. These allow fewer TZIR instructions to be emitted from zir_sema. See the commit diff for per-instruction documentation. - is_null - is_non_null - is_null_ptr - is_non_null_ptr - is_err - is_err_ptr - optional_payload - optional_payload_ptr * TZIR: removed old naming convention instructions: - isnonnull - isnull - iserr - unwrap_optional * ZIR: add instruction variants. These allow fewer ZIR instructions to be emitted from astgen. See the commit diff for per-instruction documentation. - is_non_null - is_null - is_non_null_ptr - is_null_ptr - is_err - is_err_ptr - optional_payload_safe - optional_payload_unsafe - optional_payload_safe_ptr - optional_payload_unsafe_ptr - err_union_payload_safe - err_union_payload_unsafe - err_union_payload_safe_ptr - err_union_payload_unsafe_ptr - err_union_code - err_union_code_ptr * ZIR: removed old naming convention instructions: - isnonnull - isnull - iserr - unwrap_optional_safe - unwrap_optional_unsafe - unwrap_err_safe - unwrap_err_unsafe - unwrap_err_code
1 parent 3c2a922
src/astgen.zig
@@ -118,7 +118,6 @@ fn lvalExpr(mod: *Module, scope: *Scope, node: *ast.Node) InnerError!*zir.Inst {
         .LabeledBlock,
         .Break,
         .PtrType,
-        .GroupedExpression,
         .ArrayType,
         .ArrayTypeSentinel,
         .EnumLiteral,
@@ -129,7 +128,6 @@ fn lvalExpr(mod: *Module, scope: *Scope, node: *ast.Node) InnerError!*zir.Inst {
         .ErrorUnion,
         .MergeErrorSets,
         .Range,
-        .OrElse,
         .Await,
         .BitNot,
         .Negation,
@@ -168,7 +166,14 @@ fn lvalExpr(mod: *Module, scope: *Scope, node: *ast.Node) InnerError!*zir.Inst {
         },
 
         // can be assigned to
-        .UnwrapOptional, .Deref, .Period, .ArrayAccess, .Identifier => {},
+        .UnwrapOptional,
+        .Deref,
+        .Period,
+        .ArrayAccess,
+        .Identifier,
+        .GroupedExpression,
+        .OrElse,
+        => {},
     }
     return expr(mod, scope, .ref, node);
 }
@@ -913,8 +918,12 @@ fn unwrapOptional(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Si
     const tree = scope.tree();
     const src = tree.token_locs[node.rtoken].start;
 
-    const operand = try expr(mod, scope, .ref, node.lhs);
-    return rlWrapPtr(mod, scope, rl, try addZIRUnOp(mod, scope, src, .unwrap_optional_safe, operand));
+    const operand = try expr(mod, scope, rl, node.lhs);
+    const op: zir.Inst.Tag = switch (rl) {
+        .ref => .optional_payload_safe_ptr,
+        else => .optional_payload_safe,
+    };
+    return addZIRUnOp(mod, scope, src, op, operand);
 }
 
 fn containerField(
@@ -1110,6 +1119,7 @@ fn errorSetDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Erro
     }
 
     // analyzing the error set results in a decl ref, so we might need to dereference it
+    // TODO remove all callsites to rlWrapPtr
     return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ErrorSet, .{ .fields = fields }, .{}));
 }
 
@@ -1123,11 +1133,61 @@ fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*
 }
 
 fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) InnerError!*zir.Inst {
-    return orelseCatchExpr(mod, scope, rl, node.lhs, node.op_token, .iserr, .unwrap_err_unsafe, node.rhs, node.payload);
+    switch (rl) {
+        .ref => return orelseCatchExpr(
+            mod,
+            scope,
+            rl,
+            node.lhs,
+            node.op_token,
+            .is_err_ptr,
+            .err_union_payload_unsafe_ptr,
+            .err_union_code_ptr,
+            node.rhs,
+            node.payload,
+        ),
+        else => return orelseCatchExpr(
+            mod,
+            scope,
+            rl,
+            node.lhs,
+            node.op_token,
+            .is_err,
+            .err_union_payload_unsafe,
+            .err_union_code,
+            node.rhs,
+            node.payload,
+        ),
+    }
 }
 
 fn orelseExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst {
-    return orelseCatchExpr(mod, scope, rl, node.lhs, node.op_token, .isnonnull, .unwrap_optional_unsafe, node.rhs, null);
+    switch (rl) {
+        .ref => return orelseCatchExpr(
+            mod,
+            scope,
+            rl,
+            node.lhs,
+            node.op_token,
+            .is_null_ptr,
+            .optional_payload_unsafe_ptr,
+            undefined,
+            node.rhs,
+            null,
+        ),
+        else => return orelseCatchExpr(
+            mod,
+            scope,
+            rl,
+            node.lhs,
+            node.op_token,
+            .is_null,
+            .optional_payload_unsafe,
+            undefined,
+            node.rhs,
+            null,
+        ),
+    }
 }
 
 fn orelseCatchExpr(
@@ -1138,17 +1198,13 @@ fn orelseCatchExpr(
     op_token: ast.TokenIndex,
     cond_op: zir.Inst.Tag,
     unwrap_op: zir.Inst.Tag,
+    unwrap_code_op: zir.Inst.Tag,
     rhs: *ast.Node,
     payload_node: ?*ast.Node,
 ) InnerError!*zir.Inst {
     const tree = scope.tree();
     const src = tree.token_locs[op_token].start;
 
-    const operand_ptr = try expr(mod, scope, .ref, lhs);
-    // TODO we could avoid an unnecessary copy if .iserr, .isnull took a pointer
-    const err_union = try addZIRUnOp(mod, scope, src, .deref, operand_ptr);
-    const cond = try addZIRUnOp(mod, scope, src, cond_op, err_union);
-
     var block_scope: Scope.GenZIR = .{
         .parent = scope,
         .decl = scope.ownerDecl().?,
@@ -1157,14 +1213,8 @@ fn orelseCatchExpr(
     };
     defer block_scope.instructions.deinit(mod.gpa);
 
-    const condbr = try addZIRInstSpecial(mod, &block_scope.base, src, zir.Inst.CondBr, .{
-        .condition = cond,
-        .then_body = undefined, // populated below
-        .else_body = undefined, // populated below
-    }, .{});
-
     const block = try addZIRInstBlock(mod, scope, src, .block, .{
-        .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
+        .instructions = undefined, // populated below
     });
 
     // Most result location types can be forwarded directly; however
@@ -1175,9 +1225,18 @@ fn orelseCatchExpr(
         .discard, .none, .ty, .ptr, .ref => rl,
         .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block },
     };
+    // This could be a pointer or value depending on the `rl` parameter.
+    const operand = try expr(mod, &block_scope.base, branch_rl, lhs);
+    const cond = try addZIRUnOp(mod, &block_scope.base, src, cond_op, operand);
+
+    const condbr = try addZIRInstSpecial(mod, &block_scope.base, src, zir.Inst.CondBr, .{
+        .condition = cond,
+        .then_body = undefined, // populated below
+        .else_body = undefined, // populated below
+    }, .{});
 
     var then_scope: Scope.GenZIR = .{
-        .parent = scope,
+        .parent = &block_scope.base,
         .decl = block_scope.decl,
         .arena = block_scope.arena,
         .instructions = .{},
@@ -1193,38 +1252,41 @@ fn orelseCatchExpr(
         if (mem.eql(u8, err_name, "_"))
             break :blk &then_scope.base;
 
-        const unwrapped_err_ptr = try addZIRUnOp(mod, &then_scope.base, src, .unwrap_err_code, operand_ptr);
         err_val_scope = .{
             .parent = &then_scope.base,
             .gen_zir = &then_scope,
             .name = err_name,
-            .inst = try addZIRUnOp(mod, &then_scope.base, src, .deref, unwrapped_err_ptr),
+            .inst = try addZIRUnOp(mod, &then_scope.base, src, unwrap_code_op, operand),
         };
         break :blk &err_val_scope.base;
     };
 
     _ = try addZIRInst(mod, &then_scope.base, src, zir.Inst.Break, .{
         .block = block,
-        .operand = try rlWrap(mod, then_sub_scope, .{ .ref = {} }, try expr(mod, then_sub_scope, branch_rl, rhs)),
+        .operand = try expr(mod, then_sub_scope, branch_rl, rhs),
     }, .{});
 
     var else_scope: Scope.GenZIR = .{
-        .parent = scope,
+        .parent = &block_scope.base,
         .decl = block_scope.decl,
         .arena = block_scope.arena,
         .instructions = .{},
     };
     defer else_scope.instructions.deinit(mod.gpa);
 
-    const unwrapped_payload = try addZIRUnOp(mod, &else_scope.base, src, unwrap_op, operand_ptr);
+    // This could be a pointer or value depending on `unwrap_op`.
+    const unwrapped_payload = try addZIRUnOp(mod, &else_scope.base, src, unwrap_op, operand);
     _ = try addZIRInst(mod, &else_scope.base, src, zir.Inst.Break, .{
         .block = block,
         .operand = unwrapped_payload,
     }, .{});
 
+    // All branches have been generated, add the instructions to the block.
+    block.positionals.body.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items);
+
     condbr.positionals.then_body = .{ .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items) };
     condbr.positionals.else_body = .{ .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items) };
-    return rlWrapPtr(mod, scope, rl, &block.base);
+    return &block.base;
 }
 
 /// Return whether the identifier names of two tokens are equal. Resolves @""
@@ -1253,6 +1315,7 @@ fn field(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleInfix
     const lhs = try expr(mod, scope, .ref, node.lhs);
     const field_name = try identifierStringInst(mod, scope, node.rhs.castTag(.Identifier).?);
 
+    // TODO remove all callsites to rlWrapPtr
     return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{}));
 }
 
@@ -1263,6 +1326,7 @@ fn arrayAccess(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Array
     const array_ptr = try expr(mod, scope, .ref, node.lhs);
     const index = try expr(mod, scope, .none, node.index_expr);
 
+    // TODO remove all callsites to rlWrapPtr
     return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ElemPtr, .{ .array_ptr = array_ptr, .index = index }, .{}));
 }
 
@@ -1420,13 +1484,13 @@ const CondKind = union(enum) {
                 const cond_ptr = try expr(mod, &block_scope.base, .ref, cond_node);
                 self.* = .{ .optional = cond_ptr };
                 const result = try addZIRUnOp(mod, &block_scope.base, src, .deref, cond_ptr);
-                return try addZIRUnOp(mod, &block_scope.base, src, .isnonnull, result);
+                return try addZIRUnOp(mod, &block_scope.base, src, .is_non_null, result);
             },
             .err_union => {
                 const err_ptr = try expr(mod, &block_scope.base, .ref, cond_node);
                 self.* = .{ .err_union = err_ptr };
                 const result = try addZIRUnOp(mod, &block_scope.base, src, .deref, err_ptr);
-                return try addZIRUnOp(mod, &block_scope.base, src, .iserr, result);
+                return try addZIRUnOp(mod, &block_scope.base, src, .is_err, result);
             },
         }
     }
@@ -1456,7 +1520,7 @@ const CondKind = union(enum) {
     fn elseSubScope(self: CondKind, mod: *Module, else_scope: *Scope.GenZIR, src: usize, payload_node: ?*ast.Node) !*Scope {
         if (self != .err_union) return &else_scope.base;
 
-        const payload_ptr = try addZIRUnOp(mod, &else_scope.base, src, .unwrap_err_unsafe, self.err_union.?);
+        const payload_ptr = try addZIRUnOp(mod, &else_scope.base, src, .err_union_payload_unsafe_ptr, self.err_union.?);
 
         const payload = payload_node.?.castTag(.Payload).?;
         const ident_node = payload.error_symbol.castTag(.Identifier).?;
@@ -2264,6 +2328,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo
             .local_ptr => {
                 const local_ptr = s.cast(Scope.LocalPtr).?;
                 if (mem.eql(u8, local_ptr.name, ident_name)) {
+                    // TODO remove all callsites to rlWrapPtr
                     return rlWrapPtr(mod, scope, rl, local_ptr.ptr);
                 }
                 s = local_ptr.parent;
@@ -3047,6 +3112,7 @@ fn rlWrapVoid(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node, resul
 
 /// TODO go over all the callsites and see where we can introduce "by-value" ZIR instructions
 /// to save ZIR memory. For example, see DeclVal vs DeclRef.
+/// Do not add additional callsites to this function.
 fn rlWrapPtr(mod: *Module, scope: *Scope, rl: ResultLoc, ptr: *zir.Inst) InnerError!*zir.Inst {
     if (rl == .ref) return ptr;
 
src/codegen.zig
@@ -860,9 +860,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .dbg_stmt => return self.genDbgStmt(inst.castTag(.dbg_stmt).?),
                 .floatcast => return self.genFloatCast(inst.castTag(.floatcast).?),
                 .intcast => return self.genIntCast(inst.castTag(.intcast).?),
-                .isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?),
-                .isnull => return self.genIsNull(inst.castTag(.isnull).?),
-                .iserr => return self.genIsErr(inst.castTag(.iserr).?),
+                .is_non_null => return self.genIsNonNull(inst.castTag(.is_non_null).?),
+                .is_non_null_ptr => return self.genIsNonNullPtr(inst.castTag(.is_non_null_ptr).?),
+                .is_null => return self.genIsNull(inst.castTag(.is_null).?),
+                .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).?),
                 .load => return self.genLoad(inst.castTag(.load).?),
                 .loop => return self.genLoop(inst.castTag(.loop).?),
                 .not => return self.genNot(inst.castTag(.not).?),
@@ -874,7 +877,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .sub => return self.genSub(inst.castTag(.sub).?),
                 .switchbr => return self.genSwitch(inst.castTag(.switchbr).?),
                 .unreach => return MCValue{ .unreach = {} },
-                .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?),
+                .optional_payload => return self.genOptionalPayload(inst.castTag(.optional_payload).?),
+                .optional_payload_ptr => return self.genOptionalPayloadPtr(inst.castTag(.optional_payload_ptr).?),
                 .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?),
                 .varptr => return self.genVarPtr(inst.castTag(.varptr).?),
                 .xor => return self.genXor(inst.castTag(.xor).?),
@@ -1118,12 +1122,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
-        fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genOptionalPayload(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 optional for {}", .{self.target.cpu.arch}),
+                else => return self.fail(inst.base.src, "TODO implement .optional_payload for {}", .{self.target.cpu.arch}),
+            }
+        }
+
+        fn genOptionalPayloadPtr(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 .optional_payload_ptr for {}", .{self.target.cpu.arch}),
             }
         }
 
@@ -2306,6 +2319,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
+        fn genIsNullPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+            return self.fail(inst.base.src, "TODO load the operand and call genIsNull", .{});
+        }
+
         fn genIsNonNull(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
             // Here you can specialize this instruction if it makes sense to, otherwise the default
             // will call genIsNull and invert the result.
@@ -2314,12 +2331,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
+        fn genIsNonNullPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+            return self.fail(inst.base.src, "TODO load the operand and call genIsNonNull", .{});
+        }
+
         fn genIsErr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
             switch (arch) {
                 else => return self.fail(inst.base.src, "TODO implement iserr for {}", .{self.target.cpu.arch}),
             }
         }
 
+        fn genIsErrPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+            return self.fail(inst.base.src, "TODO load the operand and call genIsErr", .{});
+        }
+
         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/ir.zig
@@ -73,9 +73,18 @@ pub const Inst = struct {
         condbr,
         constant,
         dbg_stmt,
-        isnonnull,
-        isnull,
-        iserr,
+        // ?T => bool
+        is_null,
+        // ?T => bool (inverted logic)
+        is_non_null,
+        // *?T => bool
+        is_null_ptr,
+        // *?T => bool (inverted logic)
+        is_non_null_ptr,
+        // E!T => bool
+        is_err,
+        // *E!T => bool
+        is_err_ptr,
         booland,
         boolor,
         /// Read a value from a pointer.
@@ -93,7 +102,10 @@ pub const Inst = struct {
         not,
         floatcast,
         intcast,
-        unwrap_optional,
+        // ?T => T
+        optional_payload,
+        // *?T => *T
+        optional_payload_ptr,
         wrap_optional,
         xor,
         switchbr,
@@ -111,14 +123,18 @@ pub const Inst = struct {
                 .ret,
                 .bitcast,
                 .not,
-                .isnonnull,
-                .isnull,
-                .iserr,
+                .is_non_null,
+                .is_non_null_ptr,
+                .is_null,
+                .is_null_ptr,
+                .is_err,
+                .is_err_ptr,
                 .ptrtoint,
                 .floatcast,
                 .intcast,
                 .load,
-                .unwrap_optional,
+                .optional_payload,
+                .optional_payload_ptr,
                 .wrap_optional,
                 => UnOp,
 
src/Module.zig
@@ -2453,7 +2453,7 @@ pub fn analyzeIsNull(
         return self.constBool(scope, src, bool_value);
     }
     const b = try self.requireRuntimeBlock(scope, src);
-    const inst_tag: Inst.Tag = if (invert_logic) .isnonnull else .isnull;
+    const inst_tag: Inst.Tag = if (invert_logic) .is_non_null else .is_null;
     return self.addUnOp(b, src, Type.initTag(.bool), inst_tag, operand);
 }
 
src/test.zig
@@ -696,7 +696,10 @@ pub const TestContext = struct {
                 var all_errors = try comp.getAllErrorsAlloc();
                 defer all_errors.deinit(allocator);
                 if (all_errors.list.len != 0) {
-                    std.debug.print("\nErrors occurred updating the compilation:\n{s}\n", .{hr});
+                    std.debug.print(
+                        "\nCase '{s}': unexpected errors at update_index={d}:\n{s}\n",
+                        .{ case.name, update_index, hr },
+                    );
                     for (all_errors.list) |err_msg| {
                         switch (err_msg) {
                             .src => |src| {
src/zir.zig
@@ -174,11 +174,17 @@ pub const Inst = struct {
         /// Make an integer type out of signedness and bit count.
         inttype,
         /// Return a boolean false if an optional is null. `x != null`
-        isnonnull,
+        is_non_null,
         /// Return a boolean true if an optional is null. `x == null`
-        isnull,
+        is_null,
+        /// Return a boolean false if an optional is null. `x.* != null`
+        is_non_null_ptr,
+        /// Return a boolean true if an optional is null. `x.* == null`
+        is_null_ptr,
         /// Return a boolean true if value is an error
-        iserr,
+        is_err,
+        /// Return a boolean true if dereferenced pointer is an error
+        is_err_ptr,
         /// A labeled block of code that loops forever. At the end of the body it is implied
         /// to repeat; no explicit "repeat" instruction terminates loop bodies.
         loop,
@@ -278,16 +284,42 @@ pub const Inst = struct {
         optional_type,
         /// Create a union type.
         union_type,
-        /// Unwraps an optional value 'lhs.?'
-        unwrap_optional_safe,
-        /// Same as previous, but without safety checks. Used for orelse, if and while
-        unwrap_optional_unsafe,
-        /// Gets the payload of an error union
-        unwrap_err_safe,
-        /// Same as previous, but without safety checks. Used for orelse, if and while
-        unwrap_err_unsafe,
-        /// Gets the error code value of an error union
-        unwrap_err_code,
+        /// ?T => T with safety.
+        /// Given an optional value, returns the payload value, with a safety check that
+        /// the value is non-null. Used for `orelse`, `if` and `while`.
+        optional_payload_safe,
+        /// ?T => T without safety.
+        /// Given an optional value, returns the payload value. No safety checks.
+        optional_payload_unsafe,
+        /// *?T => *T with safety.
+        /// Given a pointer to an optional value, returns a pointer to the payload value,
+        /// with a safety check that the value is non-null. Used for `orelse`, `if` and `while`.
+        optional_payload_safe_ptr,
+        /// *?T => *T without safety.
+        /// Given a pointer to an optional value, returns a pointer to the payload value.
+        /// No safety checks.
+        optional_payload_unsafe_ptr,
+        /// E!T => T with safety.
+        /// Given an error union value, returns the payload value, with a safety check
+        /// that the value is not an error. Used for catch, if, and while.
+        err_union_payload_safe,
+        /// E!T => T without safety.
+        /// Given an error union value, returns the payload value. No safety checks.
+        err_union_payload_unsafe,
+        /// *E!T => *T with safety.
+        /// Given a pointer to an error union value, returns a pointer to the payload value,
+        /// with a safety check that the value is not an error. Used for catch, if, and while.
+        err_union_payload_safe_ptr,
+        /// *E!T => *T without safety.
+        /// Given a pointer to a error union value, returns a pointer to the payload value.
+        /// No safety checks.
+        err_union_payload_unsafe_ptr,
+        /// E!T => E without safety.
+        /// Given an error union value, returns the error code. No safety checks.
+        err_union_code,
+        /// *E!T => E without safety.
+        /// Given a pointer to an error union value, returns the error code. No safety checks.
+        err_union_code_ptr,
         /// Takes a *E!T and raises a compiler error if T != void
         ensure_err_payload_void,
         /// Create a enum literal,
@@ -320,9 +352,12 @@ pub const Inst = struct {
                 .compileerror,
                 .deref,
                 .@"return",
-                .isnull,
-                .isnonnull,
-                .iserr,
+                .is_null,
+                .is_non_null,
+                .is_null_ptr,
+                .is_non_null_ptr,
+                .is_err,
+                .is_err_ptr,
                 .ptrtoint,
                 .ensure_result_used,
                 .ensure_result_non_error,
@@ -341,11 +376,16 @@ pub const Inst = struct {
                 .mut_slice_type,
                 .const_slice_type,
                 .optional_type,
-                .unwrap_optional_safe,
-                .unwrap_optional_unsafe,
-                .unwrap_err_safe,
-                .unwrap_err_unsafe,
-                .unwrap_err_code,
+                .optional_payload_safe,
+                .optional_payload_unsafe,
+                .optional_payload_safe_ptr,
+                .optional_payload_unsafe_ptr,
+                .err_union_payload_safe,
+                .err_union_payload_unsafe,
+                .err_union_payload_safe_ptr,
+                .err_union_payload_unsafe_ptr,
+                .err_union_code,
+                .err_union_code_ptr,
                 .ensure_err_payload_void,
                 .anyframe_type,
                 .bitnot,
@@ -495,9 +535,12 @@ pub const Inst = struct {
                 .int,
                 .intcast,
                 .inttype,
-                .isnonnull,
-                .isnull,
-                .iserr,
+                .is_non_null,
+                .is_null,
+                .is_non_null_ptr,
+                .is_null_ptr,
+                .is_err,
+                .is_err_ptr,
                 .mod_rem,
                 .mul,
                 .mulwrap,
@@ -525,11 +568,16 @@ pub const Inst = struct {
                 .typeof,
                 .xor,
                 .optional_type,
-                .unwrap_optional_safe,
-                .unwrap_optional_unsafe,
-                .unwrap_err_safe,
-                .unwrap_err_unsafe,
-                .unwrap_err_code,
+                .optional_payload_safe,
+                .optional_payload_unsafe,
+                .optional_payload_safe_ptr,
+                .optional_payload_unsafe_ptr,
+                .err_union_payload_safe,
+                .err_union_payload_unsafe,
+                .err_union_payload_safe_ptr,
+                .err_union_payload_unsafe_ptr,
+                .err_union_code,
+                .err_union_code_ptr,
                 .ptr_type,
                 .ensure_err_payload_void,
                 .enum_literal,
@@ -1540,14 +1588,18 @@ const DumpTzir = struct {
                 .ret,
                 .bitcast,
                 .not,
-                .isnonnull,
-                .isnull,
-                .iserr,
+                .is_non_null,
+                .is_non_null_ptr,
+                .is_null,
+                .is_null_ptr,
+                .is_err,
+                .is_err_ptr,
                 .ptrtoint,
                 .floatcast,
                 .intcast,
                 .load,
-                .unwrap_optional,
+                .optional_payload,
+                .optional_payload_ptr,
                 .wrap_optional,
                 => {
                     const un_op = inst.cast(ir.Inst.UnOp).?;
@@ -1637,14 +1689,18 @@ const DumpTzir = struct {
                 .ret,
                 .bitcast,
                 .not,
-                .isnonnull,
-                .isnull,
-                .iserr,
+                .is_non_null,
+                .is_null,
+                .is_non_null_ptr,
+                .is_null_ptr,
+                .is_err,
+                .is_err_ptr,
                 .ptrtoint,
                 .floatcast,
                 .intcast,
                 .load,
-                .unwrap_optional,
+                .optional_payload,
+                .optional_payload_ptr,
                 .wrap_optional,
                 => {
                     const un_op = inst.cast(ir.Inst.UnOp).?;
src/zir_sema.zig
@@ -127,18 +127,26 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
         .cmp_gt => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_gt).?, .gt),
         .cmp_neq => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_neq).?, .neq),
         .condbr => return analyzeInstCondBr(mod, scope, old_inst.castTag(.condbr).?),
-        .isnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnull).?, true),
-        .isnonnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnonnull).?, false),
-        .iserr => return analyzeInstIsErr(mod, scope, old_inst.castTag(.iserr).?),
+        .is_null => return isNull(mod, scope, old_inst.castTag(.is_null).?, false),
+        .is_non_null => return isNull(mod, scope, old_inst.castTag(.is_non_null).?, true),
+        .is_null_ptr => return isNullPtr(mod, scope, old_inst.castTag(.is_null_ptr).?, false),
+        .is_non_null_ptr => return isNullPtr(mod, scope, old_inst.castTag(.is_non_null_ptr).?, true),
+        .is_err => return isErr(mod, scope, old_inst.castTag(.is_err).?),
+        .is_err_ptr => return isErrPtr(mod, scope, old_inst.castTag(.is_err_ptr).?),
         .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?),
         .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?),
         .typeof_peer => return analyzeInstTypeOfPeer(mod, scope, old_inst.castTag(.typeof_peer).?),
         .optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?),
-        .unwrap_optional_safe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_safe).?, true),
-        .unwrap_optional_unsafe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_unsafe).?, false),
-        .unwrap_err_safe => return analyzeInstUnwrapErr(mod, scope, old_inst.castTag(.unwrap_err_safe).?, true),
-        .unwrap_err_unsafe => return analyzeInstUnwrapErr(mod, scope, old_inst.castTag(.unwrap_err_unsafe).?, false),
-        .unwrap_err_code => return analyzeInstUnwrapErrCode(mod, scope, old_inst.castTag(.unwrap_err_code).?),
+        .optional_payload_safe => return optionalPayload(mod, scope, old_inst.castTag(.optional_payload_safe).?, true),
+        .optional_payload_unsafe => return optionalPayload(mod, scope, old_inst.castTag(.optional_payload_unsafe).?, false),
+        .optional_payload_safe_ptr => return optionalPayloadPtr(mod, scope, old_inst.castTag(.optional_payload_safe_ptr).?, true),
+        .optional_payload_unsafe_ptr => return optionalPayloadPtr(mod, scope, old_inst.castTag(.optional_payload_unsafe_ptr).?, false),
+        .err_union_payload_safe => return errorUnionPayload(mod, scope, old_inst.castTag(.err_union_payload_safe).?, true),
+        .err_union_payload_unsafe => return errorUnionPayload(mod, scope, old_inst.castTag(.err_union_payload_unsafe).?, false),
+        .err_union_payload_safe_ptr => return errorUnionPayloadPtr(mod, scope, old_inst.castTag(.err_union_payload_safe_ptr).?, true),
+        .err_union_payload_unsafe_ptr => return errorUnionPayloadPtr(mod, scope, old_inst.castTag(.err_union_payload_unsafe_ptr).?, false),
+        .err_union_code => return errorUnionCode(mod, scope, old_inst.castTag(.err_union_code).?),
+        .err_union_code_ptr => return errorUnionCodePtr(mod, scope, old_inst.castTag(.err_union_code_ptr).?),
         .ensure_err_payload_void => return analyzeInstEnsureErrPayloadVoid(mod, scope, old_inst.castTag(.ensure_err_payload_void).?),
         .array_type => return analyzeInstArrayType(mod, scope, old_inst.castTag(.array_type).?),
         .array_type_sentinel => return analyzeInstArrayTypeSentinel(mod, scope, old_inst.castTag(.array_type_sentinel).?),
@@ -1104,48 +1112,109 @@ fn analyzeInstEnumLiteral(mod: *Module, scope: *Scope, inst: *zir.Inst.EnumLiter
     });
 }
 
-fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp, safety_check: bool) InnerError!*Inst {
+/// Pointer in, pointer out.
+fn optionalPayloadPtr(
+    mod: *Module,
+    scope: *Scope,
+    unwrap: *zir.Inst.UnOp,
+    safety_check: bool,
+) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
-    assert(operand.ty.zigTypeTag() == .Pointer);
 
-    const elem_type = operand.ty.elemType();
-    if (elem_type.zigTypeTag() != .Optional) {
-        return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{elem_type});
+    const optional_ptr = try resolveInst(mod, scope, unwrap.positionals.operand);
+    assert(optional_ptr.ty.zigTypeTag() == .Pointer);
+
+    const opt_type = optional_ptr.ty.elemType();
+    if (opt_type.zigTypeTag() != .Optional) {
+        return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{opt_type});
     }
 
-    const child_type = try elem_type.optionalChildAlloc(scope.arena());
-    const child_pointer = try mod.simplePtrType(scope, unwrap.base.src, child_type, operand.ty.isConstPtr(), .One);
+    const child_type = try opt_type.optionalChildAlloc(scope.arena());
+    const child_pointer = try mod.simplePtrType(scope, unwrap.base.src, child_type, !optional_ptr.ty.isConstPtr(), .One);
 
-    if (operand.value()) |val| {
+    if (optional_ptr.value()) |pointer_val| {
+        const val = try pointer_val.pointerDeref(scope.arena());
         if (val.isNull()) {
             return mod.fail(scope, unwrap.base.src, "unable to unwrap null", .{});
         }
+        // The same Value represents the pointer to the optional and the payload.
         return mod.constInst(scope, unwrap.base.src, .{
             .ty = child_pointer,
+            .val = pointer_val,
+        });
+    }
+
+    const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
+    if (safety_check and mod.wantSafety(scope)) {
+        const is_non_null = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .is_non_null_ptr, optional_ptr);
+        try mod.addSafetyCheck(b, is_non_null, .unwrap_null);
+    }
+    return mod.addUnOp(b, unwrap.base.src, child_pointer, .optional_payload_ptr, optional_ptr);
+}
+
+/// Value in, value out.
+fn optionalPayload(
+    mod: *Module,
+    scope: *Scope,
+    unwrap: *zir.Inst.UnOp,
+    safety_check: bool,
+) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
+    const opt_type = operand.ty;
+    if (opt_type.zigTypeTag() != .Optional) {
+        return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{opt_type});
+    }
+
+    const child_type = try opt_type.optionalChildAlloc(scope.arena());
+
+    if (operand.value()) |val| {
+        if (val.isNull()) {
+            return mod.fail(scope, unwrap.base.src, "unable to unwrap null", .{});
+        }
+        return mod.constInst(scope, unwrap.base.src, .{
+            .ty = child_type,
             .val = val,
         });
     }
 
     const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
     if (safety_check and mod.wantSafety(scope)) {
-        const is_non_null = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .isnonnull, operand);
+        const is_non_null = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .is_non_null, operand);
         try mod.addSafetyCheck(b, is_non_null, .unwrap_null);
     }
-    return mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional, operand);
+    return mod.addUnOp(b, unwrap.base.src, child_type, .optional_payload, operand);
 }
 
-fn analyzeInstUnwrapErr(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp, safety_check: bool) InnerError!*Inst {
+/// Value in, value out
+fn errorUnionPayload(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 analyzeInstUnwrapErr", .{});
+    return mod.fail(scope, unwrap.base.src, "TODO implement zir_sema.errorUnionPayload", .{});
 }
 
-fn analyzeInstUnwrapErrCode(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst {
+/// Pointer in, pointer out
+fn errorUnionPayloadPtr(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 analyzeInstUnwrapErrCode", .{});
+    return mod.fail(scope, unwrap.base.src, "TODO implement zir_sema.errorUnionPayloadPtr", .{});
+}
+
+/// Value in, value out
+fn errorUnionCode(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.errorUnionCode", .{});
+}
+
+/// Pointer in, value out
+fn errorUnionCodePtr(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.errorUnionCodePtr", .{});
 }
 
 fn analyzeInstEnsureErrPayloadVoid(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst {
@@ -2074,20 +2143,36 @@ fn analyzeInstBoolOp(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerEr
     return mod.addBinOp(b, inst.base.src, bool_type, if (is_bool_or) .boolor else .booland, lhs, rhs);
 }
 
-fn analyzeInstIsNonNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst {
+fn isNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
     const operand = try resolveInst(mod, scope, inst.positionals.operand);
     return mod.analyzeIsNull(scope, inst.base.src, operand, invert_logic);
 }
 
-fn analyzeInstIsErr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+fn isNullPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
+    const ptr = try resolveInst(mod, scope, inst.positionals.operand);
+    const loaded = try mod.analyzeDeref(scope, inst.base.src, ptr, ptr.src);
+    return mod.analyzeIsNull(scope, inst.base.src, loaded, invert_logic);
+}
+
+fn isErr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
     const operand = try resolveInst(mod, scope, inst.positionals.operand);
     return mod.analyzeIsErr(scope, inst.base.src, operand);
 }
 
+fn isErrPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
+    const ptr = try resolveInst(mod, scope, inst.positionals.operand);
+    const loaded = try mod.analyzeDeref(scope, inst.base.src, ptr, ptr.src);
+    return mod.analyzeIsErr(scope, inst.base.src, loaded);
+}
+
 fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();