Skip to content

Commit 4350a96

Browse files
Merge pull request #33640 from varungandhi-apple/vg-fix-module-trace-cycle
[ModuleTrace] Handle cycle detection edge case in module trace emission (ep2).
2 parents bf7c03b + 3e7f339 commit 4350a96

File tree

5 files changed

+115
-30
lines changed

5 files changed

+115
-30
lines changed

lib/FrontendTool/FrontendTool.cpp

Lines changed: 102 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
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+
495565
bool 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

512582
void 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 << "\ndepends on ";
591+
printModule(m, os);
592+
if (!m->isNonSwiftModule()) {
593+
os << " (isOverlay = " << isOverlayOfClangModule(m) << ")";
594+
}
595+
os << "\ndepends on ";
522596
}
523597
printModule(module, os); os << '\n';
524598
});

test/Driver/Inputs/imported_modules/ComplexModuleGraph2/DaemonKit.h renamed to test/Driver/Inputs/imported_modules/ComplexModuleGraph2/DaemonKit/DaemonKit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#import <DaemonKit/ViolateSecondLawOfThermodynamics.h>
2+
13
#import <CoreDaemon.h>
24

35
struct DaemonPair {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#import <CoreDaemon.h>
2+
3+
struct MaxwellsDaemon {
4+
Runner *doorOperation;
5+
};

test/Driver/Inputs/imported_modules/ComplexModuleGraph2/module.modulemap

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ module CoreDaemon {
1414
}
1515

1616
module DaemonKit {
17-
header "DaemonKit.h"
17+
umbrella header "DaemonKit/DaemonKit.h"
18+
explicit module ViolateSecondLawOfThermodynamics {
19+
header "DaemonKit/ViolateSecondLawOfThermodynamics.h"
20+
export *
21+
}
1822
export *
1923
}
2024

test/Driver/loaded_module_trace_directness_2.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public func runBoth(_ pair: DaemonKit.DaemonPair) {
114114
#endif
115115
#if V2
116116
import DaemonKit
117-
public func noop(_: CoreDaemon.Daemon) {}
117+
public func noop(_: CoreDaemon.Daemon, _: MaxwellsDaemon) {}
118118
#endif
119119
#if V3
120120
import CoreDaemon

0 commit comments

Comments
 (0)