Commit e16872ca81d for php

commit e16872ca81d423c72f9a3859c5578a8cc56e4b9d
Author: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com>
Date:   Wed Sep 30 14:27:07 2026 +0200

    Update IR (#24013)

    IR commit: 93f92f5b5a49a02295685e340ebb0b83562cbe79

diff --git a/.github/scripts/download-bundled/jit-ir.sh b/.github/scripts/download-bundled/jit-ir.sh
index fed7a9938d2..bc9c0a428b7 100755
--- a/.github/scripts/download-bundled/jit-ir.sh
+++ b/.github/scripts/download-bundled/jit-ir.sh
@@ -5,7 +5,7 @@ cd "$(dirname "$0")/../../.."
 tmp_dir=/tmp/php-src-download-bundled/jit-ir
 rm -rf "$tmp_dir"

-revision=00bec1ca490b8bc51f636a42040c5e9f64c1a91b
+revision=93f92f5b5a49a02295685e340ebb0b83562cbe79

 git clone --depth 1 --revision="$revision" https://github.com/dstogov/ir.git "$tmp_dir"

diff --git a/ext/opcache/jit/ir/ir_emit.c b/ext/opcache/jit/ir/ir_emit.c
index f27a58d388b..8c01f56c9e3 100644
--- a/ext/opcache/jit/ir/ir_emit.c
+++ b/ext/opcache/jit/ir/ir_emit.c
@@ -1528,7 +1528,9 @@ static ir_reg _get_free_reg2(ir_ctx *ctx, ir_type type, ir_reg_alloc_simple_data

 	reg = IR_REGSET_FIRST(available);
 	if (IR_REGSET_IN(x->preserved_regs, reg)) {
-		IR_REGSET_INCL(ctx->used_preserved_regs, reg);
+		ir_regset tmp = ctx->used_preserved_regs;
+		IR_REGSET_INCL(tmp, reg);
+		ctx->used_preserved_regs = tmp;
 	}
 	return reg;
 }
diff --git a/ext/opcache/jit/ir/ir_fold.h b/ext/opcache/jit/ir/ir_fold.h
index c5fd2f28894..abab986e0a3 100644
--- a/ext/opcache/jit/ir/ir_fold.h
+++ b/ext/opcache/jit/ir/ir_fold.h
@@ -1840,8 +1840,7 @@ IR_FOLD(REPLACE(LONG_CONST, _))
 	if (IR_IS_CONST_REF(op3)
 	 && IR_IS_CONST_REF(op2)
 	 && IR_IS_TYPE_INT(op2_insn->type)
-	 && op2_insn->val.i64 >= 0
-	 && op2_insn->val.i64 < IR_VECTOR_LENGTH(op1_insn->type)) {
+	 && op2_insn->val.u64 < (uint64_t)IR_VECTOR_LENGTH(op1_insn->type)) {
 		IR_ASSERT(IR_IS_TYPE_VECTOR(op1_insn->type)
 		  && (IR_VECTOR_BASE_TYPE(op1_insn->type) == op3_insn->type
 		   || (IR_IS_TYPE_INT(IR_VECTOR_BASE_TYPE(op1_insn->type))
@@ -1942,8 +1941,7 @@ IR_FOLD(EXTRACT(LONG_CONST, _))
 {
 	if (IR_IS_CONST_REF(op2)
 	 && IR_IS_TYPE_INT(op2_insn->type)
-	 && op2_insn->val.i64 >= 0
-	 && op2_insn->val.i64 < IR_VECTOR_LENGTH(op1_insn->type)) {
+	 && op2_insn->val.u64 < (uint64_t)IR_VECTOR_LENGTH(op1_insn->type)) {
 		uint32_t idx = op2_insn->val.u32;
 		void *ptr;

@@ -3212,7 +3210,7 @@ IR_FOLD(LT(ABS, C_FLOAT))
 IR_FOLD(LT(ABS, C_DOUBLE))
 {
 	if (op2_insn->val.u64 == 0) {
-		/* abs() < 0 => false */
+		/* abs() < 0 => false (abs(INT_MIN) is UB, so this optimization may change the result) */
 		IR_FOLD_COPY(IR_FALSE);
 	}
 	IR_FOLD_NEXT;
@@ -3226,7 +3224,7 @@ IR_FOLD(GE(ABS, C_FLOAT))
 IR_FOLD(GE(ABS, C_DOUBLE))
 {
 	if (op2_insn->val.u64 == 0) {
-		/* abs() >= 0 => true */
+		/* abs() >= 0 => true (abs(INT_MIN) is UB, so this optimization may change the result) */
 		IR_FOLD_COPY(IR_TRUE);
 	}
 	IR_FOLD_NEXT;
diff --git a/ext/opcache/jit/ir/ir_private.h b/ext/opcache/jit/ir/ir_private.h
index 4179eb21ee5..b4409f97df7 100644
--- a/ext/opcache/jit/ir/ir_private.h
+++ b/ext/opcache/jit/ir/ir_private.h
@@ -1273,6 +1273,8 @@ typedef struct _ir_use_pos       ir_use_pos;
 #define IR_USE_MUST_BE_IN_REG            (1<<0)
 #define IR_USE_SHOULD_BE_IN_REG          (1<<1)
 #define IR_HINT_TWO_REGS                 (1<<2)
+#define IR_HINT_NEEDS_HOLE               (1<<3) /* create a fake hole in live ranges to avoid conflict with fxed reg */
+
 #define IR_DEF_REUSES_OP1_REG            (1<<3)
 #define IR_DEF_CONFLICTS_WITH_INPUT_REGS (1<<4)
 #define IR_EXTEND_INPUTS_TO_NEXT         (1<<5) /* used for SNAPSHOT followed by GUARD */
@@ -1280,17 +1282,20 @@ typedef struct _ir_use_pos       ir_use_pos;
 #define IR_FUSED_USE                     (1<<6)
 #define IR_PHI_USE                       (1<<7)

-#define IR_OP1_MUST_BE_IN_REG            (1<<8)
-#define IR_OP1_SHOULD_BE_IN_REG          (1<<9)
-#define IR_OP1_HINT_TWO_REGS             (1<<10)
-#define IR_OP2_MUST_BE_IN_REG            (1<<11)
-#define IR_OP2_SHOULD_BE_IN_REG          (1<<12)
-#define IR_OP2_HINT_TWO_REGS             (1<<13)
-#define IR_OP3_MUST_BE_IN_REG            (1<<14)
-#define IR_OP3_SHOULD_BE_IN_REG          (1<<15)
-#define IR_OP3_HINT_TWO_REGS             (1<<16)
-
-#define IR_USE_FLAGS(def_flags, op_num) (((def_flags) >> (5 + (IR_MIN((op_num), 3) * 3))) & 7)
+#define IR_OP1_MUST_BE_IN_REG            (1<<16)
+#define IR_OP1_SHOULD_BE_IN_REG          (1<<17)
+#define IR_OP1_HINT_TWO_REGS             (1<<18)
+#define IR_OP1_HINT_NEEDS_HOLE           (1<<19)
+#define IR_OP2_MUST_BE_IN_REG            (1<<20)
+#define IR_OP2_SHOULD_BE_IN_REG          (1<<21)
+#define IR_OP2_HINT_TWO_REGS             (1<<22)
+#define IR_OP2_HINT_NEEDS_HOLE           (1<<23)
+#define IR_OP3_MUST_BE_IN_REG            (1<<24)
+#define IR_OP3_SHOULD_BE_IN_REG          (1<<25)
+#define IR_OP3_HINT_TWO_REGS             (1<<26)
+#define IR_OP3_HINT_NEEDS_HOLE           (1<<27)
+
+#define IR_USE_FLAGS(def_flags, op_num) (((def_flags) >> (12 + (IR_MIN((op_num), 3) * 4))) & 15)

 struct _ir_use_pos {
 	uint16_t       op_num; /* 0 - means result */
diff --git a/ext/opcache/jit/ir/ir_ra.c b/ext/opcache/jit/ir/ir_ra.c
index 17f41319bb1..fe052b2a097 100644
--- a/ext/opcache/jit/ir/ir_ra.c
+++ b/ext/opcache/jit/ir/ir_ra.c
@@ -564,6 +564,10 @@ static void ir_add_fusion_ranges(ir_ctx *ctx, ir_ref ref, ir_ref input, ir_block
 						/* intervals[opd].addRange(b.from, op.id) */
 						ival = ir_add_live_range(ctx, v,
 							IR_START_LIVE_POS_FROM_REF(bb->start), use_pos);
+					} else if (reg != IR_REG_NONE && (IR_USE_FLAGS(def_flags, j) & IR_HINT_NEEDS_HOLE)) {
+						ir_fix_live_range(ctx, v,
+							IR_START_LIVE_POS_FROM_REF(bb->start), use_pos + IR_DEF_SUB_REF);
+						ival = ir_add_live_range(ctx, v, IR_START_LIVE_POS_FROM_REF(bb->start), use_pos);
 					} else {
 						ival = ctx->live_intervals[v];
 					}
@@ -692,14 +696,15 @@ int ir_compute_live_ranges(ir_ctx *ctx)
 						if (input > 0) {
 							uint32_t v = ctx->vregs[input];

-							/* live.add(phi.inputOf(b)) */
-							IR_ASSERT(v);
-							ir_bitset_incl(live, v);
-							/* intervals[phi.inputOf(b)].addRange(b.from, b.to) */
-							ival = ir_add_prev_live_range(ctx, v,
-								IR_START_LIVE_POS_FROM_REF(bb->start),
-								IR_END_LIVE_POS_FROM_REF(bb->end));
-							ir_add_phi_use(ctx, ival, k, IR_DEF_LIVE_POS_FROM_REF(bb->end), use);
+							if (v) {
+								/* live.add(phi.inputOf(b)) */
+								ir_bitset_incl(live, v);
+								/* intervals[phi.inputOf(b)].addRange(b.from, b.to) */
+								ival = ir_add_prev_live_range(ctx, v,
+									IR_START_LIVE_POS_FROM_REF(bb->start),
+									IR_END_LIVE_POS_FROM_REF(bb->end));
+								ir_add_phi_use(ctx, ival, k, IR_DEF_LIVE_POS_FROM_REF(bb->end), use);
+							}
 						}
 					}
 				}
@@ -766,8 +771,6 @@ int ir_compute_live_ranges(ir_ctx *ctx)
 			insn = &ctx->ir_base[ref];
 			v = ctx->vregs[ref];
 			if (v) {
-				IR_ASSERT(ir_bitset_in(live, v));
-
 				if (insn->op != IR_PHI) {
 					ir_live_pos def_pos;
 					ir_ref hint_ref = 0;
@@ -878,6 +881,17 @@ int ir_compute_live_ranges(ir_ctx *ctx)
 							} else
 #endif
 							ir_add_fixed_live_range(ctx, reg, use_pos, use_pos + IR_USE_SUB_REF);
+							if ((use_flags & IR_HINT_NEEDS_HOLE) && ir_bitset_in(live, v)) {
+								/* This is a special case for x86 SHIFT instructions that uses %rcx for op2.
+								 * SHIFT adds short fixed live range for %rcx and this makes a conflict with
+								 * operand live interval, if the operand is still alive after the SHIFT.
+								 * To avoid the conflict we create a "fake" hole in the operand live interval.
+								 * See: ./tests/debug/ra_004.irt
+								 */
+								ir_fix_live_range(ctx, v,
+									IR_START_LIVE_POS_FROM_REF(bb->start), use_pos + IR_DEF_SUB_REF);
+								ir_add_live_range(ctx, v, IR_START_LIVE_POS_FROM_REF(bb->start), use_pos);
+							}
 						} else if (def_flags & IR_DEF_REUSES_OP1_REG) {
 							if (j == 1) {
 								use_pos = IR_LOAD_LIVE_POS_FROM_REF(ref);
@@ -1286,6 +1300,10 @@ static void ir_add_fusion_ranges(ir_ctx *ctx, ir_ref ref, ir_ref input, ir_block
 						/* intervals[opd].addRange(b.from, op.id) */
 						ival = ir_add_live_range(ctx, v,
 							IR_START_LIVE_POS_FROM_REF(bb->start), use_pos);
+					} else if (reg != IR_REG_NONE && (IR_USE_FLAGS(def_flags, j) & IR_HINT_NEEDS_HOLE)) {
+						ir_fix_live_range(ctx, v,
+							IR_START_LIVE_POS_FROM_REF(bb->start), use_pos + IR_DEF_SUB_REF);
+						ival = ir_add_live_range(ctx, v, IR_START_LIVE_POS_FROM_REF(bb->start), use_pos);
 					} else {
 						ival = ctx->live_intervals[v];
 					}
@@ -1559,6 +1577,17 @@ int ir_compute_live_ranges(ir_ctx *ctx)
 							} else
 #endif
 							ir_add_fixed_live_range(ctx, reg, use_pos, use_pos + IR_USE_SUB_REF);
+							if ((use_flags & IR_HINT_NEEDS_HOLE) && IS_LIVE_IN_BLOCK(v, b)) {
+								/* This is a special case for x86 SHIFT instructions that uses %rcx for op2.
+								 * SHIFT adds short fixed live range for %rcx and this makes a conflict with
+								 * operand live interval, if the operand is still alive after the SHIFT.
+								 * To avoid the conflict we create a "fake" hole in the operand live interval.
+								 * See: ./tests/debug/ra_004.irt
+								 */
+								ir_fix_live_range(ctx, v,
+									IR_START_LIVE_POS_FROM_REF(bb->start), use_pos + IR_DEF_SUB_REF);
+								ir_add_live_range(ctx, v, IR_START_LIVE_POS_FROM_REF(bb->start), use_pos);
+							}
 						} else if (def_flags & IR_DEF_REUSES_OP1_REG) {
 							if (j == 1) {
 								if (def_flags & IR_DEF_CONFLICTS_WITH_INPUT_REGS) {
diff --git a/ext/opcache/jit/ir/ir_sccp.c b/ext/opcache/jit/ir/ir_sccp.c
index 5d37f42734d..4745c7000d7 100644
--- a/ext/opcache/jit/ir/ir_sccp.c
+++ b/ext/opcache/jit/ir/ir_sccp.c
@@ -2451,6 +2451,20 @@ static bool ir_try_promote_ext(ir_ctx *ctx, ir_ref ext_ref, ir_insn *insn)
 		 && ir_is_loop_invariant(ctx, insn->op1, ctx->ir_base[insn->op2].op1)) {
 			return ir_try_promote_induction_var_ext(ctx, ext_ref, insn->op2, ref);
 		}
+	} else if (insn->op == IR_COND
+		&& IR_IS_CONST_REF(insn->op2)
+		&& IR_IS_CONST_REF(insn->op3)
+		&& ctx->use_lists[ref].count == 1) {
+		IR_ASSERT(!IR_IS_SYM_CONST(ctx->ir_base[insn->op2].op));
+		IR_ASSERT(!IR_IS_SYM_CONST(ctx->ir_base[insn->op3].op));
+
+		ir_op op = ctx->ir_base[ext_ref].op;
+		ir_type type = ctx->ir_base[ext_ref].type;
+
+		insn->type = type;
+		insn->op2 = ir_ext_const(ctx, &ctx->ir_base[insn->op2], op, type);;
+		insn->op3 = ir_ext_const(ctx, &ctx->ir_base[insn->op3], op, type);;
+		ir_iter_replace_insn(ctx, ext_ref, ref);
 	}

 	return 0;
@@ -2837,6 +2851,8 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re
 					MAKE_NOP(end2);   CLEAR_USES(end2_ref);
 					MAKE_NOP(merge);  CLEAR_USES(merge_ref);

+					ir_bitqueue_add(ctx->iter_worklist, ref);
+					ir_iter_add_uses(ctx, ref, ctx->iter_worklist);
 					if (ctx->ir_base[next->op1].op == IR_BEGIN || ctx->ir_base[next->op1].op == IR_MERGE) {
 						ir_bitqueue_add(ctx->iter_worklist, next->op1);
 					}
@@ -2928,6 +2944,8 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re
 					MAKE_NOP(merge);  CLEAR_USES(merge_ref);
 					MAKE_NOP(&ctx->ir_base[neg_ref]); CLEAR_USES(neg_ref);

+					ir_bitqueue_add(ctx->iter_worklist, ref);
+					ir_iter_add_uses(ctx, ref, ctx->iter_worklist);
 					if (ctx->ir_base[next->op1].op == IR_BEGIN || ctx->ir_base[next->op1].op == IR_MERGE) {
 						ir_bitqueue_add(ctx->iter_worklist, next->op1);
 					}
@@ -2997,6 +3015,7 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re
 					MAKE_NOP(merge);  CLEAR_USES(merge_ref);

 					ir_bitqueue_add(ctx->iter_worklist, ref);
+					ir_iter_add_uses(ctx, ref, ctx->iter_worklist);
 					if (ctx->ir_base[next->op1].op == IR_BEGIN || ctx->ir_base[next->op1].op == IR_MERGE) {
 						ir_bitqueue_add(ctx->iter_worklist, next->op1);
 					}
diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc
index faa14c0cfd1..5ce20343bd0 100644
--- a/ext/opcache/jit/ir/ir_x86.dasc
+++ b/ext/opcache/jit/ir/ir_x86.dasc
@@ -685,6 +685,11 @@ IR_ALWAYS_INLINE ir_mem IR_MEM(ir_reg base, int32_t offset, ir_reg index, int32_
 ||	}
 |.endmacro

+/* Zero a register. A 32-bit xor has a shorter encoding and also clears the upper 32 bits. */
+|.macro ASM_REG_ZERO, type, reg
+|	xor Rd(reg), Rd(reg)
+|.endmacro
+
 |.macro ASM_REG_REG_OP2, op, type, op1, op2
 ||	switch (ir_type_size[type]) {
 || 		default:
@@ -1851,9 +1856,10 @@ int ir_get_target_constraints(ir_ctx *ctx, ir_ref ref, ir_target_constraints *co
 			break;
 		case IR_SHIFT:
 			if (rule & IR_FUSED) {
-				flags = IR_OP2_MUST_BE_IN_REG;
+				flags = IR_OP2_MUST_BE_IN_REG | IR_OP2_HINT_NEEDS_HOLE;
 			} else {
-				flags = IR_DEF_REUSES_OP1_REG | IR_DEF_CONFLICTS_WITH_INPUT_REGS | IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG | IR_OP2_SHOULD_BE_IN_REG;
+				flags = IR_DEF_REUSES_OP1_REG | IR_DEF_CONFLICTS_WITH_INPUT_REGS | IR_USE_MUST_BE_IN_REG |
+					IR_OP1_SHOULD_BE_IN_REG | IR_OP2_SHOULD_BE_IN_REG | IR_OP2_HINT_NEEDS_HOLE;
 			}
 			constraints->hints[1] = IR_REG_NONE;
 			constraints->hints[2] = IR_REG_RCX;
@@ -3268,6 +3274,15 @@ static bool ir_match_has_flags_deps(ir_ctx *ctx, ir_ref ref, ir_ref root)
 	return pos != ref;
 }

+static uint32_t ir_match_fuse_load_const_addr(ir_ctx *ctx, ir_ref ref, const ir_insn *addr_insn)
+{
+	if (ir_may_fuse_addr(ctx, addr_insn)) {
+		return IR_FUSED | IR_SIMPLE | IR_LOAD;
+	}
+	/* The address doesn't fit into the displacement. Load it into a temporary register. */
+	return IR_FUSED | (IR_IS_TYPE_INT(ctx->ir_base[ref].type) ? IR_LOAD_INT : IR_LOAD_FP);
+}
+
 static void ir_match_fuse_load(ir_ctx *ctx, ir_ref ref, ir_ref root)
 {
 	if (ir_in_same_block(ctx, ref) &&
@@ -3279,10 +3294,8 @@ static void ir_match_fuse_load(ir_ctx *ctx, ir_ref ref, ir_ref root)
 			ir_insn *addr_insn = &ctx->ir_base[addr_ref];

 			if (IR_IS_CONST_REF(addr_ref)) {
-				if (ir_may_fuse_addr(ctx, addr_insn)) {
-					ctx->rules[ref] = IR_FUSED | IR_SIMPLE | IR_LOAD;
-					return;
-				}
+				ctx->rules[ref] = ir_match_fuse_load_const_addr(ctx, ref, addr_insn);
+				return;
 			} else if (addr_insn->op == IR_TLS_ADDR) {
 				// TODO: try to fuse static TLS addr ???
 				return;
@@ -3307,10 +3320,8 @@ static bool ir_match_try_fuse_load(ir_ctx *ctx, ir_ref ref, ir_ref root)
 			ir_insn *addr_insn = &ctx->ir_base[addr_ref];

 			if (IR_IS_CONST_REF(addr_ref)) {
-				if (ir_may_fuse_addr(ctx, addr_insn)) {
-					ctx->rules[ref] = IR_FUSED | IR_SIMPLE | IR_LOAD;
-					return 1;
-				}
+				ctx->rules[ref] = ir_match_fuse_load_const_addr(ctx, ref, addr_insn);
+				return 1;
 			} else if (addr_insn->op == IR_TLS_ADDR) {
 				// TODO: try to fuse static TLS addr ???
 				return 0;
@@ -5348,7 +5359,11 @@ static void ir_match_insn2(ir_ctx *ctx, ir_ref ref, uint32_t rule)
 	if (rule == IR_LEA_IB) {
 		if (!ir_match_try_revert_lea_to_add(ctx, ref) && !(ctx->flags & IR_OPT_CODEGEN)) {
 			/* revert to ADD to avoid extra register spill load with -O0 */
-			ctx->rules[ref] = IR_BINOP_INT | IR_MAY_SWAP;
+			ir_insn *insn = &ctx->ir_base[ref];
+
+			if (ctx->ir_base[insn->op1].op != IR_ALLOCA && ctx->ir_base[insn->op2].op != IR_ALLOCA) {
+				ctx->rules[ref] = IR_BINOP_INT | IR_MAY_SWAP;
+			}
 		}
 	}
 }
@@ -5458,7 +5473,7 @@ static void ir_emit_load_imm_int(ir_ctx *ctx, ir_type type, ir_reg reg, int64_t

 	IR_ASSERT(IR_IS_TYPE_INT(type));
 	if (val == 0) {
-		|	ASM_REG_REG_OP xor, type, reg, reg
+		|	ASM_REG_ZERO type, reg
 	} else {
 		ir_emit_mov_imm_int(ctx, type, reg, val);
 	}
@@ -8234,7 +8249,7 @@ static void ir_emit_mul_div_mod(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 			if (ir_type_size[type] == 1) {
 				|	movzx ax, al
 			} else {
-				|	ASM_REG_REG_OP xor, type, IR_REG_RDX, IR_REG_RDX
+				|	ASM_REG_ZERO type, IR_REG_RDX
 			}
 			if (op2_reg != IR_REG_NONE) {
 				|	ASM_REG_OP div, type, op2_reg
@@ -8801,7 +8816,7 @@ static void ir_emit_cmp_int(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 	if (IR_IS_CONST_REF(op2) && !IR_IS_SYM_CONST(ctx->ir_base[op2].op) && ctx->ir_base[op2].val.u64 == 0) {
 		if (op == IR_ULT) {
 			/* always false */
-			|	xor Ra(def_reg), Ra(def_reg)
+			|	xor Rd(def_reg), Rd(def_reg)
 			if (IR_REG_SPILLED(ctx->regs[def][0])) {
 				ir_emit_store(ctx, insn->type, def, def_reg);
 			}
@@ -14572,7 +14587,7 @@ static void ir_emit_cmp_i64(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 	if (IR_IS_CONST_REF(op2) && !IR_IS_SYM_CONST(ctx->ir_base[op2].op) && ctx->ir_base[op2].val.u64 == 0) {
 		if (op == IR_ULT) {
 			/* always false */
-			|	xor Ra(def_reg), Ra(def_reg)
+			|	xor Rd(def_reg), Rd(def_reg)
 			if (IR_REG_SPILLED(ctx->regs[def][0])) {
 				ir_emit_store(ctx, insn->type, def, def_reg);
 			}
@@ -22205,7 +22220,7 @@ static void ir_emit_vector_binop_expand(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 						} else if (element_size == 1) {
 							|	movzx ax, al
 						} else {
-							|	ASM_REG_REG_OP xor, element_type, IR_REG_RDX, IR_REG_RDX
+							|	ASM_REG_ZERO element_type, IR_REG_RDX
 						}
 						if (IR_IS_TYPE_SIGNED(element_type)) {
 							|	ASM_REG_OP idiv, element_type, tmp2_reg
@@ -22652,7 +22667,7 @@ static void ir_emit_vector_binop_expand(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 					} else if (element_size == 1) {
 						|	movzx ax, al
 					} else {
-						|	ASM_REG_REG_OP xor, element_type, IR_REG_RDX, IR_REG_RDX
+						|	ASM_REG_ZERO element_type, IR_REG_RDX
 					}
 					if (IR_IS_TYPE_SIGNED(element_type)) {
 						|	ASM_MEM_OP idiv, element_type, mem
@@ -25988,8 +26003,10 @@ void ir_fix_stack_frame(ir_ctx *ctx)

 #ifdef IR_TARGET_X86
 	if (ctx->flags2 & IR_HAS_MEMCPY) {
-		IR_REGSET_INCL(ctx->used_preserved_regs, IR_REG_RSI);
-		IR_REGSET_INCL(ctx->used_preserved_regs, IR_REG_RDI);
+		ir_regset tmp = ctx->used_preserved_regs;
+		IR_REGSET_INCL(tmp, IR_REG_RSI);
+		IR_REGSET_INCL(tmp, IR_REG_RDI);
+		ctx->used_preserved_regs = tmp;
 	}
 #endif