@@ -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