commit 586ad5a64c6b437ca1ea066d3845e42aadc1f491
parent 2b9dd35027292377b9741f0e6cbd9d154699024f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 29 May 2026 12:32:52 -0700
opt/x64: report the rdx:rax clobber of the unsigned multiply-overflow intrinsic
__builtin_umul*_overflow lowers to a one-operand MUL whose rdx:rax product
clobbers rdx (allocable). A value living in rdx across the intrinsic (e.g. a
constant kept for a later use) was silently destroyed. Teach machinize to build
a NativeMachineOp for IR_INTRINSIC and have the x64 hook report {rax,rdx} for
INTRIN_UMUL_OVERFLOW; the signed variant uses two-operand IMUL and clobbers
nothing. Fixes builtin_26_sadd_overflow at -O1.
Diffstat:
2 files changed, 17 insertions(+), 0 deletions(-)
diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c
@@ -3699,6 +3699,16 @@ static int x64_machine_op_clobbers(NativeTarget* t, const NativeMachineOp* op,
if (!op->result_is_fp) return 0;
mask[NATIVE_REG_INT] = (1u << X64_RAX);
return 1;
+ case NATIVE_MOP_INTRINSIC:
+ /* The unsigned multiply-overflow intrinsic emits a one-operand MUL, whose
+ * rdx:rax product clobbers both registers. The signed variant uses a
+ * two-operand IMUL (no fixed-register clobber); other intrinsics keep to
+ * the reserved emit scratch. */
+ if ((IntrinKind)op->intrin == INTRIN_UMUL_OVERFLOW) {
+ mask[NATIVE_REG_INT] = (1u << X64_RAX) | (1u << X64_RDX);
+ return 1;
+ }
+ return 0;
default:
return 0;
}
diff --git a/src/opt/pass_machinize.c b/src/opt/pass_machinize.c
@@ -173,6 +173,13 @@ static void machinize_inst_clobbers(Func* f, NativeTarget* target) {
break;
case IR_ATOMIC_CAS: mop.kind = NATIVE_MOP_ATOMIC_CAS; break;
case IR_ATOMIC_RMW: mop.kind = NATIVE_MOP_ATOMIC_RMW; break;
+ case IR_INTRINSIC: {
+ const IRIntrinAux* aux = (const IRIntrinAux*)in->extra.aux;
+ if (!aux) continue;
+ mop.kind = NATIVE_MOP_INTRINSIC;
+ mop.intrin = (u8)aux->kind;
+ break;
+ }
default: continue;
}
mask[0] = mask[1] = mask[2] = 0;