| author | |
| committer | |
| log | 49a067ccfe6cc3ee59b0fe0f2dc32f80ab75d574 |
| tree | 349a61aee17121afb7a63e37c6ce4c49073ab8cb |
| parent | 39013619b943956f0c26422a01f026d845dc96a9 |
| signature |
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( | ... | @@ -6319,6 +6319,9 @@ fn resolveAnalyzedBlock( |
| 6319 | for (merges.results.items, merges.src_locs.items) |merge_inst, merge_src| { | 6319 | for (merges.results.items, merges.src_locs.items) |merge_inst, merge_src| { |
| 6320 | try sema.validateRuntimeValue(child_block, merge_src orelse src, merge_inst); | 6320 | try sema.validateRuntimeValue(child_block, merge_src orelse src, merge_inst); |
| 6321 | } | 6321 | } |
| 6322 | |||
| 6323 | try sema.checkMergeAllowed(child_block, type_src, resolved_ty); | ||
| 6324 | |||
| 6322 | const ty_inst = Air.internedToRef(resolved_ty.toIntern()); | 6325 | const ty_inst = Air.internedToRef(resolved_ty.toIntern()); |
| 6323 | switch (block_tag) { | 6326 | switch (block_tag) { |
| 6324 | .block => { | 6327 | .block => { |
| ... | @@ -9754,6 +9757,39 @@ fn checkCallConvSupportsVarArgs(sema: *Sema, block: *Block, src: LazySrcLoc, cc: | ... | @@ -9754,6 +9757,39 @@ fn checkCallConvSupportsVarArgs(sema: *Sema, block: *Block, src: LazySrcLoc, cc: |
| 9754 | } | 9757 | } |
| 9755 | } | 9758 | } |
| 9756 | 9759 | ||
| 9760 | fn 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 | |||
| 9757 | const Section = union(enum) { | 9793 | const Section = union(enum) { |
| 9758 | generic, | 9794 | generic, |
| 9759 | default, | 9795 | default, |
src/target.zig+25| ... | @@ -398,6 +398,31 @@ pub fn addrSpaceCastIsValid( | ... | @@ -398,6 +398,31 @@ pub fn addrSpaceCastIsValid( |
| 398 | } | 398 | } |
| 399 | } | 399 | } |
| 400 | 400 | ||
| 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. | ||
| 406 | pub 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 | |||
| 401 | pub fn llvmMachineAbi(target: std.Target) ?[:0]const u8 { | 426 | pub fn llvmMachineAbi(target: std.Target) ?[:0]const u8 { |
| 402 | // LLD does not support ELFv1. Rather than having LLVM produce ELFv1 code and then linking it | 427 | // LLD does not support ELFv1. Rather than having LLVM produce ELFv1 code and then linking it |
| 403 | // into a broken ELFv2 binary, just force LLVM to use ELFv2 as well. This will break when glibc | 428 | // 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 @@ | ||
| 1 | export 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 @@ | ||
| 1 | export 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( | ... | @@ -467,7 +467,7 @@ fn addFromDirInner( |
| 467 | const target = resolved_target.result; | 467 | const target = resolved_target.result; |
| 468 | for (backends) |backend| { | 468 | for (backends) |backend| { |
| 469 | if (backend == .stage2 and | 469 | 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) |
| 471 | { | 471 | { |
| 472 | // Other backends don't support new liveness format | 472 | // Other backends don't support new liveness format |
| 473 | continue; | 473 | continue; |