authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-06-30 07:04:41+03:30
committergravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-06-30 07:04:41+03:30
log199f8603ed3b6c9fb600c6f4feb380c995dd6b40
tree790ad4f05cff873d02aaaff981a2f0e4e7fece9b
parent7f82c31d40dedde6e8b709d07cc836d629e09021

spirv: emit layout-decorated type variants for Vulkan interface blocks


1 files changed, 170 insertions(+), 45 deletions(-)

src/codegen/spirv/CodeGen.zig+170-45
......@@ -17,6 +17,8 @@ uav_link: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass
1717entry_points: std.array_hash_map.Auto(Id, EntryPoint) = .empty,
1818error_buffer: ?Decl.Index = null,
1919struct_types: std.array_hash_map.Custom(StructType, Id, StructType.HashContext, true) = .empty,
20/// SPIR-V ids of OpVariables whose pointee is a Block struct
21block_var_ids: std.AutoHashMapUnmanaged(Id, void) = .empty,
2022builtins: std.AutoHashMapUnmanaged(struct { spec.BuiltIn, spec.StorageClass }, Decl.Index) = .empty,
2123sections: struct {
2224 // Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module".
......@@ -181,6 +183,7 @@ pub fn deinit(cg: *CodeGen) void {
181183 cg.sections.functions.deinit(gpa);
182184
183185 cg.struct_types.deinit(gpa);
186 cg.block_var_ids.deinit(gpa);
184187 cg.builtins.deinit(gpa);
185188
186189 cg.decls.deinit(gpa);
......@@ -587,6 +590,71 @@ pub fn structType(
587590 return result_id;
588591}
589592
593/// Returns the layout-decorated variant of `ty` for use inside a Vulkan/OpenGL
594/// interface block. Vulkan forbids nested Block decorations, so recursive calls
595/// always pass `false`.
596///
597/// This is distinct from `resolveType` because SPIR-V forbids such decorations
598/// on the pointee of a Function-scope variable.
599pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id {
600 const gpa = cg.gpa;
601 const zcu = cg.zcu;
602 const ip = &zcu.intern_pool;
603
604 const result_id: Id = switch (ty.zigTypeTag(zcu)) {
605 .@"struct" => id: {
606 const struct_type = ip.loadStructType(ty.toIntern());
607 if (struct_type.layout == .@"packed") return cg.resolveType(ty, .indirect);
608
609 var member_types: std.ArrayList(Id) = .empty;
610 defer member_types.deinit(gpa);
611 const id = cg.allocId();
612 if (is_block_root) try cg.decorate(id, .block);
613 var it = struct_type.iterateRuntimeOrder(ip);
614 while (it.next()) |field_index| {
615 const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
616 if (!field_ty.hasRuntimeBits(zcu)) continue;
617 try cg.decorateMember(id, @intCast(member_types.items.len), .{ .offset = .{
618 .byte_offset = @intCast(ty.structFieldOffset(field_index, zcu)),
619 } });
620 try member_types.append(gpa, try cg.layoutType(field_ty, false));
621 }
622 try cg.sections.globals.emit(gpa, .OpTypeStruct, .{
623 .id_result = id,
624 .id_ref = member_types.items,
625 });
626 break :id id;
627 },
628 .array => id: {
629 const elem_ty = ty.childType(zcu);
630 const elem_ty_id = try cg.layoutType(elem_ty, false);
631 const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel(zcu)) orelse
632 return cg.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel(zcu)});
633 const id = try cg.arrayType(try cg.constInt(.u32, total_len), elem_ty_id);
634 if (elem_ty.hasRuntimeBits(zcu)) try cg.decorate(id, .{
635 .array_stride = .{ .array_stride = @intCast(elem_ty.abiSize(zcu)) },
636 });
637 break :id id;
638 },
639 .spirv => if (ty.isSpirvRuntimeArray(zcu)) id: {
640 const elem_ty = ty.childType(zcu);
641 const elem_ty_id = try cg.layoutType(elem_ty, false);
642 const id = cg.allocId();
643 try cg.sections.globals.emit(gpa, .OpTypeRuntimeArray, .{
644 .id_result = id,
645 .element_type = elem_ty_id,
646 });
647 if (elem_ty.hasRuntimeBits(zcu)) try cg.decorate(id, .{
648 .array_stride = .{ .array_stride = @intCast(elem_ty.abiSize(zcu)) },
649 });
650 break :id id;
651 } else return cg.resolveType(ty, .indirect),
652 else => return cg.resolveType(ty, .indirect),
653 };
654
655 return result_id;
656}
657
590658pub fn functionType(cg: *CodeGen, return_ty_id: Id, param_type_ids: []const Id) !Id {
591659 const result_id = cg.allocId();
592660 try cg.sections.globals.emit(cg.gpa, .OpTypeFunction, .{
......@@ -806,7 +874,8 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {
806874 const storage_class = cg.storageClass(nav.resolved.?.@"addrspace");
807875 assert(storage_class != .generic); // These should be instance globals
808876
809 const ty_id = try cg.resolveType(ty, .indirect);
877 const as = nav.resolved.?.@"addrspace";
878 const ty_id = try cg.pointeeType(as, ty, true);
810879 const ptr_ty_id = try cg.ptrType(ty_id, storage_class);
811880
812881 try cg.sections.globals.emit(gpa, .OpVariable, .{
......@@ -819,15 +888,11 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {
819888 .vulkan, .opengl => {
820889 switch (storage_class) {
821890 .uniform, .push_constant, .storage_buffer, .physical_storage_buffer => {
822 if (ty.zigTypeTag(zcu) == .@"struct" and storage_class != .physical_storage_buffer) {
823 try cg.decorate(ty_id, .block);
824 }
825
826891 if (ty.hasRuntimeBits(zcu)) {
827892 try cg.decorate(ptr_ty_id, .{
828893 .array_stride = .{ .array_stride = @intCast(ty.abiSize(zcu)) },
829894 });
830 try cg.decorateLayout(ty, ty_id);
895 if (!cg.needsLayout(as, ty)) try cg.decorateLayout(ty, ty_id);
831896 }
832897 },
833898 else => {},
......@@ -1815,6 +1880,9 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
18151880
18161881 const nav_ty_id = try cg.resolveType(nav_ty, .indirect);
18171882 const decl_ptr_ty_id = try cg.ptrType(nav_ty_id, storage_class);
1883 if (nav_ty.zigTypeTag(zcu) == .@"struct" and cg.needsLayout(nav.resolved.?.@"addrspace", nav_ty)) {
1884 try cg.block_var_ids.put(gpa, spv_decl.result_id, {});
1885 }
18181886 if (decl_ptr_ty_id == ty_id) return spv_decl.result_id;
18191887 switch (target.os.tag) {
18201888 .vulkan, .opengl => return spv_decl.result_id,
......@@ -1879,7 +1947,6 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
18791947 .offset_and_cast => |oac| {
18801948 const parent_ptr_id = try cg.derivePtr(oac.parent.*);
18811949 const parent_ptr_ty = try oac.parent.ptrType(pt);
1882 const result_ty_id = try cg.resolveType(oac.new_ptr_ty, .direct);
18831950
18841951 if (oac.new_ptr_ty.ptrInfo(zcu).flags.vector_index != .none) {
18851952 return parent_ptr_id;
......@@ -1890,13 +1957,25 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
18901957 var cur = parent_ptr_ty.childType(zcu);
18911958 const dst_child = oac.new_ptr_ty.childType(zcu);
18921959 while (cur.toIntern() != dst_child.toIntern()) {
1893 if (cur.zigTypeTag(zcu) == .array) {
1894 cur = cur.childType(zcu);
1895 depth += 1;
1896 } else break;
1960 switch (cur.zigTypeTag(zcu)) {
1961 .array => {
1962 cur = cur.childType(zcu);
1963 depth += 1;
1964 },
1965 .@"struct" => {
1966 if (cur.structFieldCount(zcu) == 0) break;
1967 if (cur.structFieldOffset(0, zcu) != 0) break;
1968 cur = cur.fieldType(0, zcu);
1969 depth += 1;
1970 },
1971 else => break,
1972 }
18971973 }
18981974 if (cur.toIntern() == dst_child.toIntern()) {
18991975 if (depth != 0) {
1976 const as = oac.new_ptr_ty.ptrAddressSpace(zcu);
1977 const child_ty_id = try cg.pointeeType(as, dst_child, false);
1978 const result_ty_id = try cg.ptrType(child_ty_id, cg.storageClass(as));
19001979 const scratch_top = cg.id_scratch.items.len;
19011980 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
19021981 const zero = try cg.constInt(.u32, 0);
......@@ -1907,6 +1986,7 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
19071986 return parent_ptr_id;
19081987 }
19091988 }
1989 const result_ty_id = try cg.resolveType(oac.new_ptr_ty, .direct);
19101990 if (target.os.tag == .opencl) {
19111991 const result_ptr_id = cg.allocId();
19121992 try cg.body.emit(gpa, .OpBitcast, .{
......@@ -4079,33 +4159,77 @@ fn extractVectorComponent(cg: *CodeGen, result_ty: Type, vector_id: Id, field: u
40794159
40804160const MemoryOptions = struct {
40814161 is_volatile: bool = false,
4162 ptr_address_space: std.lang.AddressSpace = .generic,
40824163};
40834164
4165/// Returns true if a pointee at address space must use the
4166/// layout-decorated variant rather than the bare type.
4167fn needsLayout(cg: *CodeGen, as: std.lang.AddressSpace, pointee_ty: Type) bool {
4168 const target = cg.zcu.getTarget();
4169 if (target.os.tag != .vulkan and target.os.tag != .opengl) return false;
4170 switch (as) {
4171 .uniform, .push_constant, .storage_buffer => {},
4172 else => return false,
4173 }
4174 return switch (pointee_ty.zigTypeTag(cg.zcu)) {
4175 .@"struct", .array => true,
4176 .spirv => pointee_ty.isSpirvRuntimeArray(cg.zcu),
4177 else => false,
4178 };
4179}
4180
4181fn pointeeType(cg: *CodeGen, as: std.lang.AddressSpace, ty: Type, is_block_root: bool) !Id {
4182 return if (cg.needsLayout(as, ty))
4183 cg.layoutType(ty, is_block_root)
4184 else
4185 cg.resolveType(ty, .indirect);
4186}
4187
4188fn convertLayout(cg: *CodeGen, dst_ty_id: Id, src_id: Id, src_ty_id: Id) !Id {
4189 if (dst_ty_id == src_ty_id) return src_id;
4190 const id = cg.allocId();
4191 try cg.body.emit(cg.gpa, .OpCopyLogical, .{
4192 .id_result_type = dst_ty_id,
4193 .id_result = id,
4194 .operand = src_id,
4195 });
4196 return id;
4197}
4198
40844199fn load(cg: *CodeGen, value_ty: Type, ptr_id: Id, options: MemoryOptions) !Id {
40854200 const zcu = cg.zcu;
40864201 const alignment: u32 = @intCast(value_ty.abiAlignment(zcu).toByteUnits().?);
4087 const indirect_value_ty_id = try cg.resolveType(value_ty, .indirect);
4088 const result_id = cg.allocId();
4089 const access: spec.MemoryAccess.Extended = .{
4090 .@"volatile" = options.is_volatile,
4091 .aligned = .{ .literal_integer = alignment },
4092 };
4202 const bare_ty_id = try cg.resolveType(value_ty, .indirect);
4203 const load_ty_id = if (cg.needsLayout(options.ptr_address_space, value_ty))
4204 try cg.layoutType(value_ty, cg.block_var_ids.contains(ptr_id))
4205 else
4206 bare_ty_id;
4207 const loaded_id = cg.allocId();
40934208 try cg.body.emit(cg.gpa, .OpLoad, .{
4094 .id_result_type = indirect_value_ty_id,
4095 .id_result = result_id,
4209 .id_result_type = load_ty_id,
4210 .id_result = loaded_id,
40964211 .pointer = ptr_id,
4097 .memory_access = access,
4212 .memory_access = .{
4213 .@"volatile" = options.is_volatile,
4214 .aligned = .{ .literal_integer = alignment },
4215 },
40984216 });
4217 const result_id = try cg.convertLayout(bare_ty_id, loaded_id, load_ty_id);
40994218 return try cg.convertToDirect(value_ty, result_id);
41004219}
41014220
41024221fn store(cg: *CodeGen, value_ty: Type, ptr_id: Id, value_id: Id, options: MemoryOptions) !void {
4103 const indirect_value_id = try cg.convertToIndirect(value_ty, value_id);
4104 const access: spec.MemoryAccess.Extended = .{ .@"volatile" = options.is_volatile };
4222 const bare_value_id = try cg.convertToIndirect(value_ty, value_id);
4223 const bare_ty_id = try cg.resolveType(value_ty, .indirect);
4224 const store_ty_id = if (cg.needsLayout(options.ptr_address_space, value_ty))
4225 try cg.layoutType(value_ty, cg.block_var_ids.contains(ptr_id))
4226 else
4227 bare_ty_id;
4228 const object_id = try cg.convertLayout(store_ty_id, bare_value_id, bare_ty_id);
41054229 try cg.body.emit(cg.gpa, .OpStore, .{
41064230 .pointer = ptr_id,
4107 .object = indirect_value_id,
4108 .memory_access = access,
4231 .object = object_id,
4232 .memory_access = .{ .@"volatile" = options.is_volatile },
41094233 });
41104234}
41114235
......@@ -5520,23 +5644,18 @@ fn ptrAccessChain(
55205644
55215645fn ptrAdd(cg: *CodeGen, result_ty: Type, ptr_ty: Type, ptr_id: Id, offset_id: Id) !Id {
55225646 const zcu = cg.zcu;
5523 const result_ty_id = try cg.resolveType(result_ty, .direct);
5524
5525 switch (ptr_ty.ptrSize(zcu)) {
5526 .one => {
5527 // Pointer to array
5528 // TODO: Is this correct?
5529 return try cg.accessChainId(result_ty_id, ptr_id, &.{offset_id});
5530 },
5531 .c, .many => {
5532 return try cg.ptrAccessChain(result_ty_id, ptr_id, offset_id, &.{});
5533 },
5534 .slice => {
5647 const as = result_ty.ptrAddressSpace(zcu);
5648 const child_ty_id = try cg.pointeeType(as, result_ty.childType(zcu), false);
5649 const result_ty_id = try cg.ptrType(child_ty_id, cg.storageClass(as));
5650 return switch (ptr_ty.ptrSize(zcu)) {
5651 .one => cg.accessChainId(result_ty_id, ptr_id, &.{offset_id}),
5652 .c, .many => cg.ptrAccessChain(result_ty_id, ptr_id, offset_id, &.{}),
5653 .slice => blk: {
55355654 // TODO: This is probably incorrect. A slice should be returned here, though this is what llvm does.
55365655 const slice_ptr_id = try cg.extractField(result_ty, ptr_id, 0);
5537 return try cg.ptrAccessChain(result_ty_id, slice_ptr_id, offset_id, &.{});
5656 break :blk cg.ptrAccessChain(result_ty_id, slice_ptr_id, offset_id, &.{});
55385657 },
5539 }
5658 };
55405659}
55415660
55425661fn airPtrAdd(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
......@@ -6482,16 +6601,16 @@ fn airSliceElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
64826601fn ptrElemPtr(cg: *CodeGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id {
64836602 const zcu = cg.zcu;
64846603 // Construct new pointer type for the resulting pointer
6485 const elem_ty = ptr_ty.indexableElem(zcu);
6486 const elem_ty_id = try cg.resolveType(elem_ty, .indirect);
6487 const elem_ptr_ty_id = try cg.ptrType(elem_ty_id, cg.storageClass(ptr_ty.ptrAddressSpace(zcu)));
6604 const as = ptr_ty.ptrAddressSpace(zcu);
6605 const elem_ty_id = try cg.pointeeType(as, ptr_ty.indexableElem(zcu), false);
6606 const elem_ptr_ty_id = try cg.ptrType(elem_ty_id, cg.storageClass(as));
64886607 if (ptr_ty.isSinglePointer(zcu)) {
64896608 // Pointer-to-array. In this case, the resulting pointer is not of the same type
64906609 // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.
6491 return try cg.accessChainId(elem_ptr_ty_id, ptr_id, &.{index_id});
6610 return cg.accessChainId(elem_ptr_ty_id, ptr_id, &.{index_id});
64926611 } else {
64936612 // Resulting pointer type is the same as the ptr_ty, so use ptrAccessChain
6494 return try cg.ptrAccessChain(elem_ptr_ty_id, ptr_id, index_id, &.{});
6613 return cg.ptrAccessChain(elem_ptr_ty_id, ptr_id, index_id, &.{});
64956614 }
64966615}
64976616
......@@ -7375,7 +7494,10 @@ fn airLoad(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
73757494 break :ptr_id try cg.accessChain(elem_ptr_ty_id, operand_ptr_id, &.{@intFromEnum(index)});
73767495 },
73777496 };
7378 return try cg.load(elem_ty, ptr_id, .{ .is_volatile = ptr_info.flags.is_volatile });
7497 return try cg.load(elem_ty, ptr_id, .{
7498 .is_volatile = ptr_info.flags.is_volatile,
7499 .ptr_address_space = ptr_info.flags.address_space,
7500 });
73797501}
73807502
73817503fn airStore(cg: *CodeGen, inst: Air.Inst.Index) !void {
......@@ -7447,7 +7569,10 @@ fn airStore(cg: *CodeGen, inst: Air.Inst.Index) !void {
74477569 },
74487570 };
74497571
7450 try cg.store(elem_ty, ptr_id, value_id, .{ .is_volatile = ptr_info.flags.is_volatile });
7572 try cg.store(elem_ty, ptr_id, value_id, .{
7573 .is_volatile = ptr_info.flags.is_volatile,
7574 .ptr_address_space = ptr_info.flags.address_space,
7575 });
74517576}
74527577
74537578fn airRet(cg: *CodeGen, inst: Air.Inst.Index) !void {