Skip to content

Commit e18ae65

Browse files
committed
Move dataflow analysis to a separate PR
1 parent e044ec2 commit e18ae65

File tree

2 files changed

+1
-452
lines changed

2 files changed

+1
-452
lines changed

clang/lib/Analysis/LifetimeSafety.cpp

Lines changed: 1 addition & 254 deletions
Original file line numberDiff line numberDiff line change
@@ -491,247 +491,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
491491
};
492492

493493
// ========================================================================= //
494-
// The Dataflow Lattice
495-
// ========================================================================= //
496-
497-
// Using LLVM's immutable collections is efficient for dataflow analysis
498-
// as it avoids deep copies during state transitions.
499-
// TODO(opt): Consider using a bitset to represent the set of loans.
500-
using LoanSet = llvm::ImmutableSet<LoanID>;
501-
using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
502-
503-
/// An object to hold the factories for immutable collections, ensuring
504-
/// that all created states share the same underlying memory management.
505-
struct LifetimeFactory {
506-
OriginLoanMap::Factory OriginMapFact;
507-
LoanSet::Factory LoanSetFact;
508-
509-
LoanSet createLoanSet(LoanID LID) {
510-
return LoanSetFact.add(LoanSetFact.getEmptySet(), LID);
511-
}
512-
};
513-
514-
/// LifetimeLattice represents the state of our analysis at a given program
515-
/// point. It is an immutable object, and all operations produce a new
516-
/// instance rather than modifying the existing one.
517-
struct LifetimeLattice {
518-
/// The map from an origin to the set of loans it contains.
519-
/// TODO(opt): To reduce the lattice size, propagate origins of declarations,
520-
/// not expressions, because expressions are not visible across blocks.
521-
OriginLoanMap Origins = OriginLoanMap(nullptr);
522-
523-
explicit LifetimeLattice(const OriginLoanMap &S) : Origins(S) {}
524-
LifetimeLattice() = default;
525-
526-
bool operator==(const LifetimeLattice &Other) const {
527-
return Origins == Other.Origins;
528-
}
529-
bool operator!=(const LifetimeLattice &Other) const {
530-
return !(*this == Other);
531-
}
532-
533-
LoanSet getLoans(OriginID OID, LifetimeFactory &Factory) const {
534-
if (auto *Loans = Origins.lookup(OID))
535-
return *Loans;
536-
return Factory.LoanSetFact.getEmptySet();
537-
}
538-
539-
/// Computes the union of two lattices by performing a key-wise join of
540-
/// their OriginLoanMaps.
541-
// TODO(opt): This key-wise join is a performance bottleneck. A more
542-
// efficient merge could be implemented using a Patricia Trie or HAMT
543-
// instead of the current AVL-tree-based ImmutableMap.
544-
LifetimeLattice join(const LifetimeLattice &Other,
545-
LifetimeFactory &Factory) const {
546-
/// Merge the smaller map into the larger one ensuring we iterate over the
547-
/// smaller map.
548-
if (Origins.getHeight() < Other.Origins.getHeight())
549-
return Other.join(*this, Factory);
550-
551-
OriginLoanMap JoinedState = Origins;
552-
// For each origin in the other map, union its loan set with ours.
553-
for (const auto &Entry : Other.Origins) {
554-
OriginID OID = Entry.first;
555-
LoanSet OtherLoanSet = Entry.second;
556-
JoinedState = Factory.OriginMapFact.add(
557-
JoinedState, OID,
558-
join(getLoans(OID, Factory), OtherLoanSet, Factory));
559-
}
560-
return LifetimeLattice(JoinedState);
561-
}
562-
563-
LoanSet join(LoanSet a, LoanSet b, LifetimeFactory &Factory) const {
564-
/// Merge the smaller set into the larger one ensuring we iterate over the
565-
/// smaller set.
566-
if (a.getHeight() < b.getHeight())
567-
std::swap(a, b);
568-
LoanSet Result = a;
569-
for (LoanID LID : b) {
570-
/// TODO(opt): Profiling shows that this loop is a major performance
571-
/// bottleneck. Investigate using a BitVector to represent the set of
572-
/// loans for improved join performance.
573-
Result = Factory.LoanSetFact.add(Result, LID);
574-
}
575-
return Result;
576-
}
577-
578-
void dump(llvm::raw_ostream &OS) const {
579-
OS << "LifetimeLattice State:\n";
580-
if (Origins.isEmpty())
581-
OS << " <empty>\n";
582-
for (const auto &Entry : Origins) {
583-
if (Entry.second.isEmpty())
584-
OS << " Origin " << Entry.first << " contains no loans\n";
585-
for (const LoanID &LID : Entry.second)
586-
OS << " Origin " << Entry.first << " contains Loan " << LID << "\n";
587-
}
588-
}
589-
};
590-
591-
// ========================================================================= //
592-
// The Transfer Function
593-
// ========================================================================= //
594-
class Transferer {
595-
FactManager &AllFacts;
596-
LifetimeFactory &Factory;
597-
598-
public:
599-
explicit Transferer(FactManager &F, LifetimeFactory &Factory)
600-
: AllFacts(F), Factory(Factory) {}
601-
602-
/// Computes the exit state of a block by applying all its facts sequentially
603-
/// to a given entry state.
604-
/// TODO: We might need to store intermediate states per-fact in the block for
605-
/// later analysis.
606-
LifetimeLattice transferBlock(const CFGBlock *Block,
607-
LifetimeLattice EntryState) {
608-
LifetimeLattice BlockState = EntryState;
609-
llvm::ArrayRef<const Fact *> Facts = AllFacts.getFacts(Block);
610-
611-
for (const Fact *F : Facts) {
612-
BlockState = transferFact(BlockState, F);
613-
}
614-
return BlockState;
615-
}
616-
617-
private:
618-
LifetimeLattice transferFact(LifetimeLattice In, const Fact *F) {
619-
switch (F->getKind()) {
620-
case Fact::Kind::Issue:
621-
return transfer(In, *F->getAs<IssueFact>());
622-
case Fact::Kind::AssignOrigin:
623-
return transfer(In, *F->getAs<AssignOriginFact>());
624-
// Expire and ReturnOfOrigin facts don't modify the Origins and the State.
625-
case Fact::Kind::Expire:
626-
case Fact::Kind::ReturnOfOrigin:
627-
return In;
628-
}
629-
llvm_unreachable("Unknown fact kind");
630-
}
631-
632-
/// A new loan is issued to the origin. Old loans are erased.
633-
LifetimeLattice transfer(LifetimeLattice In, const IssueFact &F) {
634-
OriginID OID = F.getOriginID();
635-
LoanID LID = F.getLoanID();
636-
return LifetimeLattice(
637-
Factory.OriginMapFact.add(In.Origins, OID, Factory.createLoanSet(LID)));
638-
}
639-
640-
/// The destination origin's loan set is replaced by the source's.
641-
/// This implicitly "resets" the old loans of the destination.
642-
LifetimeLattice transfer(LifetimeLattice InState, const AssignOriginFact &F) {
643-
OriginID DestOID = F.getDestOriginID();
644-
OriginID SrcOID = F.getSrcOriginID();
645-
LoanSet SrcLoans = InState.getLoans(SrcOID, Factory);
646-
return LifetimeLattice(
647-
Factory.OriginMapFact.add(InState.Origins, DestOID, SrcLoans));
648-
}
649-
};
650-
// ========================================================================= //
651-
// Dataflow analysis
652-
// ========================================================================= //
653-
654-
/// Drives the intra-procedural dataflow analysis.
655-
///
656-
/// Orchestrates the analysis by iterating over the CFG using a worklist
657-
/// algorithm. It computes a fixed point by propagating the LifetimeLattice
658-
/// state through each block until the state no longer changes.
659-
/// TODO: Maybe use the dataflow framework! The framework might need changes
660-
/// to support the current comparison done at block-entry.
661-
class LifetimeDataflow {
662-
const CFG &Cfg;
663-
AnalysisDeclContext &AC;
664-
LifetimeFactory LifetimeFact;
665-
666-
Transferer Xfer;
667-
668-
/// Stores the merged analysis state at the entry of each CFG block.
669-
llvm::DenseMap<const CFGBlock *, LifetimeLattice> BlockEntryStates;
670-
/// Stores the analysis state at the exit of each CFG block, after the
671-
/// transfer function has been applied.
672-
llvm::DenseMap<const CFGBlock *, LifetimeLattice> BlockExitStates;
673-
674-
public:
675-
LifetimeDataflow(const CFG &C, FactManager &FS, AnalysisDeclContext &AC)
676-
: Cfg(C), AC(AC), Xfer(FS, LifetimeFact) {}
677-
678-
void run() {
679-
llvm::TimeTraceScope TimeProfile("Lifetime Dataflow");
680-
ForwardDataflowWorklist Worklist(Cfg, AC);
681-
const CFGBlock *Entry = &Cfg.getEntry();
682-
BlockEntryStates[Entry] = LifetimeLattice{};
683-
Worklist.enqueueBlock(Entry);
684-
while (const CFGBlock *B = Worklist.dequeue()) {
685-
LifetimeLattice EntryState = getEntryState(B);
686-
LifetimeLattice ExitState = Xfer.transferBlock(B, EntryState);
687-
BlockExitStates[B] = ExitState;
688-
689-
for (const CFGBlock *Successor : B->succs()) {
690-
auto SuccIt = BlockEntryStates.find(Successor);
691-
LifetimeLattice OldSuccEntryState = (SuccIt != BlockEntryStates.end())
692-
? SuccIt->second
693-
: LifetimeLattice{};
694-
LifetimeLattice NewSuccEntryState =
695-
OldSuccEntryState.join(ExitState, LifetimeFact);
696-
// Enqueue the successor if its entry state has changed.
697-
// TODO(opt): Consider changing 'join' to report a change if !=
698-
// comparison is found expensive.
699-
if (SuccIt == BlockEntryStates.end() ||
700-
NewSuccEntryState != OldSuccEntryState) {
701-
BlockEntryStates[Successor] = NewSuccEntryState;
702-
Worklist.enqueueBlock(Successor);
703-
}
704-
}
705-
}
706-
}
707-
708-
void dump() const {
709-
llvm::dbgs() << "==========================================\n";
710-
llvm::dbgs() << " Dataflow results:\n";
711-
llvm::dbgs() << "==========================================\n";
712-
const CFGBlock &B = Cfg.getExit();
713-
getExitState(&B).dump(llvm::dbgs());
714-
}
715-
716-
LifetimeLattice getEntryState(const CFGBlock *B) const {
717-
auto It = BlockEntryStates.find(B);
718-
if (It != BlockEntryStates.end()) {
719-
return It->second;
720-
}
721-
return LifetimeLattice{};
722-
}
723-
724-
LifetimeLattice getExitState(const CFGBlock *B) const {
725-
auto It = BlockExitStates.find(B);
726-
if (It != BlockExitStates.end()) {
727-
return It->second;
728-
}
729-
return LifetimeLattice{};
730-
}
731-
};
732-
733-
// ========================================================================= //
734-
// TODO: Analysing dataflow results and error reporting.
494+
// TODO: Run dataflow analysis to propagate loans, analyse and error reporting.
735495
// ========================================================================= //
736496
} // anonymous namespace
737497

@@ -744,18 +504,5 @@ void runLifetimeAnalysis(const DeclContext &DC, const CFG &Cfg,
744504
FactGenerator FactGen(FactMgr, AC);
745505
FactGen.run();
746506
DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC));
747-
748-
/// TODO(opt): Consider optimizing individual blocks before running the
749-
/// dataflow analysis.
750-
/// 1. Expression Origins: These are assigned once and read at most once,
751-
/// forming simple chains. These chains can be compressed into a single
752-
/// assignment.
753-
/// 2. Block-Local Loans: Origins of expressions are never read by other
754-
/// blocks; only Decls are visible. Therefore, loans in a block that
755-
/// never reach an Origin associated with a Decl can be safely dropped by
756-
/// the analysis.
757-
LifetimeDataflow Dataflow(Cfg, FactMgr, AC);
758-
Dataflow.run();
759-
DEBUG_WITH_TYPE("LifetimeDataflow", Dataflow.dump());
760507
}
761508
} // namespace clang

0 commit comments

Comments
 (0)