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(...@@ -22151,34 +22151,7 @@ fn ptrCastFull(
22151 }22151 }
2215222152
22153 try sema.validateRuntimeValue(block, operand_src, operand);22153 try sema.validateRuntimeValue(block, operand_src, operand);
2215422154 try sema.checkLogicalPtrCast(block, src, operand_ty, dest_ty);
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 }
2218222155
22183 const can_cast_to_int = !target_util.shouldBlockPointerOps(zcu.getTarget(), operand_ty.ptrAddressSpace(zcu));22156 const can_cast_to_int = !target_util.shouldBlockPointerOps(zcu.getTarget(), operand_ty.ptrAddressSpace(zcu));
22184 const need_null_check = can_cast_to_int and block.wantSafety() and operand_ty.ptrAllowsZero(zcu) and !dest_ty.ptrAllowsZero(zcu);22157 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(...@@ -22721,6 +22694,8 @@ fn checkPtrType(
22721fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void {22694fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void {
22722 const pt = sema.pt;22695 const pt = sema.pt;
22723 const zcu = pt.zcu;22696 const zcu = pt.zcu;
22697
22698 if (block.isComptime() or block.is_typeof) return;
22724 if (zcu.intern_pool.indexToKey(ty.toIntern()) == .ptr_type) {22699 if (zcu.intern_pool.indexToKey(ty.toIntern()) == .ptr_type) {
22725 const target = zcu.getTarget();22700 const target = zcu.getTarget();
22726 const as = ty.ptrAddressSpace(zcu);22701 const as = ty.ptrAddressSpace(zcu);
...@@ -22731,12 +22706,8 @@ fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Typ...@@ -22731,12 +22706,8 @@ fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Typ
22731 try sema.errNote(22706 try sema.errNote(
22732 src,22707 src,
22733 msg,22708 msg,
22734 "cannot perform arithmetic on pointers with address space '{s}' on target {s}-{s}",22709 "pointers with address space '{t}' do not support arithmetic or indexing on target {t}-{t}",
22735 .{22710 .{ as, target.cpu.arch.family(), target.os.tag },
22736 @tagName(as),
22737 @tagName(target.cpu.arch.family()),
22738 @tagName(target.os.tag),
22739 },
22740 );22711 );
22741 break :msg msg;22712 break :msg msg;
22742 });22713 });
...@@ -22744,6 +22715,49 @@ fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Typ...@@ -22744,6 +22715,49 @@ fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Typ
22744 }22715 }
22745}22716}
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
22747fn checkVectorElemType(22761fn checkVectorElemType(
22748 sema: *Sema,22762 sema: *Sema,
22749 block: *Block,22763 block: *Block,
...@@ -27367,6 +27381,7 @@ fn elemPtrOneLayerOnly(...@@ -27367,6 +27381,7 @@ fn elemPtrOneLayerOnly(
2736727381
27368 try sema.validateRuntimeElemAccess(block, elem_index_src, result_ty, indexable_src);27382 try sema.validateRuntimeElemAccess(block, elem_index_src, result_ty, indexable_src);
27369 try sema.validateRuntimeValue(block, indexable_src, indexable);27383 try sema.validateRuntimeValue(block, indexable_src, indexable);
27384 try sema.checkLogicalPtrOperation(block, src, indexable_ty);
2737027385
27371 if (child_ty.abiSize(zcu) == 0) {27386 if (child_ty.abiSize(zcu) == 0) {
27372 // zero-bit child type; just bitcast the pointer27387 // zero-bit child type; just bitcast the pointer
...@@ -27439,6 +27454,7 @@ fn elemVal(...@@ -27439,6 +27454,7 @@ fn elemVal(
27439 },27454 },
27440 .partially_comptime, .fully_comptime => unreachable, // caught by `validateRuntimeElemAccess`27455 .partially_comptime, .fully_comptime => unreachable, // caught by `validateRuntimeElemAccess`
27441 }27456 }
27457 try sema.checkLogicalPtrOperation(block, src, indexable_ty);
2744227458
27443 return block.addBinOp(.ptr_elem_val, indexable, elem_index);27459 return block.addBinOp(.ptr_elem_val, indexable, elem_index);
27444 },27460 },
...@@ -27840,6 +27856,7 @@ fn elemValSlice(...@@ -27840,6 +27856,7 @@ fn elemValSlice(
27840 const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;27856 const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
27841 try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op);27857 try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op);
27842 }27858 }
27859 try sema.checkLogicalPtrOperation(block, src, slice_ty);
27843 return block.addBinOp(.slice_elem_val, slice, elem_index);27860 return block.addBinOp(.slice_elem_val, slice, elem_index);
27844}27861}
2784527862
...@@ -27891,6 +27908,7 @@ fn elemPtrSlice(...@@ -27891,6 +27908,7 @@ fn elemPtrSlice(
2789127908
27892 try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ptr_ty, slice_src);27909 try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ptr_ty, slice_src);
27893 try sema.validateRuntimeValue(block, slice_src, slice);27910 try sema.validateRuntimeValue(block, slice_src, slice);
27911 try sema.checkLogicalPtrOperation(block, src, slice_ty);
2789427912
27895 if (oob_safety and block.wantSafety()) {27913 if (oob_safety and block.wantSafety()) {
27896 const len_inst = len: {27914 const len_inst = len: {
src/codegen/spirv/CodeGen.zig+27-4
...@@ -2059,6 +2059,13 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {...@@ -2059,6 +2059,13 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
2059 while (cur.toIntern() != dst_child.toIntern()) {2059 while (cur.toIntern() != dst_child.toIntern()) {
2060 switch (cur.zigTypeTag(zcu)) {2060 switch (cur.zigTypeTag(zcu)) {
2061 .array => {2061 .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 }
2062 cur = cur.childType(zcu);2069 cur = cur.childType(zcu);
2063 depth += 1;2070 depth += 1;
2064 },2071 },
...@@ -2098,7 +2105,7 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {...@@ -2098,7 +2105,7 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
2098 }2105 }
2099 }2106 }
21002107
2101 return cg.fail("cannot perform pointer cast: '{f}' to '{f}'", .{2108 return cg.fail("cannot cast pointer '{f}' to '{f}'", .{
2102 parent_ptr_ty.fmt(pt),2109 parent_ptr_ty.fmt(pt),
2103 oac.new_ptr_ty.fmt(pt),2110 oac.new_ptr_ty.fmt(pt),
2104 });2111 });
...@@ -6041,7 +6048,13 @@ fn bitCast(...@@ -6041,7 +6048,13 @@ fn bitCast(
6041 while (cur.toIntern() != dst_child.toIntern()) : (try indices.append(gpa, 0)) {6048 while (cur.toIntern() != dst_child.toIntern()) : (try indices.append(gpa, 0)) {
6042 cur = switch (cur.zigTypeTag(zcu)) {6049 cur = switch (cur.zigTypeTag(zcu)) {
6043 .array, .vector => cur.childType(zcu),6050 .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 },
6045 else => unreachable,6058 else => unreachable,
6046 };6059 };
6047 }6060 }
...@@ -6751,9 +6764,19 @@ fn ptrElemPtr(cg: *CodeGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id {...@@ -6751,9 +6764,19 @@ fn ptrElemPtr(cg: *CodeGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id {
6751 const zcu = cg.zcu;6764 const zcu = cg.zcu;
6752 // Construct new pointer type for the resulting pointer6765 // Construct new pointer type for the resulting pointer
6753 const as = ptr_ty.ptrAddressSpace(zcu);6766 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);
6755 const elem_ptr_ty_id = try cg.ptrType(elem_ty_id, cg.storageClass(as));6778 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) {
6757 // Pointer-to-array. In this case, the resulting pointer is not of the same type6780 // Pointer-to-array. In this case, the resulting pointer is not of the same type
6758 // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.6781 // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.
6759 return cg.accessChainId(elem_ptr_ty_id, ptr_id, &.{index_id});6782 return cg.accessChainId(elem_ptr_ty_id, ptr_id, &.{index_id});
test/behavior/spirv.zig+5-6
...@@ -49,13 +49,12 @@ test "@SpirvType" {...@@ -49,13 +49,12 @@ test "@SpirvType" {
49 _ = runtime_array;49 _ = runtime_array;
50}50}
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" {52test "@ptrCast to first field type" {
57 const pc_inner: *addrspace(.push_constant) const InnerStruct = @ptrCast(outer_pc);53 const Inner = extern struct { a: u32 };
58 _ = pc_inner;54 const Outer = extern struct { a: Inner, b: u32 };
55 var outer: Outer = undefined;
56 var inner: *Inner = @ptrCast(&outer);
57 _ = &inner;
59}58}
6059
61test "@SpirvType equality" {60test "@SpirvType equality" {
test/cases/compile_errors/illegal_operation_on_logical_ptr.zig+40-4
...@@ -22,14 +22,50 @@ export fn ptrIntArithmetic() void {...@@ -22,14 +22,50 @@ export fn ptrIntArithmetic() void {
22 _ = ptr0 - 10;22 _ = ptr0 - 10;
23}23}
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
25// error53// error
26// target=spirv64-vulkan54// target=spirv64-vulkan
27//55//
28// :3:21: error: illegal operation on logical pointer of type '*u8'56// :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-vulkan57// :3:21: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan
30// :8:20: error: illegal operation on logical pointer of type '*u8'58// :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-vulkan59// :8:20: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan
32// :16:17: error: illegal operation on logical pointer of type '*u8'60// :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-vulkan61// :16:17: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan
34// :22:14: error: illegal operation on logical pointer of type '[*]u8'62// :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-vulkan63// :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 @@...@@ -1,20 +1,15 @@
1const A = extern struct { x: u32, y: u32 };1const Inner = extern struct { a: u32 };
2const B = extern struct { a: u64 };2const Outer = extern struct { a: u32, b: Inner };
3
4const a = @extern(*addrspace(.uniform) const A, .{
5 .name = "a",
6 .decoration = .{ .descriptor = .{ .set = 0, .binding = 0 } },
7});
83
9export fn main() callconv(.kernel) void {4export fn main() callconv(.kernel) void {
10 const b: *addrspace(.uniform) const B = @ptrCast(a);5 var outer: Outer = undefined;
11 _ = &b;6 const inner: *Inner = @ptrCast(&outer);
7 _ = &inner;
12}8}
139
14// error10// error
15// backend=selfhosted11// backend=selfhosted
16// target=spirv32-vulkan12// target=spirv32-vulkan
17//13//
18// :10:44: error: cannot cast pointer '*addrspace(.uniform) const A' to '*addrspace(.uniform) const B'14// :6:27: error: cannot cast pointer '*tmp.Outer' to '*tmp.Inner'
19// :10:44: note: 'B' must appear at offset 0 inside 'A'15// :6:27: note: 'tmp.Inner' must appear at offset 0 inside 'tmp.Outer'
20// :10:44: note: 'uniform' pointers can only reach nested types through a first struct field or an array element