@@ -496,20 +496,25 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
496496};
497497
498498// ========================================================================= //
499- // Generic Dataflow Framework
499+ // Generic Dataflow Analysis
500500// ========================================================================= //
501501// / A generic, policy-based driver for forward dataflow analyses. It combines
502502// / the dataflow runner and the transferer logic into a single class hierarchy.
503503// /
504504// / The derived class is expected to provide:
505505// / - A `Lattice` type.
506- // / - `Lattice getInitialState()`
507- // / - `Lattice join(Lattice, Lattice)`
508- // / - `Lattice transfer(Lattice, const FactType&)` for relevant fact types.
506+ // / - `const char *getAnalysisName() const`
507+ // / - `Lattice getInitialState();` The initial state at the function entry.
508+ // / - `Lattice join(Lattice, Lattice);` Merges states from multiple CFG paths.
509+ // / - `Lattice transfer(Lattice, const FactType&);` Defines how a single
510+ // / lifetime-relevant `Fact` transforms the lattice state. Only overloads
511+ // / for facts relevant to the analysis need to be implemented.
509512// /
510513// / \tparam Derived The CRTP derived class that implements the specific
511514// / analysis.
512515// / \tparam LatticeType The lattice type used by the analysis.
516+ // / TODO: Maybe use the dataflow framework! The framework might need changes
517+ // / to support the current comparison done at block-entry.
513518template <typename Derived, typename LatticeType> class DataflowAnalysis {
514519public:
515520 using Lattice = LatticeType;
@@ -530,10 +535,12 @@ template <typename Derived, typename LatticeType> class DataflowAnalysis {
530535
531536public:
532537 void run () {
533- Derived &d = static_cast <Derived &>(*this );
538+ Derived &D = static_cast <Derived &>(*this );
539+ llvm::TimeTraceScope Time (D.getAnalysisName ());
540+
534541 ForwardDataflowWorklist Worklist (Cfg, AC);
535542 const CFGBlock *Entry = &Cfg.getEntry ();
536- BlockEntryStates[Entry] = d .getInitialState ();
543+ BlockEntryStates[Entry] = D .getInitialState ();
537544 Worklist.enqueueBlock (Entry);
538545
539546 while (const CFGBlock *B = Worklist.dequeue ()) {
@@ -545,9 +552,11 @@ template <typename Derived, typename LatticeType> class DataflowAnalysis {
545552 auto SuccIt = BlockEntryStates.find (Successor);
546553 Lattice OldSuccEntryState = (SuccIt != BlockEntryStates.end ())
547554 ? SuccIt->second
548- : d.getInitialState ();
549- Lattice NewSuccEntryState = d.join (OldSuccEntryState, ExitState);
550-
555+ : D.getInitialState ();
556+ Lattice NewSuccEntryState = D.join (OldSuccEntryState, ExitState);
557+ // Enqueue the successor if its entry state has changed.
558+ // TODO(opt): Consider changing 'join' to report a change if !=
559+ // comparison is found expensive.
551560 if (SuccIt == BlockEntryStates.end () ||
552561 NewSuccEntryState != OldSuccEntryState) {
553562 BlockEntryStates[Successor] = NewSuccEntryState;
@@ -565,7 +574,20 @@ template <typename Derived, typename LatticeType> class DataflowAnalysis {
565574 return BlockExitStates.lookup (B);
566575 }
567576
577+ void dump () const {
578+ const Derived *D = static_cast <const Derived *>(this );
579+ llvm::dbgs () << " ==========================================\n " ;
580+ llvm::dbgs () << " " << D->getAnalysisName () << " results:\n " ;
581+ llvm::dbgs () << " ==========================================\n " ;
582+ const CFGBlock &B = Cfg.getExit ();
583+ getExitState (&B).dump (llvm::dbgs ());
584+ }
585+
568586private:
587+ // / Computes the exit state of a block by applying all its facts sequentially
588+ // / to a given entry state.
589+ // / TODO: We might need to store intermediate states per-fact in the block for
590+ // / later analysis.
569591 Lattice transferBlock (const CFGBlock *Block, Lattice EntryState) {
570592 Lattice BlockState = EntryState;
571593 for (const Fact *F : AllFacts.getFacts (Block)) {
@@ -618,24 +640,24 @@ struct LifetimeFactory {
618640 }
619641};
620642
621- // / LifetimeLattice represents the state of our analysis at a given program
622- // / point. It is an immutable object, and all operations produce a new
643+ // / LoanPropagationLattice represents the state of our analysis at a given
644+ // / program point. It is an immutable object, and all operations produce a new
623645// / instance rather than modifying the existing one.
624- struct LifetimeLattice {
646+ struct LoanPropagationLattice {
625647 // / The map from an origin to the set of loans it contains.
626648 // / The lattice has a finite height: An origin's loan set is bounded by the
627649 // / total number of loans in the function.
628650 // / TODO(opt): To reduce the lattice size, propagate origins of declarations,
629651 // / not expressions, because expressions are not visible across blocks.
630652 OriginLoanMap Origins = OriginLoanMap(nullptr );
631653
632- explicit LifetimeLattice (const OriginLoanMap &S) : Origins(S) {}
633- LifetimeLattice () = default ;
654+ explicit LoanPropagationLattice (const OriginLoanMap &S) : Origins(S) {}
655+ LoanPropagationLattice () = default ;
634656
635- bool operator ==(const LifetimeLattice &Other) const {
657+ bool operator ==(const LoanPropagationLattice &Other) const {
636658 return Origins == Other.Origins ;
637659 }
638- bool operator !=(const LifetimeLattice &Other) const {
660+ bool operator !=(const LoanPropagationLattice &Other) const {
639661 return !(*this == Other);
640662 }
641663
@@ -653,7 +675,7 @@ struct LifetimeLattice {
653675};
654676
655677class LoanPropagationAnalysis
656- : public DataflowAnalysis<LoanPropagationAnalysis, LifetimeLattice > {
678+ : public DataflowAnalysis<LoanPropagationAnalysis, LoanPropagationLattice > {
657679
658680 LifetimeFactory &Factory;
659681
@@ -662,49 +684,58 @@ class LoanPropagationAnalysis
662684 LifetimeFactory &Factory)
663685 : DataflowAnalysis(C, AC, F), Factory(Factory) {}
664686
665- // Make the base class's transfer overloads visible.
666687 using DataflowAnalysis<LoanPropagationAnalysis, Lattice>::transfer;
667688
689+ const char *getAnalysisName () const { return " LoanPropagation" ; }
690+
668691 Lattice getInitialState () { return Lattice{}; }
669692
670- Lattice join (Lattice L1, Lattice L2) {
693+ // / Computes the union of two lattices by performing a key-wise join of
694+ // / their OriginLoanMaps.
695+ // TODO(opt): This key-wise join is a performance bottleneck. A more
696+ // efficient merge could be implemented using a Patricia Trie or HAMT
697+ // instead of the current AVL-tree-based ImmutableMap.
698+ // TODO(opt): Keep the state small by removing origins which become dead.
699+ Lattice join (Lattice A, Lattice B) {
671700 // / Merge the smaller map into the larger one ensuring we iterate over the
672701 // / smaller map.
673- if (L1 .Origins .getHeight () < L2 .Origins .getHeight ())
674- std::swap (L1, L2 );
702+ if (A .Origins .getHeight () < B .Origins .getHeight ())
703+ std::swap (A, B );
675704
676- OriginLoanMap JoinedState = L1 .Origins ;
705+ OriginLoanMap JoinedState = A .Origins ;
677706 // For each origin in the other map, union its loan set with ours.
678- for (const auto &Entry : L2 .Origins ) {
707+ for (const auto &Entry : B .Origins ) {
679708 OriginID OID = Entry.first ;
680709 LoanSet OtherLoanSet = Entry.second ;
681710 JoinedState = Factory.OriginMapFactory .add (
682- JoinedState, OID, join (getLoans (L1 , OID), OtherLoanSet));
711+ JoinedState, OID, join (getLoans (A , OID), OtherLoanSet));
683712 }
684713 return Lattice (JoinedState);
685714 }
686715
687- LoanSet join (LoanSet S1 , LoanSet S2 ) {
688- if (S1 .getHeight () < S2 .getHeight ())
689- std::swap (S1, S2 );
690- for (LoanID L : S2 )
691- S1 = Factory.LoanSetFact .add (S1 , L);
692- return S1 ;
716+ LoanSet join (LoanSet A , LoanSet B ) {
717+ if (A .getHeight () < B .getHeight ())
718+ std::swap (A, B );
719+ for (LoanID L : B )
720+ A = Factory.LoanSetFact .add (A , L);
721+ return A ;
693722 }
694723
695- // Overloads for specific fact types this transferer cares about .
724+ // / A new loan is issued to the origin. Old loans are erased .
696725 Lattice transfer (Lattice In, const IssueFact &F) {
697726 OriginID OID = F.getOriginID ();
698727 LoanID LID = F.getLoanID ();
699- return LifetimeLattice (Factory.OriginMapFactory .add (
728+ return LoanPropagationLattice (Factory.OriginMapFactory .add (
700729 In.Origins , OID, Factory.createLoanSet (LID)));
701730 }
702731
732+ // / The destination origin's loan set is replaced by the source's.
733+ // / This implicitly "resets" the old loans of the destination.
703734 Lattice transfer (Lattice In, const AssignOriginFact &F) {
704735 OriginID DestOID = F.getDestOriginID ();
705736 OriginID SrcOID = F.getSrcOriginID ();
706737 LoanSet SrcLoans = getLoans (In, SrcOID);
707- return LifetimeLattice (
738+ return LoanPropagationLattice (
708739 Factory.OriginMapFactory .add (In.Origins , DestOID, SrcLoans));
709740 }
710741
@@ -717,63 +748,12 @@ class LoanPropagationAnalysis
717748};
718749
719750// ========================================================================= //
720- // Expired Loans Analysis
751+ // TODO:
752+ // - Modifying loan propagation to answer `LoanSet getLoans(Origin O, Point P)`
753+ // - Adding loan expiry analysis to answer `bool isExpired(Loan L, Point P)`
754+ // - Adding origin liveness analysis to answer `bool isLive(Origin O, Point P)`
755+ // - Using the above three to perform the final error reporting.
721756// ========================================================================= //
722-
723- // / The lattice for tracking expired loans. It is a set of loan IDs.
724- struct ExpiredLattice {
725- LoanSet Expired;
726-
727- ExpiredLattice () : Expired(nullptr ) {};
728- explicit ExpiredLattice (LoanSet S) : Expired(S) {}
729-
730- bool operator ==(const ExpiredLattice &Other) const {
731- return Expired == Other.Expired ;
732- }
733- bool operator !=(const ExpiredLattice &Other) const {
734- return !(*this == Other);
735- }
736-
737- void dump (llvm::raw_ostream &OS) const {
738- OS << " ExpiredLattice State:\n " ;
739- if (Expired.isEmpty ())
740- OS << " <empty>\n " ;
741- for (const LoanID &LID : Expired)
742- OS << " Loan " << LID << " is expired\n " ;
743- }
744- };
745-
746- // / Transfer function for the expired loans analysis.
747- class ExpiredLoansAnalysis
748- : public DataflowAnalysis<ExpiredLoansAnalysis, ExpiredLattice> {
749-
750- LoanSet::Factory &SetFactory;
751-
752- public:
753- ExpiredLoansAnalysis (const CFG &C, AnalysisDeclContext &AC, FactManager &F,
754- LoanSet::Factory &SF)
755- : DataflowAnalysis(C, AC, F), SetFactory(SF) {}
756-
757- using DataflowAnalysis<ExpiredLoansAnalysis, Lattice>::transfer;
758-
759- Lattice getInitialState () { return Lattice (SetFactory.getEmptySet ()); }
760-
761- Lattice join (Lattice L1, Lattice L2) const {
762- LoanSet JoinedSet = L1.Expired ;
763- for (LoanID LID : L2.Expired )
764- JoinedSet = SetFactory.add (JoinedSet, LID);
765- return Lattice (JoinedSet);
766- }
767-
768- // Overloads for specific fact types this transferer cares about.
769- Lattice transfer (Lattice In, const ExpireFact &F) {
770- return Lattice (SetFactory.add (In.Expired , F.getLoanID ()));
771- }
772-
773- Lattice transfer (Lattice In, const IssueFact &F) {
774- return Lattice (SetFactory.remove (In.Expired , F.getLoanID ()));
775- }
776- };
777757} // anonymous namespace
778758
779759void runLifetimeSafetyAnalysis (const DeclContext &DC, const CFG &Cfg,
@@ -786,20 +766,18 @@ void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
786766 FactGen.run ();
787767 DEBUG_WITH_TYPE (" LifetimeFacts" , FactMgr.dump (Cfg, AC));
788768
789- // Run Loan Propagation Analysis
769+ // / TODO(opt): Consider optimizing individual blocks before running the
770+ // / dataflow analysis.
771+ // / 1. Expression Origins: These are assigned once and read at most once,
772+ // / forming simple chains. These chains can be compressed into a single
773+ // / assignment.
774+ // / 2. Block-Local Loans: Origins of expressions are never read by other
775+ // / blocks; only Decls are visible. Therefore, loans in a block that
776+ // / never reach an Origin associated with a Decl can be safely dropped by
777+ // / the analysis.
790778 LifetimeFactory LifetimeFact;
791779 LoanPropagationAnalysis LoanPropagation (Cfg, AC, FactMgr, LifetimeFact);
792780 LoanPropagation.run ();
793- DEBUG_WITH_TYPE (
794- " LifetimeDataflow" ,
795- LoanPropagation.getExitState (&Cfg.getExit ()).dump (llvm::dbgs ()));
796-
797- // Run Expired Loans Analysis
798- ExpiredLoansAnalysis ExpiredAnalysis (Cfg, AC, FactMgr,
799- LifetimeFact.LoanSetFact );
800- ExpiredAnalysis.run ();
801- DEBUG_WITH_TYPE (
802- " ExpiredLoans" ,
803- ExpiredAnalysis.getExitState (&Cfg.getExit ()).dump (llvm::dbgs ()));
781+ DEBUG_WITH_TYPE (" LifetimeLoanPropagation" , LoanPropagation.dump ());
804782}
805783} // namespace clang
0 commit comments