From cb5635714cbd8c7fc2911536e59e58b96fe0ba1f Mon Sep 17 00:00:00 2001 From: sphaerophoria Date: Sat, 11 Jul 2026 19:50:17 +0200 Subject: [PATCH] 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 Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36118 Reviewed-by: Ali Cheraghi --- src/Sema.zig | 35 ++++++++++++++++++ src/target.zig | 2 +- test/cases/compile_errors/spirv_slices.zig | 41 ++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 test/cases/compile_errors/spirv_slices.zig diff --git a/src/Sema.zig b/src/Sema.zig index dfd37d70ec9787b7d8ded314ea5314b1e8513b64..3d6274a0b75ad597768276a9e32723709ae42b6f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -29752,6 +29752,39 @@ pub fn bitCastVal( } } +fn checkSpirvSliceAllowed( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + address_space: std.lang.AddressSpace, +) CompileError!void { + const zcu = sema.pt.zcu; + const target = zcu.getTarget(); + + if (!target.cpu.arch.isSpirV()) return; + if (block.isComptime()) return; + + // This probably lets some invalid OpPtrAccessChains slip through, but it's better than nothing + if (!target.cpu.has(.spirv, .variable_pointers) and !target.cpu.has(.spirv, .variable_pointers_storage_buffer)) { + return sema.failWithOwnedErrorMsg( + block, + try sema.errMsg(src, "cannot construct slices without the 'variable_pointers' or 'variable_pointers_storage_buffer' features", .{}), + ); + } + + switch (address_space) { + .shared, .storage_buffer => {}, + else => { + return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(src, "cannot construct slice from address space '{t}'", .{address_space}); + errdefer msg.destroy(sema.gpa); + try sema.errNote(src, msg, "only 'shared' and 'storage_buffer' address spaces support slicing on SPIR-V", .{}); + break :msg msg; + }); + }, + } +} + fn coerceArrayPtrToSlice( sema: *Sema, block: *Block, @@ -29773,6 +29806,7 @@ fn coerceArrayPtrToSlice( } }); return Air.internedToRef(slice_val); } + try sema.checkSpirvSliceAllowed(block, inst_src, dest_ty.ptrInfo(zcu).flags.address_space); try sema.requireRuntimeBlock(block, inst_src, null); return block.addTyOp(.array_to_slice, dest_ty, inst); } @@ -31066,6 +31100,7 @@ fn analyzeSlice( } try sema.ensureLayoutResolved(elem_ty, src, .ptr_access); + try sema.checkSpirvSliceAllowed(block, src, slice_ty.ptrInfo(zcu).flags.address_space); const ptr = if (slice_ty.isSlice(zcu)) try sema.analyzeSlicePtr(block, ptr_src, ptr_or_slice, slice_ty) diff --git a/src/target.zig b/src/target.zig index f7e9e5581b34accbceaffc94ed5ef16ecf7bdd0d..7c568d239bf978ac342e6466224110ccafc7788b 100644 --- a/src/target.zig +++ b/src/target.zig @@ -625,7 +625,7 @@ pub fn addrSpaceCastIsValid( /// (c) some logical pointers (.storage_buffer, .shared) do support operations when /// the VariablePointers capability is enabled (which enables OpPtrAccessChain). pub fn shouldBlockPointerOps(target: *const std.Target, as: AddressSpace) bool { - if (target.os.tag != .vulkan) return false; + if (target.os.tag != .vulkan and target.os.tag != .opengl) return false; return switch (as) { // TODO: Vulkan doesn't support pointers in the generic address space, we diff --git a/test/cases/compile_errors/spirv_slices.zig b/test/cases/compile_errors/spirv_slices.zig new file mode 100644 index 0000000000000000000000000000000000000000..8e6e657025982ad316bc058025e1374e30520aec --- /dev/null +++ b/test/cases/compile_errors/spirv_slices.zig @@ -0,0 +1,41 @@ +export fn a() void { + var buf: [3]f32 = undefined; + takesSlice(&buf); // error +} + +fn takesSlice(buf: []f32) void { + _ = buf; +} + +export fn b() void { + var buf: [3]f32 = undefined; + for (buf[0..3]) |_| {} // error +} + +export fn c() void { + var buf: [3]f32 = undefined; + for (&buf) |_| {} // not an error +} + +export fn d() void { + const buf: [3]f32 = .{1, 2, 3}; + for (comptime buf[0..3]) |_| {} // not an error +} + +export fn e() void { + const buf: [3]f32 = .{1, 2, 3}; + for (comptime buf[0..3]) |_| {} // not an error + for (buf[0..3]) |_| {} // error +} + +// error +// backend=auto +// target=spirv32-opengl,spirv32-vulkan +// cpu_features=baseline+variable_pointers +// +// :3:16: error: cannot construct slice from address space 'generic' +// :3:16: note: only 'shared' and 'storage_buffer' address spaces support slicing on SPIR-V +// :12:13: error: cannot construct slice from address space 'generic' +// :12:13: note: only 'shared' and 'storage_buffer' address spaces support slicing on SPIR-V +// :28:13: error: cannot construct slice from address space 'generic' +// :28:13: note: only 'shared' and 'storage_buffer' address spaces support slicing on SPIR-V -- 2.54.0