Commit 0552a8b11f

Robin Voetter <robin@voetter.nl>
2023-05-29 23:54:09
spirv: translate remaining types
1 parent fcb4225
src/codegen/spirv/Assembler.zig
@@ -11,7 +11,8 @@ const IdRef = spec.IdRef;
 const IdResult = spec.IdResult;
 
 const SpvModule = @import("Module.zig");
-const SpvType = @import("type.zig").Type;
+const CacheRef = SpvModule.CacheRef;
+const CacheKey = SpvModule.CacheKey;
 
 /// Represents a token in the assembly template.
 const Token = struct {
@@ -126,7 +127,7 @@ const AsmValue = union(enum) {
     value: IdRef,
 
     /// This result-value represents a type registered into the module's type system.
-    ty: SpvType.Ref,
+    ty: CacheRef,
 
     /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue
     /// is of a variant that allows the result to be obtained (not an unresolved
@@ -135,7 +136,7 @@ const AsmValue = union(enum) {
         return switch (self) {
             .just_declared, .unresolved_forward_reference => unreachable,
             .value => |result| result,
-            .ty => |ref| spv.typeId(ref),
+            .ty => |ref| spv.resultId(ref),
         };
     }
 };
@@ -267,9 +268,9 @@ fn processInstruction(self: *Assembler) !void {
 /// refers to the result.
 fn processTypeInstruction(self: *Assembler) !AsmValue {
     const operands = self.inst.operands.items;
-    const ty = switch (self.inst.opcode) {
-        .OpTypeVoid => SpvType.initTag(.void),
-        .OpTypeBool => SpvType.initTag(.bool),
+    const ref = switch (self.inst.opcode) {
+        .OpTypeVoid => try self.spv.resolve(.void_type),
+        .OpTypeBool => try self.spv.resolve(.bool_type),
         .OpTypeInt => blk: {
             const signedness: std.builtin.Signedness = switch (operands[2].literal32) {
                 0 => .unsigned,
@@ -282,7 +283,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
             const width = std.math.cast(u16, operands[1].literal32) orelse {
                 return self.fail(0, "int type of {} bits is too large", .{operands[1].literal32});
             };
-            break :blk try SpvType.int(self.spv.arena, signedness, width);
+            break :blk try self.spv.intType(signedness, width);
         },
         .OpTypeFloat => blk: {
             const bits = operands[1].literal32;
@@ -292,136 +293,36 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
                     return self.fail(0, "{} is not a valid bit count for floats (expected 16, 32 or 64)", .{bits});
                 },
             }
-            break :blk SpvType.float(@intCast(u16, bits));
-        },
-        .OpTypeVector => blk: {
-            const payload = try self.spv.arena.create(SpvType.Payload.Vector);
-            payload.* = .{
-                .component_type = try self.resolveTypeRef(operands[1].ref_id),
-                .component_count = operands[2].literal32,
-            };
-            break :blk SpvType.initPayload(&payload.base);
-        },
-        .OpTypeMatrix => blk: {
-            const payload = try self.spv.arena.create(SpvType.Payload.Matrix);
-            payload.* = .{
-                .column_type = try self.resolveTypeRef(operands[1].ref_id),
-                .column_count = operands[2].literal32,
-            };
-            break :blk SpvType.initPayload(&payload.base);
-        },
-        .OpTypeImage => blk: {
-            const payload = try self.spv.arena.create(SpvType.Payload.Image);
-            payload.* = .{
-                .sampled_type = try self.resolveTypeRef(operands[1].ref_id),
-                .dim = @intToEnum(spec.Dim, operands[2].value),
-                .depth = switch (operands[3].literal32) {
-                    0 => .no,
-                    1 => .yes,
-                    2 => .maybe,
-                    else => {
-                        return self.fail(0, "'{}' is not a valid image depth (expected 0, 1 or 2)", .{operands[3].literal32});
-                    },
-                },
-                .arrayed = switch (operands[4].literal32) {
-                    0 => false,
-                    1 => true,
-                    else => {
-                        return self.fail(0, "'{}' is not a valid image arrayed-ness (expected 0 or 1)", .{operands[4].literal32});
-                    },
-                },
-                .multisampled = switch (operands[5].literal32) {
-                    0 => false,
-                    1 => true,
-                    else => {
-                        return self.fail(0, "'{}' is not a valid image multisampled-ness (expected 0 or 1)", .{operands[5].literal32});
-                    },
-                },
-                .sampled = switch (operands[6].literal32) {
-                    0 => .known_at_runtime,
-                    1 => .with_sampler,
-                    2 => .without_sampler,
-                    else => {
-                        return self.fail(0, "'{}' is not a valid image sampled-ness (expected 0, 1 or 2)", .{operands[6].literal32});
-                    },
-                },
-                .format = @intToEnum(spec.ImageFormat, operands[7].value),
-                .access_qualifier = if (operands.len > 8)
-                    @intToEnum(spec.AccessQualifier, operands[8].value)
-                else
-                    null,
-            };
-            break :blk SpvType.initPayload(&payload.base);
-        },
-        .OpTypeSampler => SpvType.initTag(.sampler),
-        .OpTypeSampledImage => blk: {
-            const payload = try self.spv.arena.create(SpvType.Payload.SampledImage);
-            payload.* = .{
-                .image_type = try self.resolveTypeRef(operands[1].ref_id),
-            };
-            break :blk SpvType.initPayload(&payload.base);
+            break :blk try self.spv.resolve(.{ .float_type = .{ .bits = @intCast(u16, bits) } });
         },
+        .OpTypeVector => try self.spv.resolve(.{ .vector_type = .{
+            .component_type = try self.resolveTypeRef(operands[1].ref_id),
+            .component_count = operands[2].literal32,
+        } }),
         .OpTypeArray => {
             // TODO: The length of an OpTypeArray is determined by a constant (which may be a spec constant),
             // and so some consideration must be taken when entering this in the type system.
             return self.todo("process OpTypeArray", .{});
         },
-        .OpTypeRuntimeArray => blk: {
-            const payload = try self.spv.arena.create(SpvType.Payload.RuntimeArray);
-            payload.* = .{
-                .element_type = try self.resolveTypeRef(operands[1].ref_id),
-                // TODO: Fetch array stride from decorations.
-                .array_stride = 0,
-            };
-            break :blk SpvType.initPayload(&payload.base);
-        },
-        .OpTypeOpaque => blk: {
-            const payload = try self.spv.arena.create(SpvType.Payload.Opaque);
-            const name_offset = operands[1].string;
-            payload.* = .{
-                .name = std.mem.sliceTo(self.inst.string_bytes.items[name_offset..], 0),
-            };
-            break :blk SpvType.initPayload(&payload.base);
-        },
-        .OpTypePointer => blk: {
-            const payload = try self.spv.arena.create(SpvType.Payload.Pointer);
-            payload.* = .{
-                .storage_class = @intToEnum(spec.StorageClass, operands[1].value),
-                .child_type = try self.resolveTypeRef(operands[2].ref_id),
-                // TODO: Fetch decorations
-            };
-            break :blk SpvType.initPayload(&payload.base);
-        },
+        .OpTypePointer => try self.spv.ptrType(
+            try self.resolveTypeRef(operands[2].ref_id),
+            @intToEnum(spec.StorageClass, operands[1].value),
+        ),
         .OpTypeFunction => blk: {
             const param_operands = operands[2..];
-            const param_types = try self.spv.arena.alloc(SpvType.Ref, param_operands.len);
+            const param_types = try self.spv.gpa.alloc(CacheRef, param_operands.len);
+            defer self.spv.gpa.free(param_types);
             for (param_types, 0..) |*param, i| {
                 param.* = try self.resolveTypeRef(param_operands[i].ref_id);
             }
-            const payload = try self.spv.arena.create(SpvType.Payload.Function);
-            payload.* = .{
+            break :blk try self.spv.resolve(.{ .function_type = .{
                 .return_type = try self.resolveTypeRef(operands[1].ref_id),
                 .parameters = param_types,
-            };
-            break :blk SpvType.initPayload(&payload.base);
+            } });
         },
-        .OpTypeEvent => SpvType.initTag(.event),
-        .OpTypeDeviceEvent => SpvType.initTag(.device_event),
-        .OpTypeReserveId => SpvType.initTag(.reserve_id),
-        .OpTypeQueue => SpvType.initTag(.queue),
-        .OpTypePipe => blk: {
-            const payload = try self.spv.arena.create(SpvType.Payload.Pipe);
-            payload.* = .{
-                .qualifier = @intToEnum(spec.AccessQualifier, operands[1].value),
-            };
-            break :blk SpvType.initPayload(&payload.base);
-        },
-        .OpTypePipeStorage => SpvType.initTag(.pipe_storage),
-        .OpTypeNamedBarrier => SpvType.initTag(.named_barrier),
         else => return self.todo("process type instruction {s}", .{@tagName(self.inst.opcode)}),
     };
 
-    const ref = try self.spv.resolveType(ty);
     return AsmValue{ .ty = ref };
 }
 
@@ -528,7 +429,7 @@ fn resolveRef(self: *Assembler, ref: AsmValue.Ref) !AsmValue {
 }
 
 /// Resolve a value reference as type.
-fn resolveTypeRef(self: *Assembler, ref: AsmValue.Ref) !SpvType.Ref {
+fn resolveTypeRef(self: *Assembler, ref: AsmValue.Ref) !CacheRef {
     const value = try self.resolveRef(ref);
     switch (value) {
         .just_declared, .unresolved_forward_reference => unreachable,
@@ -761,19 +662,20 @@ fn parseContextDependentNumber(self: *Assembler) !void {
 
     const tok = self.currentToken();
     const result_type_ref = try self.resolveTypeRef(self.inst.operands.items[0].ref_id);
-    const result_type = self.spv.type_cache.keys()[@enumToInt(result_type_ref)];
-    if (result_type.isInt()) {
-        try self.parseContextDependentInt(result_type.intSignedness(), result_type.intFloatBits());
-    } else if (result_type.isFloat()) {
-        const width = result_type.intFloatBits();
-        switch (width) {
-            16 => try self.parseContextDependentFloat(16),
-            32 => try self.parseContextDependentFloat(32),
-            64 => try self.parseContextDependentFloat(64),
-            else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}),
-        }
-    } else {
-        return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())});
+    const result_type = self.spv.cache.lookup(result_type_ref);
+    switch (result_type) {
+        .int_type => |int| {
+            try self.parseContextDependentInt(int.signedness, int.bits);
+        },
+        .float_type => |float| {
+            switch (float.bits) {
+                16 => try self.parseContextDependentFloat(16),
+                32 => try self.parseContextDependentFloat(32),
+                64 => try self.parseContextDependentFloat(64),
+                else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{float.bits}),
+            }
+        },
+        else => return self.fail(tok.start, "cannot parse literal constant", .{}),
     }
 }
 
src/codegen/spirv/Module.zig
@@ -20,12 +20,13 @@ const IdResult = spec.IdResult;
 const IdResultType = spec.IdResultType;
 
 const Section = @import("Section.zig");
-const Type = @import("type.zig").Type;
-pub const TypeConstantCache = @import("TypeConstantCache.zig");
 
-const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true);
+const Cache = @import("TypeConstantCache.zig");
+pub const CacheKey = Cache.Key;
+pub const CacheRef = Cache.Ref;
+pub const CacheString = Cache.String;
 
-/// This structure represents a function that is in-progress of being emitted.
+/// This structure represents a function that isc in-progress of being emitted.
 /// Commonly, the contents of this structure will be merged with the appropriate
 /// sections of the module and re-used. Note that the SPIR-V module system makes
 /// no attempt of compacting result-id's, so any Fn instance should ultimately
@@ -130,7 +131,7 @@ sections: struct {
     /// From this section, OpLine and OpNoLine is allowed.
     /// According to the SPIR-V documentation, this section normally
     /// also holds type and constant instructions. These are managed
-    /// via the tc_cache instead, which is the sole structure that
+    /// via the cache instead, which is the sole structure that
     /// manages that section. These will be inserted between this and
     /// the previous section when emitting the final binary.
     /// TODO: Do we need this section? Globals are also managed with another mechanism.
@@ -152,10 +153,9 @@ next_result_id: Word,
 /// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.
 source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
 
-type_cache: TypeCache = .{},
 /// SPIR-V type- and constant cache. This structure is used to store information about these in a more
 /// efficient manner.
-tc_cache: TypeConstantCache = .{},
+cache: Cache = .{},
 
 /// Set of Decls, referred to by Decl.Index.
 decls: std.ArrayListUnmanaged(Decl) = .{},
@@ -196,7 +196,7 @@ pub fn deinit(self: *Module) void {
     self.sections.functions.deinit(self.gpa);
 
     self.source_file_names.deinit(self.gpa);
-    self.tc_cache.deinit(self);
+    self.cache.deinit(self);
 
     self.decls.deinit(self.gpa);
     self.decl_deps.deinit(self.gpa);
@@ -223,20 +223,20 @@ pub fn idBound(self: Module) Word {
     return self.next_result_id;
 }
 
-pub fn resolve(self: *Module, key: TypeConstantCache.Key) !TypeConstantCache.Ref {
-    return self.tc_cache.resolve(self, key);
+pub fn resolve(self: *Module, key: CacheKey) !CacheRef {
+    return self.cache.resolve(self, key);
 }
 
-pub fn resultId(self: *Module, ref: TypeConstantCache.Ref) IdResult {
-    return self.tc_cache.resultId(ref);
+pub fn resultId(self: *const Module, ref: CacheRef) IdResult {
+    return self.cache.resultId(ref);
 }
 
-pub fn resolveId(self: *Module, key: TypeConstantCache.Key) !IdResult {
+pub fn resolveId(self: *Module, key: CacheKey) !IdResult {
     return self.resultId(try self.resolve(key));
 }
 
-pub fn resolveString(self: *Module, str: []const u8) !TypeConstantCache.String {
-    return try self.tc_cache.addString(self, str);
+pub fn resolveString(self: *Module, str: []const u8) !CacheString {
+    return try self.cache.addString(self, str);
 }
 
 fn orderGlobalsInto(
@@ -350,7 +350,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
     var entry_points = try self.entryPoints();
     defer entry_points.deinit(self.gpa);
 
-    var types_constants = try self.tc_cache.materialize(self);
+    var types_constants = try self.cache.materialize(self);
     defer types_constants.deinit(self.gpa);
 
     // Note: needs to be kept in order according to section 2.3!
@@ -364,6 +364,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
         self.sections.debug_names.toWords(),
         self.sections.annotations.toWords(),
         types_constants.toWords(),
+        self.sections.types_globals_constants.toWords(),
         self.sections.globals.toWords(),
         globals.toWords(),
         self.sections.functions.toWords(),
@@ -416,364 +417,14 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
     return result.value_ptr.*;
 }
 
-/// Fetch a result-id for a spir-v type. This function deduplicates the type as appropriate,
-/// and returns a cached version if that exists.
-/// Note: This function does not attempt to perform any validation on the type.
-/// The type is emitted in a shallow fashion; any child types should already
-/// be emitted at this point.
-pub fn resolveType(self: *Module, ty: Type) !Type.Ref {
-    const result = try self.type_cache.getOrPut(self.gpa, ty);
-    const index = @intToEnum(Type.Ref, result.index);
-
-    if (!result.found_existing) {
-        const ref = try self.emitType(ty);
-        self.type_cache.values()[result.index] = ref;
-    }
-
-    return index;
-}
-
-pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType {
-    const ty_ref = try self.resolveType(ty);
-    return self.typeId(ty_ref);
-}
-
-pub fn typeRefType(self: Module, ty_ref: Type.Ref) Type {
-    return self.type_cache.keys()[@enumToInt(ty_ref)];
-}
-
-/// Get the result-id of a particular type, by reference. Asserts type_ref is valid.
-pub fn typeId(self: Module, ty_ref: Type.Ref) IdResultType {
-    return self.type_cache.values()[@enumToInt(ty_ref)];
-}
-
-/// Unconditionally emit a spir-v type into the appropriate section.
-/// Note: If this function is called with a type that is already generated, it may yield an invalid module
-/// as non-pointer non-aggregrate types must me unique!
-/// Note: This function does not attempt to perform any validation on the type.
-/// The type is emitted in a shallow fashion; any child types should already
-/// be emitted at this point.
-pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType {
-    const result_id = self.allocId();
-    const ref_id = result_id;
-    const types = &self.sections.types_globals_constants;
-    const debug_names = &self.sections.debug_names;
-    const result_id_operand = .{ .id_result = result_id };
-
-    switch (ty.tag()) {
-        .void => {
-            try types.emit(self.gpa, .OpTypeVoid, result_id_operand);
-            try debug_names.emit(self.gpa, .OpName, .{
-                .target = result_id,
-                .name = "void",
-            });
-        },
-        .bool => {
-            try types.emit(self.gpa, .OpTypeBool, result_id_operand);
-            try debug_names.emit(self.gpa, .OpName, .{
-                .target = result_id,
-                .name = "bool",
-            });
-        },
-        .u8,
-        .u16,
-        .u32,
-        .u64,
-        .i8,
-        .i16,
-        .i32,
-        .i64,
-        .int,
-        => {
-            // TODO: Kernels do not support OpTypeInt that is signed. We can probably
-            // can get rid of the signedness all together, in Shaders also.
-            const bits = ty.intFloatBits();
-            const signedness: spec.LiteralInteger = switch (ty.intSignedness()) {
-                .unsigned => 0,
-                .signed => 1,
-            };
-
-            try types.emit(self.gpa, .OpTypeInt, .{
-                .id_result = result_id,
-                .width = bits,
-                .signedness = signedness,
-            });
-
-            const ui: []const u8 = switch (signedness) {
-                0 => "u",
-                1 => "i",
-                else => unreachable,
-            };
-            const name = try std.fmt.allocPrint(self.gpa, "{s}{}", .{ ui, bits });
-            defer self.gpa.free(name);
-
-            try debug_names.emit(self.gpa, .OpName, .{
-                .target = result_id,
-                .name = name,
-            });
-        },
-        .f16, .f32, .f64 => {
-            const bits = ty.intFloatBits();
-            try types.emit(self.gpa, .OpTypeFloat, .{
-                .id_result = result_id,
-                .width = bits,
-            });
-
-            const name = try std.fmt.allocPrint(self.gpa, "f{}", .{bits});
-            defer self.gpa.free(name);
-            try debug_names.emit(self.gpa, .OpName, .{
-                .target = result_id,
-                .name = name,
-            });
-        },
-        .vector => try types.emit(self.gpa, .OpTypeVector, .{
-            .id_result = result_id,
-            .component_type = self.typeId(ty.childType()),
-            .component_count = ty.payload(.vector).component_count,
-        }),
-        .matrix => try types.emit(self.gpa, .OpTypeMatrix, .{
-            .id_result = result_id,
-            .column_type = self.typeId(ty.childType()),
-            .column_count = ty.payload(.matrix).column_count,
-        }),
-        .image => {
-            const info = ty.payload(.image);
-            try types.emit(self.gpa, .OpTypeImage, .{
-                .id_result = result_id,
-                .sampled_type = self.typeId(ty.childType()),
-                .dim = info.dim,
-                .depth = @enumToInt(info.depth),
-                .arrayed = @boolToInt(info.arrayed),
-                .ms = @boolToInt(info.multisampled),
-                .sampled = @enumToInt(info.sampled),
-                .image_format = info.format,
-                .access_qualifier = info.access_qualifier,
-            });
-        },
-        .sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand),
-        .sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{
-            .id_result = result_id,
-            .image_type = self.typeId(ty.childType()),
-        }),
-        .array => {
-            const info = ty.payload(.array);
-            assert(info.length != 0);
-
-            const size_type = Type.initTag(.u32);
-            const size_type_id = try self.resolveTypeId(size_type);
-            const length_id = self.allocId();
-            try self.emitConstant(size_type_id, length_id, .{ .uint32 = info.length });
-
-            try types.emit(self.gpa, .OpTypeArray, .{
-                .id_result = result_id,
-                .element_type = self.typeId(ty.childType()),
-                .length = length_id,
-            });
-            if (info.array_stride != 0) {
-                try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
-            }
-        },
-        .runtime_array => {
-            const info = ty.payload(.runtime_array);
-            try types.emit(self.gpa, .OpTypeRuntimeArray, .{
-                .id_result = result_id,
-                .element_type = self.typeId(ty.childType()),
-            });
-            if (info.array_stride != 0) {
-                try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
-            }
-        },
-        .@"struct" => {
-            const info = ty.payload(.@"struct");
-            try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len);
-            types.writeOperand(IdResult, result_id);
-            for (info.members) |member| {
-                types.writeOperand(IdRef, self.typeId(member.ty));
-            }
-            try self.decorateStruct(ref_id, info);
-        },
-        .@"opaque" => try types.emit(self.gpa, .OpTypeOpaque, .{
-            .id_result = result_id,
-            .literal_string = ty.payload(.@"opaque").name,
-        }),
-        .pointer => {
-            const info = ty.payload(.pointer);
-            try types.emit(self.gpa, .OpTypePointer, .{
-                .id_result = result_id,
-                .storage_class = info.storage_class,
-                .type = self.typeId(ty.childType()),
-            });
-            if (info.array_stride != 0) {
-                try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
-            }
-            if (info.alignment != 0) {
-                try self.decorate(ref_id, .{ .Alignment = .{ .alignment = info.alignment } });
-            }
-            if (info.max_byte_offset) |max_byte_offset| {
-                try self.decorate(ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } });
-            }
-        },
-        .function => {
-            const info = ty.payload(.function);
-            try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len);
-            types.writeOperand(IdResult, result_id);
-            types.writeOperand(IdRef, self.typeId(info.return_type));
-            for (info.parameters) |parameter_type| {
-                types.writeOperand(IdRef, self.typeId(parameter_type));
-            }
-        },
-        .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand),
-        .device_event => try types.emit(self.gpa, .OpTypeDeviceEvent, result_id_operand),
-        .reserve_id => try types.emit(self.gpa, .OpTypeReserveId, result_id_operand),
-        .queue => try types.emit(self.gpa, .OpTypeQueue, result_id_operand),
-        .pipe => try types.emit(self.gpa, .OpTypePipe, .{
-            .id_result = result_id,
-            .qualifier = ty.payload(.pipe).qualifier,
-        }),
-        .pipe_storage => try types.emit(self.gpa, .OpTypePipeStorage, result_id_operand),
-        .named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand),
-    }
-
-    return result_id;
-}
-
-fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void {
-    const debug_names = &self.sections.debug_names;
-
-    if (info.name.len != 0) {
-        try debug_names.emit(self.gpa, .OpName, .{
-            .target = target,
-            .name = info.name,
-        });
-    }
-
-    // Decorations for the struct type itself.
-    if (info.decorations.block)
-        try self.decorate(target, .Block);
-    if (info.decorations.buffer_block)
-        try self.decorate(target, .BufferBlock);
-    if (info.decorations.glsl_shared)
-        try self.decorate(target, .GLSLShared);
-    if (info.decorations.glsl_packed)
-        try self.decorate(target, .GLSLPacked);
-    if (info.decorations.c_packed)
-        try self.decorate(target, .CPacked);
-
-    // Decorations for the struct members.
-    const extra = info.member_decoration_extra;
-    var extra_i: u32 = 0;
-    for (info.members, 0..) |member, i| {
-        const d = member.decorations;
-        const index = @intCast(Word, i);
-
-        if (member.name.len != 0) {
-            try debug_names.emit(self.gpa, .OpMemberName, .{
-                .type = target,
-                .member = index,
-                .name = member.name,
-            });
-        }
-
-        switch (member.offset) {
-            .none => {},
-            else => try self.decorateMember(
-                target,
-                index,
-                .{ .Offset = .{ .byte_offset = @enumToInt(member.offset) } },
-            ),
-        }
-
-        switch (d.matrix_layout) {
-            .row_major => try self.decorateMember(target, index, .RowMajor),
-            .col_major => try self.decorateMember(target, index, .ColMajor),
-            .none => {},
-        }
-        if (d.matrix_layout != .none) {
-            try self.decorateMember(target, index, .{
-                .MatrixStride = .{ .matrix_stride = extra[extra_i] },
-            });
-            extra_i += 1;
-        }
-
-        if (d.no_perspective)
-            try self.decorateMember(target, index, .NoPerspective);
-        if (d.flat)
-            try self.decorateMember(target, index, .Flat);
-        if (d.patch)
-            try self.decorateMember(target, index, .Patch);
-        if (d.centroid)
-            try self.decorateMember(target, index, .Centroid);
-        if (d.sample)
-            try self.decorateMember(target, index, .Sample);
-        if (d.invariant)
-            try self.decorateMember(target, index, .Invariant);
-        if (d.@"volatile")
-            try self.decorateMember(target, index, .Volatile);
-        if (d.coherent)
-            try self.decorateMember(target, index, .Coherent);
-        if (d.non_writable)
-            try self.decorateMember(target, index, .NonWritable);
-        if (d.non_readable)
-            try self.decorateMember(target, index, .NonReadable);
-
-        if (d.builtin) {
-            try self.decorateMember(target, index, .{
-                .BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) },
-            });
-            extra_i += 1;
-        }
-        if (d.stream) {
-            try self.decorateMember(target, index, .{
-                .Stream = .{ .stream_number = extra[extra_i] },
-            });
-            extra_i += 1;
-        }
-        if (d.location) {
-            try self.decorateMember(target, index, .{
-                .Location = .{ .location = extra[extra_i] },
-            });
-            extra_i += 1;
-        }
-        if (d.component) {
-            try self.decorateMember(target, index, .{
-                .Component = .{ .component = extra[extra_i] },
-            });
-            extra_i += 1;
-        }
-        if (d.xfb_buffer) {
-            try self.decorateMember(target, index, .{
-                .XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] },
-            });
-            extra_i += 1;
-        }
-        if (d.xfb_stride) {
-            try self.decorateMember(target, index, .{
-                .XfbStride = .{ .xfb_stride = extra[extra_i] },
-            });
-            extra_i += 1;
-        }
-        if (d.user_semantic) {
-            const len = extra[extra_i];
-            extra_i += 1;
-            const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len];
-            try self.decorateMember(target, index, .{
-                .UserSemantic = .{ .semantic = semantic },
-            });
-            extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable;
-        }
-    }
-}
-
-pub fn simpleStructType(self: *Module, members: []const Type.Payload.Struct.Member) !Type.Ref {
-    const payload = try self.arena.create(Type.Payload.Struct);
-    payload.* = .{
-        .members = try self.arena.dupe(Type.Payload.Struct.Member, members),
-        .decorations = .{},
-    };
-    return try self.resolveType(Type.initPayload(&payload.base));
+pub fn intType(self: *Module, signedness: std.builtin.Signedness, bits: u16) !CacheRef {
+    return try self.resolve(.{ .int_type = .{
+        .signedness = signedness,
+        .bits = bits,
+    } });
 }
 
-pub fn arrayType2(self: *Module, len: u32, elem_ty_ref: TypeConstantCache.Ref) !TypeConstantCache.Ref {
+pub fn arrayType(self: *Module, len: u32, elem_ty_ref: CacheRef) !CacheRef {
     const len_ty_ref = try self.resolve(.{ .int_type = .{
         .signedness = .unsigned,
         .bits = 32,
@@ -788,41 +439,45 @@ pub fn arrayType2(self: *Module, len: u32, elem_ty_ref: TypeConstantCache.Ref) !
     } });
 }
 
-pub fn arrayType(self: *Module, len: u32, ty: Type.Ref) !Type.Ref {
-    const payload = try self.arena.create(Type.Payload.Array);
-    payload.* = .{
-        .element_type = ty,
-        .length = len,
-    };
-    return try self.resolveType(Type.initPayload(&payload.base));
-}
-
 pub fn ptrType(
     self: *Module,
-    child: Type.Ref,
+    child: CacheRef,
     storage_class: spec.StorageClass,
-    alignment: u32,
-) !Type.Ref {
-    const ptr_payload = try self.arena.create(Type.Payload.Pointer);
-    ptr_payload.* = .{
+) !CacheRef {
+    return try self.resolve(.{ .ptr_type = .{
         .storage_class = storage_class,
         .child_type = child,
-        .alignment = alignment,
-    };
-    return try self.resolveType(Type.initPayload(&ptr_payload.base));
+    } });
+}
+
+pub fn constInt(self: *Module, ty_ref: CacheRef, value: anytype) !IdRef {
+    const ty = self.cache.lookup(ty_ref).int_type;
+    const Value = Cache.Key.Int.Value;
+    return try self.resolveId(.{ .int = .{
+        .ty = ty_ref,
+        .value = switch (ty.signedness) {
+            .signed => Value{ .int64 = @intCast(i64, value) },
+            .unsigned => Value{ .uint64 = @intCast(u64, value) },
+        },
+    } });
+}
+
+pub fn constUndef(self: *Module, ty_ref: CacheRef) !IdRef {
+    return try self.resolveId(.{ .undef = .{ .ty = ty_ref } });
+}
+
+pub fn constNull(self: *Module, ty_ref: CacheRef) !IdRef {
+    return try self.resolveId(.{ .null = .{ .ty = ty_ref } });
 }
 
-pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref {
-    const payload = try self.arena.create(Type.Payload.Pointer);
-    payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*;
-    payload.storage_class = new_storage_class;
-    return try self.resolveType(Type.initPayload(&payload.base));
+pub fn constBool(self: *Module, ty_ref: CacheRef, value: bool) !IdRef {
+    return try self.resolveId(.{ .bool = .{ .ty = ty_ref, .value = value } });
 }
 
-pub fn constComposite(self: *Module, ty_ref: Type.Ref, members: []const IdRef) !IdRef {
+pub fn constComposite(self: *Module, ty_ref: CacheRef, members: []const IdRef) !IdRef {
     const result_id = self.allocId();
     try self.sections.types_globals_constants.emit(self.gpa, .OpSpecConstantComposite, .{
-        .id_result_type = self.typeId(ty_ref),
+        .id_result_type = self.resultId(ty_ref),
         .id_result = result_id,
         .constituents = members,
     });
src/codegen/spirv/type.zig
@@ -1,567 +0,0 @@
-//! This module models a SPIR-V Type. These are distinct from Zig types, with some types
-//! which are not representable by Zig directly.
-
-const std = @import("std");
-const assert = std.debug.assert;
-const Signedness = std.builtin.Signedness;
-const Allocator = std.mem.Allocator;
-
-const spec = @import("spec.zig");
-
-pub const Type = extern union {
-    tag_if_small_enough: Tag,
-    ptr_otherwise: *Payload,
-
-    /// A reference to another SPIR-V type.
-    pub const Ref = enum(u32) { _ };
-
-    pub fn initTag(comptime small_tag: Tag) Type {
-        comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
-        return .{ .tag_if_small_enough = small_tag };
-    }
-
-    pub fn initPayload(pl: *Payload) Type {
-        assert(@enumToInt(pl.tag) >= Tag.no_payload_count);
-        return .{ .ptr_otherwise = pl };
-    }
-
-    pub fn int(arena: Allocator, signedness: Signedness, bits: u16) !Type {
-        const bits_and_signedness = switch (signedness) {
-            .signed => -@as(i32, bits),
-            .unsigned => @as(i32, bits),
-        };
-
-        return switch (bits_and_signedness) {
-            8 => initTag(.u8),
-            16 => initTag(.u16),
-            32 => initTag(.u32),
-            64 => initTag(.u64),
-            -8 => initTag(.i8),
-            -16 => initTag(.i16),
-            -32 => initTag(.i32),
-            -64 => initTag(.i64),
-            else => {
-                const int_payload = try arena.create(Payload.Int);
-                int_payload.* = .{
-                    .width = bits,
-                    .signedness = signedness,
-                };
-                return initPayload(&int_payload.base);
-            },
-        };
-    }
-
-    pub fn float(bits: u16) Type {
-        return switch (bits) {
-            16 => initTag(.f16),
-            32 => initTag(.f32),
-            64 => initTag(.f64),
-            else => unreachable, // Enable more types if required.
-        };
-    }
-
-    pub fn tag(self: Type) Tag {
-        if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) {
-            return self.tag_if_small_enough;
-        } else {
-            return self.ptr_otherwise.tag;
-        }
-    }
-
-    pub fn castTag(self: Type, comptime t: Tag) ?*t.Type() {
-        if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count)
-            return null;
-
-        if (self.ptr_otherwise.tag == t)
-            return self.payload(t);
-
-        return null;
-    }
-
-    /// Access the payload of a type directly.
-    pub fn payload(self: Type, comptime t: Tag) *t.Type() {
-        assert(self.tag() == t);
-        return @fieldParentPtr(t.Type(), "base", self.ptr_otherwise);
-    }
-
-    /// Perform a shallow equality test, comparing two types while assuming that any child types
-    /// are equal only if their references are equal.
-    pub fn eqlShallow(a: Type, b: Type) bool {
-        if (a.tag_if_small_enough == b.tag_if_small_enough)
-            return true;
-
-        const tag_a = a.tag();
-        const tag_b = b.tag();
-        if (tag_a != tag_b)
-            return false;
-
-        inline for (@typeInfo(Tag).Enum.fields) |field| {
-            const t = @field(Tag, field.name);
-            if (t == tag_a) {
-                return eqlPayloads(t, a, b);
-            }
-        }
-
-        unreachable;
-    }
-
-    /// Compare the payload of two compatible tags, given that we already know the tag of both types.
-    fn eqlPayloads(comptime t: Tag, a: Type, b: Type) bool {
-        switch (t) {
-            .void,
-            .bool,
-            .sampler,
-            .event,
-            .device_event,
-            .reserve_id,
-            .queue,
-            .pipe_storage,
-            .named_barrier,
-            .u8,
-            .u16,
-            .u32,
-            .u64,
-            .i8,
-            .i16,
-            .i32,
-            .i64,
-            .f16,
-            .f32,
-            .f64,
-            => return true,
-            .int,
-            .vector,
-            .matrix,
-            .sampled_image,
-            .array,
-            .runtime_array,
-            .@"opaque",
-            .pointer,
-            .pipe,
-            .image,
-            => return std.meta.eql(a.payload(t).*, b.payload(t).*),
-            .@"struct" => {
-                const struct_a = a.payload(.@"struct");
-                const struct_b = b.payload(.@"struct");
-                if (struct_a.members.len != struct_b.members.len)
-                    return false;
-                for (struct_a.members, 0..) |mem_a, i| {
-                    if (!std.meta.eql(mem_a, struct_b.members[i]))
-                        return false;
-                }
-                return true;
-            },
-            .function => {
-                const fn_a = a.payload(.function);
-                const fn_b = b.payload(.function);
-                if (fn_a.return_type != fn_b.return_type)
-                    return false;
-                return std.mem.eql(Ref, fn_a.parameters, fn_b.parameters);
-            },
-        }
-    }
-
-    /// Perform a shallow hash, which hashes the reference value of child types instead of recursing.
-    pub fn hashShallow(self: Type) u64 {
-        var hasher = std.hash.Wyhash.init(0);
-        const t = self.tag();
-        std.hash.autoHash(&hasher, t);
-
-        inline for (@typeInfo(Tag).Enum.fields) |field| {
-            if (@field(Tag, field.name) == t) {
-                switch (@field(Tag, field.name)) {
-                    .void,
-                    .bool,
-                    .sampler,
-                    .event,
-                    .device_event,
-                    .reserve_id,
-                    .queue,
-                    .pipe_storage,
-                    .named_barrier,
-                    .u8,
-                    .u16,
-                    .u32,
-                    .u64,
-                    .i8,
-                    .i16,
-                    .i32,
-                    .i64,
-                    .f16,
-                    .f32,
-                    .f64,
-                    => {},
-                    else => self.hashPayload(@field(Tag, field.name), &hasher),
-                }
-            }
-        }
-
-        return hasher.final();
-    }
-
-    /// Perform a shallow hash, given that we know the tag of the field ahead of time.
-    fn hashPayload(self: Type, comptime t: Tag, hasher: *std.hash.Wyhash) void {
-        const fields = @typeInfo(t.Type()).Struct.fields;
-        const pl = self.payload(t);
-        comptime assert(std.mem.eql(u8, fields[0].name, "base"));
-        inline for (fields[1..]) |field| { // Skip the 'base' field.
-            std.hash.autoHashStrat(hasher, @field(pl, field.name), .DeepRecursive);
-        }
-    }
-
-    /// Hash context that hashes and compares types in a shallow fashion, useful for type caches.
-    pub const ShallowHashContext32 = struct {
-        pub fn hash(self: @This(), t: Type) u32 {
-            _ = self;
-            return @truncate(u32, t.hashShallow());
-        }
-        pub fn eql(self: @This(), a: Type, b: Type, b_index: usize) bool {
-            _ = self;
-            _ = b_index;
-            return a.eqlShallow(b);
-        }
-    };
-
-    /// Return the reference to any child type. Asserts the type is one of:
-    /// - Vectors
-    /// - Matrices
-    /// - Images
-    /// - SampledImages,
-    /// - Arrays
-    /// - RuntimeArrays
-    /// - Pointers
-    pub fn childType(self: Type) Ref {
-        return switch (self.tag()) {
-            .vector => self.payload(.vector).component_type,
-            .matrix => self.payload(.matrix).column_type,
-            .image => self.payload(.image).sampled_type,
-            .sampled_image => self.payload(.sampled_image).image_type,
-            .array => self.payload(.array).element_type,
-            .runtime_array => self.payload(.runtime_array).element_type,
-            .pointer => self.payload(.pointer).child_type,
-            else => unreachable,
-        };
-    }
-
-    pub fn isInt(self: Type) bool {
-        return switch (self.tag()) {
-            .u8,
-            .u16,
-            .u32,
-            .u64,
-            .i8,
-            .i16,
-            .i32,
-            .i64,
-            .int,
-            => true,
-            else => false,
-        };
-    }
-
-    pub fn isFloat(self: Type) bool {
-        return switch (self.tag()) {
-            .f16, .f32, .f64 => true,
-            else => false,
-        };
-    }
-
-    /// Returns the number of bits that make up an int or float type.
-    /// Asserts type is either int or float.
-    pub fn intFloatBits(self: Type) u16 {
-        return switch (self.tag()) {
-            .u8, .i8 => 8,
-            .u16, .i16, .f16 => 16,
-            .u32, .i32, .f32 => 32,
-            .u64, .i64, .f64 => 64,
-            .int => self.payload(.int).width,
-            else => unreachable,
-        };
-    }
-
-    /// Returns the signedness of an integer type.
-    /// Asserts that the type is an int.
-    pub fn intSignedness(self: Type) Signedness {
-        return switch (self.tag()) {
-            .u8, .u16, .u32, .u64 => .unsigned,
-            .i8, .i16, .i32, .i64 => .signed,
-            .int => self.payload(.int).signedness,
-            else => unreachable,
-        };
-    }
-
-    pub const Tag = enum(usize) {
-        void,
-        bool,
-        sampler,
-        event,
-        device_event,
-        reserve_id,
-        queue,
-        pipe_storage,
-        named_barrier,
-        u8,
-        u16,
-        u32,
-        u64,
-        i8,
-        i16,
-        i32,
-        i64,
-        f16,
-        f32,
-        f64,
-
-        // After this, the tag requires a payload.
-        int,
-        vector,
-        matrix,
-        image,
-        sampled_image,
-        array,
-        runtime_array,
-        @"struct",
-        @"opaque",
-        pointer,
-        function,
-        pipe,
-
-        pub const last_no_payload_tag = Tag.f64;
-        pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
-
-        pub fn Type(comptime t: Tag) type {
-            return switch (t) {
-                .void,
-                .bool,
-                .sampler,
-                .event,
-                .device_event,
-                .reserve_id,
-                .queue,
-                .pipe_storage,
-                .named_barrier,
-                .u8,
-                .u16,
-                .u32,
-                .u64,
-                .i8,
-                .i16,
-                .i32,
-                .i64,
-                .f16,
-                .f32,
-                .f64,
-                => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
-                .int => Payload.Int,
-                .vector => Payload.Vector,
-                .matrix => Payload.Matrix,
-                .image => Payload.Image,
-                .sampled_image => Payload.SampledImage,
-                .array => Payload.Array,
-                .runtime_array => Payload.RuntimeArray,
-                .@"struct" => Payload.Struct,
-                .@"opaque" => Payload.Opaque,
-                .pointer => Payload.Pointer,
-                .function => Payload.Function,
-                .pipe => Payload.Pipe,
-            };
-        }
-    };
-
-    pub const Payload = struct {
-        tag: Tag,
-
-        pub const Int = struct {
-            base: Payload = .{ .tag = .int },
-            width: u16,
-            signedness: Signedness,
-        };
-
-        pub const Vector = struct {
-            base: Payload = .{ .tag = .vector },
-            component_type: Ref,
-            component_count: u32,
-        };
-
-        pub const Matrix = struct {
-            base: Payload = .{ .tag = .matrix },
-            column_type: Ref,
-            column_count: u32,
-        };
-
-        pub const Image = struct {
-            base: Payload = .{ .tag = .image },
-            sampled_type: Ref,
-            dim: spec.Dim,
-            depth: enum(u2) {
-                no = 0,
-                yes = 1,
-                maybe = 2,
-            },
-            arrayed: bool,
-            multisampled: bool,
-            sampled: enum(u2) {
-                known_at_runtime = 0,
-                with_sampler = 1,
-                without_sampler = 2,
-            },
-            format: spec.ImageFormat,
-            access_qualifier: ?spec.AccessQualifier,
-        };
-
-        pub const SampledImage = struct {
-            base: Payload = .{ .tag = .sampled_image },
-            image_type: Ref,
-        };
-
-        pub const Array = struct {
-            base: Payload = .{ .tag = .array },
-            element_type: Ref,
-            /// Note: Must be emitted as constant, not as literal!
-            length: u32,
-            /// Type has the 'ArrayStride' decoration.
-            /// If zero, no stride is present.
-            array_stride: u32 = 0,
-        };
-
-        pub const RuntimeArray = struct {
-            base: Payload = .{ .tag = .runtime_array },
-            element_type: Ref,
-            /// Type has the 'ArrayStride' decoration.
-            /// If zero, no stride is present.
-            array_stride: u32 = 0,
-        };
-
-        pub const Struct = struct {
-            base: Payload = .{ .tag = .@"struct" },
-            members: []Member,
-            name: []const u8 = "",
-            decorations: StructDecorations = .{},
-
-            /// Extra information for decorations, packed for efficiency. Fields are stored sequentially by
-            /// order of the `members` slice and `MemberDecorations` struct.
-            member_decoration_extra: []u32 = &.{},
-
-            pub const Member = struct {
-                ty: Ref,
-                name: []const u8 = "",
-                offset: MemberOffset = .none,
-                decorations: MemberDecorations = .{},
-            };
-
-            pub const MemberOffset = enum(u32) { none = 0xFFFF_FFFF, _ };
-
-            pub const StructDecorations = packed struct {
-                /// Type has the 'Block' decoration.
-                block: bool = false,
-                /// Type has the 'BufferBlock' decoration.
-                buffer_block: bool = false,
-                /// Type has the 'GLSLShared' decoration.
-                glsl_shared: bool = false,
-                /// Type has the 'GLSLPacked' decoration.
-                glsl_packed: bool = false,
-                /// Type has the 'CPacked' decoration.
-                c_packed: bool = false,
-            };
-
-            pub const MemberDecorations = packed struct {
-                /// Matrix layout for (arrays of) matrices. If this field is not .none,
-                /// then there is also an extra field containing the matrix stride corresponding
-                /// to the 'MatrixStride' decoration.
-                matrix_layout: enum(u2) {
-                    /// Member has the 'RowMajor' decoration. The member type
-                    /// must be a matrix or an array of matrices.
-                    row_major,
-                    /// Member has the 'ColMajor' decoration. The member type
-                    /// must be a matrix or an array of matrices.
-                    col_major,
-                    /// Member is not a matrix or array of matrices.
-                    none,
-                } = .none,
-
-                // Regular decorations, these do not imply extra fields.
-
-                /// Member has the 'NoPerspective' decoration.
-                no_perspective: bool = false,
-                /// Member has the 'Flat' decoration.
-                flat: bool = false,
-                /// Member has the 'Patch' decoration.
-                patch: bool = false,
-                /// Member has the 'Centroid' decoration.
-                centroid: bool = false,
-                /// Member has the 'Sample' decoration.
-                sample: bool = false,
-                /// Member has the 'Invariant' decoration.
-                /// Note: requires parent struct to have 'Block'.
-                invariant: bool = false,
-                /// Member has the 'Volatile' decoration.
-                @"volatile": bool = false,
-                /// Member has the 'Coherent' decoration.
-                coherent: bool = false,
-                /// Member has the 'NonWritable' decoration.
-                non_writable: bool = false,
-                /// Member has the 'NonReadable' decoration.
-                non_readable: bool = false,
-
-                // The following decorations all imply extra field(s).
-
-                /// Member has the 'BuiltIn' decoration.
-                /// This decoration has an extra field of type `spec.BuiltIn`.
-                /// Note: If any member of a struct has the BuiltIn decoration, all members must have one.
-                /// Note: Each builtin may only be reachable once for a particular entry point.
-                /// Note: The member type may be constrained by a particular built-in, defined in the client API specification.
-                builtin: bool = false,
-                /// Member has the 'Stream' decoration.
-                /// This member has an extra field of type `u32`.
-                stream: bool = false,
-                /// Member has the 'Location' decoration.
-                /// This member has an extra field of type `u32`.
-                location: bool = false,
-                /// Member has the 'Component' decoration.
-                /// This member has an extra field of type `u32`.
-                component: bool = false,
-                /// Member has the 'XfbBuffer' decoration.
-                /// This member has an extra field of type `u32`.
-                xfb_buffer: bool = false,
-                /// Member has the 'XfbStride' decoration.
-                /// This member has an extra field of type `u32`.
-                xfb_stride: bool = false,
-                /// Member has the 'UserSemantic' decoration.
-                /// This member has an extra field of type `[]u8`, which is encoded
-                /// by an `u32` containing the number of chars exactly, and then the string padded to
-                /// a multiple of 4 bytes with zeroes.
-                user_semantic: bool = false,
-            };
-        };
-
-        pub const Opaque = struct {
-            base: Payload = .{ .tag = .@"opaque" },
-            name: []u8,
-        };
-
-        pub const Pointer = struct {
-            base: Payload = .{ .tag = .pointer },
-            storage_class: spec.StorageClass,
-            child_type: Ref,
-            /// Type has the 'ArrayStride' decoration.
-            /// This is valid for pointers to elements of an array.
-            /// If zero, no stride is present.
-            array_stride: u32 = 0,
-            /// If nonzero, type has the 'Alignment' decoration.
-            alignment: u32 = 0,
-            /// Type has the 'MaxByteOffset' decoration.
-            max_byte_offset: ?u32 = null,
-        };
-
-        pub const Function = struct {
-            base: Payload = .{ .tag = .function },
-            return_type: Ref,
-            parameters: []Ref,
-        };
-
-        pub const Pipe = struct {
-            base: Payload = .{ .tag = .pipe },
-            qualifier: spec.AccessQualifier,
-        };
-    };
-};
src/codegen/spirv/TypeConstantCache.zig
@@ -111,6 +111,18 @@ const Tag = enum {
     /// Value of type f64
     /// data is payload to Float16
     float64,
+    /// Undefined value
+    /// data is type
+    undef,
+    /// Null value
+    /// data is type
+    null,
+    /// Bool value that is true
+    /// data is (bool) type
+    bool_true,
+    /// Bool value that is false
+    /// data is (bool) type
+    bool_false,
 
     const SimpleType = enum { void, bool };
 
@@ -227,6 +239,9 @@ pub const Key = union(enum) {
     // -- values
     int: Int,
     float: Float,
+    undef: Undef,
+    null: Null,
+    bool: Bool,
 
     pub const IntType = std.builtin.Type.Int;
     pub const FloatType = std.builtin.Type.Float;
@@ -323,6 +338,19 @@ pub const Key = union(enum) {
         };
     };
 
+    pub const Undef = struct {
+        ty: Ref,
+    };
+
+    pub const Null = struct {
+        ty: Ref,
+    };
+
+    pub const Bool = struct {
+        ty: Ref,
+        value: bool,
+    };
+
     fn hash(self: Key) u32 {
         var hasher = std.hash.Wyhash.init(0);
         switch (self) {
@@ -539,6 +567,32 @@ fn emit(
                 .value = lit,
             });
         },
+        .undef => |undef| {
+            try section.emit(spv.gpa, .OpUndef, .{
+                .id_result_type = self.resultId(undef.ty),
+                .id_result = result_id,
+            });
+        },
+        .null => |null_info| {
+            try section.emit(spv.gpa, .OpConstantNull, .{
+                .id_result_type = self.resultId(null_info.ty),
+                .id_result = result_id,
+            });
+        },
+        .bool => |bool_info| switch (bool_info.value) {
+            true => {
+                try section.emit(spv.gpa, .OpConstantTrue, .{
+                    .id_result_type = self.resultId(bool_info.ty),
+                    .id_result = result_id,
+                });
+            },
+            false => {
+                try section.emit(spv.gpa, .OpConstantFalse, .{
+                    .id_result_type = self.resultId(bool_info.ty),
+                    .id_result = result_id,
+                });
+            },
+        },
     }
 }
 
@@ -713,6 +767,24 @@ pub fn resolve(self: *Self, spv: *Module, key: Key) !Ref {
             },
             else => unreachable,
         },
+        .undef => |undef| .{
+            .tag = .undef,
+            .result_id = result_id,
+            .data = @enumToInt(undef.ty),
+        },
+        .null => |null_info| .{
+            .tag = .null,
+            .result_id = result_id,
+            .data = @enumToInt(null_info.ty),
+        },
+        .bool => |bool_info| .{
+            .tag = switch (bool_info.value) {
+                true => Tag.bool_true,
+                false => Tag.bool_false,
+            },
+            .result_id = result_id,
+            .data = @enumToInt(bool_info.ty),
+        },
     };
     try self.items.append(spv.gpa, item);
 
@@ -850,6 +922,20 @@ pub fn lookup(self: *const Self, ref: Ref) Key {
                 .value = .{ .uint64 = payload.decode() },
             } };
         },
+        .undef => .{ .undef = .{
+            .ty = @intToEnum(Ref, data),
+        } },
+        .null => .{ .null = .{
+            .ty = @intToEnum(Ref, data),
+        } },
+        .bool_true => .{ .bool = .{
+            .ty = @intToEnum(Ref, data),
+            .value = true,
+        } },
+        .bool_false => .{ .bool = .{
+            .ty = @intToEnum(Ref, data),
+            .value = false,
+        } },
     };
 }
 
src/codegen/spirv.zig
@@ -22,11 +22,10 @@ const IdResultType = spec.IdResultType;
 const StorageClass = spec.StorageClass;
 
 const SpvModule = @import("spirv/Module.zig");
-const SpvCacheRef = SpvModule.TypeConstantCache.Ref;
-const SpvCacheString = SpvModule.TypeConstantCache.String;
+const CacheRef = SpvModule.CacheRef;
+const CacheString = SpvModule.CacheString;
 
 const SpvSection = @import("spirv/Section.zig");
-const SpvType = @import("spirv/type.zig").Type;
 const SpvAssembler = @import("spirv/Assembler.zig");
 
 const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
@@ -380,74 +379,23 @@ pub const DeclGen = struct {
         };
     }
 
-    fn genConstInt(self: *DeclGen, ty_ref: SpvType.Ref, result_id: IdRef, value: anytype) !void {
-        const ty = self.spv.typeRefType(ty_ref);
-        const ty_id = self.typeId(ty_ref);
-
-        const Lit = spec.LiteralContextDependentNumber;
-        const literal = switch (ty.intSignedness()) {
-            .signed => switch (ty.intFloatBits()) {
-                1...32 => Lit{ .int32 = @intCast(i32, value) },
-                33...64 => Lit{ .int64 = @intCast(i64, value) },
-                else => unreachable, // TODO: composite integer literals
-            },
-            .unsigned => switch (ty.intFloatBits()) {
-                1...32 => Lit{ .uint32 = @intCast(u32, value) },
-                33...64 => Lit{ .uint64 = @intCast(u64, value) },
-                else => unreachable,
-            },
-        };
-
-        try self.spv.emitConstant(ty_id, result_id, literal);
-    }
-
-    fn constInt(self: *DeclGen, ty_ref: SpvType.Ref, value: anytype) !IdRef {
-        const result_id = self.spv.allocId();
-        try self.genConstInt(ty_ref, result_id, value);
-        return result_id;
-    }
-
-    fn constUndef(self: *DeclGen, ty_ref: SpvType.Ref) !IdRef {
-        const result_id = self.spv.allocId();
-        try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpUndef, .{
-            .id_result_type = self.typeId(ty_ref),
-            .id_result = result_id,
-        });
-        return result_id;
-    }
-
-    fn constNull(self: *DeclGen, ty_ref: SpvType.Ref) !IdRef {
-        const result_id = self.spv.allocId();
-        try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantNull, .{
-            .id_result_type = self.typeId(ty_ref),
-            .id_result = result_id,
-        });
-        return result_id;
-    }
-
+    /// Emits a bool constant in a particular representation.
     fn constBool(self: *DeclGen, value: bool, repr: Repr) !IdRef {
         switch (repr) {
             .indirect => {
                 const int_ty_ref = try self.intType(.unsigned, 1);
-                return self.constInt(int_ty_ref, @boolToInt(value));
+                return self.spv.constInt(int_ty_ref, @boolToInt(value));
             },
             .direct => {
                 const bool_ty_ref = try self.resolveType(Type.bool, .direct);
-                const result_id = self.spv.allocId();
-                const operands = .{ .id_result_type = self.typeId(bool_ty_ref), .id_result = result_id };
-                if (value) {
-                    try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantTrue, operands);
-                } else {
-                    try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantFalse, operands);
-                }
-                return result_id;
+                return self.spv.constBool(bool_ty_ref, value);
             },
         }
     }
 
     /// Construct a struct at runtime.
     /// result_ty_ref must be a struct type.
-    fn constructStruct(self: *DeclGen, result_ty_ref: SpvType.Ref, constituents: []const IdRef) !IdRef {
+    fn constructStruct(self: *DeclGen, result_ty_ref: CacheRef, constituents: []const IdRef) !IdRef {
         // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which'
         // operands are not constant.
         // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349
@@ -456,11 +404,13 @@ pub const DeclGen = struct {
         const ptr_composite_id = try self.alloc(result_ty_ref, null);
         // Note: using 32-bit ints here because usize crashes the translator as well
         const index_ty_ref = try self.intType(.unsigned, 32);
-        const spv_composite_ty = self.spv.typeRefType(result_ty_ref);
-        const members = spv_composite_ty.payload(.@"struct").members;
-        for (constituents, members, 0..) |constitent_id, member, index| {
-            const index_id = try self.constInt(index_ty_ref, index);
-            const ptr_member_ty_ref = try self.spv.ptrType(member.ty, .Generic, 0);
+
+        const spv_composite_ty = self.spv.cache.lookup(result_ty_ref).struct_type;
+        const member_types = spv_composite_ty.member_types;
+
+        for (constituents, member_types, 0..) |constitent_id, member_ty_ref, index| {
+            const index_id = try self.spv.constInt(index_ty_ref, index);
+            const ptr_member_ty_ref = try self.spv.ptrType(member_ty_ref, .Generic);
             const ptr_id = try self.accessChain(ptr_member_ty_ref, ptr_composite_id, &.{index_id});
             try self.func.body.emit(self.spv.gpa, .OpStore, .{
                 .pointer = ptr_id,
@@ -481,11 +431,11 @@ pub const DeclGen = struct {
 
         dg: *DeclGen,
         /// Cached reference of the u32 type.
-        u32_ty_ref: SpvType.Ref,
+        u32_ty_ref: CacheRef,
         /// Cached type id of the u32 type.
         u32_ty_id: IdRef,
         /// The members of the resulting structure type
-        members: std.ArrayList(SpvType.Payload.Struct.Member),
+        members: std.ArrayList(CacheRef),
         /// The initializers of each of the members.
         initializers: std.ArrayList(IdRef),
         /// The current size of the structure. Includes
@@ -519,7 +469,7 @@ pub const DeclGen = struct {
             const result_id = self.dg.spv.allocId();
             // TODO: Integrate with caching mechanism
             try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word });
-            try self.members.append(.{ .ty = self.u32_ty_ref });
+            try self.members.append(self.u32_ty_ref);
             try self.initializers.append(result_id);
 
             self.partial_word.len = 0;
@@ -555,7 +505,7 @@ pub const DeclGen = struct {
             }
         }
 
-        fn addPtr(self: *@This(), ptr_ty_ref: SpvType.Ref, ptr_id: IdRef) !void {
+        fn addPtr(self: *@This(), ptr_ty_ref: CacheRef, ptr_id: IdRef) !void {
             // TODO: Double check pointer sizes here.
             // shared pointers might be u32...
             const target = self.dg.getTarget();
@@ -563,12 +513,12 @@ pub const DeclGen = struct {
             if (self.size % width != 0) {
                 return self.dg.todo("misaligned pointer constants", .{});
             }
-            try self.members.append(.{ .ty = ptr_ty_ref });
+            try self.members.append(ptr_ty_ref);
             try self.initializers.append(ptr_id);
             self.size += width;
         }
 
-        fn addNullPtr(self: *@This(), ptr_ty_ref: SpvType.Ref) !void {
+        fn addNullPtr(self: *@This(), ptr_ty_ref: CacheRef) !void {
             const result_id = self.dg.spv.allocId();
             try self.dg.spv.sections.types_globals_constants.emit(self.dg.spv.gpa, .OpConstantNull, .{
                 .id_result_type = self.dg.typeId(ptr_ty_ref),
@@ -931,7 +881,7 @@ pub const DeclGen = struct {
         const section = &self.spv.globals.section;
 
         const ty_ref = try self.resolveType(ty, .indirect);
-        const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, 0);
+        const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class);
 
         // const target = self.getTarget();
 
@@ -960,7 +910,7 @@ pub const DeclGen = struct {
             .dg = self,
             .u32_ty_ref = u32_ty_ref,
             .u32_ty_id = self.typeId(u32_ty_ref),
-            .members = std.ArrayList(SpvType.Payload.Struct.Member).init(self.gpa),
+            .members = std.ArrayList(CacheRef).init(self.gpa),
             .initializers = std.ArrayList(IdRef).init(self.gpa),
             .decl_deps = std.AutoArrayHashMap(SpvModule.Decl.Index, void).init(self.gpa),
         };
@@ -972,8 +922,10 @@ pub const DeclGen = struct {
         try icl.lower(ty, val);
         try icl.flush();
 
-        const constant_struct_ty_ref = try self.spv.simpleStructType(icl.members.items);
-        const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class, 0);
+        const constant_struct_ty_ref = try self.spv.resolve(.{ .struct_type = .{
+            .member_types = icl.members.items,
+        } });
+        const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class);
 
         const constant_struct_id = self.spv.allocId();
         try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{
@@ -1007,7 +959,7 @@ pub const DeclGen = struct {
         });
 
         if (cast_to_generic) {
-            const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0);
+            const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic);
             try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
                 .id_result_type = self.typeId(generic_ptr_ty_ref),
                 .id_result = result_id,
@@ -1044,9 +996,9 @@ pub const DeclGen = struct {
         switch (ty.zigTypeTag()) {
             .Int => {
                 if (ty.isSignedInt()) {
-                    return try self.constInt(result_ty_ref, val.toSignedInt(target));
+                    return try self.spv.constInt(result_ty_ref, val.toSignedInt(target));
                 } else {
-                    return try self.constInt(result_ty_ref, val.toUnsignedInt(target));
+                    return try self.spv.constInt(result_ty_ref, val.toUnsignedInt(target));
                 }
             },
             .Bool => switch (repr) {
@@ -1060,7 +1012,7 @@ pub const DeclGen = struct {
                     }
                     return result_id;
                 },
-                .indirect => return try self.constInt(result_ty_ref, @boolToInt(val.toBool())),
+                .indirect => return try self.spv.constInt(result_ty_ref, @boolToInt(val.toBool())),
             },
             .Float => {
                 const result_id = self.spv.allocId();
@@ -1084,7 +1036,7 @@ pub const DeclGen = struct {
                     else => unreachable,
                 };
 
-                return try self.constInt(result_ty_ref, value);
+                return try self.spv.constInt(result_ty_ref, value);
             },
             .ErrorUnion => {
                 const payload_ty = ty.errorUnionPayload();
@@ -1143,45 +1095,31 @@ pub const DeclGen = struct {
     /// Turn a Zig type into a SPIR-V Type, and return its type result-id.
     fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType {
         const type_ref = try self.resolveType(ty, .direct);
-        return self.typeId(type_ref);
+        return self.spv.resultId(type_ref);
     }
 
-    fn typeId(self: *DeclGen, ty_ref: SpvType.Ref) IdRef {
-        return self.spv.typeId(ty_ref);
+    fn typeId(self: *DeclGen, ty_ref: CacheRef) IdRef {
+        return self.spv.resultId(ty_ref);
     }
 
     /// Create an integer type suitable for storing at least 'bits' bits.
-    fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvType.Ref {
+    /// The integer type that is returned by this function is the type that is used to perform
+    /// actual operations (as well as store) a Zig type of a particular number of bits. To create
+    /// a type with an exact size, use SpvModule.intType.
+    fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !CacheRef {
         const backing_bits = self.backingIntBits(bits) orelse {
             // TODO: Integers too big for any native type are represented as "composite integers":
             // An array of largestSupportedIntBits.
             return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });
         };
-
-        return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits));
-    }
-
-    fn intType2(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvCacheRef {
-        const backing_bits = self.backingIntBits(bits) orelse {
-            // TODO: Integers too big for any native type are represented as "composite integers":
-            // An array of largestSupportedIntBits.
-            return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });
-        };
-        return try self.spv.resolve(.{ .int_type = .{
-            .signedness = signedness,
-            .bits = backing_bits,
-        } });
+        return self.spv.intType(signedness, backing_bits);
     }
 
     /// Create an integer type that represents 'usize'.
-    fn sizeType(self: *DeclGen) !SpvType.Ref {
+    fn sizeType(self: *DeclGen) !CacheRef {
         return try self.intType(.unsigned, self.getTarget().ptrBitWidth());
     }
 
-    fn sizeType2(self: *DeclGen) !SpvCacheRef {
-        return try self.intType2(.unsigned, self.getTarget().ptrBitWidth());
-    }
-
     /// Generate a union type, optionally with a known field. If the tag alignment is greater
     /// than that of the payload, a regular union (non-packed, with both tag and payload), will
     /// be generated as follows:
@@ -1204,7 +1142,7 @@ pub const DeclGen = struct {
     /// If any of the fields' size is 0, it will be omitted.
     /// NOTE: When the active field is set to something other than the most aligned field, the
     ///   resulting struct will be *underaligned*.
-    fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvType.Ref {
+    fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !CacheRef {
         const target = self.getTarget();
         const layout = ty.unionGetLayout(target);
         const union_ty = ty.cast(Type.Payload.Union).?.data;
@@ -1218,7 +1156,8 @@ pub const DeclGen = struct {
             return try self.resolveType(union_ty.tag_ty, .indirect);
         }
 
-        var members = std.BoundedArray(SpvType.Payload.Struct.Member, 4){};
+        var member_types = std.BoundedArray(CacheRef, 4){};
+        var member_names = std.BoundedArray(CacheString, 4){};
 
         const has_tag = layout.tag_size != 0;
         const tag_first = layout.tag_align >= layout.payload_align;
@@ -1226,82 +1165,6 @@ pub const DeclGen = struct {
 
         if (has_tag and tag_first) {
             const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect);
-            members.appendAssumeCapacity(.{ .name = "tag", .ty = tag_ty_ref });
-        }
-
-        const active_field = maybe_active_field orelse layout.most_aligned_field;
-        const active_field_ty = union_ty.fields.values()[active_field].ty;
-
-        const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: {
-            const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect);
-            members.appendAssumeCapacity(.{ .name = "payload", .ty = active_payload_ty_ref });
-            break :blk active_field_ty.abiSize(target);
-        } else 0;
-
-        const payload_padding_len = layout.payload_size - active_field_size;
-        if (payload_padding_len != 0) {
-            const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref);
-            members.appendAssumeCapacity(.{ .name = "padding_payload", .ty = payload_padding_ty_ref });
-        }
-
-        if (has_tag and !tag_first) {
-            const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect);
-            members.appendAssumeCapacity(.{ .name = "tag", .ty = tag_ty_ref });
-        }
-
-        if (layout.padding != 0) {
-            const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref);
-            members.appendAssumeCapacity(.{ .name = "padding", .ty = padding_ty_ref });
-        }
-
-        return try self.spv.simpleStructType(members.slice());
-    }
-
-    /// Generate a union type, optionally with a known field. If the tag alignment is greater
-    /// than that of the payload, a regular union (non-packed, with both tag and payload), will
-    /// be generated as follows:
-    /// If the active field is known:
-    ///  struct {
-    ///    tag: TagType,
-    ///    payload: ActivePayloadType,
-    ///    payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8,
-    ///    padding: [padding_size]u8,
-    ///  }
-    /// If the payload alignment is greater than that of the tag:
-    ///  struct {
-    ///    payload: ActivePayloadType,
-    ///    payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8,
-    ///    tag: TagType,
-    ///    padding: [padding_size]u8,
-    ///  }
-    /// If the active payload is unknown, it will default back to the most aligned field. This is
-    /// to make sure that the overal struct has the correct alignment in spir-v.
-    /// If any of the fields' size is 0, it will be omitted.
-    /// NOTE: When the active field is set to something other than the most aligned field, the
-    ///   resulting struct will be *underaligned*.
-    fn resolveUnionType2(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvCacheRef {
-        const target = self.getTarget();
-        const layout = ty.unionGetLayout(target);
-        const union_ty = ty.cast(Type.Payload.Union).?.data;
-
-        if (union_ty.layout == .Packed) {
-            return self.todo("packed union types", .{});
-        }
-
-        if (layout.payload_size == 0) {
-            // No payload, so represent this as just the tag type.
-            return try self.resolveType2(union_ty.tag_ty, .indirect);
-        }
-
-        var member_types = std.BoundedArray(SpvCacheRef, 4){};
-        var member_names = std.BoundedArray(SpvCacheString, 4){};
-
-        const has_tag = layout.tag_size != 0;
-        const tag_first = layout.tag_align >= layout.payload_align;
-        const u8_ty_ref = try self.intType2(.unsigned, 8); // TODO: What if Int8Type is not enabled?
-
-        if (has_tag and tag_first) {
-            const tag_ty_ref = try self.resolveType2(union_ty.tag_ty, .indirect);
             member_types.appendAssumeCapacity(tag_ty_ref);
             member_names.appendAssumeCapacity(try self.spv.resolveString("tag"));
         }
@@ -1310,7 +1173,7 @@ pub const DeclGen = struct {
         const active_field_ty = union_ty.fields.values()[active_field].ty;
 
         const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: {
-            const active_payload_ty_ref = try self.resolveType2(active_field_ty, .indirect);
+            const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect);
             member_types.appendAssumeCapacity(active_payload_ty_ref);
             member_names.appendAssumeCapacity(try self.spv.resolveString("payload"));
             break :blk active_field_ty.abiSize(target);
@@ -1318,19 +1181,19 @@ pub const DeclGen = struct {
 
         const payload_padding_len = layout.payload_size - active_field_size;
         if (payload_padding_len != 0) {
-            const payload_padding_ty_ref = try self.spv.arrayType2(@intCast(u32, payload_padding_len), u8_ty_ref);
+            const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref);
             member_types.appendAssumeCapacity(payload_padding_ty_ref);
             member_names.appendAssumeCapacity(try self.spv.resolveString("payload_padding"));
         }
 
         if (has_tag and !tag_first) {
-            const tag_ty_ref = try self.resolveType2(union_ty.tag_ty, .indirect);
+            const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect);
             member_types.appendAssumeCapacity(tag_ty_ref);
             member_names.appendAssumeCapacity(try self.spv.resolveString("tag"));
         }
 
         if (layout.padding != 0) {
-            const padding_ty_ref = try self.spv.arrayType2(layout.padding, u8_ty_ref);
+            const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref);
             member_types.appendAssumeCapacity(padding_ty_ref);
             member_names.appendAssumeCapacity(try self.spv.resolveString("padding"));
         }
@@ -1341,22 +1204,24 @@ pub const DeclGen = struct {
         } });
     }
 
-    fn resolveType2(self: *DeclGen, ty: Type, repr: Repr) Error!SpvCacheRef {
+    /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
+    fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!CacheRef {
+        log.debug("resolveType: ty = {}", .{ty.fmt(self.module)});
         const target = self.getTarget();
         switch (ty.zigTypeTag()) {
             .Void, .NoReturn => return try self.spv.resolve(.void_type),
             .Bool => switch (repr) {
                 .direct => return try self.spv.resolve(.bool_type),
-                .indirect => return try self.intType2(.unsigned, 1),
+                .indirect => return try self.intType(.unsigned, 1),
             },
             .Int => {
                 const int_info = ty.intInfo(target);
-                return try self.intType2(int_info.signedness, int_info.bits);
+                return try self.intType(int_info.signedness, int_info.bits);
             },
             .Enum => {
                 var buffer: Type.Payload.Bits = undefined;
                 const tag_ty = ty.intTagType(&buffer);
-                return self.resolveType2(tag_ty, repr);
+                return self.resolveType(tag_ty, repr);
             },
             .Float => {
                 // We can (and want) not really emulate floating points with other floating point types like with the integer types,
@@ -1378,11 +1243,11 @@ pub const DeclGen = struct {
             },
             .Array => {
                 const elem_ty = ty.childType();
-                const elem_ty_ref = try self.resolveType2(elem_ty, .direct);
+                const elem_ty_ref = try self.resolveType(elem_ty, .direct);
                 const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse {
                     return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()});
                 };
-                return self.spv.arrayType2(total_len, elem_ty_ref);
+                return self.spv.arrayType(total_len, elem_ty_ref);
             },
             .Fn => switch (repr) {
                 .direct => {
@@ -1390,12 +1255,12 @@ pub const DeclGen = struct {
                     if (ty.fnIsVarArgs())
                         return self.fail("VarArgs functions are unsupported for SPIR-V", .{});
 
-                    const param_ty_refs = try self.gpa.alloc(SpvCacheRef, ty.fnParamLen());
+                    const param_ty_refs = try self.gpa.alloc(CacheRef, ty.fnParamLen());
                     defer self.gpa.free(param_ty_refs);
                     for (param_ty_refs, 0..) |*param_type, i| {
-                        param_type.* = try self.resolveType2(ty.fnParamType(i), .direct);
+                        param_type.* = try self.resolveType(ty.fnParamType(i), .direct);
                     }
-                    const return_ty_ref = try self.resolveType2(ty.fnReturnType(), .direct);
+                    const return_ty_ref = try self.resolveType(ty.fnReturnType(), .direct);
 
                     return try self.spv.resolve(.{ .function_type = .{
                         .return_type = return_ty_ref,
@@ -1405,14 +1270,14 @@ pub const DeclGen = struct {
                 .indirect => {
                     // TODO: Represent function pointers properly.
                     // For now, just use an usize type.
-                    return try self.sizeType2();
+                    return try self.sizeType();
                 },
             },
             .Pointer => {
                 const ptr_info = ty.ptrInfo().data;
 
                 const storage_class = spvStorageClass(ptr_info.@"addrspace");
-                const child_ty_ref = try self.resolveType2(ptr_info.pointee_type, .indirect);
+                const child_ty_ref = try self.resolveType(ptr_info.pointee_type, .indirect);
                 const ptr_ty_ref = try self.spv.resolve(.{ .ptr_type = .{
                     .storage_class = storage_class,
                     .child_type = child_ty_ref,
@@ -1420,7 +1285,15 @@ pub const DeclGen = struct {
                 if (ptr_info.size != .Slice) {
                     return ptr_ty_ref;
                 }
-                unreachable; // TODO
+
+                const size_ty_ref = try self.sizeType();
+                return self.spv.resolve(.{ .struct_type = .{
+                    .member_types = &.{ ptr_ty_ref, size_ty_ref },
+                    .member_names = &.{
+                        try self.spv.resolveString("ptr"),
+                        try self.spv.resolveString("len"),
+                    },
+                } });
             },
             .Vector => {
                 // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
@@ -1433,33 +1306,47 @@ pub const DeclGen = struct {
                 // TODO: Properly verify sizes and child type.
 
                 return try self.spv.resolve(.{ .vector_type = .{
-                    .component_type = try self.resolveType2(ty.elemType(), repr),
+                    .component_type = try self.resolveType(ty.elemType(), repr),
                     .component_count = @intCast(u32, ty.vectorLen()),
                 } });
             },
             .Struct => {
                 if (ty.isSimpleTupleOrAnonStruct()) {
-                    unreachable; // TODO
+                    const tuple = ty.tupleFields();
+                    const member_types = try self.gpa.alloc(CacheRef, tuple.types.len);
+                    defer self.gpa.free(member_types);
+
+                    var member_index: usize = 0;
+                    for (tuple.types, 0..) |field_ty, i| {
+                        const field_val = tuple.values[i];
+                        if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue;
+
+                        member_types[member_index] = try self.resolveType(field_ty, .indirect);
+                        member_index += 1;
+                    }
+
+                    return try self.spv.resolve(.{ .struct_type = .{
+                        .member_types = member_types[0..member_index],
+                    } });
                 }
 
                 const struct_ty = ty.castTag(.@"struct").?.data;
 
                 if (struct_ty.layout == .Packed) {
-                    return try self.resolveType2(struct_ty.backing_int_ty, .direct);
+                    return try self.resolveType(struct_ty.backing_int_ty, .direct);
                 }
 
-                const member_types = try self.gpa.alloc(SpvCacheRef, struct_ty.fields.count());
+                const member_types = try self.gpa.alloc(CacheRef, struct_ty.fields.count());
                 defer self.gpa.free(member_types);
 
-                const member_names = try self.gpa.alloc(SpvCacheString, struct_ty.fields.count());
+                const member_names = try self.gpa.alloc(CacheString, struct_ty.fields.count());
                 defer self.gpa.free(member_names);
 
-                // const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count());
                 var member_index: usize = 0;
                 for (struct_ty.fields.values(), 0..) |field, i| {
                     if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
 
-                    member_types[member_index] = try self.resolveType2(field.ty, .indirect);
+                    member_types[member_index] = try self.resolveType(field.ty, .indirect);
                     member_names[member_index] = try self.spv.resolveString(struct_ty.fields.keys()[i]);
                     member_index += 1;
                 }
@@ -1480,16 +1367,16 @@ pub const DeclGen = struct {
                     // Just use a bool.
                     // Note: Always generate the bool with indirect format, to save on some sanity
                     // Perform the conversion to a direct bool when the field is extracted.
-                    return try self.resolveType2(Type.bool, .indirect);
+                    return try self.resolveType(Type.bool, .indirect);
                 }
 
-                const payload_ty_ref = try self.resolveType2(payload_ty, .indirect);
+                const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
                 if (ty.optionalReprIsPayload()) {
                     // Optional is actually a pointer or a slice.
                     return payload_ty_ref;
                 }
 
-                const bool_ty_ref = try self.resolveType2(Type.bool, .indirect);
+                const bool_ty_ref = try self.resolveType(Type.bool, .indirect);
 
                 return try self.spv.resolve(.{ .struct_type = .{
                     .member_types = &.{ payload_ty_ref, bool_ty_ref },
@@ -1499,21 +1386,21 @@ pub const DeclGen = struct {
                     },
                 } });
             },
-            .Union => return try self.resolveUnionType2(ty, null),
-            .ErrorSet => return try self.intType2(.unsigned, 16),
+            .Union => return try self.resolveUnionType(ty, null),
+            .ErrorSet => return try self.intType(.unsigned, 16),
             .ErrorUnion => {
                 const payload_ty = ty.errorUnionPayload();
-                const error_ty_ref = try self.resolveType2(Type.anyerror, .indirect);
+                const error_ty_ref = try self.resolveType(Type.anyerror, .indirect);
 
                 const eu_layout = self.errorUnionLayout(payload_ty);
                 if (!eu_layout.payload_has_bits) {
                     return error_ty_ref;
                 }
 
-                const payload_ty_ref = try self.resolveType2(payload_ty, .indirect);
+                const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
 
-                var member_types: [2]SpvCacheRef = undefined;
-                var member_names: [2]SpvCacheString = undefined;
+                var member_types: [2]CacheRef = undefined;
+                var member_names: [2]CacheString = undefined;
                 if (eu_layout.error_first) {
                     // Put the error first
                     member_types = .{ error_ty_ref, payload_ty_ref };
@@ -1550,226 +1437,6 @@ pub const DeclGen = struct {
         }
     }
 
-    /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
-    fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!SpvType.Ref {
-        log.debug("resolveType: ty = {}", .{ty.fmt(self.module)});
-        _ = try self.resolveType2(ty, repr);
-        const target = self.getTarget();
-        switch (ty.zigTypeTag()) {
-            .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)),
-            .Bool => switch (repr) {
-                .direct => return try self.spv.resolveType(SpvType.initTag(.bool)),
-                // SPIR-V booleans are opaque, which is fine for operations, but they cant be stored.
-                // This function returns the *stored* type, for values directly we convert this into a bool when
-                // it is loaded, and convert it back to this type when stored.
-                .indirect => return try self.intType(.unsigned, 1),
-            },
-            .Int => {
-                const int_info = ty.intInfo(target);
-                return try self.intType(int_info.signedness, int_info.bits);
-            },
-            .Enum => {
-                var buffer: Type.Payload.Bits = undefined;
-                const tag_ty = ty.intTagType(&buffer);
-                return self.resolveType(tag_ty, repr);
-            },
-            .Float => {
-                // We can (and want) not really emulate floating points with other floating point types like with the integer types,
-                // so if the float is not supported, just return an error.
-                const bits = ty.floatBits(target);
-                const supported = switch (bits) {
-                    16 => Target.spirv.featureSetHas(target.cpu.features, .Float16),
-                    // 32-bit floats are always supported (see spec, 2.16.1, Data rules).
-                    32 => true,
-                    64 => Target.spirv.featureSetHas(target.cpu.features, .Float64),
-                    else => false,
-                };
-
-                if (!supported) {
-                    return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
-                }
-
-                return try self.spv.resolveType(SpvType.float(bits));
-            },
-            .Array => {
-                const elem_ty = ty.childType();
-                const elem_ty_ref = try self.resolveType(elem_ty, .indirect);
-                const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse {
-                    return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()});
-                };
-                return try self.spv.arrayType(total_len, elem_ty_ref);
-            },
-            .Fn => switch (repr) {
-                .direct => {
-                    // TODO: Put this somewhere in Sema.zig
-                    if (ty.fnIsVarArgs())
-                        return self.fail("VarArgs functions are unsupported for SPIR-V", .{});
-
-                    // TODO: Parameter passing convention etc.
-
-                    const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen());
-                    for (param_types, 0..) |*param, i| {
-                        param.* = try self.resolveType(ty.fnParamType(i), .direct);
-                    }
-
-                    const return_type = try self.resolveType(ty.fnReturnType(), .direct);
-
-                    const payload = try self.spv.arena.create(SpvType.Payload.Function);
-                    payload.* = .{ .return_type = return_type, .parameters = param_types };
-                    return try self.spv.resolveType(SpvType.initPayload(&payload.base));
-                },
-                .indirect => {
-                    // TODO: Represent function pointers properly.
-                    // For now, just use an usize type.
-                    return try self.sizeType();
-                },
-            },
-            .Pointer => {
-                const ptr_info = ty.ptrInfo().data;
-
-                const storage_class = spvStorageClass(ptr_info.@"addrspace");
-                const child_ty_ref = try self.resolveType(ptr_info.pointee_type, .indirect);
-                const ptr_ty_ref = try self.spv.ptrType(child_ty_ref, storage_class, 0);
-
-                if (ptr_info.size != .Slice) {
-                    return ptr_ty_ref;
-                }
-
-                return try self.spv.simpleStructType(&.{
-                    .{ .ty = ptr_ty_ref, .name = "ptr" },
-                    .{ .ty = try self.sizeType(), .name = "len" },
-                });
-            },
-            .Vector => {
-                // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
-                // which work on them), so simply use those.
-                // Note: SPIR-V vectors only support bools, ints and floats, so pointer vectors need to be supported another way.
-                // "composite integers" (larger than the largest supported native type) can probably be represented by an array of vectors.
-                // TODO: The SPIR-V spec mentions that vector sizes may be quite restricted! look into which we can use, and whether OpTypeVector
-                // is adequate at all for this.
-
-                // TODO: Properly verify sizes and child type.
-
-                const payload = try self.spv.arena.create(SpvType.Payload.Vector);
-                payload.* = .{
-                    .component_type = try self.resolveType(ty.elemType(), repr),
-                    .component_count = @intCast(u32, ty.vectorLen()),
-                };
-                return try self.spv.resolveType(SpvType.initPayload(&payload.base));
-            },
-            .Struct => {
-                if (ty.isSimpleTupleOrAnonStruct()) {
-                    const tuple = ty.tupleFields();
-                    const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, tuple.types.len);
-                    var member_index: u32 = 0;
-                    for (tuple.types, 0..) |field_ty, i| {
-                        const field_val = tuple.values[i];
-                        if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBitsIgnoreComptime()) continue;
-                        members[member_index] = .{
-                            .ty = try self.resolveType(field_ty, .indirect),
-                        };
-                        member_index += 1;
-                    }
-                    const payload = try self.spv.arena.create(SpvType.Payload.Struct);
-                    payload.* = .{
-                        .members = members[0..member_index],
-                    };
-                    return try self.spv.resolveType(SpvType.initPayload(&payload.base));
-                }
-
-                const struct_ty = ty.castTag(.@"struct").?.data;
-
-                if (struct_ty.layout == .Packed) {
-                    return try self.resolveType(struct_ty.backing_int_ty, .indirect);
-                }
-
-                const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count());
-                var member_index: usize = 0;
-                for (struct_ty.fields.values(), 0..) |field, i| {
-                    if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
-
-                    members[member_index] = .{
-                        .ty = try self.resolveType(field.ty, .indirect),
-                        .name = struct_ty.fields.keys()[i],
-                    };
-                    member_index += 1;
-                }
-
-                const name = try struct_ty.getFullyQualifiedName(self.module);
-                defer self.module.gpa.free(name);
-
-                const payload = try self.spv.arena.create(SpvType.Payload.Struct);
-                payload.* = .{
-                    .members = members[0..member_index],
-                    .name = try self.spv.arena.dupe(u8, name),
-                };
-                return try self.spv.resolveType(SpvType.initPayload(&payload.base));
-            },
-            .Optional => {
-                var buf: Type.Payload.ElemType = undefined;
-                const payload_ty = ty.optionalChild(&buf);
-                if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
-                    // Just use a bool.
-                    // Note: Always generate the bool with indirect format, to save on some sanity
-                    // Perform the converison to a direct bool when the field is extracted.
-                    return try self.resolveType(Type.bool, .indirect);
-                }
-
-                const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
-                if (ty.optionalReprIsPayload()) {
-                    // Optional is actually a pointer or a slice.
-                    return payload_ty_ref;
-                }
-
-                const bool_ty_ref = try self.resolveType(Type.bool, .indirect);
-
-                // its an actual optional
-                return try self.spv.simpleStructType(&.{
-                    .{ .ty = payload_ty_ref, .name = "payload" },
-                    .{ .ty = bool_ty_ref, .name = "valid" },
-                });
-            },
-            .Union => return try self.resolveUnionType(ty, null),
-            .ErrorSet => return try self.intType(.unsigned, 16),
-            .ErrorUnion => {
-                const payload_ty = ty.errorUnionPayload();
-                const error_ty_ref = try self.resolveType(Type.anyerror, .indirect);
-
-                const eu_layout = self.errorUnionLayout(payload_ty);
-                if (!eu_layout.payload_has_bits) {
-                    return error_ty_ref;
-                }
-
-                const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
-
-                var members = std.BoundedArray(SpvType.Payload.Struct.Member, 2){};
-                if (eu_layout.error_first) {
-                    // Put the error first
-                    members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
-                    members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
-                    // TODO: ABI padding?
-                } else {
-                    // Put the payload first.
-                    members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
-                    members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
-                    // TODO: ABI padding?
-                }
-
-                return try self.spv.simpleStructType(members.slice());
-            },
-
-            .Null,
-            .Undefined,
-            .EnumLiteral,
-            .ComptimeFloat,
-            .ComptimeInt,
-            .Type,
-            => unreachable, // Must be comptime.
-
-            else => |tag| return self.todo("Implement zig type '{}'", .{tag}),
-        }
-    }
-
     fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass {
         return switch (as) {
             .generic => .Generic,
@@ -1839,17 +1506,13 @@ pub const DeclGen = struct {
     /// the name of an error in the text executor.
     fn generateTestEntryPoint(self: *DeclGen, name: []const u8, spv_test_decl_index: SpvModule.Decl.Index) !void {
         const anyerror_ty_ref = try self.resolveType(Type.anyerror, .direct);
-        const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup, 0);
+        const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup);
         const void_ty_ref = try self.resolveType(Type.void, .direct);
 
-        const kernel_proto_ty_ref = blk: {
-            const proto_payload = try self.spv.arena.create(SpvType.Payload.Function);
-            proto_payload.* = .{
-                .return_type = void_ty_ref,
-                .parameters = try self.spv.arena.dupe(SpvType.Ref, &.{ptr_anyerror_ty_ref}),
-            };
-            break :blk try self.spv.resolveType(SpvType.initPayload(&proto_payload.base));
-        };
+        const kernel_proto_ty_ref = try self.spv.resolve(.{ .function_type = .{
+            .return_type = void_ty_ref,
+            .parameters = &.{ptr_anyerror_ty_ref},
+        } });
 
         const test_id = self.spv.declPtr(spv_test_decl_index).result_id;
 
@@ -1983,9 +1646,9 @@ pub const DeclGen = struct {
         }
     }
 
-    fn boolToInt(self: *DeclGen, result_ty_ref: SpvType.Ref, condition_id: IdRef) !IdRef {
-        const zero_id = try self.constInt(result_ty_ref, 0);
-        const one_id = try self.constInt(result_ty_ref, 1);
+    fn boolToInt(self: *DeclGen, result_ty_ref: CacheRef, condition_id: IdRef) !IdRef {
+        const zero_id = try self.spv.constInt(result_ty_ref, 0);
+        const one_id = try self.spv.constInt(result_ty_ref, 1);
         const result_id = self.spv.allocId();
         try self.func.body.emit(self.spv.gpa, .OpSelect, .{
             .id_result_type = self.typeId(result_ty_ref),
@@ -2004,7 +1667,7 @@ pub const DeclGen = struct {
             .Bool => blk: {
                 const direct_bool_ty_ref = try self.resolveType(ty, .direct);
                 const indirect_bool_ty_ref = try self.resolveType(ty, .indirect);
-                const zero_id = try self.constInt(indirect_bool_ty_ref, 0);
+                const zero_id = try self.spv.constInt(indirect_bool_ty_ref, 0);
                 const result_id = self.spv.allocId();
                 try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
                     .id_result_type = self.typeId(direct_bool_ty_ref),
@@ -2242,10 +1905,10 @@ pub const DeclGen = struct {
         return result_id;
     }
 
-    fn maskStrangeInt(self: *DeclGen, ty_ref: SpvType.Ref, value_id: IdRef, bits: u16) !IdRef {
+    fn maskStrangeInt(self: *DeclGen, ty_ref: CacheRef, value_id: IdRef, bits: u16) !IdRef {
         const mask_value = if (bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @intCast(u6, bits)) - 1;
         const result_id = self.spv.allocId();
-        const mask_id = try self.constInt(ty_ref, mask_value);
+        const mask_id = try self.spv.constInt(ty_ref, mask_value);
         try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{
             .id_result_type = self.typeId(ty_ref),
             .id_result = result_id,
@@ -2384,7 +2047,7 @@ pub const DeclGen = struct {
                 // Note that signed overflow is also wrapping in spir-v.
 
                 const rhs_lt_zero_id = self.spv.allocId();
-                const zero_id = try self.constInt(operand_ty_ref, 0);
+                const zero_id = try self.spv.constInt(operand_ty_ref, 0);
                 try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{
                     .id_result_type = self.typeId(bool_ty_ref),
                     .id_result = rhs_lt_zero_id,
@@ -2463,7 +2126,7 @@ pub const DeclGen = struct {
     /// is the latter and PtrAccessChain is the former.
     fn accessChain(
         self: *DeclGen,
-        result_ty_ref: SpvType.Ref,
+        result_ty_ref: CacheRef,
         base: IdRef,
         indexes: []const IdRef,
     ) !IdRef {
@@ -2479,7 +2142,7 @@ pub const DeclGen = struct {
 
     fn ptrAccessChain(
         self: *DeclGen,
-        result_ty_ref: SpvType.Ref,
+        result_ty_ref: CacheRef,
         base: IdRef,
         element: IdRef,
         indexes: []const IdRef,
@@ -2854,7 +2517,7 @@ pub const DeclGen = struct {
         // Construct new pointer type for the resulting pointer
         const elem_ty = ptr_ty.elemType2(); // use elemType() so that we get T for *[N]T.
         const elem_ty_ref = try self.resolveType(elem_ty, .direct);
-        const elem_ptr_ty_ref = try self.spv.ptrType(elem_ty_ref, spvStorageClass(ptr_ty.ptrAddressSpace()), 0);
+        const elem_ptr_ty_ref = try self.spv.ptrType(elem_ty_ref, spvStorageClass(ptr_ty.ptrAddressSpace()));
         if (ptr_ty.isSinglePointer()) {
             // Pointer-to-array. In this case, the resulting pointer is not of the same type
             // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.
@@ -2970,7 +2633,7 @@ pub const DeclGen = struct {
     fn makePointerConstant(
         self: *DeclGen,
         section: *SpvSection,
-        ptr_ty_ref: SpvType.Ref,
+        ptr_ty_ref: CacheRef,
         ptr_id: IdRef,
     ) !IdRef {
         const result_id = self.spv.allocId();
@@ -2988,11 +2651,11 @@ pub const DeclGen = struct {
     // placed in the Function address space.
     fn alloc(
         self: *DeclGen,
-        ty_ref: SpvType.Ref,
+        ty_ref: CacheRef,
         initializer: ?IdRef,
     ) !IdRef {
-        const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function, 0);
-        const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0);
+        const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function);
+        const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic);
 
         // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to
         // directly generate them into func.prologue instead of the body.
@@ -3146,7 +2809,7 @@ pub const DeclGen = struct {
 
         const val_is_undef = if (self.air.value(bin_op.rhs)) |val| val.isUndefDeep() else false;
         if (val_is_undef) {
-            const undef = try self.constUndef(ptr_ty_ref);
+            const undef = try self.spv.constUndef(ptr_ty_ref);
             try self.store(ptr_ty, ptr, undef);
         } else {
             try self.store(ptr_ty, ptr, value);
@@ -3217,7 +2880,7 @@ pub const DeclGen = struct {
             else
                 err_union_id;
 
-            const zero_id = try self.constInt(err_ty_ref, 0);
+            const zero_id = try self.spv.constInt(err_ty_ref, 0);
             const is_err_id = self.spv.allocId();
             try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
                 .id_result_type = self.typeId(bool_ty_ref),
@@ -3266,7 +2929,7 @@ pub const DeclGen = struct {
 
         if (err_union_ty.errorUnionSet().errorSetIsEmpty()) {
             // No error possible, so just return undefined.
-            return try self.constUndef(err_ty_ref);
+            return try self.spv.constUndef(err_ty_ref);
         }
 
         const payload_ty = err_union_ty.errorUnionPayload();
@@ -3295,7 +2958,7 @@ pub const DeclGen = struct {
 
         const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
         var members = std.BoundedArray(IdRef, 2){};
-        const payload_id = try self.constUndef(payload_ty_ref);
+        const payload_id = try self.spv.constUndef(payload_ty_ref);
         if (eu_layout.error_first) {
             members.appendAssumeCapacity(operand_id);
             members.appendAssumeCapacity(payload_id);
@@ -3337,7 +3000,7 @@ pub const DeclGen = struct {
                 operand_id;
 
             const payload_ty_ref = try self.resolveType(ptr_ty, .direct);
-            const null_id = try self.constNull(payload_ty_ref);
+            const null_id = try self.spv.constNull(payload_ty_ref);
             const result_id = self.spv.allocId();
             const operands = .{
                 .id_result_type = self.typeId(bool_ty_ref),