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