-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[RISCV] Handle recurrences in RISCVVLOptimizer #151285
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
Changes from 3 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
9aba342
Precommit tests
lukel97 ea2b861
[RISCV] Handle recurrences in RISCVVLOptimizer
lukel97 9f24fe7
Link to talk in header comment
lukel97 9b61df6
Fix test name
lukel97 a78cc60
Merge branch 'main' of github.com:llvm/llvm-project into vloptimizer/…
lukel97 257ed3c
Reword a comment to be more clear
lukel97 4d80e45
Merge branch 'main' of github.com:llvm/llvm-project into vloptimizer/…
lukel97 5465920
Merge branch 'main' of github.com:llvm/llvm-project into vloptimizer/…
lukel97 a31269c
Merge branch 'main' of github.com:llvm/llvm-project into vloptimizer/…
lukel97 97a12b1
Merge branch 'main' of github.com:llvm/llvm-project into vloptimizer/…
lukel97 8f75db7
Make vector_uses just a static function
lukel97 d550870
clang-format
lukel97 dc0ca0e
Remove debug code
lukel97 1dd36a4
Merge branch 'main' into vloptimizer/dataflow-analysis
lukel97 ebce546
Add vleff recurrence test case
lukel97 cadf393
Move max into DemandedVL
lukel97 0ef2baf
Move isVirtualVec into virtual_vec_uses method, avoid pointer chasing
lukel97 1068f3a
Rename Worklist in checkUsers to avoid shadowing
lukel97 62635d5
Merge branch 'main' into vloptimizer/dataflow-analysis
lukel97 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,9 +10,19 @@ | |
| // instructions are inserted. | ||
| // | ||
| // The purpose of this optimization is to make the VL argument, for instructions | ||
| // that have a VL argument, as small as possible. This is implemented by | ||
| // visiting each instruction in reverse order and checking that if it has a VL | ||
| // argument, whether the VL can be reduced. | ||
| // that have a VL argument, as small as possible. | ||
| // | ||
| // This is split into a sparse dataflow analysis where we determine what VL is | ||
| // demanded by each instruction first, and then afterwards try to reduce the VL | ||
| // of each instruction if it demands less than its VL operand. | ||
| // | ||
| // The analysis is explained in more detail in the 2025 EuroLLVM Developers' | ||
| // Meeting talk "Accidental Dataflow Analysis: Extending the RISC-V VL | ||
| // Optimizer", which is available on YouTube at | ||
| // https://www.youtube.com/watch?v=Mfb5fRSdJAc | ||
| // | ||
| // The slides for the talk are available at | ||
| // https://llvm.org/devmtg/2025-04/slides/technical_talk/lau_accidental_dataflow.pdf | ||
| // | ||
| //===---------------------------------------------------------------------===// | ||
|
|
||
|
|
@@ -30,6 +40,27 @@ using namespace llvm; | |
|
|
||
| namespace { | ||
|
|
||
| /// Wrapper around MachineOperand that defaults to immediate 0. | ||
| struct DemandedVL { | ||
| MachineOperand VL; | ||
| DemandedVL() : VL(MachineOperand::CreateImm(0)) {} | ||
| DemandedVL(MachineOperand VL) : VL(VL) {} | ||
| static DemandedVL vlmax() { | ||
| return DemandedVL(MachineOperand::CreateImm(RISCV::VLMaxSentinel)); | ||
| } | ||
| bool operator!=(const DemandedVL &Other) const { | ||
| return !VL.isIdenticalTo(Other.VL); | ||
| } | ||
| }; | ||
|
|
||
| static DemandedVL max(const DemandedVL &LHS, const DemandedVL &RHS) { | ||
| if (RISCV::isVLKnownLE(LHS.VL, RHS.VL)) | ||
| return RHS; | ||
| if (RISCV::isVLKnownLE(RHS.VL, LHS.VL)) | ||
| return LHS; | ||
| return DemandedVL::vlmax(); | ||
wangpc-pp marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| class RISCVVLOptimizer : public MachineFunctionPass { | ||
| const MachineRegisterInfo *MRI; | ||
| const MachineDominatorTree *MDT; | ||
|
|
@@ -51,17 +82,26 @@ class RISCVVLOptimizer : public MachineFunctionPass { | |
| StringRef getPassName() const override { return PASS_NAME; } | ||
|
|
||
| private: | ||
| std::optional<MachineOperand> | ||
| getMinimumVLForUser(const MachineOperand &UserOp) const; | ||
| /// Returns the largest common VL MachineOperand that may be used to optimize | ||
| /// MI. Returns std::nullopt if it failed to find a suitable VL. | ||
| std::optional<MachineOperand> checkUsers(const MachineInstr &MI) const; | ||
| DemandedVL getMinimumVLForUser(const MachineOperand &UserOp) const; | ||
| /// Returns true if the users of \p MI have compatible EEWs and SEWs. | ||
| bool checkUsers(const MachineInstr &MI) const; | ||
| bool tryReduceVL(MachineInstr &MI) const; | ||
| bool isCandidate(const MachineInstr &MI) const; | ||
| void transfer(const MachineInstr &MI); | ||
|
|
||
| /// Returns all uses of vector virtual registers. | ||
| auto vector_uses(const MachineInstr &MI) const { | ||
| auto Pred = [this](const MachineOperand &MO) -> bool { | ||
| return MO.isReg() && MO.getReg().isVirtual() && | ||
| RISCVRegisterInfo::isRVVRegClass(MRI->getRegClass(MO.getReg())); | ||
| }; | ||
| return make_filter_range(MI.uses(), Pred); | ||
| } | ||
|
|
||
| /// For a given instruction, records what elements of it are demanded by | ||
| /// downstream users. | ||
| DenseMap<const MachineInstr *, std::optional<MachineOperand>> DemandedVLs; | ||
| DenseMap<const MachineInstr *, DemandedVL> DemandedVLs; | ||
| SetVector<const MachineInstr *> Worklist; | ||
| }; | ||
|
|
||
| /// Represents the EMUL and EEW of a MachineOperand. | ||
|
|
@@ -821,6 +861,9 @@ getOperandInfo(const MachineOperand &MO, const MachineRegisterInfo *MRI) { | |
| /// white-list approach simplifies this optimization for instructions that may | ||
| /// have more complex semantics with relation to how it uses VL. | ||
| static bool isSupportedInstr(const MachineInstr &MI) { | ||
| if (MI.isPHI() || MI.isFullCopy()) | ||
| return true; | ||
|
|
||
| const RISCVVPseudosTable::PseudoInfo *RVV = | ||
| RISCVVPseudosTable::getPseudoInfo(MI.getOpcode()); | ||
|
|
||
|
|
@@ -1321,21 +1364,24 @@ bool RISCVVLOptimizer::isCandidate(const MachineInstr &MI) const { | |
| return true; | ||
| } | ||
|
|
||
| std::optional<MachineOperand> | ||
| DemandedVL | ||
| RISCVVLOptimizer::getMinimumVLForUser(const MachineOperand &UserOp) const { | ||
| const MachineInstr &UserMI = *UserOp.getParent(); | ||
| const MCInstrDesc &Desc = UserMI.getDesc(); | ||
|
|
||
| if (UserMI.isPHI() || UserMI.isFullCopy()) | ||
| return DemandedVLs.lookup(&UserMI); | ||
|
|
||
| if (!RISCVII::hasVLOp(Desc.TSFlags) || !RISCVII::hasSEWOp(Desc.TSFlags)) { | ||
| LLVM_DEBUG(dbgs() << " Abort due to lack of VL, assume that" | ||
| " use VLMAX\n"); | ||
| return std::nullopt; | ||
| return DemandedVL::vlmax(); | ||
| } | ||
|
|
||
| if (RISCVII::readsPastVL( | ||
| TII->get(RISCV::getRVVMCOpcode(UserMI.getOpcode())).TSFlags)) { | ||
| LLVM_DEBUG(dbgs() << " Abort because used by unsafe instruction\n"); | ||
| return std::nullopt; | ||
| return DemandedVL::vlmax(); | ||
| } | ||
|
|
||
| unsigned VLOpNum = RISCVII::getVLOpNum(Desc); | ||
|
|
@@ -1349,11 +1395,10 @@ RISCVVLOptimizer::getMinimumVLForUser(const MachineOperand &UserOp) const { | |
| if (UserOp.isTied()) { | ||
| assert(UserOp.getOperandNo() == UserMI.getNumExplicitDefs() && | ||
| RISCVII::isFirstDefTiedToFirstUse(UserMI.getDesc())); | ||
| auto DemandedVL = DemandedVLs.lookup(&UserMI); | ||
| if (!DemandedVL || !RISCV::isVLKnownLE(*DemandedVL, VLOp)) { | ||
| if (!RISCV::isVLKnownLE(DemandedVLs.lookup(&UserMI).VL, VLOp)) { | ||
| LLVM_DEBUG(dbgs() << " Abort because user is passthru in " | ||
| "instruction with demanded tail\n"); | ||
| return std::nullopt; | ||
| return DemandedVL::vlmax(); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1366,18 +1411,16 @@ RISCVVLOptimizer::getMinimumVLForUser(const MachineOperand &UserOp) const { | |
|
|
||
| // If we know the demanded VL of UserMI, then we can reduce the VL it | ||
| // requires. | ||
| if (auto DemandedVL = DemandedVLs.lookup(&UserMI)) { | ||
| assert(isCandidate(UserMI)); | ||
| if (RISCV::isVLKnownLE(*DemandedVL, VLOp)) | ||
| return DemandedVL; | ||
| } | ||
| if (RISCV::isVLKnownLE(DemandedVLs.lookup(&UserMI).VL, VLOp)) | ||
| return DemandedVLs.lookup(&UserMI); | ||
|
|
||
| return VLOp; | ||
| } | ||
|
|
||
| std::optional<MachineOperand> | ||
| RISCVVLOptimizer::checkUsers(const MachineInstr &MI) const { | ||
| std::optional<MachineOperand> CommonVL; | ||
| bool RISCVVLOptimizer::checkUsers(const MachineInstr &MI) const { | ||
| if (MI.isPHI() || MI.isFullCopy()) | ||
| return true; | ||
|
|
||
| SmallSetVector<MachineOperand *, 8> Worklist; | ||
|
||
| SmallPtrSet<const MachineInstr *, 4> PHISeen; | ||
| for (auto &UserOp : MRI->use_operands(MI.getOperand(0).getReg())) | ||
|
|
@@ -1405,23 +1448,9 @@ RISCVVLOptimizer::checkUsers(const MachineInstr &MI) const { | |
| continue; | ||
| } | ||
|
|
||
| auto VLOp = getMinimumVLForUser(UserOp); | ||
| if (!VLOp) | ||
| return std::nullopt; | ||
|
|
||
| // Use the largest VL among all the users. If we cannot determine this | ||
| // statically, then we cannot optimize the VL. | ||
| if (!CommonVL || RISCV::isVLKnownLE(*CommonVL, *VLOp)) { | ||
| CommonVL = *VLOp; | ||
| LLVM_DEBUG(dbgs() << " User VL is: " << VLOp << "\n"); | ||
| } else if (!RISCV::isVLKnownLE(*VLOp, *CommonVL)) { | ||
| LLVM_DEBUG(dbgs() << " Abort because cannot determine a common VL\n"); | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| if (!RISCVII::hasSEWOp(UserMI.getDesc().TSFlags)) { | ||
| LLVM_DEBUG(dbgs() << " Abort due to lack of SEW operand\n"); | ||
| return std::nullopt; | ||
| return false; | ||
| } | ||
|
|
||
| std::optional<OperandInfo> ConsumerInfo = getOperandInfo(UserOp, MRI); | ||
|
|
@@ -1431,7 +1460,7 @@ RISCVVLOptimizer::checkUsers(const MachineInstr &MI) const { | |
| LLVM_DEBUG(dbgs() << " Abort due to unknown operand information.\n"); | ||
| LLVM_DEBUG(dbgs() << " ConsumerInfo is: " << ConsumerInfo << "\n"); | ||
| LLVM_DEBUG(dbgs() << " ProducerInfo is: " << ProducerInfo << "\n"); | ||
| return std::nullopt; | ||
| return false; | ||
| } | ||
|
|
||
| if (!OperandInfo::areCompatible(*ProducerInfo, *ConsumerInfo)) { | ||
|
|
@@ -1440,11 +1469,11 @@ RISCVVLOptimizer::checkUsers(const MachineInstr &MI) const { | |
| << " Abort due to incompatible information for EMUL or EEW.\n"); | ||
| LLVM_DEBUG(dbgs() << " ConsumerInfo is: " << ConsumerInfo << "\n"); | ||
| LLVM_DEBUG(dbgs() << " ProducerInfo is: " << ProducerInfo << "\n"); | ||
| return std::nullopt; | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| return CommonVL; | ||
| return true; | ||
| } | ||
|
|
||
| bool RISCVVLOptimizer::tryReduceVL(MachineInstr &MI) const { | ||
|
|
@@ -1460,9 +1489,7 @@ bool RISCVVLOptimizer::tryReduceVL(MachineInstr &MI) const { | |
| return false; | ||
| } | ||
|
|
||
| auto CommonVL = DemandedVLs.lookup(&MI); | ||
| if (!CommonVL) | ||
| return false; | ||
| auto *CommonVL = &DemandedVLs.at(&MI).VL; | ||
|
|
||
| assert((CommonVL->isImm() || CommonVL->getReg().isVirtual()) && | ||
| "Expected VL to be an Imm or virtual Reg"); | ||
|
|
@@ -1497,6 +1524,24 @@ bool RISCVVLOptimizer::tryReduceVL(MachineInstr &MI) const { | |
| return true; | ||
| } | ||
|
|
||
| static bool isPhysical(const MachineOperand &MO) { | ||
| return MO.isReg() && MO.getReg().isPhysical(); | ||
| } | ||
|
|
||
| /// Look through \p MI's operands and propagate what it demands to its uses. | ||
| void RISCVVLOptimizer::transfer(const MachineInstr &MI) { | ||
| if (!isSupportedInstr(MI) || !checkUsers(MI) || any_of(MI.defs(), isPhysical)) | ||
| DemandedVLs[&MI] = DemandedVL::vlmax(); | ||
|
|
||
| for (const MachineOperand &MO : vector_uses(MI)) { | ||
| const MachineInstr *Def = MRI->getVRegDef(MO.getReg()); | ||
| DemandedVL Prev = DemandedVLs[Def]; | ||
| DemandedVLs[Def] = max(DemandedVLs[Def], getMinimumVLForUser(MO)); | ||
| if (DemandedVLs[Def] != Prev) | ||
| Worklist.insert(Def); | ||
| } | ||
| } | ||
|
|
||
| bool RISCVVLOptimizer::runOnMachineFunction(MachineFunction &MF) { | ||
| if (skipFunction(MF.getFunction())) | ||
| return false; | ||
|
|
@@ -1513,14 +1558,17 @@ bool RISCVVLOptimizer::runOnMachineFunction(MachineFunction &MF) { | |
| assert(DemandedVLs.empty()); | ||
|
|
||
| // For each instruction that defines a vector, compute what VL its | ||
| // downstream users demand. | ||
| // upstream uses demand. | ||
| for (MachineBasicBlock *MBB : post_order(&MF)) { | ||
| assert(MDT->isReachableFromEntry(MBB)); | ||
| for (MachineInstr &MI : reverse(*MBB)) { | ||
| if (!isCandidate(MI)) | ||
| continue; | ||
| DemandedVLs.insert({&MI, checkUsers(MI)}); | ||
| } | ||
| for (MachineInstr &MI : reverse(*MBB)) | ||
| Worklist.insert(&MI); | ||
| } | ||
|
|
||
| while (!Worklist.empty()) { | ||
| const MachineInstr *MI = Worklist.front(); | ||
| Worklist.remove(MI); | ||
| transfer(*MI); | ||
| } | ||
|
|
||
| // Then go through and see if we can reduce the VL of any instructions to | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this into the DemandedVL class so that it has to be called as
DemandedVL::max?