8585#include " llvm/Support/YAMLTraits.h"
8686#include " llvm/Target/TargetMachine.h"
8787
88+ #include < algorithm>
8889#include < memory>
8990#include < unordered_set>
9091#include < utility>
@@ -351,22 +352,25 @@ class ABIDependencyEvaluator {
351352
352353 // / Check for cases where we have a fake cycle through an overlay.
353354 // /
354- // / \code
355- // / Actual stack:
356- // / sandwichedModule -> Overlay (Swift) -> ... -> sandwichedModule
357- // / ^^--- wrong!
358- // / Ideal stack:
359- // / sandwichedModule -> Underlying (Clang)
360- // / \endcode
355+ // / Sometimes, we have fake cycles in the import graph due to the Clang
356+ // / importer injecting overlays between Clang modules. These don't represent
357+ // / an actual cycle in the build, so we should ignore them.
361358 // /
362- // / This happens when we have a dependency like:
363- // / \code
364- // / Overlay (Swift) -> sandwichedModule -> Underlying (Clang)
365- // / \endcode
359+ // / We check this lazily after detecting a cycle because it is difficult to
360+ // / determine at the point where we see the overlay whether it was incorrectly
361+ // / injected by the Clang importer or whether any of its imports will
362+ // / eventually lead to a cycle.
363+ // /
364+ // / For more details, see [NOTE: ABIDependencyEvaluator-fake-cycle-detection]
366365 // /
367- // / We check this lazily because eagerly detecting if the dependency on an
368- // / overlay is correct or not is difficult.
369- bool isFakeCycleThroughOverlay (ModuleDecl **sandwichedModuleIter);
366+ // / \param startOfCycle A pointer to the element of \c searchStack where
367+ // / the module \em first appeared.
368+ // /
369+ // / \pre The module on top of \c searchStack is the same module as
370+ // / *startOfCycle.
371+ // /
372+ // / \pre searchStack.begin() <= startOfCycle < searchStack.end()
373+ bool isFakeCycleThroughOverlay (ModuleDecl **startOfCycle);
370374
371375 // / Recursive step in computing ABI dependencies.
372376 // /
@@ -492,21 +496,87 @@ bool ABIDependencyEvaluator::isOverlayOfClangModule(ModuleDecl *swiftModule) {
492496 return isOverlay;
493497}
494498
499+ // [NOTE: ABIDependencyEvaluator-fake-cycle-detection]
500+ //
501+ // First, let's consider a concrete example.
502+ // - In Clang-land, ToyKit #imports CoreDoll.
503+ // - The Swift overlay for CoreDoll imports both CoreDoll and ToyKit.
504+ // Importing ToyKit from CoreDoll's overlay informally violates the layering
505+ // of frameworks, but it doesn't actually create any cycles in the build
506+ // dependencies.
507+ // ┌───────────────────────────┐
508+ // ┌───│ CoreDoll.swiftmodule │
509+ // │ └───────────────────────────┘
510+ // │ │
511+ // import ToyKit @_exported import CoreDoll
512+ // │ │
513+ // │ │
514+ // ▼ │
515+ // ┌──────────────────────────┐ │
516+ // │ ToyKit (ToyKit/ToyKit.h) │ │
517+ // └──────────────────────────┘ │
518+ // │ │
519+ // #import <CoreDoll/CoreDoll.h> │
520+ // │ │
521+ // ▼ │
522+ // ┌──────────────────────────────┐ │
523+ // │CoreDoll (CoreDoll/CoreDoll.h)│◀──┘
524+ // └──────────────────────────────┘
525+ //
526+ // Say we are trying to build a Swift module that imports ToyKit. Due to how
527+ // module loading works, the Clang importer inserts the CoreDoll overlay
528+ // between the ToyKit and CoreDoll Clang modules, creating a cycle in the
529+ // import graph.
530+ //
531+ // ┌──────────────────────────┐
532+ // │ ToyKit (ToyKit/ToyKit.h) │◀──────────┐
533+ // └──────────────────────────┘ │
534+ // │ │
535+ // #import <CoreDoll/CoreDoll.h> import ToyKit
536+ // │ │
537+ // ▼ │
538+ // ┌────────────────────────────┐ │
539+ // │ CoreDoll.swiftmodule │─────────┘
540+ // └────────────────────────────┘
541+ // │
542+ // @_exported import CoreDoll
543+ // │
544+ // ▼
545+ // ┌──────────────────────────────┐
546+ // │CoreDoll (CoreDoll/CoreDoll.h)│
547+ // └──────────────────────────────┘
548+ //
549+ // This means that, at some point, searchStack will look like:
550+ //
551+ // [others] → ToyKit → CoreDoll (overlay) → ToyKit
552+ //
553+ // In the general case, there may be arbitrarily many modules in the cycle,
554+ // including submodules.
555+ //
556+ // [others] → ToyKit → [others] → CoreDoll (overlay) → [others] → ToyKit
557+ //
558+ // where "[others]" indicates 0 or more modules of any kind.
559+ //
560+ // To detect this, we check that the start of the cycle is a Clang module and
561+ // that there is at least one overlay between it and its recurrence at the end
562+ // of the searchStack. If so, we assume we have detected a benign cycle which
563+ // can be safely ignored.
564+
495565bool ABIDependencyEvaluator::isFakeCycleThroughOverlay (
496- ModuleDecl **sandwichModuleIter) {
497- assert (sandwichModuleIter >= searchStack.begin ()
498- && sandwichModuleIter < searchStack.end ()
499- && " sandwichModuleIter points to an element in searchStack" );
500- // The sandwichedModule must be a Clang module.
501- if (!(*sandwichModuleIter)->isNonSwiftModule ())
502- return false ;
503- auto nextModuleIter = sandwichModuleIter + 1 ;
504- if (nextModuleIter == searchStack.end ())
505- return false ;
506- // The next module must be a Swift overlay for a Clang module
507- if ((*nextModuleIter)->isNonSwiftModule ())
566+ ModuleDecl **startOfCycle) {
567+ assert (startOfCycle >= searchStack.begin () &&
568+ startOfCycle < searchStack.end () &&
569+ " startOfCycleIter points to an element in searchStack" );
570+ // The startOfCycle module must be a Clang module.
571+ if (!(*startOfCycle)->isNonSwiftModule ())
508572 return false ;
509- return isOverlayOfClangModule (*nextModuleIter);
573+ // Next, we must have zero or more modules followed by a Swift overlay for a
574+ // Clang module.
575+ return std::any_of (startOfCycle + 1 , searchStack.end (),
576+ [this ](ModuleDecl *module ) {
577+ return !module ->isNonSwiftModule () &&
578+ isOverlayOfClangModule (module );
579+ });
510580}
511581
512582void ABIDependencyEvaluator::computeABIDependenciesForModule (
@@ -518,7 +588,11 @@ void ABIDependencyEvaluator::computeABIDependenciesForModule(
518588 crashOnInvariantViolation ([&](llvm::raw_string_ostream &os) {
519589 os << " unexpected cycle in import graph!\n " ;
520590 for (auto m: searchStack) {
521- printModule (m, os); os << " \n depends on " ;
591+ printModule (m, os);
592+ if (!m->isNonSwiftModule ()) {
593+ os << " (isOverlay = " << isOverlayOfClangModule (m) << " )" ;
594+ }
595+ os << " \n depends on " ;
522596 }
523597 printModule (module , os); os << ' \n ' ;
524598 });
0 commit comments