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