From f02d21c8c888b07ead4034562beb1872faee11d6 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Wed, 8 Jul 2026 09:10:54 +0330 Subject: [PATCH] Sema: reject more unrepresentable pointer casts for SPIR-V --- src/Sema.zig | 28 +++++++++++++++++++ src/codegen/spirv/CodeGen.zig | 22 ++++++++++++++- test/behavior/spirv.zig | 9 ++++++ ...pirv_pointer_cast_requires_offset_zero.zig | 20 +++++++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig diff --git a/src/Sema.zig b/src/Sema.zig index 0e58f0c819bf00d512bd58aa7f346e634bdb0368..48303df878684068303d127882203272cba3ea84 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -21849,6 +21849,34 @@ fn ptrCastFull( try sema.validateRuntimeValue(block, operand_src, operand); + if (zcu.getTarget().cpu.arch.isSpirV() and + src_info.flags.address_space != .physical_storage_buffer and + src_info.flags.address_space == dest_info.flags.address_space and + src_info.child != dest_info.child and + Type.fromInterned(dest_info.child).hasRuntimeBits(zcu)) + { + var cur: Type = .fromInterned(src_info.child); + while (cur.toIntern() != dest_info.child) { + cur = switch (cur.zigTypeTag(zcu)) { + .array, .vector => cur.childType(zcu), + .@"struct" => if (cur.structFieldOffset(0, zcu) == 0) cur.fieldType(0, zcu) else null, + else => null, + } orelse return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(src, "cannot cast pointer '{f}' to '{f}'", .{ + operand_ty.fmt(pt), dest_ty.fmt(pt), + }); + errdefer msg.destroy(sema.gpa); + try sema.errNote(src, msg, "'{f}' must appear at offset 0 inside '{f}'", .{ + Type.fromInterned(dest_info.child).fmt(pt), Type.fromInterned(src_info.child).fmt(pt), + }); + try sema.errNote(src, msg, "'{s}' pointers can only reach nested types through a first struct field or an array element", .{ + @tagName(src_info.flags.address_space), + }); + break :msg msg; + }); + } + } + const can_cast_to_int = !target_util.shouldBlockPointerOps(zcu.getTarget(), operand_ty.ptrAddressSpace(zcu)); const need_null_check = can_cast_to_int and block.wantSafety() and operand_ty.ptrAllowsZero(zcu) and !dest_ty.ptrAllowsZero(zcu); const need_align_check = can_cast_to_int and block.wantSafety() and dest_align.compare(.gt, src_align); diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index 0b9e8b547e68185f889ed52f607e1df57d4cf952..e2a42ce78fe6a2907f0437525d269921ca4dd629 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -5966,7 +5966,27 @@ fn bitCast( if (src_ty.toIntern() == dst_ty.toIntern()) return src_id; if (src_ty.isPtrAtRuntime(zcu) and dst_ty.isPtrAtRuntime(zcu)) switch (target.os.tag) { - .vulkan, .opengl => if (src_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) return src_id, + .vulkan, .opengl => if (src_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) { + const src_child = src_ty.childType(zcu); + const dst_child = dst_ty.childType(zcu); + if (!dst_child.hasRuntimeBits(zcu)) return src_id; + if (src_child.toIntern() == dst_child.toIntern()) return src_id; + if (src_ty.ptrInfo(zcu).packed_offset.host_size != 0 or + dst_ty.ptrInfo(zcu).packed_offset.host_size != 0) return src_id; + + var indices: std.ArrayList(u32) = .empty; + defer indices.deinit(gpa); + var cur = src_child; + while (cur.toIntern() != dst_child.toIntern()) : (try indices.append(gpa, 0)) { + cur = switch (cur.zigTypeTag(zcu)) { + .array, .vector => cur.childType(zcu), + .@"struct" => cur.fieldType(0, zcu), + else => unreachable, + }; + } + const dst_ty_id = try cg.resolveType(dst_ty, .direct); + return try cg.accessChain(dst_ty_id, src_id, indices.items); + }, else => {}, }; diff --git a/test/behavior/spirv.zig b/test/behavior/spirv.zig index 7bb632b483297fa1e058d6e1940b09cc883a107b..ed07aa33c5455bbf5b5a08093dfc7fe561cb36ab 100644 --- a/test/behavior/spirv.zig +++ b/test/behavior/spirv.zig @@ -49,6 +49,15 @@ test "@SpirvType" { _ = runtime_array; } +const InnerStruct = extern struct { x: u32 }; +const OuterStruct = extern struct { inner: InnerStruct, y: u32 }; +const outer_pc = @extern(*addrspace(.push_constant) const OuterStruct, .{ .name = "outer_pc" }); + +test "@ptrCast to first field type" { + const pc_inner: *addrspace(.push_constant) const InnerStruct = @ptrCast(outer_pc); + _ = pc_inner; +} + test "@SpirvType equality" { try expect(@SpirvType(.sampler) == Sampler); try expect(@SpirvType(.{ .runtime_array = u32 }) == RuntimeArray); diff --git a/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig b/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig new file mode 100644 index 0000000000000000000000000000000000000000..da77d7cd169be3f6bb7274f7424bec87101e01dc --- /dev/null +++ b/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig @@ -0,0 +1,20 @@ +const A = extern struct { x: u32, y: u32 }; +const B = extern struct { a: u64 }; + +const a = @extern(*addrspace(.uniform) const A, .{ + .name = "a", + .decoration = .{ .descriptor = .{ .set = 0, .binding = 0 } }, +}); + +export fn main() callconv(.{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }) void { + const b: *addrspace(.uniform) const B = @ptrCast(a); + _ = &b; +} + +// error +// backend=selfhosted +// target=spirv32-vulkan +// +// :10:44: error: cannot cast pointer '*addrspace(.uniform) const A' to '*addrspace(.uniform) const B' +// :10:44: note: 'B' must appear at offset 0 inside 'A' +// :10:44: note: 'uniform' pointers can only reach nested types through a first struct field or an array element -- 2.54.0