Skip to content

Commit e9968ea

Browse files
alexmalyshevmeta-codesync[bot]
authored andcommitted
Fuse compare + CondBranch into cmp + jcc
Summary: When a CondBranch's input is produced by a compare instruction in the same basic block with no intervening flag-clobbering instructions, fuse them into a direct cmp + jcc sequence instead of the previous cmp + setcc + test + je sequence. The post-register-allocation rewrite in doRewriteCondBranch now walks backwards to find a fusible compare using the existing compareToBranchCC() infrastructure. When found, it skips emitting the test instruction and uses the appropriate conditional branch opcode (e.g., jb for unsigned less-than) directly from the compare's flags. The setcc materialization from the compare is still emitted but becomes dead code when the CBool result has no other uses. Eliminating this dead setcc by converting the compare to kCmp is left as a future improvement — it requires liveness information from the register allocator to verify the output register isn't live-out from the block. Reviewed By: yoney Differential Revision: D96956101 fbshipit-source-id: 2cde9ca7b9834d2691c56e38ad8d4c5f24ed1622
1 parent 5aa7aca commit e9968ea

1 file changed

Lines changed: 68 additions & 10 deletions

File tree

cinderx/Jit/lir/postalloc.cpp

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -542,29 +542,87 @@ RewriteResult rewriteLoadInstrs(instr_iter_t instr_iter) {
542542
return kChanged;
543543
}
544544

545+
// Try to find a compare instruction that defines the CondBranch's input
546+
// register, with no flag-clobbering instructions in between.
547+
Instruction* findFusibleCompare(
548+
instr_iter_t cond_branch_iter,
549+
BasicBlock* block) {
550+
auto cond_branch = cond_branch_iter->get();
551+
auto input_reg = cond_branch->getInput(0)->getPhyRegister();
552+
553+
// Walk backwards from the CondBranch looking for the defining compare.
554+
auto& instrs = block->instructions();
555+
for (auto it = cond_branch_iter; it != instrs.begin();) {
556+
--it;
557+
auto* candidate = it->get();
558+
559+
// Check if this is a compare that writes to our input register.
560+
if (candidate->isCompare() && candidate->output()->isReg() &&
561+
candidate->output()->getPhyRegister() == input_reg) {
562+
return candidate;
563+
}
564+
565+
// If this instruction clobbers flags, we can't fuse past it.
566+
auto effects =
567+
InstrProperty::getProperties(candidate->opcode()).flag_effects;
568+
if (effects == FlagEffects::kInvalidate) {
569+
return nullptr;
570+
}
571+
572+
// If this instruction sets flags (but isn't our compare), we can't
573+
// use the flags from an earlier compare.
574+
if (effects == FlagEffects::kSet) {
575+
return nullptr;
576+
}
577+
578+
// If this instruction writes to the same register as the CondBranch, we
579+
// can't fuse past it.
580+
auto output = candidate->output();
581+
if (output->isReg() && output->getPhyRegister() == input_reg) {
582+
return nullptr;
583+
}
584+
}
585+
return nullptr;
586+
}
587+
545588
// Convert CondBranch to Test and BranchCC instructions.
546589
void doRewriteCondBranch(instr_iter_t instr_iter, BasicBlock* next_block) {
547590
auto instr = instr_iter->get();
548591

549592
auto input = instr->getInput(0);
550593
auto block = instr->basicblock();
551594

552-
// insert test Reg, Reg instruction
553-
auto size = input->dataType();
554-
block->allocateInstrBefore(
555-
instr_iter,
556-
Instruction::kTest,
557-
PhyReg(input->getPhyRegister(), size),
558-
PhyReg(input->getPhyRegister(), size));
559-
560-
// convert the current CondBranch instruction to a BranchCC instruction
561595
auto true_block = block->getTrueSuccessor();
562596
auto false_block = block->getFalseSuccessor();
563597

564598
BasicBlock* target_block = nullptr;
565599
BasicBlock* fallthrough_block = nullptr;
566600

567-
auto opcode = Instruction::kBranchNZ;
601+
// Try to fuse with a preceding compare instruction. If we find one, we
602+
// can use its flags directly (cmp + jcc) instead of setcc + test + je.
603+
Instruction* compare = findFusibleCompare(instr_iter, block);
604+
Instruction::Opcode opcode;
605+
if (compare != nullptr) {
606+
// Use the compare's condition directly for the branch.
607+
opcode = Instruction::compareToBranchCC(compare->opcode());
608+
// If no instruction between the compare and the CondBranch reads the
609+
// compare's output register, we could convert to kCmp to skip emitting the
610+
// (now dead) setcc. However, the register allocator may have assigned the
611+
// compare's output register to overlap with a value that is live-out from
612+
// the block. Converting to kCmp would leave that register unwritten,
613+
// causing the live-out value to be stale. A proper fix requires liveness
614+
// information from the register allocator.
615+
} else {
616+
// No fusible compare found. Insert test Reg, Reg instruction.
617+
auto size = input->dataType();
618+
block->allocateInstrBefore(
619+
instr_iter,
620+
Instruction::kTest,
621+
PhyReg(input->getPhyRegister(), size),
622+
PhyReg(input->getPhyRegister(), size));
623+
opcode = Instruction::kBranchNZ;
624+
}
625+
568626
if (true_block == next_block) {
569627
opcode = Instruction::negateBranchCC(opcode);
570628
target_block = false_block;

0 commit comments

Comments
 (0)