Skip to content

Commit f10742a

Browse files
alexmalyshevmeta-codesync[bot]
authored andcommitted
Stop widening moves from blocking move optimization
Summary: Follow-up to keeping 32 -> 64 bit zero-extends as `MovZX`. Two places still assumed that width of zero-extend was a plain `Move`: - `emitBoxPrimitive` hand-rolled the unsigned 32-bit case as a 32-bit `Move`, the last spot outside codegen that spelled a zero-extend as something other than what it is. It now emits `MovZX`, matching the signed branch directly above it. The encoding is unchanged, `MovZX` from a 32-bit source lowers to exactly that 32-bit move. - `optimizeMoveSequence` caches register-to-spill-slot copies so a later read of the slot can use the register instead, and it dropped the whole cache on any instruction that was not a Move, Push or Pop. A widening move only writes its register output, so it now invalidates just that location. Before this, a `MovZX` sitting in the middle of a run of argument copies would throw away everything learned before it. `optimizeMoveInstrs` is deliberately left alone. It drops a `Move` whose source and destination are the same location, which is not valid for a widening move, the part of the destination the source does not cover still has to be written. The 32-bit return value in `emitBoxPrimitive` is exactly that case, hence the comment added there. Reviewed By: yoney Differential Revision: D116031677 fbshipit-source-id: 8b5234cd3575c2a987d4def76e4874d5ac0a3b9e
1 parent 252f688 commit f10742a

3 files changed

Lines changed: 58 additions & 17 deletions

File tree

cinderx/Jit/lir/generator.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,7 +1196,6 @@ void GenerateBoxedReturnWrapperBlocks(
11961196
OutPhyReg{arg0, DataType::k64bit},
11971197
PhyReg{ret32, DataType::k32bit});
11981198
} else if (return_type <= TCUInt32) {
1199-
// Unsigned 32->64: a 32-bit move zero-extends into the full register.
12001199
// Skippable when the registers already match, except on aarch64 pre-3.14:
12011200
// there arg0 == ret64, but PyLong_FromSize_t reads all 64 bits and AAPCS64
12021201
// leaves the upper half of a 32-bit return unspecified, so we must still
@@ -1205,9 +1204,9 @@ void GenerateBoxedReturnWrapperBlocks(
12051204
PY_VERSION_HEX < 0x030E0000 && kBuildArch == Arch::kAarch64;
12061205
if (arg0 != ret64 || needs_extend) {
12071206
box_block->allocateInstr(
1208-
Opcode::kMove,
1207+
Opcode::kMovZX,
12091208
nullptr,
1210-
OutPhyReg{arg0, DataType::k32bit},
1209+
OutPhyReg{arg0, DataType::k64bit},
12111210
PhyReg{ret32, DataType::k32bit});
12121211
}
12131212
} else if (return_type <= (TCInt64 | TCUInt64) && arg0 != ret64) {

cinderx/Jit/lir/postalloc.cpp

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,9 @@ RewriteResult rewriteBranchInstrs(Function* function) {
636636
RewriteResult optimizeMoveInstrs(instr_iter_t instr_iter) {
637637
auto instr = instr_iter->get();
638638
auto instr_opcode = instr->opcode();
639+
// Deliberately not MovZX/MovSX: a widening move still has to write the part
640+
// of the destination that the source does not cover, even when the two name
641+
// the same register.
639642
if (instr_opcode != Opcode::kMove) {
640643
return kUnchanged;
641644
}
@@ -1560,19 +1563,16 @@ RewriteResult optimizeMoveSequence(BasicBlock* basicblock) {
15601563
}
15611564
};
15621565

1563-
if (instr->isMove() || instr->isPush() || instr->isPop()) {
1564-
if (instr->isMove()) {
1565-
Operand* out = instr->output();
1566-
Operand* in = instr->getInput(0);
1567-
if (out->isStack() && in->isReg()) {
1568-
registerMemoryMoves.addRegisterToMemoryMove(
1569-
in->getPhyRegister(), out->getStackSlot(), instr_iter);
1570-
} else {
1571-
invalidateOperand(out);
1572-
}
1573-
} else if (instr->isPop()) {
1574-
auto opnd = instr->output();
1575-
invalidateOperand(opnd);
1566+
if (instr->isMove() || instr->isPush() || instr->isPop() ||
1567+
instr->isMovZX() || instr->isMovSX()) {
1568+
Operand* out = instr->output();
1569+
if (instr->isMove() && out->isStack() && instr->getInput(0)->isReg()) {
1570+
registerMemoryMoves.addRegisterToMemoryMove(
1571+
instr->getInput(0)->getPhyRegister(),
1572+
out->getStackSlot(),
1573+
instr_iter);
1574+
} else {
1575+
invalidateOperand(out);
15761576
}
15771577
} else {
15781578
// TODO: for now, we always clear the cache when we hit an instruction

cinderx/RuntimeTests/lir_postalloc_test.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,6 @@ BB %1 - preds: %0 - section: .coldtext
230230
ASSERT_TRUE(verifyPostRegAllocInvariants(parsed_func.get(), std::cout));
231231
}
232232

233-
#if defined(CINDER_AARCH64)
234233
// Helper to collect instructions from a block into a vector for easy indexing.
235234
static std::vector<Instruction*> collectInstrs(BasicBlock& bb) {
236235
std::vector<Instruction*> result;
@@ -240,6 +239,49 @@ static std::vector<Instruction*> collectInstrs(BasicBlock& bb) {
240239
return result;
241240
}
242241

242+
// optimizeMoveSequence forwards a spill slot back to the register it was
243+
// copied from. A widening move in between only writes its own output, so it
244+
// must not throw away the rest of what the pass knows.
245+
TEST_F(LIRPostAllocRewriteTest, MoveSequenceLooksPastWideningMoves) {
246+
constexpr PhyLocation kSpilled = ARGUMENT_REGS[0];
247+
constexpr PhyLocation kWidenOut = ARGUMENT_REGS[1];
248+
constexpr PhyLocation kReloaded = ARGUMENT_REGS[2];
249+
constexpr PhyLocation kWidenIn = ARGUMENT_REGS[3];
250+
constexpr PhyLocation kSlot{-16, 64};
251+
252+
Function func;
253+
auto* bb = func.allocateBasicBlock();
254+
255+
bb->allocateInstr(
256+
Opcode::kMove,
257+
nullptr,
258+
OutStk{kSlot, DataType::k64bit},
259+
PhyReg{kSpilled, DataType::k64bit});
260+
bb->allocateInstr(
261+
Opcode::kMovZX,
262+
nullptr,
263+
OutPhyReg{kWidenOut, DataType::k64bit},
264+
PhyReg{kWidenIn, DataType::k32bit});
265+
bb->allocateInstr(
266+
Opcode::kMove,
267+
nullptr,
268+
OutPhyReg{kReloaded, DataType::k64bit},
269+
Stk{kSlot, DataType::k64bit});
270+
271+
Environ env;
272+
PostRegAllocRewrite rewrite(&func, &env);
273+
rewrite.run();
274+
275+
auto instrs = collectInstrs(*bb);
276+
ASSERT_EQ(instrs.size(), 3);
277+
278+
// The reload reads the register the spill came from rather than the slot.
279+
EXPECT_TRUE(instrs[2]->isMove());
280+
EXPECT_TRUE(instrs[2]->getInput(0)->isReg());
281+
EXPECT_EQ(instrs[2]->getInput(0)->getPhyRegister(), kSpilled);
282+
}
283+
284+
#if defined(CINDER_AARCH64)
243285
// kAdd with one register input and one stack input should insert a Move from
244286
// stack to GP scratch register before the Add, then rewrite the Add's stack
245287
// input to use the scratch register.

0 commit comments

Comments
 (0)