authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-07-08 09:10:54+03:30
committergravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-07-10 11:39:54+03:30
logf02d21c8c888b07ead4034562beb1872faee11d6
treeddc7fd2ee705b06addbea1e6fed394fa05731631
parent587bdfc8e5fdc63911070243c308d182f63a8555

Sema: reject more unrepresentable pointer casts for SPIR-V


4 files changed, 78 insertions(+), 1 deletions(-)

src/Sema.zig+28
......@@ -21849,6 +21849,34 @@ fn ptrCastFull(
2184921849
2185021850 try sema.validateRuntimeValue(block, operand_src, operand);
2185121851
21852 if (zcu.getTarget().cpu.arch.isSpirV() and
21853 src_info.flags.address_space != .physical_storage_buffer and
21854 src_info.flags.address_space == dest_info.flags.address_space and
21855 src_info.child != dest_info.child and
21856 Type.fromInterned(dest_info.child).hasRuntimeBits(zcu))
21857 {
21858 var cur: Type = .fromInterned(src_info.child);
21859 while (cur.toIntern() != dest_info.child) {
21860 cur = switch (cur.zigTypeTag(zcu)) {
21861 .array, .vector => cur.childType(zcu),
21862 .@"struct" => if (cur.structFieldOffset(0, zcu) == 0) cur.fieldType(0, zcu) else null,
21863 else => null,
21864 } orelse return sema.failWithOwnedErrorMsg(block, msg: {
21865 const msg = try sema.errMsg(src, "cannot cast pointer '{f}' to '{f}'", .{
21866 operand_ty.fmt(pt), dest_ty.fmt(pt),
21867 });
21868 errdefer msg.destroy(sema.gpa);
21869 try sema.errNote(src, msg, "'{f}' must appear at offset 0 inside '{f}'", .{
21870 Type.fromInterned(dest_info.child).fmt(pt), Type.fromInterned(src_info.child).fmt(pt),
21871 });
21872 try sema.errNote(src, msg, "'{s}' pointers can only reach nested types through a first struct field or an array element", .{
21873 @tagName(src_info.flags.address_space),
21874 });
21875 break :msg msg;
21876 });
21877 }
21878 }
21879
2185221880 const can_cast_to_int = !target_util.shouldBlockPointerOps(zcu.getTarget(), operand_ty.ptrAddressSpace(zcu));
2185321881 const need_null_check = can_cast_to_int and block.wantSafety() and operand_ty.ptrAllowsZero(zcu) and !dest_ty.ptrAllowsZero(zcu);
2185421882 const need_align_check = can_cast_to_int and block.wantSafety() and dest_align.compare(.gt, src_align);
src/codegen/spirv/CodeGen.zig+21-1
......@@ -5966,7 +5966,27 @@ fn bitCast(
59665966
59675967 if (src_ty.toIntern() == dst_ty.toIntern()) return src_id;
59685968 if (src_ty.isPtrAtRuntime(zcu) and dst_ty.isPtrAtRuntime(zcu)) switch (target.os.tag) {
5969 .vulkan, .opengl => if (src_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) return src_id,
5969 .vulkan, .opengl => if (src_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) {
5970 const src_child = src_ty.childType(zcu);
5971 const dst_child = dst_ty.childType(zcu);
5972 if (!dst_child.hasRuntimeBits(zcu)) return src_id;
5973 if (src_child.toIntern() == dst_child.toIntern()) return src_id;
5974 if (src_ty.ptrInfo(zcu).packed_offset.host_size != 0 or
5975 dst_ty.ptrInfo(zcu).packed_offset.host_size != 0) return src_id;
5976
5977 var indices: std.ArrayList(u32) = .empty;
5978 defer indices.deinit(gpa);
5979 var cur = src_child;
5980 while (cur.toIntern() != dst_child.toIntern()) : (try indices.append(gpa, 0)) {
5981 cur = switch (cur.zigTypeTag(zcu)) {
5982 .array, .vector => cur.childType(zcu),
5983 .@"struct" => cur.fieldType(0, zcu),
5984 else => unreachable,
5985 };
5986 }
5987 const dst_ty_id = try cg.resolveType(dst_ty, .direct);
5988 return try cg.accessChain(dst_ty_id, src_id, indices.items);
5989 },
59705990 else => {},
59715991 };
59725992
test/behavior/spirv.zig+9
......@@ -49,6 +49,15 @@ test "@SpirvType" {
4949 _ = runtime_array;
5050}
5151
52const InnerStruct = extern struct { x: u32 };
53const OuterStruct = extern struct { inner: InnerStruct, y: u32 };
54const outer_pc = @extern(*addrspace(.push_constant) const OuterStruct, .{ .name = "outer_pc" });
55
56test "@ptrCast to first field type" {
57 const pc_inner: *addrspace(.push_constant) const InnerStruct = @ptrCast(outer_pc);
58 _ = pc_inner;
59}
60
5261test "@SpirvType equality" {
5362 try expect(@SpirvType(.sampler) == Sampler);
5463 try expect(@SpirvType(.{ .runtime_array = u32 }) == RuntimeArray);
test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig created+20
......@@ -0,0 +1,20 @@
1const A = extern struct { x: u32, y: u32 };
2const B = extern struct { a: u64 };
3
4const a = @extern(*addrspace(.uniform) const A, .{
5 .name = "a",
6 .decoration = .{ .descriptor = .{ .set = 0, .binding = 0 } },
7});
8
9export fn main() callconv(.{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }) void {
10 const b: *addrspace(.uniform) const B = @ptrCast(a);
11 _ = &b;
12}
13
14// error
15// backend=selfhosted
16// target=spirv32-vulkan
17//
18// :10:44: error: cannot cast pointer '*addrspace(.uniform) const A' to '*addrspace(.uniform) const B'
19// :10:44: note: 'B' must appear at offset 0 inside 'A'
20// :10:44: note: 'uniform' pointers can only reach nested types through a first struct field or an array element