Commit 012fac255f

Vexu <git@vexu.eu>
2020-08-14 19:25:06
stage2: fix optimization causing wrong optional child types
1 parent c52513e
src-self-hosted/astgen.zig
@@ -870,7 +870,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo
                     const int_type_payload = try scope.arena().create(Value.Payload.IntType);
                     int_type_payload.* = .{ .signed = is_signed, .bits = bit_count };
                     const result = try addZIRInstConst(mod, scope, src, .{
-                        .ty = Type.initTag(.comptime_int),
+                        .ty = Type.initTag(.type),
                         .val = Value.initPayload(&int_type_payload.base),
                     });
                     return rlWrap(mod, scope, rl, result);
src-self-hosted/codegen.zig
@@ -682,6 +682,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .sub => return self.genSub(inst.castTag(.sub).?),
                 .unreach => return MCValue{ .unreach = {} },
                 .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?),
+                .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?),
             }
         }
 
@@ -840,6 +841,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
+        fn genWrapOptional(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+            const optional_ty = inst.base.ty;
+
+            // No side effects, so if it's unreferenced, do nothing.
+            if (inst.base.isUnused())
+                return MCValue.dead;
+
+            // Optional type is just a boolean true
+            if (optional_ty.abiSize(self.target.*) == 1)
+                return MCValue{ .immediate = 1 };
+
+            switch (arch) {
+                else => return self.fail(inst.base.src, "TODO implement wrap optional for {}", .{self.target.cpu.arch}),
+            }
+        }
+
         fn genLoad(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
             const elem_ty = inst.base.ty;
             if (!elem_ty.hasCodeGenBits())
@@ -2028,9 +2045,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return mcv;
         }
 
-        fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) !MCValue {
+        fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) error{ CodegenFail, OutOfMemory }!MCValue {
             if (typed_value.val.isUndef())
-                return MCValue.undef;
+                return MCValue{ .undef = {} };
             const ptr_bits = self.target.cpu.arch.ptrBitWidth();
             const ptr_bytes: u64 = @divExact(ptr_bits, 8);
             switch (typed_value.ty.zigTypeTag()) {
@@ -2055,6 +2072,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 },
                 .ComptimeInt => unreachable, // semantic analysis prevents this
                 .ComptimeFloat => unreachable, // semantic analysis prevents this
+                .Optional => {
+                    if (typed_value.ty.isPtrLikeOptional()) {
+                        if (typed_value.val.isNull())
+                            return MCValue{ .immediate = 0 };
+
+                        var buf: Type.Payload.Pointer = undefined;
+                        return self.genTypedValue(src, .{
+                            .ty = typed_value.ty.optionalChild(&buf),
+                            .val = typed_value.val,
+                        });
+                    } else if (typed_value.ty.abiSize(self.target.*) == 1) {
+                        return MCValue{ .immediate = @boolToInt(typed_value.val.isNull()) };
+                    }
+                    return self.fail(src, "TODO non pointer optionals", .{});
+                },
                 else => return self.fail(src, "TODO implement const of type '{}'", .{typed_value.ty}),
             }
         }
src-self-hosted/ir.zig
@@ -83,6 +83,7 @@ pub const Inst = struct {
         floatcast,
         intcast,
         unwrap_optional,
+        wrap_optional,
 
         pub fn Type(tag: Tag) type {
             return switch (tag) {
@@ -104,6 +105,7 @@ pub const Inst = struct {
                 .intcast,
                 .load,
                 .unwrap_optional,
+                .wrap_optional,
                 => UnOp,
 
                 .add,
src-self-hosted/Module.zig
@@ -2200,8 +2200,11 @@ pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) Inn
     };
 
     const decl_tv = try decl.typedValue();
-    const ty_payload = try scope.arena().create(Type.Payload.SingleConstPointer);
-    ty_payload.* = .{ .pointee_type = decl_tv.ty };
+    const ty_payload = try scope.arena().create(Type.Payload.Pointer);
+    ty_payload.* = .{
+        .base = .{ .tag = .single_const_pointer },
+        .pointee_type = decl_tv.ty,
+    };
     const val_payload = try scope.arena().create(Value.Payload.DeclRef);
     val_payload.* = .{ .decl = decl };
 
@@ -2425,6 +2428,16 @@ pub fn cmpNumeric(
     return self.addBinOp(b, src, Type.initTag(.bool), Inst.Tag.fromCmpOp(op), casted_lhs, casted_rhs);
 }
 
+fn wrapOptional(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
+    if (inst.value()) |val| {
+        return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
+    }
+
+    // TODO how do we get the result location
+    const b = try self.requireRuntimeBlock(scope, inst.src);
+    return self.addUnOp(b, inst.src, dest_type, .wrap_optional, inst);
+}
+
 fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type {
     if (signed) {
         const int_payload = try scope.arena().create(Type.Payload.IntSigned);
@@ -2502,14 +2515,12 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst
 
     // T to ?T
     if (dest_type.zigTypeTag() == .Optional) {
-        const child_type = dest_type.elemType();
-        if (inst.value()) |val| {
-            if (child_type.eql(inst.ty)) {
-                return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
-            }
-            return self.fail(scope, inst.src, "TODO optional wrap {} to {}", .{ val, dest_type });
-        } else if (child_type.eql(inst.ty)) {
-            return self.fail(scope, inst.src, "TODO optional wrap {}", .{dest_type});
+        var buf: Type.Payload.Pointer = undefined;
+        const child_type = dest_type.optionalChild(&buf);
+        if (child_type.eql(inst.ty)) {
+            return self.wrapOptional(scope, dest_type, inst);
+        } else if (try self.coerceNum(scope, child_type, inst)) |some| {
+            return self.wrapOptional(scope, dest_type, some);
         }
     }
 
@@ -2527,39 +2538,8 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst
     }
 
     // comptime known number to other number
-    if (inst.value()) |val| {
-        const src_zig_tag = inst.ty.zigTypeTag();
-        const dst_zig_tag = dest_type.zigTypeTag();
-
-        if (dst_zig_tag == .ComptimeInt or dst_zig_tag == .Int) {
-            if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
-                if (val.floatHasFraction()) {
-                    return self.fail(scope, inst.src, "fractional component prevents float value {} from being casted to type '{}'", .{ val, inst.ty });
-                }
-                return self.fail(scope, inst.src, "TODO float to int", .{});
-            } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
-                if (!val.intFitsInType(dest_type, self.target())) {
-                    return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val });
-                }
-                return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
-            }
-        } else if (dst_zig_tag == .ComptimeFloat or dst_zig_tag == .Float) {
-            if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
-                const res = val.floatCast(scope.arena(), dest_type, self.target()) catch |err| switch (err) {
-                    error.Overflow => return self.fail(
-                        scope,
-                        inst.src,
-                        "cast of value {} to type '{}' loses information",
-                        .{ val, dest_type },
-                    ),
-                    error.OutOfMemory => return error.OutOfMemory,
-                };
-                return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = res });
-            } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
-                return self.fail(scope, inst.src, "TODO int to float", .{});
-            }
-        }
-    }
+    if (try self.coerceNum(scope, dest_type, inst)) |some|
+        return some;
 
     // integer widening
     if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) {
@@ -2591,6 +2571,42 @@ 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 {
+    const val = inst.value() orelse return null;
+    const src_zig_tag = inst.ty.zigTypeTag();
+    const dst_zig_tag = dest_type.zigTypeTag();
+
+    if (dst_zig_tag == .ComptimeInt or dst_zig_tag == .Int) {
+        if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
+            if (val.floatHasFraction()) {
+                return self.fail(scope, inst.src, "fractional component prevents float value {} from being casted to type '{}'", .{ val, inst.ty });
+            }
+            return self.fail(scope, inst.src, "TODO float to int", .{});
+        } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
+            if (!val.intFitsInType(dest_type, self.target())) {
+                return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val });
+            }
+            return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
+        }
+    } else if (dst_zig_tag == .ComptimeFloat or dst_zig_tag == .Float) {
+        if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
+            const res = val.floatCast(scope.arena(), dest_type, self.target()) catch |err| switch (err) {
+                error.Overflow => return self.fail(
+                    scope,
+                    inst.src,
+                    "cast of value {} to type '{}' loses information",
+                    .{ val, dest_type },
+                ),
+                error.OutOfMemory => return error.OutOfMemory,
+            };
+            return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = res });
+        } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
+            return self.fail(scope, inst.src, "TODO int to float", .{});
+        }
+    }
+    return null;
+}
+
 pub fn storePtr(self: *Module, scope: *Scope, src: usize, ptr: *Inst, uncasted_value: *Inst) !*Inst {
     if (ptr.ty.isConstPtr())
         return self.fail(scope, src, "cannot assign to constant", .{});
@@ -2878,15 +2894,12 @@ pub fn floatSub(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs:
     return Value.initPayload(val_payload);
 }
 
-pub fn singleMutPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) error{OutOfMemory}!Type {
-    const type_payload = try scope.arena().create(Type.Payload.SingleMutPointer);
-    type_payload.* = .{ .pointee_type = elem_ty };
-    return Type.initPayload(&type_payload.base);
-}
-
-pub fn singleConstPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) error{OutOfMemory}!Type {
-    const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer);
-    type_payload.* = .{ .pointee_type = elem_ty };
+pub fn singlePtrType(self: *Module, scope: *Scope, src: usize, mutable: bool, elem_ty: Type) error{OutOfMemory}!Type {
+    const type_payload = try scope.arena().create(Type.Payload.Pointer);
+    type_payload.* = .{
+        .base = .{ .tag = if (mutable) .single_mut_pointer else .single_const_pointer },
+        .pointee_type = elem_ty,
+    };
     return Type.initPayload(&type_payload.base);
 }
 
src-self-hosted/type.zig
@@ -107,6 +107,17 @@ pub const Type = extern union {
         return @fieldParentPtr(T, "base", self.ptr_otherwise);
     }
 
+    pub fn castPointer(self: Type) ?*Payload.Pointer {
+        return switch (self.tag()) {
+            .single_const_pointer,
+            .single_mut_pointer,
+            .optional_single_const_pointer,
+            .optional_single_mut_pointer,
+            => @fieldParentPtr(Payload.Pointer, "base", self.ptr_otherwise),
+            else => null,
+        };
+    }
+
     pub fn eql(a: Type, b: Type) bool {
         // As a shortcut, if the small tags / addresses match, we're done.
         if (a.tag_if_small_enough == b.tag_if_small_enough)
@@ -126,8 +137,8 @@ pub const Type = extern union {
             .Null => return true,
             .Pointer => {
                 // Hot path for common case:
-                if (a.cast(Payload.SingleConstPointer)) |a_payload| {
-                    if (b.cast(Payload.SingleConstPointer)) |b_payload| {
+                if (a.castPointer()) |a_payload| {
+                    if (b.castPointer()) |b_payload| {
                         return eql(a_payload.pointee_type, b_payload.pointee_type);
                     }
                 }
@@ -185,7 +196,9 @@ pub const Type = extern union {
                 return true;
             },
             .Optional => {
-                return a.elemType().eql(b.elemType());
+                var buf_a: Payload.Pointer = undefined;
+                var buf_b: Payload.Pointer = undefined;
+                return a.optionalChild(&buf_a).eql(b.optionalChild(&buf_b));
             },
             .Float,
             .Struct,
@@ -249,7 +262,8 @@ pub const Type = extern union {
                 }
             },
             .Optional => {
-                std.hash.autoHash(&hasher, self.elemType().hash());
+                var buf: Payload.Pointer = undefined;
+                std.hash.autoHash(&hasher, self.optionalChild(&buf).hash());
             },
             .Float,
             .Struct,
@@ -326,8 +340,6 @@ pub const Type = extern union {
                 };
                 return Type{ .ptr_otherwise = &new_payload.base };
             },
-            .single_const_pointer => return self.copyPayloadSingleField(allocator, Payload.SingleConstPointer, "pointee_type"),
-            .single_mut_pointer => return self.copyPayloadSingleField(allocator, Payload.SingleMutPointer, "pointee_type"),
             .int_signed => return self.copyPayloadShallow(allocator, Payload.IntSigned),
             .int_unsigned => return self.copyPayloadShallow(allocator, Payload.IntUnsigned),
             .function => {
@@ -346,8 +358,11 @@ pub const Type = extern union {
                 return Type{ .ptr_otherwise = &new_payload.base };
             },
             .optional => return self.copyPayloadSingleField(allocator, Payload.Optional, "child_type"),
-            .optional_single_mut_pointer => return self.copyPayloadSingleField(allocator, Payload.OptionalSingleMutPointer, "pointee_type"),
-            .optional_single_const_pointer => return self.copyPayloadSingleField(allocator, Payload.OptionalSingleConstPointer, "pointee_type"),
+            .single_const_pointer,
+            .single_mut_pointer,
+            .optional_single_mut_pointer,
+            .optional_single_const_pointer,
+             => return self.copyPayloadSingleField(allocator, Payload.Pointer, "pointee_type"),
         }
     }
 
@@ -441,13 +456,13 @@ pub const Type = extern union {
                     continue;
                 },
                 .single_const_pointer => {
-                    const payload = @fieldParentPtr(Payload.SingleConstPointer, "base", ty.ptr_otherwise);
+                    const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise);
                     try out_stream.writeAll("*const ");
                     ty = payload.pointee_type;
                     continue;
                 },
                 .single_mut_pointer => {
-                    const payload = @fieldParentPtr(Payload.SingleMutPointer, "base", ty.ptr_otherwise);
+                    const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise);
                     try out_stream.writeAll("*");
                     ty = payload.pointee_type;
                     continue;
@@ -467,13 +482,13 @@ pub const Type = extern union {
                     continue;
                 },
                 .optional_single_const_pointer => {
-                    const payload = @fieldParentPtr(Payload.OptionalSingleConstPointer, "base", ty.ptr_otherwise);
+                    const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise);
                     try out_stream.writeAll("?*const ");
                     ty = payload.pointee_type;
                     continue;
                 },
                 .optional_single_mut_pointer => {
-                    const payload = @fieldParentPtr(Payload.OptionalSingleMutPointer, "base", ty.ptr_otherwise);
+                    const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise);
                     try out_stream.writeAll("?*");
                     ty = payload.pointee_type;
                     continue;
@@ -658,7 +673,8 @@ pub const Type = extern union {
             },
 
             .optional => {
-                const child_type = self.cast(Payload.Optional).?.child_type;
+                var buf: Payload.Pointer = undefined;
+                const child_type = self.optionalChild(&buf);
                 if (!child_type.hasCodeGenBits()) return 1;
 
                 if (child_type.zigTypeTag() == .Pointer and !child_type.isCPtr())
@@ -750,7 +766,8 @@ pub const Type = extern union {
             },
 
             .optional => {
-                const child_type = self.cast(Payload.Optional).?.child_type;
+                var buf: Payload.Pointer = undefined;
+                const child_type = self.optionalChild(&buf);
                 if (!child_type.hasCodeGenBits()) return 1;
 
                 if (child_type.zigTypeTag() == .Pointer and !child_type.isCPtr())
@@ -990,7 +1007,23 @@ pub const Type = extern union {
         };
     }
 
-    /// Asserts the type is a pointer, optional or array type.
+    /// Asserts that the type is an optional
+    pub fn isPtrLikeOptional(self: Type) bool {
+        switch (self.tag()) {
+            .optional_single_const_pointer, .optional_single_mut_pointer => return true,
+            .optional => {
+                var buf: Payload.Pointer = undefined;
+                const child_type = self.optionalChild(&buf);
+                // optionals of zero sized pointers behave like bools
+                if (!child_type.hasCodeGenBits()) return false;
+
+                return child_type.zigTypeTag() == .Pointer and !child_type.isCPtr();
+            },
+            else => unreachable,
+        }
+    }
+
+    /// Asserts the type is a pointer or array type.
     pub fn elemType(self: Type) Type {
         return switch (self.tag()) {
             .u8,
@@ -1033,16 +1066,38 @@ pub const Type = extern union {
             .function,
             .int_unsigned,
             .int_signed,
+            .optional,
+            .optional_single_const_pointer,
+            .optional_single_mut_pointer,
             => unreachable,
 
             .array => self.cast(Payload.Array).?.elem_type,
-            .single_const_pointer => self.cast(Payload.SingleConstPointer).?.pointee_type,
-            .single_mut_pointer => self.cast(Payload.SingleMutPointer).?.pointee_type,
+            .single_const_pointer => self.castPointer().?.pointee_type,
+            .single_mut_pointer => self.castPointer().?.pointee_type,
             .array_u8_sentinel_0, .const_slice_u8 => Type.initTag(.u8),
             .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int),
+        };
+    }
+
+    /// Asserts that the type is an optional.
+    pub fn optionalChild(self: Type, buf: *Payload.Pointer) Type {
+        return switch (self.tag()) {
             .optional => self.cast(Payload.Optional).?.child_type,
-            .optional_single_mut_pointer => self.cast(Payload.OptionalSingleMutPointer).?.pointee_type,
-            .optional_single_const_pointer => self.cast(Payload.OptionalSingleConstPointer).?.pointee_type,
+            .optional_single_mut_pointer => {
+                buf.* = .{
+                    .base = .{ .tag = .single_mut_pointer },
+                    .pointee_type = self.castPointer().?.pointee_type
+                };
+                return Type.initPayload(&buf.base);
+            },
+            .optional_single_const_pointer => {
+                buf.* = .{
+                    .base = .{ .tag = .single_const_pointer },
+                    .pointee_type = self.castPointer().?.pointee_type
+                };
+                return Type.initPayload(&buf.base);
+            },
+            else => unreachable,
         };
     }
 
@@ -1901,13 +1956,8 @@ pub const Type = extern union {
                 ty = array.elem_type;
                 continue;
             },
-            .single_const_pointer => {
-                const ptr = ty.cast(Payload.SingleConstPointer).?;
-                ty = ptr.pointee_type;
-                continue;
-            },
-            .single_mut_pointer => {
-                const ptr = ty.cast(Payload.SingleMutPointer).?;
+            .single_const_pointer, .single_mut_pointer => {
+                const ptr = ty.castPointer().?;
                 ty = ptr.pointee_type;
                 continue;
             },
@@ -2049,14 +2099,8 @@ pub const Type = extern union {
             len: u64,
         };
 
-        pub const SingleConstPointer = struct {
-            base: Payload = Payload{ .tag = .single_const_pointer },
-
-            pointee_type: Type,
-        };
-
-        pub const SingleMutPointer = struct {
-            base: Payload = Payload{ .tag = .single_mut_pointer },
+        pub const Pointer = struct {
+            base: Payload,
 
             pointee_type: Type,
         };
@@ -2086,18 +2130,6 @@ pub const Type = extern union {
 
             child_type: Type,
         };
-
-        pub const OptionalSingleConstPointer = struct {
-            base: Payload = Payload{ .tag = .optional_single_const_pointer },
-
-            pointee_type: Type,
-        };
-
-        pub const OptionalSingleMutPointer = struct {
-            base: Payload = Payload{ .tag = .optional_single_mut_pointer },
-
-            pointee_type: Type,
-        };
     };
 };
 
src-self-hosted/zir.zig
@@ -2017,6 +2017,7 @@ const EmitZIR = struct {
                 .load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref),
                 .ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref),
                 .unwrap_optional => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional).?, .unwrap_optional_unsafe),
+                .wrap_optional => try self.emitCast(inst.src, new_body, inst.castTag(.wrap_optional).?, .as),
 
                 .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add),
                 .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub),
@@ -2360,6 +2361,7 @@ const EmitZIR = struct {
                     }
                 },
                 .Optional => {
+                    var buf: Type.Payload.Pointer = undefined;
                     const inst = try self.arena.allocator.create(Inst.UnOp);
                     inst.* = .{
                         .base = .{
@@ -2367,7 +2369,7 @@ const EmitZIR = struct {
                             .tag = .optional_type,
                         },
                         .positionals = .{
-                            .operand = (try self.emitType(src, ty.elemType())).inst,
+                            .operand = (try self.emitType(src, ty.optionalChild(&buf))).inst,
                         },
                         .kw_args = .{},
                     };
src-self-hosted/zir_sema.zig
@@ -317,7 +317,7 @@ fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerErr
 
 fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
     const operand = try resolveInst(mod, scope, inst.positionals.operand);
-    const ptr_type = try mod.singleConstPtrType(scope, inst.base.src, operand.ty);
+    const ptr_type = try mod.singlePtrType(scope, inst.base.src, false, operand.ty);
 
     if (operand.value()) |val| {
         const ref_payload = try scope.arena().create(Value.Payload.RefVal);
@@ -358,7 +358,7 @@ fn analyzeInstEnsureResultNonError(mod: *Module, scope: *Scope, inst: *zir.Inst.
 
 fn analyzeInstAlloc(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
     const var_type = try resolveType(mod, scope, inst.positionals.operand);
-    const ptr_type = try mod.singleMutPtrType(scope, inst.base.src, var_type);
+    const ptr_type = try mod.singlePtrType(scope, inst.base.src, true, var_type);
     const b = try mod.requireRuntimeBlock(scope, inst.base.src);
     return mod.addNoOp(b, inst.base.src, ptr_type, .alloc);
 }
@@ -674,15 +674,17 @@ fn analyzeInstOptionalType(mod: *Module, scope: *Scope, optional: *zir.Inst.UnOp
 
     return mod.constType(scope, optional.base.src, Type.initPayload(switch (child_type.tag()) {
         .single_const_pointer => blk: {
-            const payload = try scope.arena().create(Type.Payload.OptionalSingleConstPointer);
+            const payload = try scope.arena().create(Type.Payload.Pointer);
             payload.* = .{
+                .base = .{ .tag = .optional_single_const_pointer },
                 .pointee_type = child_type.elemType(),
             };
             break :blk &payload.base;
         },
         .single_mut_pointer => blk: {
-            const payload = try scope.arena().create(Type.Payload.OptionalSingleMutPointer);
+            const payload = try scope.arena().create(Type.Payload.Pointer);
             payload.* = .{
+                .base = .{ .tag = .optional_single_mut_pointer },
                 .pointee_type = child_type.elemType(),
             };
             break :blk &payload.base;
@@ -705,11 +707,9 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp
         return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{operand.ty.elemType()});
     }
 
-    const child_type = operand.ty.elemType().elemType();
-    const child_pointer = if (operand.ty.isConstPtr())
-        try mod.singleConstPtrType(scope, unwrap.base.src, child_type)
-    else
-        try mod.singleMutPtrType(scope, unwrap.base.src, child_type);
+    var buf: Type.Payload.Pointer = undefined;
+    const child_type = try operand.ty.elemType().optionalChild(&buf).copy(scope.arena());
+    const child_pointer = try mod.singlePtrType(scope, unwrap.base.src, operand.ty.isConstPtr(), child_type);
 
     if (operand.value()) |val| {
         if (val.isNull()) {
@@ -913,8 +913,11 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne
                 // required a larger index.
                 const elem_ptr = try array_ptr_val.elemPtr(scope.arena(), @intCast(usize, index_u64));
 
-                const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer);
-                type_payload.* = .{ .pointee_type = array_ptr.ty.elemType().elemType() };
+                const type_payload = try scope.arena().create(Type.Payload.Pointer);
+                type_payload.* = .{
+                    .base = .{ .tag = .single_const_pointer },
+                    .pointee_type = array_ptr.ty.elemType().elemType(),
+                };
 
                 return mod.constInst(scope, inst.base.src, .{
                     .ty = Type.initPayload(&type_payload.base),
@@ -1279,13 +1282,13 @@ fn analyzeDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerErr
 
 fn analyzeInstSingleConstPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
     const elem_type = try resolveType(mod, scope, inst.positionals.operand);
-    const ty = try mod.singleConstPtrType(scope, inst.base.src, elem_type);
+    const ty = try mod.singlePtrType(scope, inst.base.src, false, elem_type);
     return mod.constType(scope, inst.base.src, ty);
 }
 
 fn analyzeInstSingleMutPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
     const elem_type = try resolveType(mod, scope, inst.positionals.operand);
-    const ty = try mod.singleMutPtrType(scope, inst.base.src, elem_type);
+    const ty = try mod.singlePtrType(scope, inst.base.src, true, elem_type);
     return mod.constType(scope, inst.base.src, ty);
 }