From af1046aca464a5cf4c0c4f8e5dde1852a528c837 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sat, 27 Jun 2026 11:54:00 +0330 Subject: [PATCH 1/5] Type: allow vectors in externs for SPIR-V target Vectors are well-defined in SPIR-V unlike other targets, and they are essential for almost any shader interface because arrays are incompatible with e.g. Vulkan data formats (`gl_Position`, vertex attributes, color outputs, etc.) --- src/Type.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Type.zig b/src/Type.zig index 546953b307df5bcb37e99942655801f6b5910676..7c45eda413906fc41865d3392eba198e73e1cdcf 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -3087,7 +3087,10 @@ pub fn validateExtern(ty: Type, position: ExternPosition, zcu: *const Zcu) bool .frame, => false, - .vector => position == .param_ty or position == .ret_ty, + .vector => { + if (zcu.getTarget().cpu.arch.isSpirV()) return true; + return position == .param_ty or position == .ret_ty; + }, .void => switch (position) { .ret_ty, -- 2.54.0 From 85b0259307706119d5fe9a2a3d7938e064499c1d Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sat, 27 Jun 2026 12:00:24 +0330 Subject: [PATCH 2/5] Target: chain spirv versions --- lib/std/Target/spirv.zig | 24 ++++++++++++++++++------ tools/update_cpu_features.zig | 15 ++++++++++++++- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/lib/std/Target/spirv.zig b/lib/std/Target/spirv.zig index 90d43cdbad5a9ccc699f94e5f53483ec94d7e502..ecd3c056cf329c5c392807d19b18ba6dd36d361b 100644 --- a/lib/std/Target/spirv.zig +++ b/lib/std/Target/spirv.zig @@ -1766,32 +1766,44 @@ pub const all_features = blk: { result[@intFromEnum(Feature.v1_1)] = .{ .llvm_name = null, .description = "Enable v1_1 Extension.", - .dependencies = featureSet(&[_]Feature{}), + .dependencies = featureSet(&[_]Feature{ + .v1_0, + }), }; result[@intFromEnum(Feature.v1_2)] = .{ .llvm_name = null, .description = "Enable v1_2 Extension.", - .dependencies = featureSet(&[_]Feature{}), + .dependencies = featureSet(&[_]Feature{ + .v1_1, + }), }; result[@intFromEnum(Feature.v1_3)] = .{ .llvm_name = null, .description = "Enable v1_3 Extension.", - .dependencies = featureSet(&[_]Feature{}), + .dependencies = featureSet(&[_]Feature{ + .v1_2, + }), }; result[@intFromEnum(Feature.v1_4)] = .{ .llvm_name = null, .description = "Enable v1_4 Extension.", - .dependencies = featureSet(&[_]Feature{}), + .dependencies = featureSet(&[_]Feature{ + .v1_3, + }), }; result[@intFromEnum(Feature.v1_5)] = .{ .llvm_name = null, .description = "Enable v1_5 Extension.", - .dependencies = featureSet(&[_]Feature{}), + .dependencies = featureSet(&[_]Feature{ + .v1_4, + }), }; result[@intFromEnum(Feature.v1_6)] = .{ .llvm_name = null, .description = "Enable v1_6 Extension.", - .dependencies = featureSet(&[_]Feature{}), + .dependencies = featureSet(&[_]Feature{ + .v1_5, + }), }; result[@intFromEnum(Feature.variable_pointers)] = .{ .llvm_name = null, diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 42432c0c36f48c831524773b882d1da75caaa723..7ee0fb9af538dc8aec13ec7cd98ac7d471f13299 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -80,7 +80,20 @@ const spirv_extra_features = blk: { feature.* = .{ .zig_name = name, .desc = "Enable " ++ name ++ " extension", - .deps = &.{}, + .deps = if (std.mem.eql(u8, name, "v1_6")) + &.{"v1_5"} + else if (std.mem.eql(u8, name, "v1_5")) + &.{"v1_4"} + else if (std.mem.eql(u8, name, "v1_4")) + &.{"v1_3"} + else if (std.mem.eql(u8, name, "v1_3")) + &.{"v1_2"} + else if (std.mem.eql(u8, name, "v1_2")) + &.{"v1_1"} + else if (std.mem.eql(u8, name, "v1_1")) + &.{"v1_0"} + else + &.{}, }; } -- 2.54.0 From 56827cf78f37b6d2ec0a95d385fd4b7bed4866ea Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sat, 27 Jun 2026 12:02:12 +0330 Subject: [PATCH 3/5] Sema: handle `@SpirvType(.runtime_array)` in `elemPtrOneLayerOnly()` --- src/Sema.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sema.zig b/src/Sema.zig index 7f6035e62d503341b5e136b2a7fc6158b3e626cc..2469444831e1037c7e526a33a0f4c2f39b26474a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -27032,6 +27032,7 @@ fn elemPtrOneLayerOnly( .vector => try sema.elemPtrVector(block, indexable_src, indexable, elem_index_src, elem_index, init), .array => try sema.elemPtrArray(block, src, indexable_src, indexable, elem_index_src, elem_index, init, oob_safety), .@"struct" => try sema.tupleElemPtr(block, indexable_src, indexable, elem_index, elem_index_src), + .spirv => try sema.elemPtrSpirvRuntimeArray(block, indexable, elem_index), else => unreachable, // Guaranteed by checkIndexable }; try sema.checkKnownAllocPtr(block, indexable, elem_ptr); -- 2.54.0 From ce07ba754c877ae376b002e5bd6aad238d9f3d3d Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sun, 28 Jun 2026 14:04:56 +0330 Subject: [PATCH 4/5] spirv: fix vector pointer access --- src/codegen/spirv/CodeGen.zig | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index 69aa87e27900259904a9dc635a5e94df8f4ff8f4..dd8567c8ef4f18a1aa8e8a7b7110ddeb9b2b0d79 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -1881,17 +1881,8 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { const parent_ptr_ty = try oac.parent.ptrType(pt); const result_ty_id = try cg.resolveType(oac.new_ptr_ty, .direct); - if (parent_ptr_ty.childType(zcu).isVector(zcu)) { - // Vector element ptr accesses are derived as offset_and_cast. - // We can just use OpAccessChain. - 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.new_ptr_ty.ptrInfo(zcu).flags.vector_index != .none) { + return parent_ptr_id; } if (oac.byte_offset == 0) { -- 2.54.0 From bae68c8a458690a7fedeed7541f80b960a4bd1f6 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sun, 28 Jun 2026 14:27:03 +0330 Subject: [PATCH 5/5] test: unskip passing behavior tests for spirv backend --- test/behavior/align.zig | 1 - test/behavior/array.zig | 1 - test/behavior/cast.zig | 2 -- test/behavior/destructure.zig | 2 -- test/behavior/error.zig | 2 -- test/behavior/eval.zig | 2 -- test/behavior/fn.zig | 1 - test/behavior/generics.zig | 1 - test/behavior/globals.zig | 1 - test/behavior/inline_switch.zig | 2 +- test/behavior/math.zig | 1 - test/behavior/muladd.zig | 1 - test/behavior/optional.zig | 1 - test/behavior/slice.zig | 3 --- test/behavior/struct.zig | 1 - test/behavior/switch.zig | 3 ++- test/behavior/tuple.zig | 1 - test/behavior/vector.zig | 1 - 18 files changed, 3 insertions(+), 24 deletions(-) diff --git a/test/behavior/align.zig b/test/behavior/align.zig index 06c12eaea14622bbeaaa0aa85cede2fda9feb504..cb300d535060891096337de5c0996e48e8870eb9 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -432,7 +432,6 @@ test "read 128-bit field from default aligned struct in stack memory" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; var default_aligned = DefaultAligned{ .nevermind = 1, diff --git a/test/behavior/array.zig b/test/behavior/array.zig index f7e179034a9a2f16807a6682f8ee7f1f50ba97e8..e4b7b6d75d1f891212d2e877779e41afa6f7d6db 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -1077,7 +1077,6 @@ test "initialize sentinel-terminated slice with reference to empty array initial } test "initialize sentinel-terminated many-pointer with reference to empty array initializer" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const a: [*:0]const u8 = &.{}; comptime assert(a[0] == 0); } diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 178c95de4037cc415d0caefd89e35baed3455cb8..1cd6d009e6b07031bbcf8fe0f17f56b7b479c24e 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -433,8 +433,6 @@ test "implicit cast from *[N]T to [*c]T" { } test "*usize to *void" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; - var i = @as(usize, 0); const v: *void = @ptrCast(&i); v.* = {}; diff --git a/test/behavior/destructure.zig b/test/behavior/destructure.zig index 300ce2d815b956dad90e07d0113ee5c65a7d7b4d..43ddbb7a4de088d57f1a4f9b14f795369037fcc3 100644 --- a/test/behavior/destructure.zig +++ b/test/behavior/destructure.zig @@ -94,8 +94,6 @@ test "destructure from labeled block" { } test "destructure tuple value" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; - const tup: struct { f32, u32, i64 } = .{ 10.0, 20, 30 }; const x, const y, const z = tup; diff --git a/test/behavior/error.zig b/test/behavior/error.zig index 725f52bfbee8ab78a3185caa5a20183b292944a3..63014707d9b72088668422fc2680699420aa10d7 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -145,8 +145,6 @@ test "implicit cast to optional to error union to return result loc" { } test "fn returning empty error set can be passed as fn returning any error" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; - entry(); comptime entry(); } diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index 6b60920e8560c6dfa7cd4381d8b6ede61706cd7e..60a06886d45ac988b55d0a51f5bc40101f098e7e 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -1445,8 +1445,6 @@ test "struct in comptime false branch is not evaluated" { } test "result of nested switch assigned to variable" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO - var zds: u32 = 0; zds = switch (zds) { 0 => switch (zds) { diff --git a/test/behavior/fn.zig b/test/behavior/fn.zig index 7b22be363ca285bdbdcefa203086538292c05654..14cb20f5442a452695ccad2134bd4d0c306ab4b2 100644 --- a/test/behavior/fn.zig +++ b/test/behavior/fn.zig @@ -292,7 +292,6 @@ fn voidFun(a: i32, b: void, c: i32, d: void) !void { test "call function with empty string" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; acceptsString(""); } diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index 1314925420c7b8a7c31c32fdaaa7e0fcac7bd9b0..95dbcd393d69aefc7a893d0b35a19fa0b48b18e9 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -519,7 +519,6 @@ test "call generic function with from function called by the generic function" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const GET = struct { key: []const u8, diff --git a/test/behavior/globals.zig b/test/behavior/globals.zig index 98ebedae41cf85ac2f7e6500e5d8cb380159a3d4..eab88cdfbdc4da73079c041fdc71cde54ba16821 100644 --- a/test/behavior/globals.zig +++ b/test/behavior/globals.zig @@ -16,7 +16,6 @@ var vpos = @Vector(2, f32){ 0.0, 0.0 }; test "store to global vector" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try expect(vpos[1] == 0.0); vpos = @Vector(2, f32){ 0.0, 1.0 }; diff --git a/test/behavior/inline_switch.zig b/test/behavior/inline_switch.zig index 2e35307167d7ec9ec3764da6cf349315d4c0e72d..405ea5a9c6fd9e446d1b2c8526154ff83276bc96 100644 --- a/test/behavior/inline_switch.zig +++ b/test/behavior/inline_switch.zig @@ -81,7 +81,7 @@ test "inline else bool" { test "inline else error" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; + const Err = error{ a, b, c }; var a = Err.a; _ = &a; diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 4f22fd04aef6e2dcb7db0c3387ef02df34409667..1a8cae41eb87e2dd4b2ff551839365d221c65f89 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -1389,7 +1389,6 @@ fn testOr(comptime T: type, a: T, b: T, expected: T) !void { test "or > 128 bits" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testOr(u140, 0, 1 << 139, 1 << 139); try testOr(u140, (1 << 70) | 0xa, (1 << 69) | 0x5, (1 << 70) | (1 << 69) | 0xf); diff --git a/test/behavior/muladd.zig b/test/behavior/muladd.zig index a6fddd62fe10a87ba72dd9b92e9fd72be46bca40..4dc461a146033030f621dfc127b3ded9279bdfd4 100644 --- a/test/behavior/muladd.zig +++ b/test/behavior/muladd.zig @@ -101,7 +101,6 @@ test "vector f16" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try comptime vector16(); try vector16(); diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index e812cd4c08b655cd2d21ad61cc8c4fd2fffd17d7..9e2789618a07a1c484a23c7800c71cf725395957 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -426,7 +426,6 @@ test "optional pointer to zero bit optional payload" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const B = struct { fn foo(_: *@This()) void {} diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index a30469821a3a279766dd6302bbc2272c7dfa826f..b3643da988edb019e7ed87af057ae51d189b7742 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -787,8 +787,6 @@ test "slice bounds in comptime concatenation" { } test "slice sentinel access at comptime" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; - { const str0 = &[_:0]u8{ '1', '2', '3' }; const slice0: [:0]const u8 = str0; @@ -809,7 +807,6 @@ test "slice sentinel access at comptime" { test "slicing array with sentinel as end index" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; - const S = struct { fn do() !void { var array = [_:0]u8{ 1, 2, 3, 4 }; diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index 103ecc8994a6c66ab2be8a0aeee6781cf7ba66ef..19342be0188f896426fe7aef5143740cc4a9987e 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -567,7 +567,6 @@ test "bit field access" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO var data = bit_field_1; diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index e56da92324e578190ea4119e04b5379abf687c2d..d5b91949f8003551fbf5c462ba836736aebb81f2 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -314,7 +314,6 @@ fn testSwitchEnumPtrCapture() !void { test "switch handles all cases of number" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testSwitchHandleAllCases(); try comptime testSwitchHandleAllCases(); @@ -1576,6 +1575,8 @@ test "repeated switch analysis overrides previous analysis results" { } test "union field pointer capture preserves alignment in inline prong" { + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; + const U = union(enum) { a: u32, b: u32, diff --git a/test/behavior/tuple.zig b/test/behavior/tuple.zig index dabc3eb01af28b08e8cdcae8de076069ff054693..8a8846c2ac196f8e76ca0bead377f2ecd520092a 100644 --- a/test/behavior/tuple.zig +++ b/test/behavior/tuple.zig @@ -242,7 +242,6 @@ test "tuple in tuple passed to generic function" { test "coerce tuple to tuple" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const T = @Tuple(&.{u8}); const S = struct { diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index deedc94eab33a748e1d76a1ca08ea0d2241f0071..79aaae3e3b5f003cf414b558e4aef048c383ebee 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -414,7 +414,6 @@ test "vector @splat" { } test "load vector elements via comptime index" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO -- 2.54.0