authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-08-25 21:55:19+03:30
committergravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-08-26 08:13:06+03:30
loge8e47b3428845a176eb5ee1c373063102ab7f6e2
treeb1de66ba4eeba3f18412e9e95148f45a04aab8e5
parent591b2a47f79fa07e87d3b625320135de8446a274

spirv: reject unrepresentable pointer casts and operations


5 files changed, 131 insertions(+), 60 deletions(-)

src/Sema.zig+52-34
......@@ -22151,34 +22151,7 @@ fn ptrCastFull(
2215122151 }
2215222152
2215322153 try sema.validateRuntimeValue(block, operand_src, operand);
22154
22155 if (zcu.getTarget().cpu.arch.isSpirV() and
22156 src_info.flags.address_space != .physical_storage_buffer and
22157 src_info.flags.address_space == dest_info.flags.address_space and
22158 src_info.child != dest_info.child and
22159 Type.fromInterned(dest_info.child).hasRuntimeBits(zcu))
22160 {
22161 var cur: Type = .fromInterned(src_info.child);
22162 while (cur.toIntern() != dest_info.child) {
22163 cur = switch (cur.zigTypeTag(zcu)) {
22164 .array, .vector => cur.childType(zcu),
22165 .@"struct" => if (cur.structFieldOffset(0, zcu) == 0) cur.fieldType(0, zcu) else null,
22166 else => null,
22167 } orelse return sema.failWithOwnedErrorMsg(block, msg: {
22168 const msg = try sema.errMsg(src, "cannot cast pointer '{f}' to '{f}'", .{
22169 operand_ty.fmt(pt), dest_ty.fmt(pt),
22170 });
22171 errdefer msg.destroy(sema.gpa);
22172 try sema.errNote(src, msg, "'{f}' must appear at offset 0 inside '{f}'", .{
22173 Type.fromInterned(dest_info.child).fmt(pt), Type.fromInterned(src_info.child).fmt(pt),
22174 });
22175 try sema.errNote(src, msg, "'{s}' pointers can only reach nested types through a first struct field or an array element", .{
22176 @tagName(src_info.flags.address_space),
22177 });
22178 break :msg msg;
22179 });
22180 }
22181 }
22154 try sema.checkLogicalPtrCast(block, src, operand_ty, dest_ty);
2218222155
2218322156 const can_cast_to_int = !target_util.shouldBlockPointerOps(zcu.getTarget(), operand_ty.ptrAddressSpace(zcu));
2218422157 const need_null_check = can_cast_to_int and block.wantSafety() and operand_ty.ptrAllowsZero(zcu) and !dest_ty.ptrAllowsZero(zcu);
......@@ -22721,6 +22694,8 @@ fn checkPtrType(
2272122694fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void {
2272222695 const pt = sema.pt;
2272322696 const zcu = pt.zcu;
22697
22698 if (block.isComptime() or block.is_typeof) return;
2272422699 if (zcu.intern_pool.indexToKey(ty.toIntern()) == .ptr_type) {
2272522700 const target = zcu.getTarget();
2272622701 const as = ty.ptrAddressSpace(zcu);
......@@ -22731,12 +22706,8 @@ fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Typ
2273122706 try sema.errNote(
2273222707 src,
2273322708 msg,
22734 "cannot perform arithmetic on pointers with address space '{s}' on target {s}-{s}",
22735 .{
22736 @tagName(as),
22737 @tagName(target.cpu.arch.family()),
22738 @tagName(target.os.tag),
22739 },
22709 "pointers with address space '{t}' do not support arithmetic or indexing on target {t}-{t}",
22710 .{ as, target.cpu.arch.family(), target.os.tag },
2274022711 );
2274122712 break :msg msg;
2274222713 });
......@@ -22744,6 +22715,49 @@ fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Typ
2274422715 }
2274522716}
2274622717
22718fn checkLogicalPtrCast(
22719 sema: *Sema,
22720 block: *Block,
22721 src: LazySrcLoc,
22722 operand_ty: Type,
22723 dest_ty: Type,
22724) CompileError!void {
22725 const pt = sema.pt;
22726 const zcu = pt.zcu;
22727 const src_info = operand_ty.ptrInfo(zcu);
22728 const dest_info = dest_ty.ptrInfo(zcu);
22729
22730 if (block.isComptime() or block.is_typeof) return;
22731 switch (zcu.getTarget().os.tag) {
22732 .vulkan, .opengl => {},
22733 else => return,
22734 }
22735 if (src_info.flags.address_space == .physical_storage_buffer) return;
22736
22737 var cur: Type = .fromInterned(src_info.child);
22738 while (cur.toIntern() != dest_info.child) {
22739 cur = switch (cur.zigTypeTag(zcu)) {
22740 .array, .vector => cur.childType(zcu),
22741 .@"struct" => field: {
22742 for (0..cur.structFieldCount(zcu)) |i| {
22743 const field_ty = cur.fieldType(i, zcu);
22744 if (field_ty.hasRuntimeBits(zcu) and cur.structFieldOffset(i, zcu) == 0) break :field field_ty;
22745 }
22746 break :field null;
22747 },
22748 else => null,
22749 } orelse return sema.failWithOwnedErrorMsg(block, msg: {
22750 const msg = try sema.errMsg(src, "cannot cast pointer '{f}' to '{f}'", .{ operand_ty.fmt(pt), dest_ty.fmt(pt) });
22751 errdefer msg.destroy(sema.gpa);
22752 try sema.errNote(src, msg, "'{f}' must appear at offset 0 inside '{f}'", .{
22753 Type.fromInterned(dest_info.child).fmt(pt),
22754 Type.fromInterned(src_info.child).fmt(pt),
22755 });
22756 break :msg msg;
22757 });
22758 }
22759}
22760
2274722761fn checkVectorElemType(
2274822762 sema: *Sema,
2274922763 block: *Block,
......@@ -27367,6 +27381,7 @@ fn elemPtrOneLayerOnly(
2736727381
2736827382 try sema.validateRuntimeElemAccess(block, elem_index_src, result_ty, indexable_src);
2736927383 try sema.validateRuntimeValue(block, indexable_src, indexable);
27384 try sema.checkLogicalPtrOperation(block, src, indexable_ty);
2737027385
2737127386 if (child_ty.abiSize(zcu) == 0) {
2737227387 // zero-bit child type; just bitcast the pointer
......@@ -27439,6 +27454,7 @@ fn elemVal(
2743927454 },
2744027455 .partially_comptime, .fully_comptime => unreachable, // caught by `validateRuntimeElemAccess`
2744127456 }
27457 try sema.checkLogicalPtrOperation(block, src, indexable_ty);
2744227458
2744327459 return block.addBinOp(.ptr_elem_val, indexable, elem_index);
2744427460 },
......@@ -27840,6 +27856,7 @@ fn elemValSlice(
2784027856 const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
2784127857 try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op);
2784227858 }
27859 try sema.checkLogicalPtrOperation(block, src, slice_ty);
2784327860 return block.addBinOp(.slice_elem_val, slice, elem_index);
2784427861}
2784527862
......@@ -27891,6 +27908,7 @@ fn elemPtrSlice(
2789127908
2789227909 try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ptr_ty, slice_src);
2789327910 try sema.validateRuntimeValue(block, slice_src, slice);
27911 try sema.checkLogicalPtrOperation(block, src, slice_ty);
2789427912
2789527913 if (oob_safety and block.wantSafety()) {
2789627914 const len_inst = len: {
src/codegen/spirv/CodeGen.zig+27-4
......@@ -2059,6 +2059,13 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
20592059 while (cur.toIntern() != dst_child.toIntern()) {
20602060 switch (cur.zigTypeTag(zcu)) {
20612061 .array => {
2062 if (dst_child.zigTypeTag(zcu) == .array and
2063 dst_child.childType(zcu).toIntern() == cur.childType(zcu).toIntern() and
2064 dst_child.arrayLenIncludingSentinel(zcu) <= cur.arrayLenIncludingSentinel(zcu))
2065 {
2066 cur = dst_child;
2067 break;
2068 }
20622069 cur = cur.childType(zcu);
20632070 depth += 1;
20642071 },
......@@ -2098,7 +2105,7 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
20982105 }
20992106 }
21002107
2101 return cg.fail("cannot perform pointer cast: '{f}' to '{f}'", .{
2108 return cg.fail("cannot cast pointer '{f}' to '{f}'", .{
21022109 parent_ptr_ty.fmt(pt),
21032110 oac.new_ptr_ty.fmt(pt),
21042111 });
......@@ -6041,7 +6048,13 @@ fn bitCast(
60416048 while (cur.toIntern() != dst_child.toIntern()) : (try indices.append(gpa, 0)) {
60426049 cur = switch (cur.zigTypeTag(zcu)) {
60436050 .array, .vector => cur.childType(zcu),
6044 .@"struct" => cur.fieldType(0, zcu),
6051 .@"struct" => field: {
6052 for (0..cur.structFieldCount(zcu)) |i| {
6053 const field_ty = cur.fieldType(i, zcu);
6054 if (field_ty.hasRuntimeBits(zcu) and cur.structFieldOffset(i, zcu) == 0) break :field field_ty;
6055 }
6056 unreachable;
6057 },
60456058 else => unreachable,
60466059 };
60476060 }
......@@ -6751,9 +6764,19 @@ fn ptrElemPtr(cg: *CodeGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id {
67516764 const zcu = cg.zcu;
67526765 // Construct new pointer type for the resulting pointer
67536766 const as = ptr_ty.ptrAddressSpace(zcu);
6754 const elem_ty_id = try cg.pointeeType(as, ptr_ty.indexableElem(zcu), false);
6767 const child_ty = ptr_ty.childType(zcu);
6768 const is_single_ptr = ptr_ty.isSinglePointer(zcu);
6769 const elem_is_block = switch (as) {
6770 .uniform, .storage_buffer => switch (child_ty.zigTypeTag(cg.zcu)) {
6771 .array => is_single_ptr,
6772 .spirv => is_single_ptr and child_ty.isSpirvRuntimeArray(cg.zcu),
6773 else => false,
6774 },
6775 else => false,
6776 };
6777 const elem_ty_id = try cg.pointeeType(as, ptr_ty.indexableElem(zcu), elem_is_block);
67556778 const elem_ptr_ty_id = try cg.ptrType(elem_ty_id, cg.storageClass(as));
6756 if (ptr_ty.isSinglePointer(zcu)) {
6779 if (is_single_ptr) {
67576780 // Pointer-to-array. In this case, the resulting pointer is not of the same type
67586781 // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.
67596782 return cg.accessChainId(elem_ptr_ty_id, ptr_id, &.{index_id});
test/behavior/spirv.zig+5-6
......@@ -49,13 +49,12 @@ 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
5652test "@ptrCast to first field type" {
57 const pc_inner: *addrspace(.push_constant) const InnerStruct = @ptrCast(outer_pc);
58 _ = pc_inner;
53 const Inner = extern struct { a: u32 };
54 const Outer = extern struct { a: Inner, b: u32 };
55 var outer: Outer = undefined;
56 var inner: *Inner = @ptrCast(&outer);
57 _ = &inner;
5958}
6059
6160test "@SpirvType equality" {
test/cases/compile_errors/illegal_operation_on_logical_ptr.zig+40-4
......@@ -22,14 +22,50 @@ export fn ptrIntArithmetic() void {
2222 _ = ptr0 - 10;
2323}
2424
25const slice: []const u8 = "abc";
26
27export fn sliceElemVal() void {
28 var i: u32 = 0;
29 _ = &i;
30 _ = slice[i];
31}
32
33export fn sliceElemPtr() void {
34 var i: u32 = 0;
35 _ = &i;
36 _ = &slice[i];
37}
38
39export fn manyElemVal() void {
40 var ptr: [*]const u8 = "abc";
41 var i: u32 = 0;
42 _ = .{ &ptr, &i };
43 _ = ptr[i];
44}
45
46export fn manyElemPtr() void {
47 var ptr: [*]const u8 = "abc";
48 var i: u32 = 0;
49 _ = .{ &ptr, &i };
50 _ = &ptr[i];
51}
52
2553// error
2654// target=spirv64-vulkan
2755//
2856// :3:21: error: illegal operation on logical pointer of type '*u8'
29// :3:21: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
57// :3:21: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan
3058// :8:20: error: illegal operation on logical pointer of type '*u8'
31// :8:20: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
59// :8:20: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan
3260// :16:17: error: illegal operation on logical pointer of type '*u8'
33// :16:17: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
61// :16:17: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan
3462// :22:14: error: illegal operation on logical pointer of type '[*]u8'
35// :22:14: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
63// :22:14: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan
64// :30:14: error: illegal operation on logical pointer of type '[]const u8'
65// :30:14: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan
66// :36:15: error: illegal operation on logical pointer of type '[]const u8'
67// :36:15: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan
68// :43:12: error: illegal operation on logical pointer of type '[*]const u8'
69// :43:12: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan
70// :50:13: error: illegal operation on logical pointer of type '[*]const u8'
71// :50:13: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan
test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig+7-12
......@@ -1,20 +1,15 @@
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});
1const Inner = extern struct { a: u32 };
2const Outer = extern struct { a: u32, b: Inner };
83
94export fn main() callconv(.kernel) void {
10 const b: *addrspace(.uniform) const B = @ptrCast(a);
11 _ = &b;
5 var outer: Outer = undefined;
6 const inner: *Inner = @ptrCast(&outer);
7 _ = &inner;
128}
139
1410// error
1511// backend=selfhosted
1612// target=spirv32-vulkan
1713//
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
14// :6:27: error: cannot cast pointer '*tmp.Outer' to '*tmp.Inner'
15// :6:27: note: 'tmp.Inner' must appear at offset 0 inside 'tmp.Outer'