Skip to content

[AArch64] Refactor redundant PTEST optimisations (NFC) #87802

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 93 additions & 78 deletions llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1355,48 +1355,52 @@ static bool areCFlagsAccessedBetweenInstrs(
return false;
}

/// optimizePTestInstr - Attempt to remove a ptest of a predicate-generating
/// operation which could set the flags in an identical manner
bool AArch64InstrInfo::optimizePTestInstr(
MachineInstr *PTest, unsigned MaskReg, unsigned PredReg,
const MachineRegisterInfo *MRI) const {
auto *Mask = MRI->getUniqueVRegDef(MaskReg);
auto *Pred = MRI->getUniqueVRegDef(PredReg);
auto NewOp = Pred->getOpcode();
bool OpChanged = false;

std::optional<unsigned>
AArch64InstrInfo::canRemovePTestInstr(MachineInstr *PTest, MachineInstr *Mask,
MachineInstr *Pred,
const MachineRegisterInfo *MRI) const {
unsigned MaskOpcode = Mask->getOpcode();
unsigned PredOpcode = Pred->getOpcode();
bool PredIsPTestLike = isPTestLikeOpcode(PredOpcode);
bool PredIsWhileLike = isWhileOpcode(PredOpcode);

if (isPTrueOpcode(MaskOpcode) && (PredIsPTestLike || PredIsWhileLike) &&
getElementSizeForOpcode(MaskOpcode) ==
getElementSizeForOpcode(PredOpcode) &&
Mask->getOperand(1).getImm() == 31) {
if (PredIsWhileLike) {
// For PTEST(PG, PG), PTEST is redundant when PG is the result of a WHILEcc
// instruction and the condition is "any" since WHILcc does an implicit
// PTEST(ALL, PG) check and PG is always a subset of ALL.
if ((Mask == Pred) && PTest->getOpcode() == AArch64::PTEST_PP_ANY)
return PredOpcode;

// For PTEST(PTRUE_ALL, WHILE), if the element size matches, the PTEST is
// redundant since WHILE performs an implicit PTEST with an all active
// mask. Must be an all active predicate of matching element size.
// mask.
if (isPTrueOpcode(MaskOpcode) && Mask->getOperand(1).getImm() == 31 &&
getElementSizeForOpcode(MaskOpcode) ==
getElementSizeForOpcode(PredOpcode))
return PredOpcode;

return {};
}

if (PredIsPTestLike) {
// For PTEST(PG, PG), PTEST is redundant when PG is the result of an
// instruction that sets the flags as PTEST would and the condition is
// "any" since PG is always a subset of the governing predicate of the
// ptest-like instruction.
if ((Mask == Pred) && PTest->getOpcode() == AArch64::PTEST_PP_ANY)
return PredOpcode;

// For PTEST(PTRUE_ALL, PTEST_LIKE), the PTEST is redundant if the
// PTEST_LIKE instruction uses the same all active mask and the element
// size matches. If the PTEST has a condition of any then it is always
// redundant.
if (PredIsPTestLike) {
// the element size matches and either the PTEST_LIKE instruction uses
// the same all active mask or the condition is "any".
if (isPTrueOpcode(MaskOpcode) && Mask->getOperand(1).getImm() == 31 &&
getElementSizeForOpcode(MaskOpcode) ==
getElementSizeForOpcode(PredOpcode)) {
auto PTestLikeMask = MRI->getUniqueVRegDef(Pred->getOperand(1).getReg());
if (Mask != PTestLikeMask && PTest->getOpcode() != AArch64::PTEST_PP_ANY)
return false;
if (Mask == PTestLikeMask || PTest->getOpcode() == AArch64::PTEST_PP_ANY)
return PredOpcode;
}

// Fallthough to simply remove the PTEST.
} else if ((Mask == Pred) && (PredIsPTestLike || PredIsWhileLike) &&
PTest->getOpcode() == AArch64::PTEST_PP_ANY) {
// For PTEST(PG, PG), PTEST is redundant when PG is the result of an
// instruction that sets the flags as PTEST would. This is only valid when
// the condition is any.

// Fallthough to simply remove the PTEST.
} else if (PredIsPTestLike) {
// For PTEST(PG, PTEST_LIKE(PG, ...)), the PTEST is redundant since the
// flags are set based on the same mask 'PG', but PTEST_LIKE must operate
// on 8-bit predicates like the PTEST. Otherwise, for instructions like
Expand All @@ -1421,55 +1425,66 @@ bool AArch64InstrInfo::optimizePTestInstr(
// identical regardless of element size.
auto PTestLikeMask = MRI->getUniqueVRegDef(Pred->getOperand(1).getReg());
uint64_t PredElementSize = getElementSizeForOpcode(PredOpcode);
if ((Mask != PTestLikeMask) ||
(PredElementSize != AArch64::ElementSizeB &&
PTest->getOpcode() != AArch64::PTEST_PP_ANY))
return false;
if (Mask == PTestLikeMask && (PredElementSize == AArch64::ElementSizeB ||
PTest->getOpcode() == AArch64::PTEST_PP_ANY))
return PredOpcode;

// Fallthough to simply remove the PTEST.
} else {
// If OP in PTEST(PG, OP(PG, ...)) has a flag-setting variant change the
// opcode so the PTEST becomes redundant.
switch (PredOpcode) {
case AArch64::AND_PPzPP:
case AArch64::BIC_PPzPP:
case AArch64::EOR_PPzPP:
case AArch64::NAND_PPzPP:
case AArch64::NOR_PPzPP:
case AArch64::ORN_PPzPP:
case AArch64::ORR_PPzPP:
case AArch64::BRKA_PPzP:
case AArch64::BRKPA_PPzPP:
case AArch64::BRKB_PPzP:
case AArch64::BRKPB_PPzPP:
case AArch64::RDFFR_PPz: {
// Check to see if our mask is the same. If not the resulting flag bits
// may be different and we can't remove the ptest.
auto *PredMask = MRI->getUniqueVRegDef(Pred->getOperand(1).getReg());
if (Mask != PredMask)
return false;
break;
}
case AArch64::BRKN_PPzP: {
// BRKN uses an all active implicit mask to set flags unlike the other
// flag-setting instructions.
// PTEST(PTRUE_B(31), BRKN(PG, A, B)) -> BRKNS(PG, A, B).
if ((MaskOpcode != AArch64::PTRUE_B) ||
(Mask->getOperand(1).getImm() != 31))
return false;
break;
}
case AArch64::PTRUE_B:
// PTEST(OP=PTRUE_B(A), OP) -> PTRUES_B(A)
break;
default:
// Bail out if we don't recognize the input
return false;
}
return {};
}

NewOp = convertToFlagSettingOpc(PredOpcode);
OpChanged = true;
// If OP in PTEST(PG, OP(PG, ...)) has a flag-setting variant change the
// opcode so the PTEST becomes redundant.
switch (PredOpcode) {
case AArch64::AND_PPzPP:
case AArch64::BIC_PPzPP:
case AArch64::EOR_PPzPP:
case AArch64::NAND_PPzPP:
case AArch64::NOR_PPzPP:
case AArch64::ORN_PPzPP:
case AArch64::ORR_PPzPP:
case AArch64::BRKA_PPzP:
case AArch64::BRKPA_PPzPP:
case AArch64::BRKB_PPzP:
case AArch64::BRKPB_PPzPP:
case AArch64::RDFFR_PPz: {
// Check to see if our mask is the same. If not the resulting flag bits
// may be different and we can't remove the ptest.
auto *PredMask = MRI->getUniqueVRegDef(Pred->getOperand(1).getReg());
if (Mask != PredMask)
return {};
break;
}
case AArch64::BRKN_PPzP: {
// BRKN uses an all active implicit mask to set flags unlike the other
// flag-setting instructions.
// PTEST(PTRUE_B(31), BRKN(PG, A, B)) -> BRKNS(PG, A, B).
if ((MaskOpcode != AArch64::PTRUE_B) ||
(Mask->getOperand(1).getImm() != 31))
return {};
break;
}
case AArch64::PTRUE_B:
// PTEST(OP=PTRUE_B(A), OP) -> PTRUES_B(A)
break;
default:
// Bail out if we don't recognize the input
return {};
}

return convertToFlagSettingOpc(PredOpcode);
}

/// optimizePTestInstr - Attempt to remove a ptest of a predicate-generating
/// operation which could set the flags in an identical manner
bool AArch64InstrInfo::optimizePTestInstr(
MachineInstr *PTest, unsigned MaskReg, unsigned PredReg,
const MachineRegisterInfo *MRI) const {
auto *Mask = MRI->getUniqueVRegDef(MaskReg);
auto *Pred = MRI->getUniqueVRegDef(PredReg);
unsigned PredOpcode = Pred->getOpcode();
auto NewOp = canRemovePTestInstr(PTest, Mask, Pred, MRI);
if (!NewOp)
return false;

const TargetRegisterInfo *TRI = &getRegisterInfo();

Expand All @@ -1482,9 +1497,9 @@ bool AArch64InstrInfo::optimizePTestInstr(
// as they are prior to PTEST. Sometimes this requires the tested PTEST
// operand to be replaced with an equivalent instruction that also sets the
// flags.
Pred->setDesc(get(NewOp));
PTest->eraseFromParent();
if (OpChanged) {
if (*NewOp != PredOpcode) {
Pred->setDesc(get(*NewOp));
bool succeeded = UpdateOperandRegClass(*Pred);
(void)succeeded;
assert(succeeded && "Operands have incompatible register classes!");
Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/Target/AArch64/AArch64InstrInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,9 @@ class AArch64InstrInfo final : public AArch64GenInstrInfo {
bool optimizePTestInstr(MachineInstr *PTest, unsigned MaskReg,
unsigned PredReg,
const MachineRegisterInfo *MRI) const;
std::optional<unsigned>
canRemovePTestInstr(MachineInstr *PTest, MachineInstr *Mask,
MachineInstr *Pred, const MachineRegisterInfo *MRI) const;
};

struct UsedNZCV {
Expand Down
Loading