| author | |
| committer | |
| log | cb5635714cbd8c7fc2911536e59e58b96fe0ba1f |
| tree | 4e997ac43fc8cccff920e77f486b90eb5389905b |
| parent | 4331ba0fb2c40934afe12b72b02e2bfebb721ac6 |
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 | 29752 | } |
| 29753 | 29753 | } |
| 29754 | 29754 | |
| 29755 | fn 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 | ||
| 29755 | 29788 | fn coerceArrayPtrToSlice( |
| 29756 | 29789 | sema: *Sema, |
| 29757 | 29790 | block: *Block, |
| ... | ... | @@ -29773,6 +29806,7 @@ fn coerceArrayPtrToSlice( |
| 29773 | 29806 | } }); |
| 29774 | 29807 | return Air.internedToRef(slice_val); |
| 29775 | 29808 | } |
| 29809 | try sema.checkSpirvSliceAllowed(block, inst_src, dest_ty.ptrInfo(zcu).flags.address_space); | |
| 29776 | 29810 | try sema.requireRuntimeBlock(block, inst_src, null); |
| 29777 | 29811 | return block.addTyOp(.array_to_slice, dest_ty, inst); |
| 29778 | 29812 | } |
| ... | ... | @@ -31066,6 +31100,7 @@ fn analyzeSlice( |
| 31066 | 31100 | } |
| 31067 | 31101 | |
| 31068 | 31102 | try sema.ensureLayoutResolved(elem_ty, src, .ptr_access); |
| 31103 | try sema.checkSpirvSliceAllowed(block, src, slice_ty.ptrInfo(zcu).flags.address_space); | |
| 31069 | 31104 | |
| 31070 | 31105 | const ptr = if (slice_ty.isSlice(zcu)) |
| 31071 | 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 | 625 | /// (c) some logical pointers (.storage_buffer, .shared) do support operations when |
| 626 | 626 | /// the VariablePointers capability is enabled (which enables OpPtrAccessChain). |
| 627 | 627 | pub 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; | |
| 629 | 629 | |
| 630 | 630 | return switch (as) { |
| 631 | 631 | // 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 @@ |
| 1 | export fn a() void { | |
| 2 | var buf: [3]f32 = undefined; | |
| 3 | takesSlice(&buf); // error | |
| 4 | } | |
| 5 | ||
| 6 | fn takesSlice(buf: []f32) void { | |
| 7 | _ = buf; | |
| 8 | } | |
| 9 | ||
| 10 | export fn b() void { | |
| 11 | var buf: [3]f32 = undefined; | |
| 12 | for (buf[0..3]) |_| {} // error | |
| 13 | } | |
| 14 | ||
| 15 | export fn c() void { | |
| 16 | var buf: [3]f32 = undefined; | |
| 17 | for (&buf) |_| {} // not an error | |
| 18 | } | |
| 19 | ||
| 20 | export fn d() void { | |
| 21 | const buf: [3]f32 = .{1, 2, 3}; | |
| 22 | for (comptime buf[0..3]) |_| {} // not an error | |
| 23 | } | |
| 24 | ||
| 25 | export 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 |