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