[llvm] [LLVM][Transforms][Attributor] - Add batch reachability optimization to forallInterferingAccesses (PR #190078)
Pranav Bhandarkar via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 30 09:15:01 PDT 2026
================
@@ -1343,13 +1363,297 @@ struct AAPointerInfoImpl
return LeastDominatingWriteInst != Acc.getRemoteInst();
};
- // Run the user callback on all accesses we cannot skip and return if
- // that succeeded for all or not.
- for (auto &It : InterferingAccesses) {
- if ((!AllInSameNoSyncFn && !IsThreadLocalObj && !ExecDomainAA) ||
- !CanSkipAccess(*It.first, It.second)) {
- if (!UserCB(*It.first, It.second))
+ {
+ // Batch reachability optimization for CanSkipAccess.
+ //
+ // Without this optimization, each interfering access would trigger two
+ // independent AA::isPotentiallyReachable calls. Each call traverses the
+ // CFG from scratch. With N interfering accesses, this is 2 * N
+ // independent BFS traversals over the same function's CFG — redundant
+ // work.
+ //
+ // This optimization pre-computes two block-level reachability sets:
+ // ReachableFromI: all basic blocks reachable forward from I's block
+ // ReachableToI: all basic blocks that can reach I's block (backward)
+ //
+ // These are computed lazily (on first use) via a single BFS each,
+ // respecting the ExclusionSet (must-write barriers) and liveness
+ // (dead edges from AAIsDead). Then for each intra-function access,
+ // a cheap set lookup replaces the full isPotentiallyReachable call:
+ // - ReadChecked: if Acc's block is NOT in ReachableFromI, I can't
+ // reach Acc, so Acc's read can't observe I's write.
+ // - WriteChecked: if Acc's block is NOT in ReachableToI, Acc can't
+ // reach I, so Acc's write can't affect I's read.
+ //
+ // If the block-level check is inconclusive, we fall back to
+ // isPotentiallyReachable.
+ //
+ //
+
+ DenseSet<const BasicBlock *> ReachableFromI;
+ bool ReachableFromIComputed = false;
+
+ DenseSet<const BasicBlock *> ReachableToI;
+ bool ReachableToIComputed = false;
+
+ // Map each basic block to the ExclusionSet instructions it contains.
+ // Built once and shared across the BFS helpers and same-block checks
+ // in CanSkipAccessBatch, replacing per-use iteration over ExclusionSet.
+ DenseMap<const BasicBlock *, SmallVector<const Instruction *, 2>>
+ ExcludedBlockInsts;
+ for (const Instruction *ExclI : ExclusionSet)
+ ExcludedBlockInsts[ExclI->getParent()].push_back(ExclI);
+
+ // Lazily compute forward reachability from I's block.
+ // BFS over successor edges within Scope, skipping dead edges (AAIsDead)
+ // and not traversing past ExclusionSet blocks (must-write barriers).
+ // I's block is always traversed (its successors are always explored).
+ // Other blocks containing ExclusionSet instructions are added to the
+ // reachable set but their successors are NOT explored.
+ //
+ // Note: we do NOT block I's block even if it contains an ExclusionSet
+ // instruction after I. This matches isPotentiallyReachable /
+ // isReachableImpl semantics, which check SuccBB == ToBB before
+ // ExclusionBlocks and thus always consider direct successors as
+ // reachable.
+ auto EnsureReachableFromI = [&]() {
+ if (ReachableFromIComputed)
+ return;
+
+ const BasicBlock *FromBB = I.getParent();
+ SmallVector<const BasicBlock *, 16> Worklist;
+ Worklist.push_back(FromBB);
+ ReachableFromI.insert(FromBB);
+
+ const auto *LivenessAA = A.getAAFor<AAIsDead>(
+ QueryingAA, IRPosition::function(Scope), DepClassTy::OPTIONAL);
+
+ while (!Worklist.empty()) {
+ const BasicBlock *BB = Worklist.pop_back_val();
+
+ // Don't traverse past ExclusionSet blocks (must-write barriers),
+ // but always traverse I's own block.
+ if (BB != FromBB && ExcludedBlockInsts.count(BB))
+ continue;
+
+ for (const BasicBlock *SuccBB : successors(BB)) {
+ if (LivenessAA && LivenessAA->isEdgeDead(BB, SuccBB))
+ continue;
+
+ if (ReachableFromI.insert(SuccBB).second)
+ Worklist.push_back(SuccBB);
+ }
+ }
+ ReachableFromIComputed = true;
+ };
+
+ // Lazily compute backward reachability to I's block.
+ // BFS over predecessor edges within Scope, skipping dead edges and
+ // not traversing past ExclusionSet blocks. This is the mirror of
+ // EnsureReachableFromI: it answers "can Acc reach I?" rather than
+ // "can I reach Acc?".
+ //
+ // Lazily compute backward reachability to I's block.
+ // BFS over predecessor edges within Scope, skipping dead edges and
+ // not traversing past ExclusionSet blocks. This is the mirror of
+ // EnsureReachableFromI: it answers "can Acc reach I?" rather than
+ // "can I reach Acc?".
+ //
+ // As with EnsureReachableFromI, I's block is always traversed
+ // (its predecessors are always explored) to match isPotentiallyReachable
+ // semantics.
+ auto EnsureReachableToI = [&]() {
+ if (ReachableToIComputed)
+ return;
+
+ const BasicBlock *ToBB = I.getParent();
+ SmallVector<const BasicBlock *, 16> Worklist;
+ Worklist.push_back(ToBB);
+ ReachableToI.insert(ToBB);
+
+ const auto *LivenessAA = A.getAAFor<AAIsDead>(
+ QueryingAA, IRPosition::function(Scope), DepClassTy::OPTIONAL);
+
+ while (!Worklist.empty()) {
+ const BasicBlock *BB = Worklist.pop_back_val();
+
+ for (const BasicBlock *PredBB : predecessors(BB)) {
+ if (LivenessAA && LivenessAA->isEdgeDead(PredBB, BB))
+ continue;
+
+ if (!ReachableToI.insert(PredBB).second)
+ continue;
+
+ // Don't traverse past ExclusionSet blocks, but always
+ // traverse I's own block (ToBB).
+ bool Blocked = (PredBB != ToBB && ExcludedBlockInsts.count(PredBB));
+
+ if (!Blocked)
+ Worklist.push_back(PredBB);
+ }
+ }
+ ReachableToIComputed = true;
+ };
+
+ // Batch variant of CanSkipAccess for intra-function accesses.
+ //
+ // The batch BFS can safely be used as a negative filter (skip iPR when
+ // BFS says "not reachable") only when iPR agrees for all paths the BFS
+ // can't model. This requires:
+ // (a) norecurse: instructionCanReach can't find paths back to Scope
+ // (b) GoBackwardsCB returns false for Scope: iPR won't step back to
+ // callers
+ // Both hold for allocas in norecurse functions where
+ // GoBackwardsCB(*Scope) = IsLiveInCalleeCB(*Scope) = (AIFn != Scope) =
+ // false.
+ //
+ // When BFSSafe is true (alloca in norecurse fn), the BFS is used as a
+ // negative filter: if Acc's block is NOT in the reachable set,
+ // isPotentiallyReachable would also return false, so we can directly
+ // set Checked = true without calling isPotentiallyReachable.
+ //
+ // When BFSSafe is false, the BFS cannot be a negative filter because
+ // isPotentiallyreachable considers inter-procedural and cross-invocation
+ // paths that the BFS can't model. In that case, we skip the BFS entirely
+ // and only use the same-block optimization (which doesn't depend on the
+ // BFS).
+ bool BFSSafe = false;
+ if (isa<AllocaInst>(&getAssociatedValue()) && IsLiveInCalleeCB)
+ BFSSafe = true;
+
+ auto CanSkipAccessBatch = [&](const Access &Acc, bool Exact) {
+ if (SkipCB && SkipCB(Acc))
+ return true;
+ if (!CanIgnoreThreading(Acc)) {
return false;
+ }
+
+ bool ReadChecked = !FindInterferingReads;
+ bool WriteChecked = !FindInterferingWrites;
+
+ // Forward reachability: can I reach Acc?
+ if (!ReadChecked) {
+ if (BFSSafe) {
+ EnsureReachableFromI();
+ bool BlockReachable =
+ ReachableFromI.count(Acc.getRemoteInst()->getParent());
+
+ if (!BlockReachable) {
+ // BFS-safe negative filter: the BFS is a complete model of
+ // reachability for allocas in norecurse functions.
+ ReadChecked = true;
+ } else if (I.getParent() == Acc.getRemoteInst()->getParent() &&
+ I.comesBefore(Acc.getRemoteInst())) {
+ // Same-block optimization.
+ auto It = ExcludedBlockInsts.find(I.getParent());
+ if (It != ExcludedBlockInsts.end()) {
+ for (const Instruction *ExclI : It->second) {
+ if (I.comesBefore(ExclI) &&
+ ExclI->comesBefore(Acc.getRemoteInst())) {
+ ReadChecked = true;
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ // Not BFS-safe: only apply same-block optimization.
+ if (I.getParent() == Acc.getRemoteInst()->getParent() &&
+ I.comesBefore(Acc.getRemoteInst())) {
+ auto It = ExcludedBlockInsts.find(I.getParent());
+ if (It != ExcludedBlockInsts.end()) {
+ for (const Instruction *ExclI : It->second) {
+ if (I.comesBefore(ExclI) &&
+ ExclI->comesBefore(Acc.getRemoteInst())) {
+ ReadChecked = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!ReadChecked) {
+ if (!AA::isPotentiallyReachable(A, I, *Acc.getRemoteInst(),
+ QueryingAA, &ExclusionSet,
+ IsLiveInCalleeCB)) {
+ ReadChecked = true;
+ }
+ }
+ }
+
+ // Backward reachability: can Acc reach I?
+ if (!WriteChecked) {
+ if (BFSSafe) {
+ EnsureReachableToI();
+ bool BlockReachable =
+ ReachableToI.count(Acc.getRemoteInst()->getParent());
+
+ if (!BlockReachable) {
+ WriteChecked = true;
+ } else if (I.getParent() == Acc.getRemoteInst()->getParent() &&
+ Acc.getRemoteInst()->comesBefore(&I)) {
+ auto It = ExcludedBlockInsts.find(I.getParent());
+ if (It != ExcludedBlockInsts.end()) {
+ for (const Instruction *ExclI : It->second) {
+ if (Acc.getRemoteInst()->comesBefore(ExclI) &&
+ ExclI->comesBefore(&I)) {
+ WriteChecked = true;
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ if (I.getParent() == Acc.getRemoteInst()->getParent() &&
+ Acc.getRemoteInst()->comesBefore(&I)) {
+ auto It = ExcludedBlockInsts.find(I.getParent());
+ if (It != ExcludedBlockInsts.end()) {
+ for (const Instruction *ExclI : It->second) {
+ if (Acc.getRemoteInst()->comesBefore(ExclI) &&
+ ExclI->comesBefore(&I)) {
+ WriteChecked = true;
+ break;
+ }
+ }
+ }
+ }
+ }
----------------
bhandarkar-pranav wrote:
Will do.
https://github.com/llvm/llvm-project/pull/190078
More information about the llvm-commits
mailing list