diff --git a/src/Sema.zig b/src/Sema.zig index 48756b0607c2041ca10d35f95b3a60782435e0ca..63bee88f76f40e607532964b911b6ec2030f6e0f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -22151,34 +22151,7 @@ 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; - }); - } - } + try sema.checkLogicalPtrCast(block, src, operand_ty, dest_ty); 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); @@ -22721,6 +22694,8 @@ fn checkPtrType( fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void { const pt = sema.pt; const zcu = pt.zcu; + + if (block.isComptime() or block.is_typeof) return; if (zcu.intern_pool.indexToKey(ty.toIntern()) == .ptr_type) { const target = zcu.getTarget(); const as = ty.ptrAddressSpace(zcu); @@ -22731,12 +22706,8 @@ fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Typ try sema.errNote( src, msg, - "cannot perform arithmetic on pointers with address space '{s}' on target {s}-{s}", - .{ - @tagName(as), - @tagName(target.cpu.arch.family()), - @tagName(target.os.tag), - }, + "pointers with address space '{t}' do not support arithmetic or indexing on target {t}-{t}", + .{ as, target.cpu.arch.family(), target.os.tag }, ); break :msg msg; }); @@ -22744,6 +22715,49 @@ fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Typ } } +fn checkLogicalPtrCast( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + operand_ty: Type, + dest_ty: Type, +) CompileError!void { + const pt = sema.pt; + const zcu = pt.zcu; + const src_info = operand_ty.ptrInfo(zcu); + const dest_info = dest_ty.ptrInfo(zcu); + + if (block.isComptime() or block.is_typeof) return; + switch (zcu.getTarget().os.tag) { + .vulkan, .opengl => {}, + else => return, + } + if (src_info.flags.address_space == .physical_storage_buffer) return; + + var cur: Type = .fromInterned(src_info.child); + while (cur.toIntern() != dest_info.child) { + cur = switch (cur.zigTypeTag(zcu)) { + .array, .vector => cur.childType(zcu), + .@"struct" => field: { + for (0..cur.structFieldCount(zcu)) |i| { + const field_ty = cur.fieldType(i, zcu); + if (field_ty.hasRuntimeBits(zcu) and cur.structFieldOffset(i, zcu) == 0) break :field field_ty; + } + break :field 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), + }); + break :msg msg; + }); + } +} + fn checkVectorElemType( sema: *Sema, block: *Block, @@ -27367,6 +27381,7 @@ fn elemPtrOneLayerOnly( try sema.validateRuntimeElemAccess(block, elem_index_src, result_ty, indexable_src); try sema.validateRuntimeValue(block, indexable_src, indexable); + try sema.checkLogicalPtrOperation(block, src, indexable_ty); if (child_ty.abiSize(zcu) == 0) { // zero-bit child type; just bitcast the pointer @@ -27439,6 +27454,7 @@ fn elemVal( }, .partially_comptime, .fully_comptime => unreachable, // caught by `validateRuntimeElemAccess` } + try sema.checkLogicalPtrOperation(block, src, indexable_ty); return block.addBinOp(.ptr_elem_val, indexable, elem_index); }, @@ -27840,6 +27856,7 @@ fn elemValSlice( const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt; try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op); } + try sema.checkLogicalPtrOperation(block, src, slice_ty); return block.addBinOp(.slice_elem_val, slice, elem_index); } @@ -27891,6 +27908,7 @@ fn elemPtrSlice( try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ptr_ty, slice_src); try sema.validateRuntimeValue(block, slice_src, slice); + try sema.checkLogicalPtrOperation(block, src, slice_ty); if (oob_safety and block.wantSafety()) { const len_inst = len: { diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index 42f65121aba5309348a701ea7454008d85385a62..574284d5cdd0991210c2f58697805ffe62e1ca1f 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -2059,6 +2059,13 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { while (cur.toIntern() != dst_child.toIntern()) { switch (cur.zigTypeTag(zcu)) { .array => { + if (dst_child.zigTypeTag(zcu) == .array and + dst_child.childType(zcu).toIntern() == cur.childType(zcu).toIntern() and + dst_child.arrayLenIncludingSentinel(zcu) <= cur.arrayLenIncludingSentinel(zcu)) + { + cur = dst_child; + break; + } cur = cur.childType(zcu); depth += 1; }, @@ -2098,7 +2105,7 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { } } - return cg.fail("cannot perform pointer cast: '{f}' to '{f}'", .{ + return cg.fail("cannot cast pointer '{f}' to '{f}'", .{ parent_ptr_ty.fmt(pt), oac.new_ptr_ty.fmt(pt), }); @@ -6041,7 +6048,13 @@ fn bitCast( 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), + .@"struct" => field: { + for (0..cur.structFieldCount(zcu)) |i| { + const field_ty = cur.fieldType(i, zcu); + if (field_ty.hasRuntimeBits(zcu) and cur.structFieldOffset(i, zcu) == 0) break :field field_ty; + } + unreachable; + }, else => unreachable, }; } @@ -6751,9 +6764,19 @@ fn ptrElemPtr(cg: *CodeGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id { const zcu = cg.zcu; // Construct new pointer type for the resulting pointer const as = ptr_ty.ptrAddressSpace(zcu); - const elem_ty_id = try cg.pointeeType(as, ptr_ty.indexableElem(zcu), false); + const child_ty = ptr_ty.childType(zcu); + const is_single_ptr = ptr_ty.isSinglePointer(zcu); + const elem_is_block = switch (as) { + .uniform, .storage_buffer => switch (child_ty.zigTypeTag(cg.zcu)) { + .array => is_single_ptr, + .spirv => is_single_ptr and child_ty.isSpirvRuntimeArray(cg.zcu), + else => false, + }, + else => false, + }; + const elem_ty_id = try cg.pointeeType(as, ptr_ty.indexableElem(zcu), elem_is_block); const elem_ptr_ty_id = try cg.ptrType(elem_ty_id, cg.storageClass(as)); - if (ptr_ty.isSinglePointer(zcu)) { + if (is_single_ptr) { // Pointer-to-array. In this case, the resulting pointer is not of the same type // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain. return cg.accessChainId(elem_ptr_ty_id, ptr_id, &.{index_id}); diff --git a/test/behavior/spirv.zig b/test/behavior/spirv.zig index ed07aa33c5455bbf5b5a08093dfc7fe561cb36ab..dd8b6d093e01625c361288c76d1a6dce033d7561 100644 --- a/test/behavior/spirv.zig +++ b/test/behavior/spirv.zig @@ -49,13 +49,12 @@ 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; + const Inner = extern struct { a: u32 }; + const Outer = extern struct { a: Inner, b: u32 }; + var outer: Outer = undefined; + var inner: *Inner = @ptrCast(&outer); + _ = &inner; } test "@SpirvType equality" { diff --git a/test/cases/compile_errors/illegal_operation_on_logical_ptr.zig b/test/cases/compile_errors/illegal_operation_on_logical_ptr.zig index 859f0be6d37f1961be9f2bc295daeb330858b3cd..b8441f0466f8e4088d8ade8de1e2a97a1424f0c8 100644 --- a/test/cases/compile_errors/illegal_operation_on_logical_ptr.zig +++ b/test/cases/compile_errors/illegal_operation_on_logical_ptr.zig @@ -22,14 +22,50 @@ export fn ptrIntArithmetic() void { _ = ptr0 - 10; } +const slice: []const u8 = "abc"; + +export fn sliceElemVal() void { + var i: u32 = 0; + _ = &i; + _ = slice[i]; +} + +export fn sliceElemPtr() void { + var i: u32 = 0; + _ = &i; + _ = &slice[i]; +} + +export fn manyElemVal() void { + var ptr: [*]const u8 = "abc"; + var i: u32 = 0; + _ = .{ &ptr, &i }; + _ = ptr[i]; +} + +export fn manyElemPtr() void { + var ptr: [*]const u8 = "abc"; + var i: u32 = 0; + _ = .{ &ptr, &i }; + _ = &ptr[i]; +} + // error // target=spirv64-vulkan // // :3:21: error: illegal operation on logical pointer of type '*u8' -// :3:21: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan +// :3:21: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan // :8:20: error: illegal operation on logical pointer of type '*u8' -// :8:20: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan +// :8:20: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan // :16:17: error: illegal operation on logical pointer of type '*u8' -// :16:17: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan +// :16:17: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan // :22:14: error: illegal operation on logical pointer of type '[*]u8' -// :22:14: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan +// :22:14: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan +// :30:14: error: illegal operation on logical pointer of type '[]const u8' +// :30:14: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan +// :36:15: error: illegal operation on logical pointer of type '[]const u8' +// :36:15: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan +// :43:12: error: illegal operation on logical pointer of type '[*]const u8' +// :43:12: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan +// :50:13: error: illegal operation on logical pointer of type '[*]const u8' +// :50:13: note: pointers with address space 'generic' do not support arithmetic or indexing on target spirv-vulkan 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 index 8eda14440d21815df25b8e9e509a46f9b1eb1db6..48b47202a7ae95f93d2a3746de8ea60d26e44c1d 100644 --- a/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig +++ b/test/cases/compile_errors/spirv_pointer_cast_requires_offset_zero.zig @@ -1,20 +1,15 @@ -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 } }, -}); +const Inner = extern struct { a: u32 }; +const Outer = extern struct { a: u32, b: Inner }; export fn main() callconv(.kernel) void { - const b: *addrspace(.uniform) const B = @ptrCast(a); - _ = &b; + var outer: Outer = undefined; + const inner: *Inner = @ptrCast(&outer); + _ = &inner; } // 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 +// :6:27: error: cannot cast pointer '*tmp.Outer' to '*tmp.Inner' +// :6:27: note: 'tmp.Inner' must appear at offset 0 inside 'tmp.Outer'