authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-02-15 02:44:58+01:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-02-22 04:12:46+01:00
logfc7a0c4878dac2d721ec18cafe1b6bcff7faa771
tree5b9e4ef2d18e3daaf4bee34ae1bfe5af97916e8a
parentd31bda13cb1ece7dd2ba22339172a8704a84823c

Sema: Fix fnptr alignment safety checks to account for potential ISA tag.

As seen on e.g. Arm/Thumb and MIPS (MIPS16/microMIPS). Fixes #22888.

4 files changed, 53 insertions(+), 6 deletions(-)

src/Sema.zig+24-6
......@@ -23099,8 +23099,14 @@ fn zirPtrFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
2309923099 }
2310023100 if (ptr_align.compare(.gt, .@"1")) {
2310123101 const align_bytes_minus_1 = ptr_align.toByteUnits().? - 1;
23102 const align_minus_1 = Air.internedToRef((try sema.splat(operand_ty, try pt.intValue(Type.usize, align_bytes_minus_1))).toIntern());
23103 const remainder = try block.addBinOp(.bit_and, operand_coerced, align_minus_1);
23102 const align_mask = Air.internedToRef((try sema.splat(operand_ty, try pt.intValue(
23103 Type.usize,
23104 if (elem_ty.fnPtrMaskOrNull(zcu)) |mask|
23105 align_bytes_minus_1 & mask
23106 else
23107 align_bytes_minus_1,
23108 ))).toIntern());
23109 const remainder = try block.addBinOp(.bit_and, operand_coerced, align_mask);
2310423110 const is_aligned = if (is_vector) all_aligned: {
2310523111 const splat_zero_usize = Air.internedToRef((try sema.splat(operand_ty, .zero_usize)).toIntern());
2310623112 const is_aligned = try block.addCmpVector(remainder, splat_zero_usize, .eq);
......@@ -23129,8 +23135,14 @@ fn zirPtrFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
2312923135 }
2313023136 if (ptr_align.compare(.gt, .@"1")) {
2313123137 const align_bytes_minus_1 = ptr_align.toByteUnits().? - 1;
23132 const align_minus_1 = Air.internedToRef((try pt.intValue(Type.usize, align_bytes_minus_1)).toIntern());
23133 const remainder = try block.addBinOp(.bit_and, elem_coerced, align_minus_1);
23138 const align_mask = Air.internedToRef((try pt.intValue(
23139 Type.usize,
23140 if (elem_ty.fnPtrMaskOrNull(zcu)) |mask|
23141 align_bytes_minus_1 & mask
23142 else
23143 align_bytes_minus_1,
23144 )).toIntern());
23145 const remainder = try block.addBinOp(.bit_and, elem_coerced, align_mask);
2313423146 const is_aligned = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
2313523147 try sema.addSafetyCheck(block, src, is_aligned, .incorrect_alignment);
2313623148 }
......@@ -23710,13 +23722,19 @@ fn ptrCastFull(
2371023722 try Type.fromInterned(dest_info.child).hasRuntimeBitsSema(pt))
2371123723 {
2371223724 const align_bytes_minus_1 = dest_align.toByteUnits().? - 1;
23713 const align_minus_1 = Air.internedToRef((try pt.intValue(Type.usize, align_bytes_minus_1)).toIntern());
23725 const align_mask = Air.internedToRef((try pt.intValue(
23726 Type.usize,
23727 if (Type.fromInterned(dest_info.child).fnPtrMaskOrNull(zcu)) |mask|
23728 align_bytes_minus_1 & mask
23729 else
23730 align_bytes_minus_1,
23731 )).toIntern());
2371423732 const actual_ptr = if (src_info.flags.size == .slice)
2371523733 try sema.analyzeSlicePtr(block, src, ptr, operand_ty)
2371623734 else
2371723735 ptr;
2371823736 const ptr_int = try block.addBitCast(.usize, actual_ptr);
23719 const remainder = try block.addBinOp(.bit_and, ptr_int, align_minus_1);
23737 const remainder = try block.addBinOp(.bit_and, ptr_int, align_mask);
2372023738 const is_aligned = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
2372123739 const ok = if (src_info.flags.size == .slice and dest_info.flags.size == .slice) ok: {
2372223740 const len = try sema.analyzeSliceLen(block, operand_src, ptr);
src/Type.zig+7
......@@ -2541,6 +2541,13 @@ pub fn fnIsVarArgs(ty: Type, zcu: *const Zcu) bool {
25412541 return zcu.intern_pool.indexToKey(ty.toIntern()).func_type.is_var_args;
25422542}
25432543
2544pub fn fnPtrMaskOrNull(ty: Type, zcu: *const Zcu) ?u64 {
2545 return switch (ty.zigTypeTag(zcu)) {
2546 .@"fn" => target_util.functionPointerMask(zcu.getTarget()),
2547 else => null,
2548 };
2549}
2550
25442551pub fn isNumeric(ty: Type, zcu: *const Zcu) bool {
25452552 return switch (ty.toIntern()) {
25462553 .f16_type,
src/target.zig+11
......@@ -626,6 +626,17 @@ pub fn supportsFunctionAlignment(target: std.Target) bool {
626626 };
627627}
628628
629pub fn functionPointerMask(target: std.Target) ?u64 {
630 // 32-bit Arm uses the LSB to mean that the target function contains Thumb code.
631 // MIPS uses the LSB to mean that the target function contains MIPS16/microMIPS code.
632 return if (target.cpu.arch.isArm() or target.cpu.arch.isMIPS32())
633 ~@as(u32, 1)
634 else if (target.cpu.arch.isMIPS64())
635 ~@as(u64, 1)
636 else
637 null;
638}
639
629640pub fn supportsTailCall(target: std.Target, backend: std.builtin.CompilerBackend) bool {
630641 switch (backend) {
631642 .stage1, .stage2_llvm => return @import("codegen/llvm.zig").supportsTailCall(target),
test/behavior/align.zig+11
......@@ -613,3 +613,14 @@ test "zero-bit fields in extern struct pad fields appropriately" {
613613 try expect(@intFromPtr(&s.y) == @intFromPtr(&s.a));
614614 try expect(@as(*S, @fieldParentPtr("a", &s.a)) == &s);
615615}
616
617test "function pointer @intFromPtr/@ptrFromInt roundtrip" {
618 // This only succeeds on Thumb if we handle the Thumb bit correctly; if not, the `@ptrFromInt`
619 // will incorrectly trip an alignment safety check.
620
621 const nothing_ptr: *const fn () callconv(.c) void = &nothing;
622 const nothing_int: usize = @intFromPtr(nothing_ptr);
623 const nothing_ptr2: *const fn () callconv(.c) void = @ptrFromInt(nothing_int);
624
625 try std.testing.expectEqual(nothing_ptr, nothing_ptr2);
626}