authorgravatar for sphaerophoria@noreply.codeberg.orgsphaerophoria <sphaerophoria@noreply.codeberg.org> 2026-07-11 19:50:17+02:00
committergravatar for alichraghi@noreply.codeberg.orgAli Cheraghi <alichraghi@noreply.codeberg.org> 2026-07-11 19:50:17+02:00
logcb5635714cbd8c7fc2911536e59e58b96fe0ba1f
tree4e997ac43fc8cccff920e77f486b90eb5389905b
parent4331ba0fb2c40934afe12b72b02e2bfebb721ac6

Sema: ban (most) runtime slices on spriv (#36118)

spirv slices are represented via an element pointer + len. On access we get pointers to individual elements with OpPtrAccessChain Unfortunately this is not a valid operation in most cases. For any array defined in a function, we are generated with storage class "function". The SPIRV spec only allows for variable pointers on data in the Workgroup (addrspace(.shared)) or StorageBuffer storage classes (definition of variable pointer in SPIRV spec 2.16) SPIRV kernels with this constraint unmet cause segfaults on my machine when trying to load them, and fail to check against spirv-val AFAICT there is no way to represent a slice of data with storage class "function". We can use OpAccessChain to extract an offset from an array, however that would require a slice representation resembling {array, offset, len}. Since arrays are typed according to their type + len, there would be no way to re-assign a slice from a [4]f32 array to a slice from a [2]f32 array. The types would just not match up So the best we can do is just ban construction of slices in the scenarios we cannot generate valid code for, and give a useful error message to the caller A few examples... var buf: [3]f32 = undefined; for (buf[0..3]) |v| {} // errors fnThatAcceptsSlice(&buf); // errors for (&buf) |v| {} // succeeds comptime cbuf: [3]f32 = .{1, 2, 3}; for (cbuf[0..3]) |v| {} // errors for (comptime cbuf[0..3]) |v| {} // succeeds This is arguably over-restrictive. We theoretically could handle buf[0..N] correctly, but currently SPIRV codegen will still produce invalid OpPtrAccessChains, so I've just completely removed the ability to produce slices Co-authored-by: Mick Sayson <mick@sayson.com> Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36118 Reviewed-by: Ali Cheraghi <alichraghi@noreply.codeberg.org>

3 files changed, 77 insertions(+), 1 deletions(-)

src/Sema.zig+35
...@@ -29752,6 +29752,39 @@ pub fn bitCastVal(...@@ -29752,6 +29752,39 @@ pub fn bitCastVal(
29752 }29752 }
29753}29753}
2975429754
29755fn checkSpirvSliceAllowed(
29756 sema: *Sema,
29757 block: *Block,
29758 src: LazySrcLoc,
29759 address_space: std.lang.AddressSpace,
29760) CompileError!void {
29761 const zcu = sema.pt.zcu;
29762 const target = zcu.getTarget();
29763
29764 if (!target.cpu.arch.isSpirV()) return;
29765 if (block.isComptime()) return;
29766
29767 // This probably lets some invalid OpPtrAccessChains slip through, but it's better than nothing
29768 if (!target.cpu.has(.spirv, .variable_pointers) and !target.cpu.has(.spirv, .variable_pointers_storage_buffer)) {
29769 return sema.failWithOwnedErrorMsg(
29770 block,
29771 try sema.errMsg(src, "cannot construct slices without the 'variable_pointers' or 'variable_pointers_storage_buffer' features", .{}),
29772 );
29773 }
29774
29775 switch (address_space) {
29776 .shared, .storage_buffer => {},
29777 else => {
29778 return sema.failWithOwnedErrorMsg(block, msg: {
29779 const msg = try sema.errMsg(src, "cannot construct slice from address space '{t}'", .{address_space});
29780 errdefer msg.destroy(sema.gpa);
29781 try sema.errNote(src, msg, "only 'shared' and 'storage_buffer' address spaces support slicing on SPIR-V", .{});
29782 break :msg msg;
29783 });
29784 },
29785 }
29786}
29787
29755fn coerceArrayPtrToSlice(29788fn coerceArrayPtrToSlice(
29756 sema: *Sema,29789 sema: *Sema,
29757 block: *Block,29790 block: *Block,
...@@ -29773,6 +29806,7 @@ fn coerceArrayPtrToSlice(...@@ -29773,6 +29806,7 @@ fn coerceArrayPtrToSlice(
29773 } });29806 } });
29774 return Air.internedToRef(slice_val);29807 return Air.internedToRef(slice_val);
29775 }29808 }
29809 try sema.checkSpirvSliceAllowed(block, inst_src, dest_ty.ptrInfo(zcu).flags.address_space);
29776 try sema.requireRuntimeBlock(block, inst_src, null);29810 try sema.requireRuntimeBlock(block, inst_src, null);
29777 return block.addTyOp(.array_to_slice, dest_ty, inst);29811 return block.addTyOp(.array_to_slice, dest_ty, inst);
29778}29812}
...@@ -31066,6 +31100,7 @@ fn analyzeSlice(...@@ -31066,6 +31100,7 @@ fn analyzeSlice(
31066 }31100 }
3106731101
31068 try sema.ensureLayoutResolved(elem_ty, src, .ptr_access);31102 try sema.ensureLayoutResolved(elem_ty, src, .ptr_access);
31103 try sema.checkSpirvSliceAllowed(block, src, slice_ty.ptrInfo(zcu).flags.address_space);
3106931104
31070 const ptr = if (slice_ty.isSlice(zcu))31105 const ptr = if (slice_ty.isSlice(zcu))
31071 try sema.analyzeSlicePtr(block, ptr_src, ptr_or_slice, slice_ty)31106 try sema.analyzeSlicePtr(block, ptr_src, ptr_or_slice, slice_ty)
src/target.zig+1-1
...@@ -625,7 +625,7 @@ pub fn addrSpaceCastIsValid(...@@ -625,7 +625,7 @@ pub fn addrSpaceCastIsValid(
625/// (c) some logical pointers (.storage_buffer, .shared) do support operations when625/// (c) some logical pointers (.storage_buffer, .shared) do support operations when
626/// the VariablePointers capability is enabled (which enables OpPtrAccessChain).626/// the VariablePointers capability is enabled (which enables OpPtrAccessChain).
627pub fn shouldBlockPointerOps(target: *const std.Target, as: AddressSpace) bool {627pub fn shouldBlockPointerOps(target: *const std.Target, as: AddressSpace) bool {
628 if (target.os.tag != .vulkan) return false;628 if (target.os.tag != .vulkan and target.os.tag != .opengl) return false;
629629
630 return switch (as) {630 return switch (as) {
631 // TODO: Vulkan doesn't support pointers in the generic address space, we631 // TODO: Vulkan doesn't support pointers in the generic address space, we
test/cases/compile_errors/spirv_slices.zig created+41
...@@ -0,0 +1,41 @@
1export fn a() void {
2 var buf: [3]f32 = undefined;
3 takesSlice(&buf); // error
4}
5
6fn takesSlice(buf: []f32) void {
7 _ = buf;
8}
9
10export fn b() void {
11 var buf: [3]f32 = undefined;
12 for (buf[0..3]) |_| {} // error
13}
14
15export fn c() void {
16 var buf: [3]f32 = undefined;
17 for (&buf) |_| {} // not an error
18}
19
20export fn d() void {
21 const buf: [3]f32 = .{1, 2, 3};
22 for (comptime buf[0..3]) |_| {} // not an error
23}
24
25export fn e() void {
26 const buf: [3]f32 = .{1, 2, 3};
27 for (comptime buf[0..3]) |_| {} // not an error
28 for (buf[0..3]) |_| {} // error
29}
30
31// error
32// backend=auto
33// target=spirv32-opengl,spirv32-vulkan
34// cpu_features=baseline+variable_pointers
35//
36// :3:16: error: cannot construct slice from address space 'generic'
37// :3:16: note: only 'shared' and 'storage_buffer' address spaces support slicing on SPIR-V
38// :12:13: error: cannot construct slice from address space 'generic'
39// :12:13: note: only 'shared' and 'storage_buffer' address spaces support slicing on SPIR-V
40// :28:13: error: cannot construct slice from address space 'generic'
41// :28:13: note: only 'shared' and 'storage_buffer' address spaces support slicing on SPIR-V