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