authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-10-27 16:31:45+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-10-27 16:31:45+01:00
log49a067ccfe6cc3ee59b0fe0f2dc32f80ab75d574
tree349a61aee17121afb7a63e37c6ce4c49073ab8cb
parent39013619b943956f0c26422a01f026d845dc96a9
signaturebadge-check Signed by SSH key SHA256:ZS52FNyUv2WUXvO4njmVaFVO46RHojFuOrxRc4LuKzg

spirv: forbid merging logical pointers

Under some architecture/operating system combinations it is forbidden to return a pointer from a merge, as these pointers must point to a location at compile time. This adds a check for those cases when returning a pointer from a block merge.

5 files changed, 97 insertions(+), 1 deletions(-)

src/Sema.zig+36
......@@ -6319,6 +6319,9 @@ fn resolveAnalyzedBlock(
63196319 for (merges.results.items, merges.src_locs.items) |merge_inst, merge_src| {
63206320 try sema.validateRuntimeValue(child_block, merge_src orelse src, merge_inst);
63216321 }
6322
6323 try sema.checkMergeAllowed(child_block, type_src, resolved_ty);
6324
63226325 const ty_inst = Air.internedToRef(resolved_ty.toIntern());
63236326 switch (block_tag) {
63246327 .block => {
......@@ -9754,6 +9757,39 @@ fn checkCallConvSupportsVarArgs(sema: *Sema, block: *Block, src: LazySrcLoc, cc:
97549757 }
97559758}
97569759
9760fn checkMergeAllowed(sema: *Sema, block: *Block, src: LazySrcLoc, peer_ty: Type) !void {
9761 const pt = sema.pt;
9762 const zcu = pt.zcu;
9763 const target = zcu.getTarget();
9764
9765 if (!peer_ty.isPtrAtRuntime(zcu)) {
9766 return;
9767 }
9768
9769 const as = peer_ty.ptrAddressSpace(zcu);
9770 if (!target_util.arePointersLogical(target, as)) {
9771 return;
9772 }
9773
9774 return sema.failWithOwnedErrorMsg(block, msg: {
9775 const msg = try sema.errMsg(src, "value with non-mergable pointer type '{}' depends on runtime control flow", .{peer_ty.fmt(pt)});
9776 errdefer msg.destroy(sema.gpa);
9777
9778 const runtime_src = block.runtime_cond orelse block.runtime_loop.?;
9779 try sema.errNote(runtime_src, msg, "runtime control flow here", .{});
9780
9781 const backend = target_util.zigBackend(target, zcu.comp.config.use_llvm);
9782 try sema.errNote(src, msg, "pointers with address space '{s}' cannot be returned from a branch on target {s}-{s} by compiler backend {s}", .{
9783 @tagName(as),
9784 target.cpu.arch.genericName(),
9785 @tagName(target.os.tag),
9786 @tagName(backend),
9787 });
9788
9789 break :msg msg;
9790 });
9791}
9792
97579793const Section = union(enum) {
97589794 generic,
97599795 default,
src/target.zig+25
......@@ -398,6 +398,31 @@ pub fn addrSpaceCastIsValid(
398398 }
399399}
400400
401/// Under SPIR-V with Vulkan, pointers are not 'real' (physical), but rather 'logical'. Effectively,
402/// this means that all such pointers have to be resolvable to a location at compile time, and places
403/// a number of restrictions on usage of such pointers. For example, a logical pointer may not be
404/// part of a merge (result of a branch) and may not be stored in memory at all. This function returns
405/// for a particular architecture and address space wether such pointers are logical.
406pub fn arePointersLogical(target: std.Target, as: AddressSpace) bool {
407 if (target.os.tag != .vulkan) {
408 return false;
409 }
410
411 return switch (as) {
412 // TODO: Vulkan doesn't support pointers in the generic address space, we
413 // should remove this case but this requires a change in defaultAddressSpace().
414 // For now, at least disable them from being regarded as physical.
415 .generic => true,
416 // For now, all global pointers are represented using PhysicalStorageBuffer, so these are real
417 // pointers.
418 .global => false,
419 // TODO: Allowed with VK_KHR_variable_pointers.
420 .shared => true,
421 .constant, .local, .input, .output, .uniform => true,
422 else => unreachable,
423 };
424}
425
401426pub fn llvmMachineAbi(target: std.Target) ?[:0]const u8 {
402427 // LLD does not support ELFv1. Rather than having LLVM produce ELFv1 code and then linking it
403428 // into a broken ELFv2 binary, just force LLVM to use ELFv2 as well. This will break when glibc
test/cases/compile_errors/spirv_merge_logical_pointers.zig created+19
......@@ -0,0 +1,19 @@
1export fn a() void {
2 var x: *i32 = undefined;
3 _ = &x;
4 var y: *i32 = undefined;
5 _ = &y;
6 var rt_cond = false;
7 _ = &rt_cond;
8
9 var z = if (rt_cond) x else y;
10 _ = &z;
11}
12
13// error
14// backend=stage2
15// target=spirv64-vulkan
16//
17// :9:13: error: value with non-mergable pointer type '*i32' depends on runtime control flow
18// :9:17: note: runtime control flow here
19// :9:13: note: pointers with address space 'generic' cannot be returned from a branch on target spirv-vulkan by compiler backend stage2_spirv64
test/cases/spirv_mergable_pointers.zig created+16
......@@ -0,0 +1,16 @@
1export fn a() void {
2 var x: *addrspace(.global) i32 = undefined;
3 _ = &x;
4 var y: *addrspace(.global) i32 = undefined;
5 _ = &y;
6 var rt_cond = false;
7 _ = &rt_cond;
8
9 var z = if (rt_cond) x else y;
10 _ = &z;
11}
12
13// compile
14// output_mode=Obj
15// backend=stage2
16// target=spirv64-vulkan
test/src/Cases.zig+1-1
......@@ -467,7 +467,7 @@ fn addFromDirInner(
467467 const target = resolved_target.result;
468468 for (backends) |backend| {
469469 if (backend == .stage2 and
470 target.cpu.arch != .wasm32 and target.cpu.arch != .x86_64)
470 target.cpu.arch != .wasm32 and target.cpu.arch != .x86_64 and target.cpu.arch != .spirv64)
471471 {
472472 // Other backends don't support new liveness format
473473 continue;