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