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