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"
@@ -493,7 +496,247 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
493
496
};
494
497
495
498
// ========================================================================= //
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.
497
740
// ========================================================================= //
498
741
} // anonymous namespace
499
742
@@ -506,5 +749,18 @@ void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
506
749
FactGenerator FactGen (FactMgr, AC);
507
750
FactGen.run ();
508
751
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 ());
509
765
}
510
766
} // namespace clang
0 commit comments