From a3fcdd71cb6ae1fe09a6884cf89ef32559f66d63 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sun, 16 Mar 2025 06:16:48 +0330 Subject: [PATCH] add `@SpirvType` builtin --- doc/langref.html.in | 7 + lib/std/builtin.zig | 51 +++ lib/std/zig.zig | 2 + lib/std/zig/AstGen.zig | 10 + lib/std/zig/AstRlAnnotate.zig | 1 + lib/std/zig/BuiltinFn.zig | 8 + lib/std/zig/Zir.zig | 20 +- src/InternPool.zig | 103 +++++ src/Sema.zig | 382 ++++++++++++++---- src/Sema/bitcast.zig | 1 + src/Type.zig | 23 +- src/Zcu.zig | 8 +- src/arch/wasm/CodeGen.zig | 1 + src/codegen.zig | 1 + src/codegen/c.zig | 2 + src/codegen/c/Type.zig | 3 + src/codegen/llvm.zig | 3 + src/codegen/spirv.zig | 87 +++- src/link/Dwarf.zig | 3 + src/print_value.zig | 1 + src/print_zir.zig | 2 +- test/behavior.zig | 4 + test/behavior/spirv.zig | 33 ++ .../compile_errors/@import_zon_bad_type.zig | 6 +- ...s_a_compile_error_in_non-SPIRV_targets.zig | 9 + .../SpirvType_vulkan_target.zig | 56 +++ .../anytype_param_requires_comptime.zig | 2 +- .../bogus_method_call_on_slice.zig | 2 +- .../compile_errors/coerce_anon_struct.zig | 2 +- ...bedding_spirv_type_in_struct_and_union.zig | 43 ++ test/cases/compile_errors/redundant_try.zig | 4 +- 31 files changed, 765 insertions(+), 115 deletions(-) create mode 100644 test/behavior/spirv.zig create mode 100644 test/cases/compile_errors/SpirvType_is_a_compile_error_in_non-SPIRV_targets.zig create mode 100644 test/cases/compile_errors/SpirvType_vulkan_target.zig create mode 100644 test/cases/compile_errors/directly_embedding_spirv_type_in_struct_and_union.zig diff --git a/doc/langref.html.in b/doc/langref.html.in index d190d195e237..53b5337727d8 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5705,6 +5705,13 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val

{#header_close#} + {#header_open|@SpirvType#} +
{#syntax#}@SpirvType(comptime info: std.builtin.SpirvType) type{#endsyntax#}
+

+ Reifies an SPIR-V type information into an {#syntax#}opaque{#endsyntax#} type. +

+ {#header_close#} + {#header_open|@tagName#}
{#syntax#}@tagName(value: anytype) [:0]const u8{#endsyntax#}

diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 5e95474569bb..2aa680752ed5 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -806,6 +806,57 @@ pub const Type = union(enum) { }; }; +pub const SpirvType = 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 FloatMode = enum { diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 23001554744d..6a688e328246 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -720,6 +720,7 @@ pub const SimpleComptimeReason = enum(u32) { // Evaluating at comptime because a builtin operand must be comptime-known. // These messages all mention a specific builtin. operand_Type, + operand_SpirvType, operand_setEvalBranchQuota, operand_setFloatMode, operand_branchHint, @@ -802,6 +803,7 @@ pub const SimpleComptimeReason = enum(u32) { return switch (r) { // zig fmt: off .operand_Type => "operand to '@Type' must be comptime-known", + .operand_SpirvType => "operand to '@SpirvType' must be comptime-known", .operand_setEvalBranchQuota => "operand to '@setEvalBranchQuota' must be comptime-known", .operand_setFloatMode => "operand to '@setFloatMode' must be comptime-known", .operand_branchHint => "operand to '@branchHint' must be comptime-known", diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index ccc870e36340..028167c25424 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -9497,6 +9497,16 @@ fn builtinCall( const result = new_index.toRef(); return rvalue(gz, ri, result, node); }, + .SpirvType => { + const spirv_type_info_ty = try gz.addBuiltinValue(node, .spirv_type_info); + const operand = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = spirv_type_info_ty } }, params[0], .operand_SpirvType); + const result = try gz.addExtendedPayload(.reify_spirv, Zir.Inst.Reify{ + .node = node, // Absolute node index -- see the definition of `Reify`. + .operand = operand, + .src_line = astgen.source_line, + }); + return rvalue(gz, ri, result, node); + }, .panic => { try emitDbgNode(gz, node); return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .coerced_ty = .slice_const_u8_type } }, params[0], .panic); diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig index d5fb0a8169cc..118ff25b4ba2 100644 --- a/lib/std/zig/AstRlAnnotate.zig +++ b/lib/std/zig/AstRlAnnotate.zig @@ -914,6 +914,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast. .error_name, .set_runtime_safety, .Type, + .SpirvType, .c_undef, .c_include, .wasm_memory_size, diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig index 1bf31cd165e9..dd69fcd45536 100644 --- a/lib/std/zig/BuiltinFn.zig +++ b/lib/std/zig/BuiltinFn.zig @@ -112,6 +112,7 @@ pub const Tag = enum { trap, truncate, Type, + SpirvType, type_info, type_name, TypeOf, @@ -951,6 +952,13 @@ pub const list = list: { .param_count = 1, }, }, + .{ + "@SpirvType", + .{ + .tag = .SpirvType, + .param_count = 1, + }, + }, .{ "@typeInfo", .{ diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 638d73410734..d079b9d4cbcc 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -2032,6 +2032,9 @@ pub const Inst = struct { /// `operand` is payload index to `Reify`. /// `small` contains `NameStrategy`. reify, + /// Implement builtin `@SpirvType`. + /// `operand` is payload index to `Reify`. + reify_spirv, /// Implements the `@asyncCall` builtin. /// `operand` is payload index to `AsyncCall`. builtin_async_call, @@ -3004,7 +3007,7 @@ pub const Inst = struct { flags: Flags, callee: Ref, - pub const Flags = packed struct { + pub const Flags = packed struct(u32) { /// std.builtin.CallModifier in packed form pub const PackedModifier = u3; pub const PackedArgsLen = u27; @@ -3015,8 +3018,6 @@ pub const Inst = struct { args_len: PackedArgsLen, comptime { - if (@sizeOf(Flags) != 4 or @bitSizeOf(Flags) != 32) - @compileError("Layout of Call.Flags needs to be updated!"); if (@bitSizeOf(std.builtin.CallModifier) != @bitSizeOf(PackedModifier)) @compileError("Call.Flags.PackedModifier needs to be updated!"); } @@ -3053,15 +3054,10 @@ pub const Inst = struct { callee: Ref, args: Ref, - pub const Flags = packed struct { + pub const Flags = packed struct(u32) { is_nosuspend: bool, ensure_result_used: bool, _: u30 = undefined, - - comptime { - if (@sizeOf(Flags) != 4 or @bitSizeOf(Flags) != 32) - @compileError("Layout of BuiltinCall.Flags needs to be updated!"); - } }; }; @@ -3471,6 +3467,7 @@ pub const Inst = struct { export_options, extern_options, type_info, + spirv_type_info, branch_hint, // Values calling_convention_c, @@ -4379,8 +4376,8 @@ fn findTrackableInner( try zir.findTrackableBody(gpa, contents, defers, body); }, - // Reifications and opaque declarations need tracking, but have no body. - .reify, .opaque_decl => return contents.other.append(gpa, inst), + // Reifications and opaque declarations need tracking, but have no body.. + .reify, .reify_spirv, .opaque_decl => return contents.other.append(gpa, inst), // Struct declarations need tracking and have bodies. .struct_decl => { @@ -5127,6 +5124,7 @@ pub fn assertTrackable(zir: Zir, inst_idx: Zir.Inst.Index) void { .enum_decl, .opaque_decl, .reify, + .reify_spirv, => {}, // tracked in order, as the owner instructions of explicit container types else => unreachable, // assertion failure; not trackable }, diff --git a/src/InternPool.zig b/src/InternPool.zig index ab264d0e8641..b1b954ebde73 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -1987,6 +1987,7 @@ pub const Key = union(enum) { enum_type: NamespaceType, func_type: FuncType, error_set_type: ErrorSetType, + spirv_type: SpirvType, /// The payload is the function body, either a `func_decl` or `func_instance`. inferred_error_set_type: Index, @@ -2201,6 +2202,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, + }; + /// A runtime variable defined in this `Zcu`. pub const Variable = struct { ty: Index, @@ -2609,6 +2617,7 @@ pub const Key = union(enum) { .opt_type, .anyframe_type, .error_union_type, + .spirv_type, .simple_type, .simple_value, .opt, @@ -2871,6 +2880,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; @@ -3172,6 +3185,7 @@ pub const Key = union(enum) { .anyframe_type, .error_union_type, .error_set_type, + .spirv_type, .inferred_error_set_type, .simple_type, .struct_type, @@ -4500,6 +4514,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. @@ -4781,6 +4803,7 @@ pub const Index = enum(u32) { type_enum_explicit: DataIsExtraIndexOfEnumExplicit, type_enum_nonexhaustive: DataIsExtraIndexOfEnumExplicit, simple_type: void, + type_spirv: struct { data: *Tag.TypeSpirv }, type_opaque: struct { data: *Tag.TypeOpaque }, type_struct: struct { data: *Tag.TypeStruct }, type_struct_packed: struct { data: *Tag.TypeStructPacked }, @@ -5273,6 +5296,9 @@ pub const Tag = enum(u8) { type_enum_nonexhaustive, /// A type that can be represented with only an enum tag. simple_type, + /// An spirv type. + /// data is index of Tag.TypeSpirv in extra. + type_spirv, /// An opaque type. /// data is index of Tag.TypeOpaque in extra. type_opaque, @@ -5545,6 +5571,7 @@ pub const Tag = enum(u8) { .type_enum_explicit = enum_explicit_encoding, .type_enum_nonexhaustive = enum_explicit_encoding, .simple_type = .{ .summary = .@"{.index%value#.}", .index = SimpleType }, + .type_spirv = .{ .summary = .@"spirv_type{%summary#\"}", .payload = Tag.TypeSpirv }, .type_opaque = .{ .summary = .@"{.payload.name%summary#\"}", .payload = TypeOpaque, @@ -6032,6 +6059,34 @@ pub const Tag = enum(u8) { /// `std.math.maxInt(u32)` indicates this type is reified. captures_len: u32, }; + + /// Trailing: + /// 0. type_hash: PackedU64 + pub const TypeSpirv = struct { + name: NullTerminatedString, + /// The index of the `reify_spirv` 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.builtin.SpirvType).@"union".tag_type.?, + // Image type flags + usage: @typeInfo(std.builtin.SpirvType.Image.Usage).@"union".tag_type.? = undefined, + format: std.builtin.SpirvType.Image.Format = undefined, + dim: std.builtin.SpirvType.Image.Dimensionality = undefined, + depth: std.builtin.SpirvType.Image.Depth = undefined, + access: std.builtin.SpirvType.Image.Access = undefined, + is_arrayed: bool = undefined, + is_multisampled: bool = undefined, + + _: u16 = 0, + }; + }; }; /// State that is mutable during semantic analysis. This data is not used for @@ -6962,6 +7017,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_function => .{ .func_type = extraFuncType(unwrapped_index.tid, unwrapped_index.getExtra(ip), data) }, .undef => .{ .undef = @enumFromInt(data) }, @@ -7769,6 +7832,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All .union_type => unreachable, // use getUnionType() instead .opaque_type => unreachable, // use getOpaqueType() instead + .spirv_type => unreachable, // use getSpirvType() instead .enum_type => unreachable, // use getEnumType() instead .func_type => unreachable, // use getFuncType() instead .@"extern" => unreachable, // use getExtern() instead @@ -10057,6 +10121,39 @@ pub fn getOpaqueType( }; } +pub const SpirvTypeInit = struct { + type_hash: u64, + type_spirv: Tag.TypeSpirv, +}; + +pub fn getSpirvType( + ip: *InternPool, + gpa: Allocator, + tid: Zcu.PerThread.Id, + ini: SpirvTypeInit, +) Allocator.Error!Index { + var gop = try ip.getOrPutKey(gpa, tid, .{ .spirv_type = .{ + .type_hash = ini.type_hash, + .zir_index = ini.type_spirv.zir_index, + } }); + defer gop.deinit(); + if (gop == .existing) return gop.existing; + + const local = ip.getLocal(tid); + const items = local.getMutableItems(gpa); + const extra = local.getMutableExtra(gpa); + try items.ensureUnusedCapacity(1); + + try extra.ensureUnusedCapacity(@typeInfo(Tag.TypeSpirv).@"struct".fields.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 getIfExists(ip: *const InternPool, key: Key) ?Index { const full_hash = key.hash64(ip); const hash: u32 = @truncate(full_hash >> 32); @@ -10191,6 +10288,7 @@ fn addExtraAssumeCapacity(extra: Local.Extra.Mutable, item: anytype) u32 { Tag.TypeStruct.Flags, Tag.TypeStructPacked.Flags, Tag.Variable.Flags, + Tag.TypeSpirv.Flags, => @bitCast(@field(item, field.name)), else => @compileError("bad field type: " ++ @typeName(field.type)), @@ -10251,6 +10349,7 @@ fn extraDataTrail(extra: Local.Extra, comptime T: type, index: u32) struct { dat Tag.TypeUnion.Flags, Tag.TypeStruct.Flags, Tag.TypeStructPacked.Flags, + Tag.TypeSpirv.Flags, Tag.Variable.Flags, FuncAnalysis, => @bitCast(extra_item), @@ -10886,6 +10985,7 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void { .type_optional => 0, .type_anyframe => 0, .type_error_union => @sizeOf(Key.ErrorUnionType), + .type_spirv => @sizeOf(Tag.TypeSpirv), .type_anyerror_union => 0, .type_error_set => b: { const info = extraData(extra_list, Tag.ErrorSet, data); @@ -11117,6 +11217,7 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void { .type_enum_explicit, .type_enum_nonexhaustive, .type_enum_auto, + .type_spirv, .type_opaque, .type_struct, .type_struct_packed, @@ -11828,6 +11929,7 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index { .type_enum_auto, .type_enum_explicit, .type_enum_nonexhaustive, + .type_spirv, .type_opaque, .type_struct, .type_struct_packed, @@ -12187,6 +12289,7 @@ pub fn zigTypeTag(ip: *const InternPool, index: Index) std.builtin.TypeId { .simple_type => unreachable, // handled via Index tag above + .type_spirv => .@"opaque", .type_opaque => .@"opaque", .type_struct, diff --git a/src/Sema.zig b/src/Sema.zig index 99e23e6f5a45..98642308e205 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1378,6 +1378,7 @@ fn analyzeBodyInner( .int_from_error => try sema.zirIntFromError( block, extended), .error_from_int => try sema.zirErrorFromInt( block, extended), .reify => try sema.zirReify( block, extended, inst), + .reify_spirv => try sema.zirReifySpirv( block, extended, inst), .builtin_async_call => try sema.zirBuiltinAsyncCall( block, extended), .cmpxchg => try sema.zirCmpxchg( block, extended), .c_va_arg => try sema.zirCVaArg( block, extended), @@ -2754,7 +2755,7 @@ fn zirTupleDecl( } }); const field_type = try sema.resolveType(block, type_src, zir_field_ty); - try sema.validateTupleFieldType(block, field_type, type_src); + try sema.validateTupleOrStructFieldType(block, field_type, type_src, "tuple"); field_ty.* = field_type.toIntern(); field_init.* = init: { @@ -2781,31 +2782,102 @@ fn zirTupleDecl( })); } -fn validateTupleFieldType( +fn validateTupleOrStructFieldType( sema: *Sema, block: *Block, field_ty: Type, - field_ty_src: LazySrcLoc, + field_src: LazySrcLoc, + type_str: []const u8, ) CompileError!void { - const gpa = sema.gpa; - const zcu = sema.pt.zcu; + const pt = sema.pt; + const zcu = pt.zcu; + const ip = &pt.zcu.intern_pool; if (field_ty.zigTypeTag(zcu) == .@"opaque") { - return sema.failWithOwnedErrorMsg(block, msg: { - const msg = try sema.errMsg(field_ty_src, "opaque types have unknown size and therefore cannot be directly embedded in tuples", .{}); - errdefer msg.destroy(gpa); + if (ip.indexToKey(field_ty.toIntern()) == .spirv_type) { + const spirv_type = ip.loadSpirvType(field_ty.toIntern()); + switch (spirv_type.flags.tag) { + .runtime_array => return, + .sampler, .image, .sampled_image => { + const msg = msg: { + const msg = try sema.errMsg( + field_src, + "SPIR-V type '{}' have unknown size and therefore cannot be directly embedded in {s}s", + .{ field_ty.fmt(pt), type_str }, + ); + errdefer msg.destroy(sema.gpa); + + try sema.addDeclaredHereNote(msg, field_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + }, + } + } + const msg = msg: { + const msg = try sema.errMsg( + field_src, + "opaque types have unknown size and therefore cannot be directly embedded in {s}s", + .{type_str}, + ); + errdefer msg.destroy(sema.gpa); try sema.addDeclaredHereNote(msg, field_ty); break :msg msg; - }); + }; + return sema.failWithOwnedErrorMsg(block, msg); } + if (field_ty.zigTypeTag(zcu) == .noreturn) { - return sema.failWithOwnedErrorMsg(block, msg: { - const msg = try sema.errMsg(field_ty_src, "tuple fields cannot be 'noreturn'", .{}); - errdefer msg.destroy(gpa); + const msg = msg: { + const msg = try sema.errMsg(field_src, "{s} fields cannot be 'noreturn'", .{type_str}); + errdefer msg.destroy(sema.gpa); try sema.addDeclaredHereNote(msg, field_ty); break :msg msg; - }); + }; + return sema.failWithOwnedErrorMsg(block, msg); + } +} + +fn validateUnionFieldType( + sema: *Sema, + block: *Block, + field_ty: Type, + field_src: LazySrcLoc, +) !void { + const pt = sema.pt; + const zcu = pt.zcu; + const ip = &pt.zcu.intern_pool; + if (field_ty.zigTypeTag(zcu) == .@"opaque") { + if (ip.indexToKey(field_ty.toIntern()) == .spirv_type) { + const spirv_type = ip.loadSpirvType(field_ty.toIntern()); + switch (spirv_type.flags.tag) { + .runtime_array => return, + .sampler, .image, .sampled_image => { + const msg = msg: { + const msg = try sema.errMsg( + field_src, + "SPIR-V type '{}' have unknown size and therefore cannot be directly embedded in unions", + .{field_ty.fmt(pt)}, + ); + errdefer msg.destroy(sema.gpa); + + try sema.addDeclaredHereNote(msg, field_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + }, + } + } + + const msg = msg: { + const msg = try sema.errMsg(field_src, "opaque types have unknown size and therefore cannot be directly embedded in unions", .{}); + errdefer msg.destroy(sema.gpa); + + try sema.addDeclaredHereNote(msg, field_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); } } @@ -10025,10 +10097,14 @@ fn analyzeAs( ) CompileError!Air.Inst.Ref { const pt = sema.pt; const zcu = pt.zcu; + const ip = &zcu.intern_pool; const operand = try sema.resolveInst(zir_operand); const dest_ty = try sema.resolveTypeOrPoison(block, src, zir_dest_type) orelse return operand; switch (dest_ty.zigTypeTag(zcu)) { - .@"opaque" => return sema.fail(block, src, "cannot cast to opaque type '{}'", .{dest_ty.fmt(pt)}), + .@"opaque" => { + const opaque_str = if (ip.indexToKey(dest_ty.toIntern()) == .spirv_type) "SPIR-V" else "opaque"; + return sema.fail(block, src, "cannot cast to {s} type '{}'", .{ opaque_str, dest_ty.fmt(pt) }); + }, .noreturn => return sema.fail(block, src, "cannot cast to noreturn", .{}), else => {}, } @@ -20179,20 +20255,11 @@ fn structInitAnon( const init = try sema.resolveInst(item.data.init); field_ty.* = sema.typeOf(init).toIntern(); - if (Type.fromInterned(field_ty.*).zigTypeTag(zcu) == .@"opaque") { - const msg = msg: { - const field_src = block.src(.{ .init_elem = .{ - .init_node_offset = src.offset.node_offset.x, - .elem_index = @intCast(i_usize), - } }); - const msg = try sema.errMsg(field_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{}); - errdefer msg.destroy(sema.gpa); - - try sema.addDeclaredHereNote(msg, Type.fromInterned(field_ty.*)); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } + const field_src = block.src(.{ .init_elem = .{ + .init_node_offset = src.offset.node_offset.x, + .elem_index = @intCast(i_usize), + } }); + try sema.validateTupleOrStructFieldType(block, .fromInterned(field_ty.*), field_src, "struct"); if (try sema.resolveValue(init)) |init_val| { field_val.* = init_val.toIntern(); any_values = true; @@ -20955,8 +21022,8 @@ fn zirReify( if (try sema.anyUndef(block, operand_src, Value.fromInterned(union_val.val))) { return sema.failWithUseOfUndef(block, operand_src); } - const tag_index = type_info_ty.unionTagFieldIndex(Value.fromInterned(union_val.tag), zcu).?; - switch (@as(std.builtin.TypeId, @enumFromInt(tag_index))) { + const tag = try sema.interpretBuiltinType(block, src, .fromInterned(union_val.tag), std.builtin.TypeId); + switch (tag) { .type => return .type_type, .void => return .void_type, .bool => return .bool_type, @@ -21440,6 +21507,177 @@ fn zirReify( } } +fn zirReifySpirv( + 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 target = zcu.getTarget(); + const gpa = sema.gpa; + const ip = &zcu.intern_pool; + const extra = sema.code.extraData(Zir.Inst.Reify, extended.operand).data; + const tracked_inst = try block.trackZir(inst); + const src: LazySrcLoc = .{ + .base_node_inst = tracked_inst, + .offset = LazySrcLoc.Offset.nodeOffset(.zero), + }; + const operand_src: LazySrcLoc = .{ + .base_node_inst = tracked_inst, + .offset = .{ + .node_offset_builtin_call_arg = .{ + .builtin_call_node = .zero, // `tracked_inst` is precisely the `spirv_reify` instruction, so offset is 0 + .arg_index = 0, + }, + }, + }; + + if (!target.cpu.arch.isSpirV()) { + return sema.fail(block, operand_src, "builtin @SpirvType is available when targeting SPIR-V; targeted CPU architecture is {s}", .{@tagName(target.cpu.arch)}); + } + + const spirv_type_info_ty = try sema.getBuiltinType(src, .SpirvType); + const uncasted_operand = try sema.resolveInst(extra.operand); + const spirv_type_info = try sema.coerce(block, spirv_type_info_ty, uncasted_operand, operand_src); + const val = try sema.resolveConstDefinedValue(block, operand_src, spirv_type_info, .{ .simple = .operand_SpirvType }); + const union_val = ip.indexToKey(val.toIntern()).un; + + if (try sema.anyUndef(block, operand_src, .fromInterned(union_val.val))) { + return sema.failWithUseOfUndef(block, operand_src); + } + + // The validation work here is non-trivial, and it's possible the type already exists. + // So in this first pass, let's just construct a hash to optimize for this case. If the + // inputs turn out to be invalid, we can cancel the WIP type later. + + // For deduplication purposes, we must create a hash including all details of this type. + // TODO: use a longer hash! + var hasher = std.hash.Wyhash.init(0); + std.hash.autoHash(&hasher, union_val.tag); + + const name = try sema.createTypeName(block, .anon, "SpirvType", inst, union_val.val); + const tag = try sema.interpretBuiltinType(block, src, .fromInterned(union_val.tag), @typeInfo(std.builtin.SpirvType).@"union".tag_type.?); + const ip_data: InternPool.Tag.TypeSpirv = switch (tag) { + .sampler => .{ + .name = name, + .zir_index = tracked_inst, + .ty = .none, + .flags = .{ .tag = .sampler }, + }, + .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, 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, 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, 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, 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, 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, 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, pt.tid, "multisampled", .no_embedded_nulls), + ).?); + const format = try sema.interpretBuiltinType(block, operand_src, format_val, std.builtin.SpirvType.Image.Format); + const dim = try sema.interpretBuiltinType(block, operand_src, dim_val, std.builtin.SpirvType.Image.Dimensionality); + const depth = try sema.interpretBuiltinType(block, operand_src, depth_val, std.builtin.SpirvType.Image.Depth); + const access = try sema.interpretBuiltinType(block, operand_src, access_val, std.builtin.SpirvType.Image.Access); + + if (target.os.tag != .opencl and access != .unknown) { + return sema.fail(block, operand_src, "access qualifer '.{s}' is only valid under the 'opencl' os", .{@tagName(access)}); + } + + const arrayed = try sema.interpretBuiltinType(block, operand_src, arrayed_val, bool); + const multisampled = try sema.interpretBuiltinType(block, operand_src, multisampled_val, bool); + + const usage_tag_val = usage_val.unionTag(zcu).?; + const usage_tag = try sema.interpretBuiltinType(block, operand_src, usage_tag_val, @typeInfo(std.builtin.SpirvType.Image.Usage).@"union".tag_type.?); + + 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.unionValue(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 '{s}' field is only valid under the 'opencl' os", .{@tagName(usage_tag)}); + } + + if (!sampled_type.hasRuntimeBits(zcu) or (!sampled_type.isRuntimeFloat() and !sampled_type.isInt(zcu))) { + return sema.fail(block, operand_src, "invalid '{s}' field value '{}'", .{ @tagName(usage_tag), sampled_type.fmt(pt) }); + } + + if (target.os.tag == .vulkan and sampled_type.bitSize(zcu) != 32 and sampled_type.bitSize(zcu) != 64) { + return sema.fail( + block, + operand_src, + "'{s}' field value must be a 32-bit int, 64-bit int or 32-bit float under the 'vulkan' os", + .{@tagName(usage_tag)}, + ); + } + + 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, .runtime_array => blk: { + std.hash.autoHash(&hasher, union_val.val); + break :blk .{ + .name = name, + .zir_index = tracked_inst, + .ty = union_val.val, + .flags = .{ .tag = tag }, + }; + }, + }; + + return Air.internedToRef(try ip.getSpirvType( + gpa, + pt.tid, + .{ .type_hash = hasher.final(), .type_spirv = ip_data }, + )); +} + fn reifyEnum( sema: *Sema, block: *Block, @@ -21779,15 +22017,7 @@ fn reifyUnion( for (field_types) |field_ty_ip| { const field_ty = Type.fromInterned(field_ty_ip); - if (field_ty.zigTypeTag(zcu) == .@"opaque") { - return sema.failWithOwnedErrorMsg(block, msg: { - const msg = try sema.errMsg(src, "opaque types have unknown size and therefore cannot be directly embedded in unions", .{}); - errdefer msg.destroy(gpa); - - try sema.addDeclaredHereNote(msg, field_ty); - break :msg msg; - }); - } + try sema.validateUnionFieldType(block, field_ty, src); if (layout == .@"extern" and !try sema.validateExternType(field_ty, .union_field)) { return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.errMsg(src, "extern unions cannot contain fields of type '{}'", .{field_ty.fmt(pt)}); @@ -21892,7 +22122,7 @@ fn reifyTuple( ); } - try sema.validateTupleFieldType(block, field_type, src); + try sema.validateTupleOrStructFieldType(block, field_type, src, "tuple"); { const alignment_ok = ok: { @@ -22106,24 +22336,7 @@ fn reifyStruct( struct_type.field_inits.get(ip)[field_idx] = field_default; } - if (field_ty.zigTypeTag(zcu) == .@"opaque") { - return sema.failWithOwnedErrorMsg(block, msg: { - const msg = try sema.errMsg(src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{}); - errdefer msg.destroy(gpa); - - try sema.addDeclaredHereNote(msg, field_ty); - break :msg msg; - }); - } - if (field_ty.zigTypeTag(zcu) == .noreturn) { - return sema.failWithOwnedErrorMsg(block, msg: { - const msg = try sema.errMsg(src, "struct fields cannot be 'noreturn'", .{}); - errdefer msg.destroy(gpa); - - try sema.addDeclaredHereNote(msg, field_ty); - break :msg msg; - }); - } + try sema.validateTupleOrStructFieldType(block, field_ty, src, "struct"); if (layout == .@"extern" and !try sema.validateExternType(field_ty, .struct_field)) { return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.errMsg(src, "extern structs cannot contain fields of type '{}'", .{field_ty.fmt(pt)}); @@ -26433,6 +26646,7 @@ fn zirBuiltinValue(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstD .export_options => try sema.getBuiltinType(src, .ExportOptions), .extern_options => try sema.getBuiltinType(src, .ExternOptions), .type_info => try sema.getBuiltinType(src, .Type), + .spirv_type_info => try sema.getBuiltinType(src, .SpirvType), .branch_hint => try sema.getBuiltinType(src, .BranchHint), // zig fmt: on @@ -34814,6 +35028,25 @@ pub fn resolveStructLayout(sema: *Sema, ty: Type) SemaError!void { continue; } + if (ip.indexToKey(field_ty.toIntern()) == .spirv_type) { + const spirv_type = ip.loadSpirvType(field_ty.toIntern()); + assert(spirv_type.flags.tag == .runtime_array); + + if (struct_type.layout != .@"extern") { + const msg = try sema.errMsg( + ty.srcLoc(zcu), + "non-extern struct cannot contain fields of type '{}'", + .{field_ty.fmt(pt)}, + ); + try sema.addFieldErrNote(ty, i, msg, "while checking this field", .{}); + return sema.failWithOwnedErrorMsg(null, msg); + } else if (i != aligns.len - 1) { + const msg = try sema.errMsg(ty.srcLoc(zcu), "struct field of type '{}' must be the last field", .{field_ty.fmt(pt)}); + try sema.addFieldErrNote(ty, i, msg, "while checking this field", .{}); + return sema.failWithOwnedErrorMsg(null, msg); + } + } + field_size.* = field_ty.abiSizeSema(pt) catch |err| switch (err) { error.AnalysisFail => { const msg = sema.err orelse return err; @@ -35686,26 +35919,7 @@ fn structFields( struct_type.field_types.get(ip)[field_i] = field_ty.toIntern(); - if (field_ty.zigTypeTag(zcu) == .@"opaque") { - const msg = msg: { - const msg = try sema.errMsg(ty_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{}); - errdefer msg.destroy(sema.gpa); - - try sema.addDeclaredHereNote(msg, field_ty); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(&block_scope, msg); - } - if (field_ty.zigTypeTag(zcu) == .noreturn) { - const msg = msg: { - const msg = try sema.errMsg(ty_src, "struct fields cannot be 'noreturn'", .{}); - errdefer msg.destroy(sema.gpa); - - try sema.addDeclaredHereNote(msg, field_ty); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(&block_scope, msg); - } + try sema.validateTupleOrStructFieldType(&block_scope, field_ty, ty_src, "struct"); switch (struct_type.layout) { .@"extern" => if (!try sema.validateExternType(field_ty, .struct_field)) { const msg = msg: { @@ -36163,16 +36377,7 @@ fn unionFields( } } - if (field_ty.zigTypeTag(zcu) == .@"opaque") { - const msg = msg: { - const msg = try sema.errMsg(type_src, "opaque types have unknown size and therefore cannot be directly embedded in unions", .{}); - errdefer msg.destroy(sema.gpa); - - try sema.addDeclaredHereNote(msg, field_ty); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(&block_scope, msg); - } + try sema.validateUnionFieldType(&block_scope, field_ty, type_src); const layout = union_type.flagsUnordered(ip).layout; if (layout == .@"extern" and !try sema.validateExternType(field_ty, .union_field)) @@ -36441,6 +36646,7 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value { .type_anyerror_union, .type_error_set, .type_inferred_error_set, + .type_spirv, .type_opaque, .type_function, => null, diff --git a/src/Sema/bitcast.zig b/src/Sema/bitcast.zig index 8fcc40c67ce0..a9318839bb71 100644 --- a/src/Sema/bitcast.zig +++ b/src/Sema/bitcast.zig @@ -248,6 +248,7 @@ const UnpackValueBits = struct { .struct_type, .tuple_type, .union_type, + .spirv_type, .opaque_type, .enum_type, .func_type, diff --git a/src/Type.zig b/src/Type.zig index 925f65106d0d..3104a713cb2f 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -344,6 +344,10 @@ pub fn print(ty: Type, writer: anytype, pt: Zcu.PerThread) @TypeOf(writer).Error const name = ip.loadEnumType(ty.toIntern()).name; try writer.print("{}", .{name.fmt(ip)}); }, + .spirv_type => { + const name = ip.loadSpirvType(ty.toIntern()).name; + try writer.print("{}", .{name.fmt(ip)}); + }, .func_type => |fn_info| { if (fn_info.is_noinline) { try writer.writeAll("noinline "); @@ -636,6 +640,7 @@ pub fn hasRuntimeBitsInner( } }, + .spirv_type => true, .opaque_type => true, .enum_type => Type.fromInterned(ip.loadEnumType(ty.toIntern()).tag_ty).hasRuntimeBitsInner( ignore_comptime_only, @@ -683,6 +688,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. @@ -1139,7 +1145,7 @@ pub fn abiAlignmentInner( return .{ .scalar = union_type.flagsUnordered(ip).alignment }; }, - .opaque_type => return .{ .scalar = .@"1" }, + .opaque_type, .spirv_type => return .{ .scalar = .@"1" }, .enum_type => return .{ .scalar = Type.fromInterned(ip.loadEnumType(ty.toIntern()).tag_ty).abiAlignment(zcu), }, @@ -1526,6 +1532,13 @@ pub fn abiSizeInner( assert(union_type.haveLayout(ip)); return .{ .scalar = union_type.sizeUnordered(ip) }; }, + .spirv_type => { + const spirv_type = ip.loadSpirvType(ty.toIntern()); + switch (spirv_type.flags.tag) { + .runtime_array => return .{ .scalar = Type.fromInterned(spirv_type.ty).abiSize(zcu) }, + else => unreachable, // no size available + } + }, .opaque_type => unreachable, // no size available .enum_type => return .{ .scalar = Type.fromInterned(ip.loadEnumType(ty.toIntern()).tag_ty).abiSize(zcu) }, @@ -1846,7 +1859,7 @@ pub fn bitSizeInner( return size; }, - .opaque_type => unreachable, + .opaque_type, .spirv_type => unreachable, .enum_type => return Type.fromInterned(ip.loadEnumType(ty.toIntern()).tag_ty) .bitSizeInner(strat, zcu, tid), @@ -2414,6 +2427,7 @@ pub fn intInfo(starting_ty: Type, zcu: *const Zcu) InternPool.Key.IntType { .simple_type => unreachable, // handled via Index enum tag above .union_type => unreachable, + .spirv_type => unreachable, .opaque_type => unreachable, // values, not types @@ -2719,6 +2733,7 @@ pub fn onePossibleValue(starting_type: Type, pt: Zcu.PerThread) !?Value { return Value.fromInterned(only); }, .opaque_type => return null, + .spirv_type => return null, .enum_type => { const enum_type = ip.loadEnumType(ty.toIntern()); switch (enum_type.tag_mode) { @@ -2966,7 +2981,7 @@ pub fn comptimeOnlyInner( }; }, - .opaque_type => false, + .spirv_type, .opaque_type => false, .enum_type => return Type.fromInterned(ip.loadEnumType(ty.toIntern()).tag_ty).comptimeOnlyInner(strat, zcu, tid), @@ -3599,7 +3614,7 @@ pub fn typeDeclSrcLine(ty: Type, zcu: *Zcu) ?u32 { .union_decl => zir.extraData(Zir.Inst.UnionDecl, inst.data.extended.operand).data.src_line, .enum_decl => zir.extraData(Zir.Inst.EnumDecl, inst.data.extended.operand).data.src_line, .opaque_decl => zir.extraData(Zir.Inst.OpaqueDecl, inst.data.extended.operand).data.src_line, - .reify => zir.extraData(Zir.Inst.Reify, inst.data.extended.operand).data.src_line, + .reify, .reify_spirv => zir.extraData(Zir.Inst.Reify, inst.data.extended.operand).data.src_line, else => unreachable, }, else => unreachable, diff --git a/src/Zcu.zig b/src/Zcu.zig index 86963f932883..f03f4c362c00 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -272,6 +272,8 @@ pub const BuiltinDecl = enum { @"Type.Opaque", @"Type.Declaration", + SpirvType, + panic, @"panic.call", @"panic.sentinelMismatch", @@ -348,6 +350,8 @@ pub const BuiltinDecl = enum { .@"Type.Declaration", => .type, + .SpirvType => .type, + .panic => .type, .@"panic.call", @@ -395,7 +399,7 @@ pub const BuiltinDecl = enum { pub fn stage(decl: BuiltinDecl) InternPool.MemoizedStateStage { if (decl == .VaList) return .va_list; - if (@intFromEnum(decl) <= @intFromEnum(BuiltinDecl.@"Type.Declaration")) { + if (@intFromEnum(decl) <= @intFromEnum(BuiltinDecl.SpirvType)) { return .main; } else { return .panic; @@ -2438,7 +2442,7 @@ pub const LazySrcLoc = struct { .union_decl => zir.extraData(Zir.Inst.UnionDecl, inst.data.extended.operand).data.src_node, .enum_decl => zir.extraData(Zir.Inst.EnumDecl, inst.data.extended.operand).data.src_node, .opaque_decl => zir.extraData(Zir.Inst.OpaqueDecl, inst.data.extended.operand).data.src_node, - .reify => zir.extraData(Zir.Inst.Reify, inst.data.extended.operand).data.node, + .reify, .reify_spirv => zir.extraData(Zir.Inst.Reify, inst.data.extended.operand).data.node, else => unreachable, }, else => unreachable, diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 641347bee150..4dfb1065c919 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -3181,6 +3181,7 @@ fn lowerConstant(cg: *CodeGen, val: Value, ty: Type) InnerError!WValue { .tuple_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, diff --git a/src/codegen.zig b/src/codegen.zig index ad241d047d3b..77a7c6d9dd58 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -219,6 +219,7 @@ pub fn generateSymbol( .tuple_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index cd4573375dbc..a8a001ff12a8 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -951,6 +951,7 @@ pub const DeclGen = struct { .struct_type, .tuple_type, .union_type, + .spirv_type, .opaque_type, .enum_type, .func_type, @@ -1803,6 +1804,7 @@ pub const DeclGen = struct { } }, .anyframe_type, + .spirv_type, .opaque_type, .func_type, => unreachable, diff --git a/src/codegen/c/Type.zig b/src/codegen/c/Type.zig index ec1bf40e0c66..32683cddbf1f 100644 --- a/src/codegen/c/Type.zig +++ b/src/codegen/c/Type.zig @@ -2342,6 +2342,9 @@ pub const Pool = struct { .bits = pt.zcu.errorSetBits(), }, mod, kind), + // Not for this backend + .spirv_type => unreachable, + .undef, .simple_value, .variable, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 167e0d1607f4..6962c8159e0f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3396,6 +3396,8 @@ pub const Object = struct { .enum_type => try o.lowerType(Type.fromInterned(ip.loadEnumType(t.toIntern()).tag_ty)), .func_type => |func_type| try o.lowerTypeFn(func_type), .error_set_type, .inferred_error_set_type => try o.errorIntType(), + // Not for this backend + .spirv_type => unreachable, // values, not types .undef, .simple_value, @@ -3620,6 +3622,7 @@ pub const Object = struct { .struct_type, .tuple_type, .union_type, + .spirv_type, .opaque_type, .enum_type, .func_type, diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index c361090b517c..f912ab39f86d 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -819,6 +819,7 @@ const NavGen = struct { .tuple_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, @@ -1294,7 +1295,7 @@ const NavGen = struct { } switch (ip.indexToKey(child_ty.toIntern())) { - .func_type, .opaque_type => {}, + .func_type, .opaque_type, .spirv_type => {}, else => { try self.spv.decorate(result_id, .{ .ArrayStride = .{ .array_stride = @intCast(child_ty.abiSize(zcu)) } }); }, @@ -1445,6 +1446,84 @@ const NavGen = struct { const section = &self.spv.sections.types_globals_constants; + switch (ip.indexToKey(ty.toIntern())) { + .spirv_type => { + const spirv_type = ip.loadSpirvType(ty.toIntern()); + const result_id = self.spv.allocId(); + switch (spirv_type.flags.tag) { + .sampler => try section.emit(self.gpa, .OpTypeSampler, .{ .id_result = result_id }), + .image => { + const sampled_type_id = blk: { + if (spirv_type.ty == .none) break :blk try self.intType(.unsigned, 32); + break :blk try self.resolveType(Type.fromInterned(spirv_type.ty), .direct); + }; + try section.emit(self.gpa, .OpTypeImage, .{ + .id_result = result_id, + .sampled_type = sampled_type_id, + .dim = switch (spirv_type.flags.dim) { + .@"1d" => .@"1D", + .@"2d" => .@"2D", + .@"3d" => .@"3D", + .cube => .Cube, + }, + .depth = switch (spirv_type.flags.depth) { + .not_depth => 0, + .depth => 1, + .unknown => 2, + }, + .arrayed = @intFromBool(spirv_type.flags.is_arrayed), + .ms = @intFromBool(spirv_type.flags.is_multisampled), + .sampled = switch (spirv_type.flags.usage) { + .unknown => 1, + .sampled => 1, + .storage => 2, + }, + .image_format = 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, + }, + .access_qualifier = switch (spirv_type.flags.access) { + .unknown => null, + .read_only => .ReadOnly, + .write_only => .WriteOnly, + .read_write => .ReadWrite, + }, + }); + }, + .sampled_image => { + const image_ty_id = try self.resolveType(.fromInterned(spirv_type.ty), .indirect); + try section.emit(self.gpa, .OpTypeSampledImage, .{ + .id_result = result_id, + .image_type = image_ty_id, + }); + }, + .runtime_array => { + const elem_ty_id = try self.resolveType(.fromInterned(spirv_type.ty), .indirect); + try section.emit(self.gpa, .OpTypeRuntimeArray, .{ + .id_result = result_id, + .element_type = elem_ty_id, + }); + try self.spv.decorate(result_id, .{ .ArrayStride = .{ .array_stride = @intCast(ty.abiSize(zcu)) } }); + }, + } + return result_id; + }, + else => {}, + } + + // TODO: switch by intern pool key switch (ty.zigTypeTag(zcu)) { .noreturn => { assert(repr == .direct); @@ -1626,6 +1705,7 @@ const NavGen = struct { } }, .@"struct" => { + // TODO: merge tuple and struct code const struct_type = switch (ip.indexToKey(ty.toIntern())) { .tuple_type => |tuple| { const member_types = try self.gpa.alloc(IdRef, tuple.values.len); @@ -1672,6 +1752,10 @@ const NavGen = struct { continue; } + if (ip.indexToKey(field_ty.toIntern()) == .spirv_type) { + try self.spv.decorate(result_id, .Block); + } + if (self.spv.hasFeature(.shader)) { try self.spv.decorateMember(result_id, index, .{ .Offset = .{ .byte_offset = @intCast(ty.structFieldOffset(field_index, zcu)), @@ -6557,6 +6641,7 @@ const NavGen = struct { .struct_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index a657667f15c7..6091db823002 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -2749,6 +2749,7 @@ fn updateComptimeNavInner(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPoo .opt_type, .error_union_type, .anyframe_type, + .spirv_type, .simple_type, .tuple_type, .func_type, @@ -3441,6 +3442,7 @@ fn updateLazyType( .struct_type, .union_type, .opaque_type, + .spirv_type, => unreachable, .tuple_type => |tuple_type| if (tuple_type.types.len == 0) { try wip_nav.abbrevCode(.generated_empty_struct_type); @@ -3678,6 +3680,7 @@ fn updateLazyValue( .tuple_type, .union_type, .opaque_type, + .spirv_type, .enum_type, .func_type, .error_set_type, diff --git a/src/print_value.zig b/src/print_value.zig index 0bd5bcee231f..81cb622038cd 100644 --- a/src/print_value.zig +++ b/src/print_value.zig @@ -71,6 +71,7 @@ pub fn print( .anyframe_type, .error_union_type, .simple_type, + .spirv_type, .struct_type, .tuple_type, .union_type, diff --git a/src/print_zir.zig b/src/print_zir.zig index 08e1a30368ea..3577ab102465 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -583,7 +583,7 @@ const Writer = struct { try self.writeSrcNode(stream, inst_data.node); }, - .reify => { + .reify, .reify_spirv => { const inst_data = self.code.extraData(Zir.Inst.Reify, extended.operand).data; try stream.print("line({d}), ", .{inst_data.src_line}); try self.writeInstRef(stream, inst_data.operand); diff --git a/test/behavior.zig b/test/behavior.zig index 8006d8364d13..80abbcc65edb 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -112,6 +112,10 @@ test { _ = @import("behavior/x86_64.zig"); + if (builtin.zig_backend == .stage2_spirv64) { + _ = @import("behavior/spirv.zig"); + } + if (builtin.zig_backend != .stage2_spirv64 and builtin.cpu.arch == .wasm32) { _ = @import("behavior/wasm.zig"); } diff --git a/test/behavior/spirv.zig b/test/behavior/spirv.zig new file mode 100644 index 000000000000..904074f98687 --- /dev/null +++ b/test/behavior/spirv.zig @@ -0,0 +1,33 @@ +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 }); + +extern const sampler: Sampler addrspace(.constant); +extern const sampled_image: SampledImage addrspace(.constant); +extern const storage_image: StorageImage addrspace(.constant); +extern const runtime_array: extern struct { e: RuntimeArray } addrspace(.storage_buffer); + +test "@SpirvType" { + _ = &sampler; + _ = &sampled_image; + _ = &storage_image; + _ = &runtime_array; +} diff --git a/test/cases/compile_errors/@import_zon_bad_type.zig b/test/cases/compile_errors/@import_zon_bad_type.zig index 93529f49512d..c572cc1ae24a 100644 --- a/test/cases/compile_errors/@import_zon_bad_type.zig +++ b/test/cases/compile_errors/@import_zon_bad_type.zig @@ -117,9 +117,9 @@ export fn testMutablePointer() void { // tmp.zig:37:38: note: imported here // neg_inf.zon:1:1: error: expected type '?u8' // tmp.zig:57:28: note: imported here -// neg_inf.zon:1:1: error: expected type 'tmp.testNonExhaustiveEnum__enum_487' +// neg_inf.zon:1:1: error: expected type 'tmp.testNonExhaustiveEnum__enum_508' // tmp.zig:62:39: note: imported here -// neg_inf.zon:1:1: error: expected type 'tmp.testUntaggedUnion__union_489' +// neg_inf.zon:1:1: error: expected type 'tmp.testUntaggedUnion__union_510' // tmp.zig:67:44: note: imported here -// neg_inf.zon:1:1: error: expected type 'tmp.testTaggedUnionVoid__union_492' +// neg_inf.zon:1:1: error: expected type 'tmp.testTaggedUnionVoid__union_513' // tmp.zig:72:50: note: imported here 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 new file mode 100644 index 000000000000..b6d21d0dc986 --- /dev/null +++ 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=stage2 +// target=x86_64-native +// +// :2:21: error: builtin @SpirvType is 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 new file mode 100644 index 000000000000..eaaba27cb047 --- /dev/null +++ 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=stage2 +// target=spirv64-vulkan +// +// :2:21: error: access qualifer '.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/anytype_param_requires_comptime.zig b/test/cases/compile_errors/anytype_param_requires_comptime.zig index abe4d957907f..453bf8e57ddd 100644 --- a/test/cases/compile_errors/anytype_param_requires_comptime.zig +++ b/test/cases/compile_errors/anytype_param_requires_comptime.zig @@ -15,6 +15,6 @@ pub export fn entry() void { // error // // :7:25: error: unable to resolve comptime value -// :7:25: note: initializer of comptime-only struct 'tmp.S.foo__anon_461.C' must be comptime-known +// :7:25: note: initializer of comptime-only struct 'tmp.S.foo__anon_482.C' must be comptime-known // :4:16: note: struct requires comptime because of this field // :4:16: note: types are not available at runtime diff --git a/test/cases/compile_errors/bogus_method_call_on_slice.zig b/test/cases/compile_errors/bogus_method_call_on_slice.zig index bfc1c4889336..ebb3c2f8fe0c 100644 --- a/test/cases/compile_errors/bogus_method_call_on_slice.zig +++ b/test/cases/compile_errors/bogus_method_call_on_slice.zig @@ -16,5 +16,5 @@ pub export fn entry2() void { // // :3:6: error: no field or member function named 'copy' in '[]const u8' // :9:8: error: no field or member function named 'bar' in '@TypeOf(.{})' -// :12:18: error: no field or member function named 'bar' in 'tmp.entry2__struct_465' +// :12:18: error: no field or member function named 'bar' in 'tmp.entry2__struct_486' // :12:6: note: struct declared here diff --git a/test/cases/compile_errors/coerce_anon_struct.zig b/test/cases/compile_errors/coerce_anon_struct.zig index 596f7c13eca0..632825143bf8 100644 --- a/test/cases/compile_errors/coerce_anon_struct.zig +++ b/test/cases/compile_errors/coerce_anon_struct.zig @@ -6,6 +6,6 @@ export fn foo() void { // error // -// :4:16: error: expected type 'tmp.T', found 'tmp.foo__struct_454' +// :4:16: error: expected type 'tmp.T', found 'tmp.foo__struct_475' // :3:16: note: struct declared here // :1:11: note: struct declared here 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 new file mode 100644 index 000000000000..feb113ad1eab --- /dev/null +++ b/test/cases/compile_errors/directly_embedding_spirv_type_in_struct_and_union.zig @@ -0,0 +1,43 @@ +const Sampler = @SpirvType(.sampler); +const RuntimeArray = @SpirvType(.{ .runtime_array = u32 }); +const Foo = struct { + s: Sampler, +}; +const Bar = union { + One: i32, + Two: 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 b() void { + var bar: Bar = undefined; + _ = &bar; +} +export fn c() void { + var baz: Baz = undefined; + _ = &baz; +} +export fn d() void { + var qux: Qux = undefined; + _ = &qux; +} + +// error +// backend=stage2 +// target=spirv64-vulkan +// +// :4:8: error: SPIR-V type 'tmp.Sampler__SpirvType_87' have unknown size and therefore cannot be directly embedded in structs +// :8:10: error: SPIR-V type 'tmp.Sampler__SpirvType_87' have unknown size and therefore cannot be directly embedded in unions +// :10:13: error: non-extern struct cannot contain fields of type 'tmp.RuntimeArray__SpirvType_8' +// :11:5: note: while checking this field +// :13:20: error: struct field of type 'tmp.RuntimeArray__SpirvType_8' must be the last field +// :14:5: note: while checking this field diff --git a/test/cases/compile_errors/redundant_try.zig b/test/cases/compile_errors/redundant_try.zig index 832ef248948d..33a2870d2441 100644 --- a/test/cases/compile_errors/redundant_try.zig +++ b/test/cases/compile_errors/redundant_try.zig @@ -44,9 +44,9 @@ comptime { // // :5:23: error: expected error union type, found 'comptime_int' // :10:23: error: expected error union type, found '@TypeOf(.{})' -// :15:23: error: expected error union type, found 'tmp.test2__struct_491' +// :15:23: error: expected error union type, found 'tmp.test2__struct_512' // :15:23: note: struct declared here -// :20:27: error: expected error union type, found 'tmp.test3__struct_493' +// :20:27: error: expected error union type, found 'tmp.test3__struct_514' // :20:27: note: struct declared here // :25:23: error: expected error union type, found 'struct { comptime *const [5:0]u8 = "hello" }' // :31:13: error: expected error union type, found 'u32'