zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit d7d131c0503ae8a02677faa01d7d518a5441cb6f (tree)
parent 7a9f8dc9393ff4084da6c77d874d08e26897b3cb
Author: Ali Cheraghi <alichraghi@proton.me>
Date:   Mon, 25 May 2026 23:47:41 +0330

add `@SpirvType` builtin

Closes #35240
Fixes #35238
Fixes #35259

Supported types are:
- `OpTypeSampler`
- `OpTypeImage`
- `OpTypeSampledImage`
- `OpTypeRuntimeArray` with indexing and `.len` field

The SPIR-V backend is bit-rotted so behavior tests no longer pass (compiler crashes).
However I've verified the new added tests are passing.

Diffstat:
Mdoc/langref.html.in | 44++++++++++++++++++++++++++++++++++++++++++++
Mlib/std/Target.zig | 3++-
Mlib/std/hash/auto_hash.zig | 1+
Mlib/std/lang.zig | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/std/mem.zig | 1+
Mlib/std/start.zig | 4+++-
Mlib/std/testing.zig | 2++
Mlib/std/zig/AstGen.zig | 10++++++++++
Mlib/std/zig/AstRlAnnotate.zig | 4++++
Mlib/std/zig/BuiltinFn.zig | 8++++++++
Mlib/std/zig/Zir.zig | 14++++++++++++++
Mlib/std/zon/Serializer.zig | 1+
Mlib/std/zon/parse.zig | 1+
Msrc/Air.zig | 7+++++++
Msrc/Air/Legalize.zig | 1+
Msrc/Air/Liveness.zig | 2+-
Msrc/Air/Liveness/Verify.zig | 2+-
Msrc/Air/print.zig | 1+
Msrc/InternPool.zig | 113++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/Sema.zig | 443++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/Sema/LowerZon.zig | 2++
Msrc/Sema/bitcast.zig | 1+
Msrc/Sema/comptime_ptr_access.zig | 2++
Msrc/Sema/type_resolution.zig | 45++++++++++++++++++++++++++++++++++++++++++++-
Msrc/Type.zig | 31++++++++++++++++++++++++++++++-
Msrc/Value.zig | 8+++++++-
Msrc/Zcu.zig | 5++++-
Msrc/codegen.zig | 1+
Msrc/codegen/aarch64/Select.zig | 3++-
Msrc/codegen/aarch64/abi.zig | 1+
Msrc/codegen/arm/abi.zig | 1+
Msrc/codegen/c.zig | 3+++
Msrc/codegen/c/type.zig | 3+++
Msrc/codegen/llvm.zig | 4+++-
Msrc/codegen/llvm/FuncGen.zig | 2++
Msrc/codegen/mips/abi.zig | 1+
Msrc/codegen/riscv64/CodeGen.zig | 1+
Msrc/codegen/riscv64/abi.zig | 1+
Msrc/codegen/sparc64/CodeGen.zig | 1+
Msrc/codegen/spirv/Assembler.zig | 2+-
Msrc/codegen/spirv/CodeGen.zig | 167++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/codegen/spirv/Module.zig | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/codegen/wasm/CodeGen.zig | 3+++
Msrc/codegen/wasm/abi.zig | 1+
Msrc/codegen/x86_64/CodeGen.zig | 2+-
Msrc/codegen/x86_64/abi.zig | 1+
Msrc/link/ConstPool.zig | 2++
Msrc/link/Dwarf.zig | 2++
Msrc/print_value.zig | 1+
Msrc/print_zir.zig | 10++++++++++
Mtest/behavior.zig | 4++++
Atest/behavior/spirv.zig | 47+++++++++++++++++++++++++++++++++++++++++++++++
Mtest/behavior/type_info.zig | 4++--
Atest/cases/compile_errors/SpirvType_is_a_compile_error_in_non-SPIRV_targets.zig | 9+++++++++
Atest/cases/compile_errors/SpirvType_vulkan_target.zig | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cases/compile_errors/directly_embedding_spirv_type_in_struct_and_union.zig | 35+++++++++++++++++++++++++++++++++++
56 files changed, 1208 insertions(+), 60 deletions(-)

diff --git a/doc/langref.html.in b/doc/langref.html.in @@ -5792,6 +5792,50 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val <p>Returns an {#link|enum#} type with the properties specified by the arguments.</p> {#header_close#} + {#header_open|@SpirvType#} + <pre>{#syntax#}@SpirvType(comptime options: std.lang.Type.Spirv) type{#endsyntax#}</pre> + <p> + Returns a SPIR-V type with the properties specified by the arguments. + </p> + <div class="table-wrapper"> + <table> + <thead> + <tr> + <th scope="col">Tag</th> + <th scope="col">SPIR-V Equivalent</th> + <th scope="col">Description</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row"><code>.sampler</code></th> + <td><code>OpTypeSampler</code></td> + <td>An opaque sampler</td> + </tr> + <tr> + <th scope="row"><code>.image</code></th> + <td><code>OpTypeImage</code></td> + <td>An opaque image</td> + </tr> + <tr> + <th scope="row"><code>.sampled_image</code></th> + <td><code>OpTypeSampledImage</code></td> + <td>An opaque image combined with a sampler</td> + </tr> + <tr> + <th scope="row"><code>.runtime_array</code></th> + <td><code>OpTypeRuntimeArray</code></td> + <td> + An array whose length is determined at runtime. + The resulting type supports indexing and exposes a {#syntax#}.len{#endsyntax#} field. + It may only appear as the last field of an {#link|extern struct#}. + </td> + </tr> + </tbody> + </table> + </div> + {#header_close#} + {#header_open|@typeInfo#} <pre>{#syntax#}@typeInfo(comptime T: type) std.lang.Type{#endsyntax#}</pre> <p> diff --git a/lib/std/Target.zig b/lib/std/Target.zig @@ -2341,7 +2341,8 @@ pub fn supportsAddressSpace( .lut => arch == .propeller and std.Target.propeller.featureSetHas(target.cpu.features, .p2), .global, .local, .shared => is_gpu, - .constant => is_gpu and (context == null or context == .constant), + .constant => (is_gpu and (context == null or context == .constant)) or + (is_spirv and (context == null or context == .constant or context == .pointer)), .param => is_nvptx, .input, .output, .uniform, .push_constant, .storage_buffer, .physical_storage_buffer => is_spirv, }; diff --git a/lib/std/hash/auto_hash.zig b/lib/std/hash/auto_hash.zig @@ -76,6 +76,7 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { switch (@typeInfo(Key)) { .noreturn, .@"opaque", + .spirv, .undefined, .null, .comptime_float, diff --git a/lib/std/lang.zig b/lib/std/lang.zig @@ -578,6 +578,7 @@ pub const Type = union(enum) { @"anyframe": AnyFrame, vector: Vector, enum_literal, + spirv: Spirv, /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. @@ -783,6 +784,59 @@ pub const Type = union(enum) { /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. + pub const Spirv = union(enum(u2)) { + sampler, + image: Image, + sampled_image: type, + runtime_array: type, + + pub const Image = struct { + usage: Usage, + format: Format, + dim: Dimensionality, + depth: Depth, + access: Access, + arrayed: bool, + multisampled: bool, + + pub const Usage = union(enum(u2)) { + unknown: type, + sampled: type, + storage, + }; + + pub const Format = enum(u4) { + unknown, + rgba32f, + rgba32i, + rgba32u, + rgba16f, + rgba16i, + rgba16u, + rgba8unorm, + rgba8snorm, + rgba8i, + rgba8u, + r32f, + r32i, + r32u, + }; + + pub const Dimensionality = enum(u2) { + @"1d", + @"2d", + @"3d", + cube, + }; + + pub const Depth = enum(u2) { unknown, depth, not_depth }; + + pub const Access = enum(u2) { unknown, read_only, write_only, read_write }; + }; + }; + + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. pub const Opaque = struct { decl_names: []const [:0]const u8, }; diff --git a/lib/std/mem.zig b/lib/std/mem.zig @@ -352,6 +352,7 @@ pub fn zeroes(comptime T: type) T { .noreturn, .undefined, .@"opaque", + .spirv, .frame, .@"anyframe", => { diff --git a/lib/std/start.zig b/lib/std/start.zig @@ -19,7 +19,9 @@ comptime { // decls there get run. _ = root; - if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) { + if (builtin.zig_backend == .stage2_spirv) { + // Do nothing + } else if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) { const dll_main_crt_startup = if (builtin.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup"; if (native_os == .windows and !builtin.link_libc and !@hasDecl(root, dll_main_crt_startup)) { @export(&DllMainCRTStartup, .{ .name = dll_main_crt_startup }); diff --git a/lib/std/testing.zig b/lib/std/testing.zig @@ -79,6 +79,7 @@ fn expectEqualInner(comptime T: type, expected: T, actual: T) !void { switch (@typeInfo(@TypeOf(actual))) { .noreturn, .@"opaque", + .spirv, .frame, .@"anyframe", => @compileError("value of type " ++ @typeName(@TypeOf(actual)) ++ " encountered"), @@ -737,6 +738,7 @@ fn expectEqualDeepInner(comptime T: type, expected: T, actual: T) error{TestExpe switch (@typeInfo(@TypeOf(actual))) { .noreturn, .@"opaque", + .spirv, .frame, .@"anyframe", => @compileError("value of type " ++ @typeName(@TypeOf(actual)) ++ " encountered"), diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig @@ -9327,6 +9327,16 @@ fn builtinCall( }); return rvalue(gz, ri, result, node); }, + .SpirvType => { + const spirv_type_options_ty = try gz.addStdLangValue(node, .spirv_type_options); + const operand = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = spirv_type_options_ty } }, params[0], .type); + const result = try gz.addExtendedPayload(.reify_spirv_type, Zir.Inst.ReifySpirvType{ + .src_line = gz.astgen.source_line, + .node = node, + .operand = operand, + }); + return rvalue(gz, ri, result, node); + }, .panic => { try emitDbgNode(gz, node); diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig @@ -1079,6 +1079,10 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast. _ = try astrl.expr(args[3], block, ResultInfo.type_only); return false; }, + .SpirvType => { + _ = try astrl.expr(args[0], block, ResultInfo.type_only); + return false; + }, .Vector => { _ = try astrl.expr(args[0], block, ResultInfo.type_only); _ = try astrl.expr(args[1], block, ResultInfo.type_only); diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig @@ -114,6 +114,7 @@ pub const Tag = enum { Struct, Union, Enum, + SpirvType, type_info, type_name, TypeOf, @@ -972,6 +973,13 @@ pub const list = list: { }, }, .{ + "@SpirvType", + .{ + .tag = .SpirvType, + .param_count = 1, + }, + }, + .{ "@typeInfo", .{ .tag = .type_info, diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig @@ -2055,6 +2055,9 @@ pub const Inst = struct { /// `operand` is payload index to `ReifyEnum`. /// `small` contains `NameStrategy`. reify_enum, + /// Implements builtin `@SpirvType`. + /// `operand` is payload index to `ReifyFn`. + reify_spirv_type, /// Implements the `@cmpxchgStrong` and `@cmpxchgWeak` builtins. /// `small` 0=>weak 1=>strong /// `operand` is payload index to `Cmpxchg`. @@ -3269,6 +3272,14 @@ pub const Inst = struct { field_values: Ref, }; + pub const ReifySpirvType = struct { + src_line: u32, + /// This node is absolute, because `reify` instructions are tracked across updates, and + /// this simplifies the logic for getting source locations for types. + node: Ast.Node.Index, + operand: Ref, + }; + /// Trailing: /// 0. multi_cases_len: u32, // If has_multi_cases is set. /// 1. payload_capture_placeholder: Inst.Index, // If payload_capture_inst_is_placeholder is set. @@ -3584,6 +3595,7 @@ pub const Inst = struct { fn_attributes, container_layout, enum_mode, + spirv_type_options, // Values calling_convention_c, calling_convention_inline, @@ -4389,6 +4401,7 @@ fn findTrackableInner( .reify_enum, .reify_struct, .reify_union, + .reify_spirv_type, => return contents.other.append(gpa, inst), // Type declarations need tracking. @@ -5181,6 +5194,7 @@ pub fn assertTrackable(zir: Zir, inst_idx: Zir.Inst.Index) void { .reify_enum, .reify_struct, .reify_union, + .reify_spirv_type, => {}, // tracked in order, as the owner instructions of explicit container types else => unreachable, // assertion failure; not trackable }, diff --git a/lib/std/zon/Serializer.zig b/lib/std/zon/Serializer.zig @@ -854,6 +854,7 @@ fn canSerializeTypeInner( .frame, .@"anyframe", .@"opaque", + .spirv, => false, .@"enum" => |@"enum"| @"enum".mode == .exhaustive, diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig @@ -1213,6 +1213,7 @@ fn canParseTypeInner( .frame, .@"anyframe", .@"opaque", + .spirv, .comptime_int, .comptime_float, .enum_literal, diff --git a/src/Air.zig b/src/Air.zig @@ -918,6 +918,11 @@ pub const Inst = struct { /// Uses the `ty` field. c_va_start, + /// Implements `.len` field for `@SpirvType(.{ .runtime_array = T })`. + /// Result type is always `u32`. + /// Uses the `ty_pl` field, payload is `StructField`. + spirv_runtime_array_len, + /// Implements @workItemId builtin. /// Result type is always `u32` /// Uses the `pl_op` field, payload is the dimension to get the work item id for. @@ -1793,6 +1798,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool) .work_item_id, .work_group_size, .work_group_id, + .spirv_runtime_array_len, => return .u32, .legalize_compiler_rt_call => return datas[@intFromEnum(inst)].legalize_compiler_rt_call.func.returnType(), @@ -2056,6 +2062,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool { .work_group_size, .work_group_id, .legalize_vec_elem_val, + .spirv_runtime_array_len, => false, .is_non_null_ptr, .is_null_ptr, .is_non_err_ptr, .is_err_ptr => air.typeOf(data.un_op, ip).isVolatilePtrIp(ip), diff --git a/src/Air/Legalize.zig b/src/Air/Legalize.zig @@ -908,6 +908,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .legalize_vec_elem_val, .legalize_vec_store_elem, .legalize_compiler_rt_call, + .spirv_runtime_array_len, => {}, } } diff --git a/src/Air/Liveness.zig b/src/Air/Liveness.zig @@ -673,7 +673,7 @@ fn analyzeInst( const extra = a.air.extraData(Air.UnionInit, inst_datas[@intFromEnum(inst)].ty_pl.payload).data; return analyzeOperands(a, pass, data, inst, .{ extra.init, .none, .none }); }, - .struct_field_ptr, .struct_field_val => { + .struct_field_ptr, .struct_field_val, .spirv_runtime_array_len => { const extra = a.air.extraData(Air.StructField, inst_datas[@intFromEnum(inst)].ty_pl.payload).data; return analyzeOperands(a, pass, data, inst, .{ extra.struct_operand, .none, .none }); }, diff --git a/src/Air/Liveness/Verify.zig b/src/Air/Liveness/Verify.zig @@ -191,7 +191,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data; try self.verifyInstOperands(inst, .{ extra.init, .none, .none }); }, - .struct_field_ptr, .struct_field_val => { + .struct_field_ptr, .struct_field_val, .spirv_runtime_array_len => { const ty_pl = data[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.StructField, ty_pl.payload).data; try self.verifyInstOperands(inst, .{ extra.struct_operand, .none, .none }); diff --git a/src/Air/print.zig b/src/Air/print.zig @@ -306,6 +306,7 @@ const Writer = struct { .struct_field_ptr => try w.writeStructField(s, inst), .struct_field_val => try w.writeStructField(s, inst), + .spirv_runtime_array_len => try w.writeStructField(s, inst), .inferred_alloc => @panic("TODO"), .inferred_alloc_comptime => @panic("TODO"), .assembly => try w.writeAssembly(s, inst), diff --git a/src/InternPool.zig b/src/InternPool.zig @@ -1983,6 +1983,7 @@ pub const Key = union(enum) { union_type: ContainerType, opaque_type: ContainerType, enum_type: ContainerType, + spirv_type: SpirvType, func_type: FuncType, error_set_type: ErrorSetType, /// The payload is the function body, either a `func_decl` or `func_instance`. @@ -2146,6 +2147,13 @@ pub const Key = union(enum) { }; }; + pub const SpirvType = struct { + /// A `spirv_reify` instruction. + zir_index: TrackedInst.Index, + /// A hash of this type's attributes generated by Sema. + type_hash: u64, + }; + pub const FuncType = struct { param_types: Index.Slice, return_type: Index, @@ -2590,6 +2598,7 @@ pub const Key = union(enum) { .opt_type, .anyframe_type, .error_union_type, + .spirv_type, .simple_type, .simple_value, .opt, @@ -2841,6 +2850,10 @@ pub const Key = union(enum) { const b_info = b.error_union_type; return std.meta.eql(a_info, b_info); }, + .spirv_type => |a_info| { + const b_info = b.spirv_type; + return std.meta.eql(a_info, b_info); + }, .simple_type => |a_info| { const b_info = b.simple_type; return a_info == b_info; @@ -3130,6 +3143,7 @@ pub const Key = union(enum) { .simple_type, .struct_type, .union_type, + .spirv_type, .opaque_type, .enum_type, .tuple_type, @@ -3877,6 +3891,14 @@ pub fn loadOpaqueType(ip: *const InternPool, index: Index) LoadedOpaqueType { }; } +pub fn loadSpirvType(ip: *const InternPool, index: Index) Tag.TypeSpirv { + const unwrapped_index = index.unwrap(ip); + const item = unwrapped_index.getItem(ip); + assert(item.tag == .type_spirv); + const extra = extraData(unwrapped_index.getExtra(ip), Tag.TypeSpirv, item.data); + return extra; +} + pub const Item = struct { tag: Tag, /// The doc comments on the respective Tag explain how to interpret this. @@ -4214,6 +4236,8 @@ pub const Index = enum(u32) { type_enum_nonexhaustive: struct { data: *Tag.TypeEnum }, type_opaque: struct { data: *Tag.TypeOpaque }, + type_spirv: struct { data: *Tag.TypeSpirv }, + undef: DataIsIndex, simple_value: void, ptr_nav: struct { data: *PtrNav }, @@ -4841,6 +4865,10 @@ pub const Tag = enum(u8) { /// data is extra index of `TypeEnum`. type_enum_nonexhaustive, + /// An spirv type. + /// data is index of `TypeSpirv` in extra. + type_spirv, + /// An opaque type. /// data is extra index of `TypeOpaque`. type_opaque, @@ -5231,6 +5259,7 @@ pub const Tag = enum(u8) { }, .type_enum_explicit = enum_explicit_encoding, .type_enum_nonexhaustive = enum_explicit_encoding, + .type_spirv = .{ .summary = .@"{.payload.name%summary#\"}", .payload = Tag.TypeSpirv }, .type_opaque = .{ .summary = .@"{.payload.name%summary#\"}", .payload = TypeOpaque, @@ -5688,6 +5717,34 @@ pub const Tag = enum(u8) { name_nav: Nav.Index.Optional, namespace: NamespaceIndex, }; + + /// Trailing: + /// 0. type_hash: PackedU64 + pub const TypeSpirv = struct { + name: NullTerminatedString, + /// The index of the `reify_spirv_type` instruction. + zir_index: TrackedInst.Index, + /// If tag is `.image`, this is the sampled type or `.none` if `usage` is `.storage`. + /// If tag is `.sampled_image`, this is the image type. + /// If tag is `.runtime_array`, this is the element type. + /// Otherwise this is `.none`. + ty: Index, + flags: Flags, + + pub const Flags = packed struct(u32) { + tag: @typeInfo(std.lang.Type.Spirv).@"union".tag_type.?, + // Image type flags + usage: @typeInfo(std.lang.Type.Spirv.Image.Usage).@"union".tag_type.?, + format: std.lang.Type.Spirv.Image.Format, + dim: std.lang.Type.Spirv.Image.Dimensionality, + depth: std.lang.Type.Spirv.Image.Depth, + access: std.lang.Type.Spirv.Image.Access, + is_arrayed: bool, + is_multisampled: bool, + + _: u16 = 0, + }; + }; }; /// Differentiates between user-provided and compiler-generated backing types for packed and tagged types. @@ -6573,6 +6630,14 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { } }, }; } }, + .type_spirv => .{ .spirv_type = ns: { + const extra_list = unwrapped_index.getExtra(ip); + const extra = extraDataTrail(extra_list, Tag.TypeSpirv, data); + break :ns .{ + .zir_index = extra.data.zir_index, + .type_hash = extraData(extra_list, PackedU64, extra.end).get(), + }; + } }, .type_opaque => .{ .opaque_type = ns: { const extra = extraDataTrail(unwrapped_index.getExtra(ip), Tag.TypeOpaque, data); break :ns .{ .declared = .{ @@ -7345,6 +7410,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, io: Io, tid: Zcu.PerThread.Id, key: .union_type => unreachable, // instead use: getDeclaredUnionType, getReifiedUnionType .enum_type => unreachable, // instead use: getDeclaredEnumType, getReifiedEnumType, getGeneratedEnumTagType .opaque_type => unreachable, // instead use: getDeclaredOpaqueType + .spirv_type => unreachable, // instead use: getSpirvType .tuple_type => unreachable, // use getTupleType() instead .func_type => unreachable, // use getFuncType() instead @@ -8717,6 +8783,39 @@ pub fn getReifiedEnumType(ip: *InternPool, gpa: Allocator, io: Io, tid: Zcu.PerT } }; } +pub fn getReifiedSpirvType( + ip: *InternPool, + gpa: Allocator, + io: Io, + tid: Zcu.PerThread.Id, + ini: struct { + zir_index: TrackedInst.Index, + type_hash: u64, + type_spirv: Tag.TypeSpirv, + }, +) Allocator.Error!Index { + var gop = try ip.getOrPutKey(gpa, io, tid, .{ .spirv_type = .{ + .zir_index = ini.zir_index, + .type_hash = ini.type_hash, + } }); + defer gop.deinit(); + if (gop == .existing) return gop.existing; + + const local = ip.getLocal(tid); + const items = local.getMutableItems(gpa, io); + const extra = local.getMutableExtra(gpa, io); + try items.ensureUnusedCapacity(1); + + try extra.ensureUnusedCapacity(@typeInfo(Tag.TypeSpirv).@"struct".field_names.len + + 2 // type_hash: PackedU64 + ); + const extra_index = addExtraAssumeCapacity(extra, ini.type_spirv); + _ = addExtraAssumeCapacity(extra, PackedU64.init(ini.type_hash)); + + items.appendAssumeCapacity(.{ .tag = .type_spirv, .data = extra_index }); + return gop.put(); +} + pub fn getGeneratedEnumTagType(ip: *InternPool, gpa: Allocator, io: Io, tid: Zcu.PerThread.Id, ini: struct { /// The union type for which this enum is a generated tag. union_type: Index, @@ -9077,7 +9176,7 @@ pub fn getExtern( }) catch unreachable; // capacity asserted above const decoration_type, const location_or_descriptor_set, const descriptor_binding = if (key.decoration) |decoration| switch (decoration) { .location => |location| .{ Tag.Extern.Flags.DecorationType.location, location, undefined }, - .descriptor => |descriptor| .{ Tag.Extern.Flags.DecorationType.descriptor, descriptor.binding, descriptor.set }, + .descriptor => |descriptor| .{ Tag.Extern.Flags.DecorationType.descriptor, descriptor.set, descriptor.binding }, } else .{ Tag.Extern.Flags.DecorationType.none, undefined, undefined }; const extra_index = addExtraAssumeCapacity(extra, Tag.Extern{ .ty = key.ty, @@ -9810,6 +9909,7 @@ fn addExtraAssumeCapacity(extra: Local.Extra.Mutable, item: anytype) u32 { Tag.TypeStructPacked.Bits, Tag.TypeUnionPacked.Bits, Tag.TypeEnum.Bits, + Tag.TypeSpirv.Flags, => @bitCast(@field(item, field_name)), else => @compileError("bad field type: " ++ @typeName(field_type)), @@ -9877,6 +9977,7 @@ fn extraDataTrail(extra: Local.Extra, comptime T: type, index: u32) struct { dat Tag.TypeStructPacked.Bits, Tag.TypeUnionPacked.Bits, Tag.TypeEnum.Bits, + Tag.TypeSpirv.Flags, => @bitCast(extra_item), else => @compileError("bad field type: " ++ @typeName(field_type)), @@ -9930,6 +10031,11 @@ pub fn childType(ip: *const InternPool, i: Index) Index { .vector_type => |vector_type| vector_type.child, .array_type => |array_type| array_type.child, .opt_type, .anyframe_type => |child| child, + .spirv_type => blk: { + const info = ip.loadSpirvType(i); + assert(info.flags.tag == .runtime_array); + break :blk info.ty; + }, else => unreachable, }; } @@ -10583,6 +10689,7 @@ fn dumpStatsFallible(ip: *const InternPool, w: *Io.Writer, arena: Allocator) !vo .type_optional => 0, .type_anyframe => 0, .type_error_union => @sizeOf(Key.ErrorUnionType), + .type_spirv => @sizeOf(Tag.TypeSpirv) + @sizeOf(PackedU64), .type_anyerror_union => 0, .type_error_set => b: { const info = extraData(extra_list, Tag.ErrorSet, data); @@ -10860,6 +10967,7 @@ fn dumpAllFallible(ip: *const InternPool, w: *Io.Writer) anyerror!void { .type_enum_explicit, .type_enum_nonexhaustive, .type_opaque, + .type_spirv, .undef, .ptr_nav, .ptr_comptime_alloc, @@ -11598,6 +11706,7 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index { .type_enum_explicit, .type_enum_nonexhaustive, .type_opaque, + .type_spirv, => .type_type, .undef, @@ -11955,6 +12064,8 @@ pub fn zigTypeTag(ip: *const InternPool, index: Index) std.lang.TypeId { .type_opaque, => .@"opaque", + .type_spirv => .spirv, + .type_function => .@"fn", // values, not types diff --git a/src/Sema.zig b/src/Sema.zig @@ -1434,6 +1434,7 @@ fn analyzeBodyInner( .reify_struct => try sema.zirReifyStruct( block, extended, inst), .reify_union => try sema.zirReifyUnion( block, extended, inst), .reify_enum => try sema.zirReifyEnum( block, extended, inst), + .reify_spirv_type => try sema.zirReifySpirvType( block, extended, inst), // zig fmt: on .set_float_mode => { @@ -9273,6 +9274,7 @@ fn zirBitcast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air .noreturn, .null, .@"opaque", + .spirv, .optional, .type, .undefined, @@ -9348,6 +9350,7 @@ fn zirBitcast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air .noreturn, .null, .@"opaque", + .spirv, .optional, .type, .undefined, @@ -15531,6 +15534,7 @@ fn zirBitSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A .undefined, .null, .@"opaque", + .spirv, .type, .enum_literal, .comptime_float, @@ -16834,6 +16838,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .val = (try pt.aggregateValue(type_opaque_ty, &field_values)).toIntern(), }))); }, + .spirv => unreachable, // TODO: ALI .frame => return sema.failWithUseOfAsync(block, src), .@"anyframe" => return sema.failWithUseOfAsync(block, src), } @@ -20449,6 +20454,306 @@ fn zirReifyEnum( } } +fn zirReifySpirvType( + sema: *Sema, + block: *Block, + extended: Zir.Inst.Extended.InstData, + inst: Zir.Inst.Index, +) CompileError!Air.Inst.Ref { + const pt = sema.pt; + const zcu = pt.zcu; + const comp = zcu.comp; + const gpa = comp.gpa; + const io = comp.io; + const ip = &zcu.intern_pool; + const target = zcu.getTarget(); + + const extra = sema.code.extraData(Zir.Inst.ReifySpirvType, extended.operand).data; + const tracked_inst = try block.trackZir(inst); + const src: LazySrcLoc = .{ + .base_node_inst = tracked_inst, + .offset = .nodeOffset(.zero), + }; + const operand_src: LazySrcLoc = .{ + .base_node_inst = tracked_inst, + .offset = .{ .node_offset_builtin_call_arg = .{ + .builtin_call_node = .zero, + .arg_index = 0, + } }, + }; + + if (!target.cpu.arch.isSpirV()) { + return sema.fail( + block, + src, + "builtin @SpirvType is only available when targeting SPIR-V; targeted CPU architecture is {t}", + .{target.cpu.arch}, + ); + } + + const spirv_type_options_ty = try sema.getStdLangType(operand_src, .@"Type.Spirv"); + const operand_uncoerced = sema.resolveInst(extra.operand); + const operand_coerced = try sema.coerce(block, spirv_type_options_ty, operand_uncoerced, operand_src); + const operand_val = try sema.resolveConstDefinedValue(block, operand_src, operand_coerced, .{ .simple = .type }); + const union_val = ip.indexToKey(operand_val.toIntern()).un; + + if (try sema.anyUndef(block, operand_src, .fromInterned(union_val.val))) { + return sema.failWithUseOfUndef(block, operand_src, null); + } + + // TODO: use a longer hash! + var hasher = std.hash.Wyhash.init(0); + std.hash.autoHash(&hasher, union_val.tag); + + const name = try ip.getOrPutStringFmt( + gpa, + io, + pt.tid, + "{f}__SpirvType_{d}", + .{ block.type_name_ctx.fmt(ip), @intFromEnum(inst) }, + .no_embedded_nulls, + ); + const tag = try sema.interpretStdLangType(block, src, .fromInterned(union_val.tag), @typeInfo(std.lang.Type.Spirv).@"union".tag_type.?); + const ip_data: InternPool.Tag.TypeSpirv = switch (tag) { + .sampler => .{ + .name = name, + .zir_index = tracked_inst, + .ty = .none, + .flags = .{ + .tag = .sampler, + .usage = .unknown, + .format = .unknown, + .dim = .@"1d", + .depth = .unknown, + .access = .unknown, + .is_arrayed = false, + .is_multisampled = false, + }, + }, + .image => ip_data: { + const struct_type = ip.loadStructType(ip.typeOf(union_val.val)); + const usage_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex( + ip, + try ip.getOrPutString(gpa, io, pt.tid, "usage", .no_embedded_nulls), + ).?); + const format_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex( + ip, + try ip.getOrPutString(gpa, io, pt.tid, "format", .no_embedded_nulls), + ).?); + const dim_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex( + ip, + try ip.getOrPutString(gpa, io, pt.tid, "dim", .no_embedded_nulls), + ).?); + const depth_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex( + ip, + try ip.getOrPutString(gpa, io, pt.tid, "depth", .no_embedded_nulls), + ).?); + const access_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex( + ip, + try ip.getOrPutString(gpa, io, pt.tid, "access", .no_embedded_nulls), + ).?); + const arrayed_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex( + ip, + try ip.getOrPutString(gpa, io, pt.tid, "arrayed", .no_embedded_nulls), + ).?); + const multisampled_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex( + ip, + try ip.getOrPutString(gpa, io, pt.tid, "multisampled", .no_embedded_nulls), + ).?); + const format = try sema.interpretStdLangType(block, operand_src, format_val, std.lang.Type.Spirv.Image.Format); + const dim = try sema.interpretStdLangType(block, operand_src, dim_val, std.lang.Type.Spirv.Image.Dimensionality); + const depth = try sema.interpretStdLangType(block, operand_src, depth_val, std.lang.Type.Spirv.Image.Depth); + const access = try sema.interpretStdLangType(block, operand_src, access_val, std.lang.Type.Spirv.Image.Access); + + switch (target.os.tag) { + .opencl => if (access == .unknown) { + return sema.fail(block, operand_src, "'access' field must be specified under the 'opencl' os", .{}); + }, + else => if (access != .unknown) { + return sema.fail(block, operand_src, "access qualifier '.{t}' is only valid under the 'opencl' os", .{access}); + }, + } + + const arrayed = try sema.interpretStdLangType(block, operand_src, arrayed_val, bool); + const multisampled = try sema.interpretStdLangType(block, operand_src, multisampled_val, bool); + + const usage_tag_val = usage_val.unionTag(zcu).?; + const usage_tag = try sema.interpretStdLangType(block, operand_src, usage_tag_val, @typeInfo(std.lang.Type.Spirv.Image.Usage).@"union".tag_type.?); + + switch (target.os.tag) { + .vulkan => { + if (usage_tag == .unknown) { + return sema.fail( + block, + operand_src, + "'usage' must be '.sampled' or '.storage' under the 'vulkan' os (Sampled == 0 is forbidden)", + .{}, + ); + } + }, + .opencl => { + if (usage_tag != .unknown) { + return sema.fail(block, operand_src, "'usage' must be '.unknown' under the 'opencl' os", .{}); + } + if (multisampled) { + return sema.fail(block, operand_src, "'multisampled' must be 'false' under the 'opencl' os", .{}); + } + if (format != .unknown) { + return sema.fail(block, operand_src, "'format' must be '.unknown' under the 'opencl' os", .{}); + } + if (dim == .cube) { + return sema.fail(block, operand_src, "'dim' '.cube' is not allowed under the 'opencl' os", .{}); + } + if (arrayed and dim != .@"1d" and dim != .@"2d") { + return sema.fail(block, operand_src, "'arrayed' may only be 'true' when 'dim' is '.1d' or '.2d' under the 'opencl' os", .{}); + } + }, + else => {}, + } + + std.hash.autoHash(&hasher, usage_tag); + std.hash.autoHash(&hasher, format); + std.hash.autoHash(&hasher, dim); + std.hash.autoHash(&hasher, depth); + std.hash.autoHash(&hasher, access); + std.hash.autoHash(&hasher, arrayed); + std.hash.autoHash(&hasher, multisampled); + + break :ip_data .{ + .name = name, + .zir_index = tracked_inst, + .ty = switch (usage_tag) { + .sampled, .unknown => blk: { + const sampled_type = usage_val.unionPayload(zcu).toType(); + std.hash.autoHash(&hasher, sampled_type.toIntern()); + + if (target.os.tag != .opencl and sampled_type.toIntern() == .void_type) { + return sema.fail(block, operand_src, "'void' type for '{t}' field is only valid under the 'opencl' os", .{usage_tag}); + } + if (target.os.tag == .opencl and sampled_type.toIntern() != .void_type) { + return sema.fail(block, operand_src, "'{t}' field type must be 'void' under the 'opencl' os", .{usage_tag}); + } + + if (sampled_type.toIntern() != .void_type and + (!sampled_type.hasRuntimeBits(zcu) or (!sampled_type.isRuntimeFloat() and !sampled_type.isInt(zcu)))) + { + return sema.fail(block, operand_src, "invalid '{t}' field value '{f}'", .{ usage_tag, sampled_type.fmt(pt) }); + } + + if (target.os.tag == .vulkan) { + const ok = (sampled_type.isRuntimeFloat() and sampled_type.bitSize(zcu) == 32) or + (sampled_type.isInt(zcu) and (sampled_type.bitSize(zcu) == 32 or sampled_type.bitSize(zcu) == 64)); + if (!ok) { + return sema.fail( + block, + operand_src, + "'{t}' field value must be a 32-bit int, 64-bit int or 32-bit float under the 'vulkan' os", + .{usage_tag}, + ); + } + + if (format != .unknown) { + const format_kind: enum { float, sint, uint } = switch (format) { + .rgba32f, .rgba16f, .rgba8unorm, .rgba8snorm, .r32f => .float, + .rgba32i, .rgba16i, .rgba8i, .r32i => .sint, + .rgba32u, .rgba16u, .rgba8u, .r32u => .uint, + .unknown => unreachable, + }; + const matches = switch (format_kind) { + .float => sampled_type.isRuntimeFloat(), + .sint => sampled_type.isInt(zcu) and sampled_type.intInfo(zcu).signedness == .signed, + .uint => sampled_type.isInt(zcu) and sampled_type.intInfo(zcu).signedness == .unsigned, + }; + if (!matches) { + return sema.fail( + block, + operand_src, + "image 'format' '.{t}' does not match '{t}' type '{f}' under the 'vulkan' os", + .{ format, usage_tag, sampled_type.fmt(pt) }, + ); + } + } + } + + break :blk sampled_type.toIntern(); + }, + .storage => .none, + }, + .flags = .{ + .tag = .image, + .usage = usage_tag, + .format = format, + .dim = dim, + .depth = depth, + .access = access, + .is_arrayed = arrayed, + .is_multisampled = multisampled, + }, + }; + }, + .sampled_image => blk: { + const image_ty = Value.fromInterned(union_val.val).toType(); + if (image_ty.zigTypeTag(zcu) != .spirv or ip.loadSpirvType(image_ty.toIntern()).flags.tag != .image) { + return sema.fail(block, operand_src, "'sampled_image' element must be an @SpirvType image, found '{f}'", .{image_ty.fmt(pt)}); + } + const image_info = ip.loadSpirvType(image_ty.toIntern()).flags; + if (image_info.usage != .sampled) { + return sema.fail(block, operand_src, "'sampled_image' element must be an image with 'usage = .sampled'", .{}); + } + std.hash.autoHash(&hasher, union_val.val); + break :blk .{ + .name = name, + .zir_index = tracked_inst, + .ty = union_val.val, + .flags = .{ + .tag = tag, + .usage = .unknown, + .format = .unknown, + .dim = .@"1d", + .depth = .unknown, + .access = .unknown, + .is_arrayed = false, + .is_multisampled = false, + }, + }; + }, + .runtime_array => blk: { + const elem_ty = Value.fromInterned(union_val.val).toType(); + if (elem_ty.toIntern() == .void_type) { + return sema.fail(block, operand_src, "'runtime_array' element type must not be 'void'", .{}); + } + if (target.os.tag == .vulkan and + elem_ty.zigTypeTag(zcu) == .spirv and + ip.loadSpirvType(elem_ty.toIntern()).flags.tag == .runtime_array) + { + return sema.fail(block, operand_src, "'runtime_array' of 'runtime_array' is not allowed under the 'vulkan' os", .{}); + } + std.hash.autoHash(&hasher, union_val.val); + break :blk .{ + .name = name, + .zir_index = tracked_inst, + .ty = union_val.val, + .flags = .{ + .tag = tag, + .usage = .unknown, + .format = .unknown, + .dim = .@"1d", + .depth = .unknown, + .access = .unknown, + .is_arrayed = false, + .is_multisampled = false, + }, + }; + }, + }; + + return .fromIntern(try ip.getReifiedSpirvType(gpa, io, pt.tid, .{ + .zir_index = tracked_inst, + .type_hash = hasher.final(), + .type_spirv = ip_data, + })); +} + fn resolveVaListRef(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) CompileError!Air.Inst.Ref { const pt = sema.pt; const va_list_ty = try sema.getStdLangType(src, .VaList); @@ -24760,6 +25065,7 @@ fn zirStdLangValue(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstD .fn_attributes, => .@"Type.Fn.Attributes", .container_layout => .@"Type.ContainerLayout", .enum_mode => .@"Type.Enum.Mode", + .spirv_type_options => .@"Type.Spirv", // zig fmt: on // Values are handled here. @@ -24957,6 +25263,7 @@ fn explainWhyTypeIsComptime( .void, .@"enum", .@"opaque", + .spirv, .pointer, => unreachable, // not comptime-only @@ -25043,6 +25350,7 @@ pub fn explainWhyTypeIsNotExtern( .noreturn => try sema.errNote(src_loc, msg, "'noreturn' is only allowed as a return type", .{}), .@"opaque", + .spirv, .bool, .float, .@"anyframe", @@ -25483,9 +25791,13 @@ fn fieldPtrLoad( ) CompileError!Air.Inst.Ref { const pt = sema.pt; const zcu = pt.zcu; + const ip = &zcu.intern_pool; const object_ptr_ty = sema.typeOf(object_ptr); assert(object_ptr_ty.zigTypeTag(zcu) == .pointer); const pointee_ty = object_ptr_ty.childType(zcu); + if (pointee_ty.isSpirvRuntimeArray(zcu) and field_name.eqlSlice("len", ip)) { + return sema.analyzeSpirvRuntimeArrayLen(block, src, object_ptr, field_name_src); + } try sema.ensureLayoutResolved(pointee_ty, src, .ptr_access); if (try pointee_ty.onePossibleValue(pt)) |opv| { const object: Air.Inst.Ref = .fromValue(opv); @@ -25675,11 +25987,123 @@ fn fieldVal( } else { return sema.unionFieldVal(block, src, object, field_name, field_name_src, inner_ty); }, + .spirv => if (inner_ty.isSpirvRuntimeArray(zcu) and field_name.eqlSlice("len", ip)) { + if (!is_pointer_to) { + return sema.fail( + block, + src, + "accessing 'len' field on a SPIR-V runtime_array requires a pointer to the array field", + .{}, + ); + } + return sema.analyzeSpirvRuntimeArrayLen(block, src, object, field_name_src); + }, else => {}, } return sema.failWithInvalidFieldAccess(block, src, object_ty, field_name); } +fn analyzeSpirvRuntimeArrayLen( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + runtime_array_ptr: Air.Inst.Ref, + src_for_err: LazySrcLoc, +) CompileError!Air.Inst.Ref { + const pt = sema.pt; + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + + const struct_operand: Air.Inst.Ref, const field_index: u32 = sf: { + if (runtime_array_ptr.toIndex()) |inst| { + const tag = sema.air_instructions.items(.tag)[@intFromEnum(inst)]; + const data = sema.air_instructions.items(.data)[@intFromEnum(inst)]; + switch (tag) { + .struct_field_ptr => { + const extra = sema.getTmpAir().extraData(Air.StructField, data.ty_pl.payload).data; + break :sf .{ extra.struct_operand, extra.field_index }; + }, + .struct_field_ptr_index_0 => break :sf .{ data.ty_op.operand, 0 }, + .struct_field_ptr_index_1 => break :sf .{ data.ty_op.operand, 1 }, + .struct_field_ptr_index_2 => break :sf .{ data.ty_op.operand, 2 }, + .struct_field_ptr_index_3 => break :sf .{ data.ty_op.operand, 3 }, + else => {}, + } + } + + const ptr_val = sema.resolveValue(runtime_array_ptr) orelse return sema.fail( + block, + src_for_err, + "'len' field on a SPIR-V runtime_array requires direct struct field access", + .{}, + ); + const ptr_key = ip.indexToKey(ptr_val.toIntern()).ptr; + if (ptr_key.base_addr == .field and ptr_key.byte_offset == 0) { + const field = ptr_key.base_addr.field; + break :sf .{ .fromIntern(field.base), @intCast(field.index) }; + } + + const parent_ty: Type = switch (ptr_key.base_addr) { + .nav => |nav| .fromInterned(ip.getNav(nav).resolved.?.type), + .uav => |uav| .fromInterned(ip.typeOf(uav.val)), + .comptime_alloc, + .comptime_field, + .eu_payload, + .opt_payload, + .arr_elem, + .field, + .int, + => return sema.fail( + block, + src_for_err, + "'len' field on a SPIR-V runtime_array requires direct struct field access", + .{}, + ), + }; + if (parent_ty.zigTypeTag(zcu) != .@"struct") return sema.fail( + block, + src_for_err, + "'len' field on a SPIR-V runtime_array requires the array to be a struct field", + .{}, + ); + + const field_ptr_info = ip.indexToKey(ptr_key.ty).ptr_type; + const rtarr_ty_ip = field_ptr_info.child; + const struct_obj = ip.loadStructType(parent_ty.toIntern()); + const field_idx: u32 = for (struct_obj.field_types.get(ip), 0..) |field_ty_ip, i| { + if (field_ty_ip == rtarr_ty_ip and + struct_obj.field_offsets.get(ip)[i] == ptr_key.byte_offset) + { + break @intCast(i); + } + } else unreachable; + const struct_ptr_ty = try pt.ptrType(.{ + .child = parent_ty.toIntern(), + .flags = field_ptr_info.flags, + }); + const struct_ptr_val = try sema.ptrSubtract( + block, + src_for_err, + ptr_val, + ptr_key.byte_offset, + struct_ptr_ty, + ); + break :sf .{ .fromIntern(struct_ptr_val.toIntern()), field_idx }; + }; + + try sema.requireRuntimeBlock(block, src, null); + return block.addInst(.{ + .tag = .spirv_runtime_array_len, + .data = .{ .ty_pl = .{ + .ty = .u32_type, + .payload = try sema.addExtra(Air.StructField{ + .struct_operand = struct_operand, + .field_index = field_index, + }), + } }, + }); +} + fn fieldPtr( sema: *Sema, block: *Block, @@ -26540,6 +26964,7 @@ fn elemPtr( .vector => try sema.elemPtrVector(block, indexable_ptr_src, indexable_ptr, elem_index_src, elem_index, init), .array => try sema.elemPtrArray(block, src, indexable_ptr_src, indexable_ptr, elem_index_src, elem_index, init, oob_safety), .@"struct" => try sema.tupleElemPtr(block, src, indexable_ptr, elem_index, elem_index_src), + .spirv => try sema.elemPtrSpirvRuntimeArray(block, indexable_ptr, elem_index), else => { const indexable = try sema.analyzeLoad(block, indexable_ptr_src, indexable_ptr, indexable_ptr_src); try sema.ensureLayoutResolved(sema.typeOf(indexable).childType(zcu), src, .ptr_access); @@ -26964,6 +27389,22 @@ fn elemPtrVector( return block.addPtrElemPtr(vector_ptr, elem_index, elem_ptr_ty); } +fn elemPtrSpirvRuntimeArray( + sema: *Sema, + block: *Block, + array_ptr: Air.Inst.Ref, + elem_index: Air.Inst.Ref, +) CompileError!Air.Inst.Ref { + const pt = sema.pt; + const zcu = pt.zcu; + const array_ptr_ty = sema.typeOf(array_ptr); + assert(array_ptr_ty.ptrSize(zcu) == .one); + const array_ty = array_ptr_ty.childType(zcu); + assert(array_ty.isSpirvRuntimeArray(zcu)); + const elem_ptr_ty = try array_ptr_ty.elemPtrType(null, pt); + return block.addPtrElemPtr(array_ptr, elem_index, elem_ptr_ty); +} + /// Asserts that the layout of the array is already resolved. fn elemPtrArray( sema: *Sema, @@ -31496,7 +31937,7 @@ const PeerResolveStrategy = enum { fn select(ty: Type, zcu: *Zcu) PeerResolveStrategy { return switch (ty.zigTypeTag(zcu)) { - .type, .void, .bool, .@"opaque", .frame, .@"anyframe" => .exact, + .type, .void, .bool, .@"opaque", .spirv, .frame, .@"anyframe" => .exact, .noreturn, .undefined => .unknown, .null => .nullable, .comptime_int => .comptime_int, diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig @@ -244,6 +244,7 @@ fn checkTypeInner( .frame, .@"anyframe", .@"opaque", + .spirv, => return self.failUnsupportedResultType(ty, null), .pointer => { @@ -408,6 +409,7 @@ fn lowerExprKnownResTyInner( .error_set, .@"fn", .@"opaque", + .spirv, .frame, .@"anyframe", .void, diff --git a/src/Sema/bitcast.zig b/src/Sema/bitcast.zig @@ -249,6 +249,7 @@ const UnpackValueBits = struct { .tuple_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, diff --git a/src/Sema/comptime_ptr_access.zig b/src/Sema/comptime_ptr_access.zig @@ -422,6 +422,7 @@ fn loadComptimePtrInner( .undefined, .enum_literal, .@"opaque", + .spirv, .@"fn", .error_union, => unreachable, // ill-defined layout @@ -854,6 +855,7 @@ fn prepareComptimePtrStore( .undefined, .enum_literal, .@"opaque", + .spirv, .@"fn", .error_union, => unreachable, // ill-defined layout diff --git a/src/Sema/type_resolution.zig b/src/Sema/type_resolution.zig @@ -87,6 +87,7 @@ fn ensureLayoutResolvedInner(sema: *Sema, ty: Type, orig_ty: Type, reason: *cons .ptr_type, .anyframe_type, .simple_type, + .spirv_type, .opaque_type, .error_set_type, .inferred_error_set_type, @@ -288,10 +289,12 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { } // Resolve the layout of all fields, and check their types are allowed. + const fields_len = struct_obj.field_types.len; for (struct_obj.field_types.get(ip), 0..) |field_ty_ip, field_index| { const field_ty: Type = .fromInterned(field_ty_ip); assert(!field_ty.isGenericPoison()); const field_ty_src = block.src(.{ .container_field_type = @intCast(field_index) }); + const field_name_src = block.src(.{ .container_field_name = @intCast(field_index) }); try sema.ensureLayoutResolved(field_ty, field_ty_src, .field); if (field_ty.zigTypeTag(zcu) == .@"opaque") { return sema.failWithOwnedErrorMsg(&block, msg: { @@ -302,6 +305,35 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { break :msg msg; }); } + if (field_ty.zigTypeTag(zcu) == .spirv) { + if (field_ty.isSpirvRuntimeArray(zcu)) { + if (struct_obj.layout != .@"extern") { + return sema.failWithOwnedErrorMsg(&block, msg: { + const msg = try sema.errMsg(struct_ty.srcLoc(zcu), "non-extern struct cannot contain fields of type '{f}'", .{field_ty.fmt(pt)}); + errdefer msg.destroy(gpa); + try sema.errNote(field_name_src, msg, "while checking this field", .{}); + break :msg msg; + }); + } + if (field_index != fields_len - 1) { + return sema.failWithOwnedErrorMsg(&block, msg: { + const msg = try sema.errMsg(struct_ty.srcLoc(zcu), "struct field of type '{f}' must be the last field", .{field_ty.fmt(pt)}); + errdefer msg.destroy(gpa); + try sema.errNote(field_name_src, msg, "while checking this field", .{}); + break :msg msg; + }); + } + } else { + return sema.failWithOwnedErrorMsg(&block, msg: { + const msg = try sema.errMsg(field_ty_src, "cannot directly embed SPIR-V type '{f}' in struct", .{field_ty.fmt(pt)}); + errdefer msg.destroy(gpa); + try sema.errNote(field_ty_src, msg, "opaque types have unknown size", .{}); + try sema.addDeclaredHereNote(msg, field_ty); + break :msg msg; + }); + } + } + if (struct_obj.layout == .@"extern" and !field_ty.validateExtern(.struct_field, zcu)) { return sema.failWithOwnedErrorMsg(&block, msg: { const msg = try sema.errMsg(field_ty_src, "extern structs cannot contain fields of type '{f}'", .{field_ty.fmt(pt)}); @@ -413,7 +445,10 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { const field_ty: Type = .fromInterned(struct_obj.field_types.get(ip)[field_idx]); const offset = resolved_field_aligns[field_idx].forward(cur_offset); struct_obj.field_offsets.get(ip)[field_idx] = @truncate(offset); // truncate because the overflow is handled below - cur_offset = offset + field_ty.abiSize(zcu); + // A SPIR-V `runtime_array` always trails the struct and + // contributes nothing to the struct's static size. + const field_size = if (field_ty.isSpirvRuntimeArray(zcu)) 0 else field_ty.abiSize(zcu); + cur_offset = offset + field_size; } const struct_size: u32 = switch (class) { .no_possible_value => 0, @@ -858,6 +893,14 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void { break :msg msg; }); } + if (field_ty.zigTypeTag(zcu) == .spirv) { + return sema.failWithOwnedErrorMsg(&block, msg: { + const msg = try sema.errMsg(field_ty_src, "SPIR-V type '{f}' have unknown size and therefore cannot be directly embedded in unions", .{field_ty.fmt(pt)}); + errdefer msg.destroy(gpa); + try sema.addDeclaredHereNote(msg, field_ty); + break :msg msg; + }); + } if (union_obj.layout == .@"extern" and !field_ty.validateExtern(.union_field, zcu)) { return sema.failWithOwnedErrorMsg(&block, msg: { const msg = try sema.errMsg(field_ty_src, "extern unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)}); diff --git a/src/Type.zig b/src/Type.zig @@ -170,6 +170,7 @@ pub fn classify(start_ty: Type, zcu: *const Zcu) Class { .func_type => .fully_comptime, + .spirv_type => if (cur_ty.isSpirvRuntimeArray(zcu)) .runtime else .no_possible_value, .opaque_type => .no_possible_value, .error_union_type => |eu| { @@ -323,6 +324,7 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool { .error_set, .@"fn", .@"opaque", + .spirv, .@"anyframe", .@"enum", .enum_literal, @@ -617,6 +619,10 @@ pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread, ctx: ?*Compari const name = ip.loadEnumType(ty.toIntern()).name; try writer.print("{f}", .{name.fmt(ip)}); }, + .spirv_type => { + const name = ip.loadSpirvType(ty.toIntern()).name; + try writer.print("{f}", .{name.fmt(ip)}); + }, .func_type => |fn_info| { if (fn_info.is_noinline) { try writer.writeAll("noinline "); @@ -704,6 +710,14 @@ pub fn toIntern(ty: Type) InternPool.Index { return ty.ip_index; } +pub fn isSpirvRuntimeArray(ty: Type, zcu: *const Zcu) bool { + const ip = &zcu.intern_pool; + return switch (ip.indexToKey(ty.toIntern())) { + .spirv_type => ip.loadSpirvType(ty.toIntern()).flags.tag == .runtime_array, + else => false, + }; +} + pub fn toValue(self: Type) Value { return .fromInterned(self.toIntern()); } @@ -751,6 +765,7 @@ pub fn hasWellDefinedLayout(ty: Type, zcu: *const Zcu) bool { .error_set_type, .inferred_error_set_type, .tuple_type, + .spirv_type, .opaque_type, .anyframe_type, // These are function bodies, not function pointers. @@ -1038,6 +1053,7 @@ pub fn abiAlignment(ty: Type, zcu: *const Zcu) Alignment { } }, .enum_type => Type.fromInterned(ip.loadEnumType(ty.toIntern()).int_tag_type).abiAlignment(zcu), + .spirv_type => if (ty.isSpirvRuntimeArray(zcu)) ty.childType(zcu).abiAlignment(zcu) else .@"1", .opaque_type => .@"1", // values, not types @@ -1183,6 +1199,7 @@ pub fn abiSize(ty: Type, zcu: *const Zcu) u64 { } }, .enum_type => Type.fromInterned(ip.loadEnumType(ty.toIntern()).int_tag_type).abiSize(zcu), + .spirv_type => unreachable, .opaque_type => unreachable, // values, not types @@ -1309,7 +1326,7 @@ pub fn bitSize(ty: Type, zcu: *const Zcu) u64 { .tuple_type, => ty.abiSize(zcu) * 8, - .opaque_type => unreachable, + .opaque_type, .spirv_type => unreachable, // values, not types .undef, @@ -1511,14 +1528,17 @@ pub fn nullablePtrElem(ty: Type, zcu: *const Zcu) Type { /// * `[]T` /// * `[*]T` /// * `[*c]T` +/// * `@SpirvType(.{ .runtime_array = T })` pub fn indexableElem(ty: Type, zcu: *const Zcu) Type { const ip = &zcu.intern_pool; return switch (ip.indexToKey(ty.toIntern())) { inline .array_type, .vector_type => |arr| .fromInterned(arr.child), + .spirv_type => ty.childType(zcu), .ptr_type => |ptr_type| switch (ptr_type.flags.size) { .many, .slice, .c => .fromInterned(ptr_type.child), .one => switch (ip.indexToKey(ptr_type.child)) { inline .array_type, .vector_type => |arr| .fromInterned(arr.child), + .spirv_type => Type.fromInterned(ptr_type.child).childType(zcu), else => unreachable, }, }, @@ -1864,6 +1884,7 @@ pub fn intInfo(starting_ty: Type, zcu: *const Zcu) InternPool.Key.IntType { .func_type => unreachable, .simple_type => unreachable, // handled via Index enum tag above + .spirv_type => unreachable, .opaque_type => unreachable, // values, not types @@ -2032,6 +2053,7 @@ pub fn onePossibleValue(ty: Type, pt: Zcu.PerThread) !?Value { .error_set_type, .inferred_error_set_type, .opaque_type, + .spirv_type, => null, .simple_type => |t| switch (t) { @@ -2219,10 +2241,12 @@ pub fn isIndexable(ty: Type, zcu: *const Zcu) bool { .one => switch (ty.childType(zcu).zigTypeTag(zcu)) { .array, .vector => true, .@"struct" => ty.childType(zcu).isTuple(zcu), + .spirv => ty.childType(zcu).isSpirvRuntimeArray(zcu), else => false, }, }, .@"struct" => ty.isTuple(zcu), + .spirv => ty.isSpirvRuntimeArray(zcu), else => false, }; } @@ -2844,6 +2868,7 @@ pub fn elemPtrType(ptr_ty: Type, index: ?u64, pt: Zcu.PerThread) Allocator.Error .slice, .many, .c => .fromInterned(ptr_info.child), .one => switch (ip.indexToKey(ptr_info.child)) { .array_type => |array_type| .fromInterned(array_type.child), + .spirv_type => Type.fromInterned(ptr_info.child).childType(zcu), else => unreachable, }, }; @@ -3095,6 +3120,7 @@ pub fn unpackable(ty: Type, zcu: *const Zcu) ?UnpackableReason { .noreturn, .@"opaque", + .spirv, .error_union, .error_set, .frame, @@ -3170,6 +3196,7 @@ pub fn validateExtern(ty: Type, position: ExternPosition, zcu: *const Zcu) bool .noreturn => position == .ret_ty, .@"opaque", + .spirv, .bool, .float, .@"anyframe", @@ -3261,6 +3288,7 @@ pub fn assertHasLayout(ty: Type, zcu: *const Zcu) void { .simple_type, .opaque_type, .error_set_type, + .spirv_type, .inferred_error_set_type, => {}, .func_type => |func_type| { @@ -3362,6 +3390,7 @@ fn collectSubtypes(ty: Type, pt: Zcu.PerThread, visited: *std.AutoArrayHashMapUn .union_type, .opaque_type, .enum_type, + .spirv_type, .simple_type, .int_type, => {}, diff --git a/src/Value.zig b/src/Value.zig @@ -2046,7 +2046,10 @@ pub fn pointerDerivation(ptr_val: Value, arena: Allocator, pt: Zcu.PerThread, op const ptr_ty_info = Type.fromInterned(ptr.ty).ptrInfo(zcu); const need_child: Type = .fromInterned(ptr_ty_info.child); - if (need_child.comptimeOnly(zcu) or need_child.zigTypeTag(zcu) == .@"opaque") { + if (need_child.comptimeOnly(zcu) or + need_child.zigTypeTag(zcu) == .@"opaque" or + need_child.isSpirvRuntimeArray(zcu)) + { // No refinement can happen - this pointer is presumably invalid. // Just offset it. const parent = try arena.create(PointerDeriveStep); @@ -2078,6 +2081,7 @@ pub fn pointerDerivation(ptr_val: Value, arena: Allocator, pt: Zcu.PerThread, op .undefined, .enum_literal, .@"opaque", + .spirv, .@"fn", .error_union, .int, @@ -2229,6 +2233,7 @@ pub fn interpret(val: Value, comptime T: type, pt: Zcu.PerThread) error{ OutOfMe .null, .@"fn", .@"opaque", + .spirv, .enum_literal, => comptime unreachable, // comptime-only or otherwise impossible @@ -2332,6 +2337,7 @@ pub fn uninterpret(val: anytype, ty: Type, pt: Zcu.PerThread) error{ OutOfMemory .null, .@"fn", .@"opaque", + .spirv, .enum_literal, => comptime unreachable, // comptime-only or otherwise impossible diff --git a/src/Zcu.zig b/src/Zcu.zig @@ -468,6 +468,7 @@ pub const StdLangDecl = enum { @"Type.Struct.FieldAttributes", @"Type.ContainerLayout", @"Type.Opaque", + @"Type.Spirv", panic, @"panic.call", @@ -548,6 +549,7 @@ pub const StdLangDecl = enum { .@"Type.Struct.FieldAttributes", .@"Type.ContainerLayout", .@"Type.Opaque", + .@"Type.Spirv", => .type, .panic => .type, @@ -601,7 +603,7 @@ pub const StdLangDecl = enum { .VaList => .va_list, .assembly, .@"assembly.Clobbers" => .assembly, else => { - if (@intFromEnum(decl) <= @intFromEnum(StdLangDecl.@"Type.Opaque")) { + if (@intFromEnum(decl) <= @intFromEnum(StdLangDecl.@"Type.Spirv")) { return .main; } else { return .panic; @@ -2740,6 +2742,7 @@ pub const LazySrcLoc = struct { .reify_enum => zir.extraData(Zir.Inst.ReifyEnum, inst.data.extended.operand).data.node, .reify_struct => zir.extraData(Zir.Inst.ReifyStruct, inst.data.extended.operand).data.node, .reify_union => zir.extraData(Zir.Inst.ReifyUnion, inst.data.extended.operand).data.node, + .reify_spirv_type => zir.extraData(Zir.Inst.ReifySpirvType, inst.data.extended.operand).data.node, else => unreachable, }, else => unreachable, diff --git a/src/codegen.zig b/src/codegen.zig @@ -329,6 +329,7 @@ pub fn generateSymbol( .tuple_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig @@ -255,6 +255,7 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { .work_item_id, .work_group_size, .work_group_id, + .spirv_runtime_array_len, => unreachable, .ret_ptr => { const ty = air_data[@intFromEnum(air_inst_index)].ty; @@ -7491,7 +7492,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, } if (air.next()) |next_air_tag| continue :air_tag next_air_tag; }, - .work_item_id, .work_group_size, .work_group_id => unreachable, + .work_item_id, .work_group_size, .work_group_id, .spirv_runtime_array_len => unreachable, } assert(air.body_index == 0); } diff --git a/src/codegen/aarch64/abi.zig b/src/codegen/aarch64/abi.zig @@ -62,6 +62,7 @@ pub fn classifyType(ty: Type, zcu: *Zcu) Class { .null, .@"fn", .@"opaque", + .spirv, .enum_literal, .array, => unreachable, diff --git a/src/codegen/arm/abi.zig b/src/codegen/arm/abi.zig @@ -113,6 +113,7 @@ pub fn classifyType(ty: Type, zcu: *Zcu, ctx: Context) Class { .null, .@"fn", .@"opaque", + .spirv, .enum_literal, .array, => unreachable, diff --git a/src/codegen/c.zig b/src/codegen/c.zig @@ -902,6 +902,7 @@ pub const DeclGen = struct { .tuple_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, @@ -1565,6 +1566,7 @@ pub const DeclGen = struct { }, .anyframe_type, .opaque_type, + .spirv_type, .func_type, => unreachable, @@ -2877,6 +2879,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) Error!void { .work_item_id, .work_group_size, .work_group_id, + .spirv_runtime_array_len, => unreachable, // Instructions that are known to always be `noreturn` based on their tag. diff --git a/src/codegen/c/type.zig b/src/codegen/c/type.zig @@ -249,6 +249,7 @@ pub const CType = union(enum) { .null, .enum_literal, .@"opaque", + .spirv, .noreturn, .void, => return .void, @@ -865,6 +866,7 @@ pub const CType = union(enum) { switch (ty.zigTypeTag(zcu)) { .frame => unreachable, .@"anyframe" => unreachable, + .spirv => unreachable, .type => try w.writeAll("type"), .void => try w.writeAll("void"), @@ -988,6 +990,7 @@ pub const CType = union(enum) { .anyframe_type, .simple_type, .opaque_type, + .spirv_type, .error_set_type, .inferred_error_set_type, => true, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig @@ -2662,6 +2662,7 @@ pub const Object = struct { }, .frame => @panic("TODO implement lowerDebugType for Frame types"), .@"anyframe" => @panic("TODO implement lowerDebugType for AnyFrame types"), + .spirv => unreachable, } } @@ -3375,7 +3376,7 @@ pub const Object = struct { ); return ty; }, - .opaque_type => unreachable, // no runtime bits + .opaque_type, .spirv_type => unreachable, // no runtime bits .enum_type => try o.lowerType(t.intTagType(zcu)), .func_type => |func_type| try o.lowerFnType(t, func_type), .error_set_type, .inferred_error_set_type => try o.errorIntType(), @@ -3497,6 +3498,7 @@ pub const Object = struct { .tuple_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, diff --git a/src/codegen/llvm/FuncGen.zig b/src/codegen/llvm/FuncGen.zig @@ -434,6 +434,7 @@ pub fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air .work_item_id => try self.airWorkItemId(inst), .work_group_size => try self.airWorkGroupSize(inst), .work_group_id => try self.airWorkGroupId(inst), + .spirv_runtime_array_len => unreachable, // Instructions that are known to always be `noreturn` based on their tag. .br => return self.airBr(inst), @@ -7255,6 +7256,7 @@ pub fn isByRef(ty: Type, zcu: *const Zcu) bool { .undefined, .null, .@"opaque", + .spirv, => unreachable, .noreturn, diff --git a/src/codegen/mips/abi.zig b/src/codegen/mips/abi.zig @@ -77,6 +77,7 @@ pub fn classifyType(ty: Type, zcu: *Zcu, ctx: Context) Class { .null, .@"fn", .@"opaque", + .spirv, .enum_literal, .array, => unreachable, diff --git a/src/codegen/riscv64/CodeGen.zig b/src/codegen/riscv64/CodeGen.zig @@ -1642,6 +1642,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { .work_item_id => unreachable, .work_group_size => unreachable, .work_group_id => unreachable, + .spirv_runtime_array_len => unreachable, // zig fmt: on } diff --git a/src/codegen/riscv64/abi.zig b/src/codegen/riscv64/abi.zig @@ -87,6 +87,7 @@ pub fn classifyType(ty: Type, zcu: *Zcu) Class { .null, .@"fn", .@"opaque", + .spirv, .enum_literal, .array, => unreachable, diff --git a/src/codegen/sparc64/CodeGen.zig b/src/codegen/sparc64/CodeGen.zig @@ -709,6 +709,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .work_item_id => unreachable, .work_group_size => unreachable, .work_group_id => unreachable, + .spirv_runtime_array_len => unreachable, // zig fmt: on } diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig @@ -269,7 +269,7 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); const ids = try cg.id_scratch.addManyAsSlice(gpa, operands[1..].len); for (operands[1..], ids) |op, *id| id.* = try ass.resolveRefId(op.ref_id); - break :blk try module.structType(ids, null, null, .none); + break :blk try module.structType(ids, null, .none); }, .OpTypeImage => blk: { const sampled_type = try ass.resolveRefId(operands[1].ref_id); diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig @@ -271,7 +271,13 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { .vulkan, .opengl => { if (ty.zigTypeTag(zcu) == .@"struct") { switch (storage_class) { - .uniform, .push_constant => try cg.module.decorate(ty_id, .block), + .uniform, + .push_constant, + .storage_buffer, + => { + try cg.module.decorate(ty_id, .block); + try cg.decorateBlockOffsets(ty, ty_id); + }, else => {}, } } @@ -313,6 +319,18 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { try cg.module.debugName(result_id, nav.fqn.toSlice(ip)); }, .invocation_global => { + // `@extern()` produces an invocation_global whose value is a + // comptime-known pointer to an underlying extern symbol's Nav. + // The pointer is inlined at use sites so we don't need a Function-scope wrapper here. + if (ip.indexToKey(val.toIntern()) == .ptr) alias: { + const ptr_key = ip.indexToKey(val.toIntern()).ptr; + if (ptr_key.base_addr != .nav or ptr_key.byte_offset != 0) break :alias; + const underlying_nav = ip.getNav(ptr_key.base_addr.nav); + if (!underlying_nav.resolved.?.is_extern_decl) break :alias; + cg.module.declPtr(spv_decl_index).end_dep = cg.module.decl_deps.items.len; + return; + } + const ty_id = try cg.resolveType(ty, .indirect); const ptr_ty_id = try cg.module.ptrType(ty_id, .function); @@ -360,6 +378,21 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { cg.module.declPtr(spv_decl_index).end_dep = cg.module.decl_deps.items.len; } +fn decorateBlockOffsets(cg: *CodeGen, ty: Type, ty_id: spec.Id) !void { + const zcu = cg.module.zcu; + const ip = &zcu.intern_pool; + const struct_type = ip.loadStructType(ty.toIntern()); + var it = struct_type.iterateRuntimeOrder(ip); + var member: u32 = 0; + while (it.next()) |field_index| { + const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]); + if (!field_ty.hasRuntimeBits(zcu)) continue; + const offset: u32 = @intCast(ty.structFieldOffset(field_index, zcu)); + try cg.module.decorateMember(ty_id, member, .{ .offset = .{ .byte_offset = offset } }); + member += 1; + } +} + pub fn fail(cg: *CodeGen, comptime format: []const u8, args: anytype) Error { @branchHint(.cold); return cg.module.zcu.codegenFail(cg.owner_nav, format, args); @@ -779,6 +812,7 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { .tuple_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, @@ -1065,16 +1099,18 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { const parent_ptr_id = try cg.derivePtr(oac.parent.*); const parent_ptr_ty = try oac.parent.ptrType(pt); const result_ty_id = try cg.resolveType(oac.new_ptr_ty, .direct); - const child_size = oac.new_ptr_ty.childType(zcu).abiSize(zcu); - if (parent_ptr_ty.childType(zcu).isVector(zcu) and oac.byte_offset % child_size == 0) { + if (parent_ptr_ty.childType(zcu).isVector(zcu)) { // Vector element ptr accesses are derived as offset_and_cast. // We can just use OpAccessChain. - return cg.accessChain( - result_ty_id, - parent_ptr_id, - &.{@intCast(@divExact(oac.byte_offset, child_size))}, - ); + const child_size = oac.new_ptr_ty.childType(zcu).abiSize(zcu); + if (oac.byte_offset % child_size == 0) { + return cg.accessChain( + result_ty_id, + parent_ptr_id, + &.{@intCast(@divExact(oac.byte_offset, child_size))}, + ); + } } if (oac.byte_offset == 0) { @@ -1269,7 +1305,6 @@ fn resolveUnionType(cg: *CodeGen, ty: Type) !Id { const result_id = try cg.module.structType( member_types[0..layout.total_fields], member_names[0..layout.total_fields], - null, .none, ); @@ -1450,7 +1485,6 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { return try cg.module.structType( &.{ ptr_ty_id, size_ty_id }, &.{ "ptr", "len" }, - null, .none, ); }, @@ -1472,7 +1506,6 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { const result_id = try cg.module.structType( member_types[0..member_index], null, - null, .none, ); const type_name = try cg.resolveTypeName(ty); @@ -1494,9 +1527,6 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { var member_names = std.array_list.Managed([]const u8).init(gpa); defer member_names.deinit(); - var member_offsets = std.array_list.Managed(u32).init(gpa); - defer member_offsets.deinit(); - var it = struct_type.iterateRuntimeOrder(ip); while (it.next()) |field_index| { const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]); @@ -1505,13 +1535,11 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { const field_name = struct_type.field_names.get(ip)[field_index]; try member_types.append(try cg.resolveType(field_ty, .indirect)); try member_names.append(field_name.toSlice(ip)); - try member_offsets.append(@intCast(ty.structFieldOffset(field_index, zcu))); } const result_id = try cg.module.structType( member_types.items, member_names.items, - member_offsets.items, ty.toIntern(), ); @@ -1541,7 +1569,6 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { return try cg.module.structType( &.{ payload_ty_id, bool_ty_id }, &.{ "payload", "valid" }, - null, .none, ); }, @@ -1576,7 +1603,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { // TODO: ABI padding? } - return try cg.module.structType(&member_types, &member_names, null, .none); + return try cg.module.structType(&member_types, &member_names, .none); }, .@"opaque" => { if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); @@ -1584,6 +1611,77 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { defer gpa.free(type_name); return try cg.module.opaqueType(type_name); }, + .spirv => { + const ip_index = ty.toIntern(); + const spirv_type = ip.loadSpirvType(ip_index); + switch (spirv_type.flags.tag) { + .sampler => return try cg.module.samplerType(ip_index), + .image => { + const sampled_type_id = blk: { + if (spirv_type.ty == .none) break :blk try cg.module.intType(.unsigned, 32); + break :blk try cg.resolveType(Type.fromInterned(spirv_type.ty), .direct); + }; + return try cg.module.imageType( + ip_index, + sampled_type_id, + switch (spirv_type.flags.dim) { + .@"1d" => .@"1d", + .@"2d" => .@"2d", + .@"3d" => .@"3d", + .cube => .cube, + }, + switch (spirv_type.flags.depth) { + .not_depth => 0, + .depth => 1, + .unknown => 2, + }, + @intFromBool(spirv_type.flags.is_arrayed), + @intFromBool(spirv_type.flags.is_multisampled), + switch (spirv_type.flags.usage) { + .unknown => 1, + .sampled => 1, + .storage => 2, + }, + switch (spirv_type.flags.format) { + .unknown => .unknown, + .rgba32f => .rgba32f, + .rgba32i => .rgba32i, + .rgba32u => .rgba32ui, + .rgba16f => .rgba16f, + .rgba16i => .rgba16i, + .rgba16u => .rgba16ui, + .rgba8unorm => .rgba8, + .rgba8snorm => .rgba8snorm, + .rgba8i => .rgba8i, + .rgba8u => .rgba8ui, + .r32f => .r32f, + .r32i => .r32i, + .r32u => .r32ui, + }, + switch (spirv_type.flags.access) { + .unknown => null, + .read_only => .read_only, + .write_only => .write_only, + .read_write => .read_write, + }, + ); + }, + .sampled_image => { + const image_ty_id = try cg.resolveType(.fromInterned(spirv_type.ty), .indirect); + return try cg.module.sampledImageType(ip_index, image_ty_id); + }, + .runtime_array => { + const elem_ty: Type = .fromInterned(spirv_type.ty); + const elem_ty_id = try cg.resolveType(elem_ty, .indirect); + const result_id = try cg.module.runtimeArrayType(ip_index, elem_ty_id); + try cg.module.decorate( + result_id, + .{ .array_stride = .{ .array_stride = @intCast(elem_ty.abiSize(zcu)) } }, + ); + return result_id; + }, + } + }, .null, .undefined, @@ -2421,7 +2519,6 @@ fn generateTestEntryPoint( const buffer_struct_ty_id = try cg.module.structType( &.{anyerror_ty_id}, &.{"error_out"}, - null, .none, ); try cg.module.decorate(buffer_struct_ty_id, .block); @@ -2708,13 +2805,14 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) Error!void { .memcpy => return cg.airMemcpy(inst), .memmove => return cg.airMemmove(inst), - .slice_ptr => try cg.airSliceField(inst, 0), - .slice_len => try cg.airSliceField(inst, 1), - .slice_elem_ptr => try cg.airSliceElemPtr(inst), - .slice_elem_val => try cg.airSliceElemVal(inst), - .ptr_elem_ptr => try cg.airPtrElemPtr(inst), - .ptr_elem_val => try cg.airPtrElemVal(inst), - .array_elem_val => try cg.airArrayElemVal(inst), + .slice_ptr => try cg.airSliceField(inst, 0), + .slice_len => try cg.airSliceField(inst, 1), + .spirv_runtime_array_len => try cg.airSpirvRuntimeArrayLen(inst), + .slice_elem_ptr => try cg.airSliceElemPtr(inst), + .slice_elem_val => try cg.airSliceElemVal(inst), + .ptr_elem_ptr => try cg.airPtrElemPtr(inst), + .ptr_elem_val => try cg.airPtrElemVal(inst), + .array_elem_val => try cg.airArrayElemVal(inst), .set_union_tag => return cg.airSetUnionTag(inst), .get_union_tag => try cg.airGetUnionTag(inst), @@ -4305,6 +4403,22 @@ fn airSliceField(cg: *CodeGen, inst: Air.Inst.Index, field: u32) !?Id { return try cg.extractField(field_ty, operand_id, field); } +fn airSpirvRuntimeArrayLen(cg: *CodeGen, inst: Air.Inst.Index) !?Id { + const gpa = cg.module.gpa; + const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; + const extra = cg.air.extraData(Air.StructField, ty_pl.payload).data; + const struct_ptr_id = try cg.resolve(extra.struct_operand); + const u32_ty_id = try cg.module.intType(.unsigned, 32); + const result_id = cg.module.allocId(); + try cg.body.emit(gpa, .OpArrayLength, .{ + .id_result_type = u32_ty_id, + .id_result = result_id, + .structure = struct_ptr_id, + .array_member = extra.field_index, + }); + return result_id; +} + fn airSliceElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const zcu = cg.module.zcu; const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; @@ -5884,6 +5998,7 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { .struct_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig @@ -70,6 +70,8 @@ cache: struct { bool_const: [2]?Id = .{ null, null }, constants: std.ArrayHashMapUnmanaged(Constant, Id, Constant.HashContext, true) = .empty, + + spirv_types: std.AutoHashMapUnmanaged(InternPool.Index, Id) = .empty, } = .{}, /// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module". sections: struct { @@ -229,6 +231,7 @@ pub fn deinit(module: *Module) void { module.cache.array_types.deinit(module.gpa); module.cache.struct_types.deinit(module.gpa); module.cache.fn_types.deinit(module.gpa); + module.cache.spirv_types.deinit(module.gpa); module.cache.capabilities.deinit(module.gpa); module.cache.extensions.deinit(module.gpa); module.cache.extended_instruction_set.deinit(module.gpa); @@ -683,10 +686,8 @@ pub fn structType( module: *Module, types: []const Id, maybe_names: ?[]const []const u8, - maybe_offsets: ?[]const u32, ip_index: InternPool.Index, ) !Id { - const target = module.zcu.getTarget(); const actual_ip_index = if (module.zcu.comp.config.root_strip) .none else ip_index; if (module.cache.struct_types.get(.{ .fields = types, .ip_index = actual_ip_index })) |id| return id; @@ -704,22 +705,6 @@ pub fn structType( } } - switch (target.os.tag) { - .vulkan, .opengl => { - if (maybe_offsets) |offsets| { - assert(offsets.len == types.len); - for (offsets, 0..) |offset, i| { - try module.decorateMember( - result_id, - @intCast(i), - .{ .offset = .{ .byte_offset = offset } }, - ); - } - } - }, - else => {}, - } - try module.cache.struct_types.put( module.gpa, .{ .fields = types_dup, .ip_index = actual_ip_index }, @@ -747,6 +732,75 @@ pub fn functionType(module: *Module, return_ty_id: Id, param_type_ids: []const I return result_id; } +pub fn samplerType(module: *Module, ip_index: InternPool.Index) !Id { + const entry = try module.cache.spirv_types.getOrPut(module.gpa, ip_index); + if (!entry.found_existing) { + const result_id = module.allocId(); + entry.value_ptr.* = result_id; + try module.sections.globals.emit(module.gpa, .OpTypeSampler, .{ + .id_result = result_id, + }); + } + return entry.value_ptr.*; +} + +pub fn imageType( + module: *Module, + ip_index: InternPool.Index, + sampled_ty_id: Id, + dim: spec.Dim, + depth: spec.LiteralInteger, + arrayed: spec.LiteralInteger, + ms: spec.LiteralInteger, + sampled: spec.LiteralInteger, + image_format: spec.ImageFormat, + access_qualifier: ?spec.AccessQualifier, +) !Id { + const entry = try module.cache.spirv_types.getOrPut(module.gpa, ip_index); + if (!entry.found_existing) { + const result_id = module.allocId(); + entry.value_ptr.* = result_id; + try module.sections.globals.emit(module.gpa, .OpTypeImage, .{ + .id_result = result_id, + .sampled_type = sampled_ty_id, + .dim = dim, + .depth = depth, + .arrayed = arrayed, + .ms = ms, + .sampled = sampled, + .image_format = image_format, + .access_qualifier = access_qualifier, + }); + } + return entry.value_ptr.*; +} + +pub fn sampledImageType(module: *Module, ip_index: InternPool.Index, image_ty_id: Id) !Id { + const entry = try module.cache.spirv_types.getOrPut(module.gpa, ip_index); + if (!entry.found_existing) { + const result_id = module.allocId(); + entry.value_ptr.* = result_id; + try module.sections.globals.emit(module.gpa, .OpTypeSampledImage, .{ + .id_result = result_id, + .image_type = image_ty_id, + }); + } + return entry.value_ptr.*; +} + +pub fn runtimeArrayType(module: *Module, ip_index: InternPool.Index, elem_ty_id: Id) !Id { + const entry = try module.cache.spirv_types.getOrPut(module.gpa, ip_index); + if (!entry.found_existing) { + const result_id = module.allocId(); + entry.value_ptr.* = result_id; + try module.sections.globals.emit(module.gpa, .OpTypeRuntimeArray, .{ + .id_result = result_id, + .element_type = elem_ty_id, + }); + } + return entry.value_ptr.*; +} + pub fn constant(module: *Module, ty_id: Id, value: spec.LiteralContextDependentNumber) !Id { const gop = try module.cache.constants.getOrPut(module.gpa, .{ .ty = ty_id, .value = value }); if (!gop.found_existing) { diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig @@ -1195,6 +1195,7 @@ fn isByRef(ty: Type, zcu: *const Zcu, target: *const std.Target) bool { .undefined, .null, .@"opaque", + .spirv, => unreachable, .noreturn, @@ -1882,6 +1883,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .work_item_id, .work_group_size, .work_group_id, + .spirv_runtime_array_len, => unreachable, }; } @@ -4698,6 +4700,7 @@ fn lowerConstant(cg: *CodeGen, val: Value) InnerError!WValue { .tuple_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, diff --git a/src/codegen/wasm/abi.zig b/src/codegen/wasm/abi.zig @@ -77,6 +77,7 @@ pub fn classifyType(ty: Type, zcu: *const Zcu) Class { .null, .@"fn", .@"opaque", + .spirv, .enum_literal, => unreachable, } diff --git a/src/codegen/x86_64/CodeGen.zig b/src/codegen/x86_64/CodeGen.zig @@ -173719,7 +173719,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { // No soft-float `Legalize` features are enabled, so this instruction never appears. .legalize_compiler_rt_call => unreachable, - .work_item_id, .work_group_size, .work_group_id => unreachable, + .work_item_id, .work_group_size, .work_group_id, .spirv_runtime_array_len => unreachable, } try cg.resetTemps(@enumFromInt(0)); cg.checkInvariantsAfterAirInst(); diff --git a/src/codegen/x86_64/abi.zig b/src/codegen/x86_64/abi.zig @@ -164,6 +164,7 @@ pub fn classifyWindows(ty: Type, zcu: *Zcu, target: *const std.Target, ctx: Cont .null, .@"fn", .@"opaque", + .spirv, .enum_literal, => unreachable, }; diff --git a/src/link/ConstPool.zig b/src/link/ConstPool.zig @@ -196,6 +196,7 @@ fn checkType(pool: *const ConstPool, ty: Type, zcu: *const Zcu) bool { .null, .error_set, .@"opaque", + .spirv, .frame, .@"anyframe", .enum_literal, @@ -241,6 +242,7 @@ fn registerTypeDeps(pool: *ConstPool, root: Index, ty: Type, zcu: *const Zcu) Al .null, .error_set, .@"opaque", + .spirv, .frame, .@"anyframe", .enum_literal, diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig @@ -3047,6 +3047,7 @@ fn updateComptimeNavInner(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPoo .func_type, .error_set_type, .inferred_error_set_type, + .spirv_type, => .alias, .struct_type => tag: { @@ -3533,6 +3534,7 @@ fn updateConstInner(dwarf: *Dwarf, pt: Zcu.PerThread, debug_const_index: link.Co switch (value_ip_key) { .func => unreachable, // handled above .@"extern" => unreachable, // handled above + .spirv_type => unreachable, .int_type => |int_type| { try wip_nav.abbrevCode(.numeric_type); diff --git a/src/print_value.zig b/src/print_value.zig @@ -60,6 +60,7 @@ pub fn print( .union_type, .opaque_type, .enum_type, + .spirv_type, .func_type, .error_set_type, .inferred_error_set_type, diff --git a/src/print_zir.zig b/src/print_zir.zig @@ -685,6 +685,16 @@ const Writer = struct { defer self.parent_decl_node = prev_parent_decl_node; try self.writeSrcNode(stream, .zero); }, + .reify_spirv_type => { + const extra = self.code.extraData(Zir.Inst.ReifySpirvType, extended.operand).data; + try stream.print("line({d}), ", .{extra.src_line}); + try self.writeInstRef(stream, extra.operand); + try stream.writeAll(")) "); + const prev_parent_decl_node = self.parent_decl_node; + self.parent_decl_node = extra.node; + defer self.parent_decl_node = prev_parent_decl_node; + try self.writeSrcNode(stream, .zero); + }, .cmpxchg => try self.writeCmpxchg(stream, extended), .ptr_cast_full => try self.writePtrCastFull(stream, extended), diff --git a/test/behavior.zig b/test/behavior.zig @@ -112,6 +112,10 @@ test { _ = @import("behavior/wasm.zig"); } + if (builtin.zig_backend == .stage2_spirv) { + _ = @import("behavior/spirv.zig"); + } + if (builtin.zig_backend != .stage2_spirv and builtin.os.tag != .wasi) { _ = @import("behavior/asm.zig"); } diff --git a/test/behavior/spirv.zig b/test/behavior/spirv.zig @@ -0,0 +1,47 @@ +const Sampler = @SpirvType(.sampler); +const Image = @SpirvType(.{ .image = .{ + .usage = .{ .sampled = u32 }, + .format = .unknown, + .dim = .@"2d", + .depth = .unknown, + .arrayed = false, + .multisampled = false, + .access = .unknown, +} }); +const SampledImage = @SpirvType(.{ .sampled_image = Image }); +const StorageImage = @SpirvType(.{ .image = .{ + .usage = .storage, + .format = .unknown, + .dim = .@"2d", + .depth = .unknown, + .arrayed = false, + .multisampled = false, + .access = .unknown, +} }); +const RuntimeArray = @SpirvType(.{ .runtime_array = u32 }); + +const RuntimeArrayBuf = extern struct { e: RuntimeArray }; + +const sampler = @extern(*addrspace(.constant) const Sampler, .{ + .name = "sampler", + .decoration = .{ .descriptor = .{ .set = 0, .binding = 0 } }, +}); +const sampled_image = @extern(*addrspace(.constant) const SampledImage, .{ + .name = "sampled_image", + .decoration = .{ .descriptor = .{ .set = 0, .binding = 1 } }, +}); +const storage_image = @extern(*addrspace(.constant) const StorageImage, .{ + .name = "storage_image", + .decoration = .{ .descriptor = .{ .set = 0, .binding = 2 } }, +}); +const runtime_array = @extern(*addrspace(.storage_buffer) const RuntimeArrayBuf, .{ + .name = "runtime_array", + .decoration = .{ .descriptor = .{ .set = 0, .binding = 3 } }, +}); + +test "@SpirvType" { + _ = sampler; + _ = sampled_image; + _ = storage_image; + _ = runtime_array; +} diff --git a/test/behavior/type_info.zig b/test/behavior/type_info.zig @@ -253,11 +253,11 @@ fn testUnion() !void { try expect(typeinfo_info == .@"union"); try expect(typeinfo_info.@"union".layout == .auto); try expect(typeinfo_info.@"union".tag_type.? == TypeId); - try expect(typeinfo_info.@"union".field_names.len == 24); + try expect(typeinfo_info.@"union".field_names.len == 25); try expect(typeinfo_info.@"union".field_names.len == typeinfo_info.@"union".field_types.len); try expect(typeinfo_info.@"union".field_names.len == typeinfo_info.@"union".field_attrs.len); try expect(typeinfo_info.@"union".field_types[4] == @TypeOf(@typeInfo(u8).int)); - try expect(typeinfo_info.@"union".decl_names.len == 16); + try expect(typeinfo_info.@"union".decl_names.len == 17); const TestNoTagUnion = union { Foo: void, diff --git a/test/cases/compile_errors/SpirvType_is_a_compile_error_in_non-SPIRV_targets.zig b/test/cases/compile_errors/SpirvType_is_a_compile_error_in_non-SPIRV_targets.zig @@ -0,0 +1,9 @@ +comptime { + _ = @SpirvType(.{ .runtime_array = u32 }); +} + +// error +// backend=selfhosted +// target=x86_64-native +// +// :2:9: error: builtin @SpirvType is only available when targeting SPIR-V; targeted CPU architecture is x86_64 diff --git a/test/cases/compile_errors/SpirvType_vulkan_target.zig b/test/cases/compile_errors/SpirvType_vulkan_target.zig @@ -0,0 +1,56 @@ +comptime { + _ = @SpirvType(.{ .image = .{ + .usage = .storage, + .format = .unknown, + .dim = .@"2d", + .depth = .unknown, + .arrayed = false, + .multisampled = false, + .access = .read_only, + } }); +} + +comptime { + _ = @SpirvType(.{ .image = .{ + .usage = .{ .sampled = bool }, + .format = .unknown, + .dim = .@"2d", + .depth = .unknown, + .arrayed = false, + .multisampled = false, + .access = .unknown, + } }); +} + +comptime { + _ = @SpirvType(.{ .image = .{ + .usage = .{ .sampled = void }, + .format = .unknown, + .dim = .@"2d", + .depth = .unknown, + .arrayed = false, + .multisampled = false, + .access = .unknown, + } }); +} + +comptime { + _ = @SpirvType(.{ .image = .{ + .usage = .{ .sampled = u24 }, + .format = .unknown, + .dim = .@"2d", + .depth = .unknown, + .arrayed = false, + .multisampled = false, + .access = .unknown, + } }); +} + +// error +// backend=selfhosted +// target=spirv64-vulkan +// +// :2:21: error: access qualifier '.read_only' is only valid under the 'opencl' os +// :14:21: error: invalid 'sampled' field value 'bool' +// :26:21: error: 'void' type for 'sampled' field is only valid under the 'opencl' os +// :38:21: error: 'sampled' field value must be a 32-bit int, 64-bit int or 32-bit float under the 'vulkan' os diff --git a/test/cases/compile_errors/directly_embedding_spirv_type_in_struct_and_union.zig b/test/cases/compile_errors/directly_embedding_spirv_type_in_struct_and_union.zig @@ -0,0 +1,35 @@ +const Sampler = @SpirvType(.sampler); +const RuntimeArray = @SpirvType(.{ .runtime_array = u32 }); +const Foo = struct { + s: Sampler, +}; +const Baz = struct { + a: RuntimeArray, +}; +const Qux = extern struct { + a: RuntimeArray, + b: u32, +}; +export fn a() void { + var foo: Foo = undefined; + _ = &foo; +} +export fn c() void { + var baz: Baz = undefined; + _ = &baz; +} +export fn d() void { + var qux: Qux = undefined; + _ = &qux; +} + +// error +// backend=selfhosted +// target=spirv64-vulkan +// +// :4:8: error: cannot directly embed SPIR-V type 'tmp.Sampler__SpirvType_4' in struct +// :4:8: note: opaque types have unknown size +// :6:13: error: non-extern struct cannot contain fields of type 'tmp.RuntimeArray__SpirvType_11' +// :7:5: note: while checking this field +// :9:20: error: struct field of type 'tmp.RuntimeArray__SpirvType_11' must be the last field +// :10:5: note: while checking this field