@@ -491,7 +491,247 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
491491};
492492
493493// ========================================================================= //
494- // TODO: Run dataflow analysis to propagate loans, analyse and error reporting.
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.
495735// ========================================================================= //
496736} // anonymous namespace
497737
@@ -504,5 +744,18 @@ void runLifetimeAnalysis(const DeclContext &DC, const CFG &Cfg,
504744 FactGenerator FactGen (FactMgr, AC);
505745 FactGen.run ();
506746 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 ());
507760}
508761} // namespace clang
0 commit comments