Skip to content

Read and Write The Driver's Dependency Graph For Cross-Module Builds #467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 5, 2021

Conversation

CodaFi
Copy link
Contributor

@CodaFi CodaFi commented Feb 4, 2021

The refactoring of the incremental compilation state class is in three pieces:

  1. Formalize the type of the InitialState computed by the initializer
  2. Internalize computeModuleDependencyGraph and instead make a unified computeInitialState entrypoint (we'll need this for testing later)
  3. Divide the status quo and cross-module initial state computations. There is no change to the status quo outside of the breakup of Driver.getBuildInfo

The cross-module path is slightly different than the status quo. For one, the incremental build need not fail entirely when either the build record or dependency graph cannot be read. In fact, it is a requirement that the incremental build proceed but schedule every single input file to rebuild, otherwise we won't have a module dependency graph to serialize!

As an aside, we ought to consider unifying the build record with the module dependency graph state we're writing here. They're read and written in lock-step when cross-module builds are turned on.

@CodaFi CodaFi requested a review from davidungar February 4, 2021 22:57
@CodaFi
Copy link
Contributor Author

CodaFi commented Feb 4, 2021

@swift-ci test

Copy link
Contributor

@davidungar davidungar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about some of the premises here, in particular the coupling of cross-module with ModuleDepGraph serialization. Want to have a chance to discuss before it lands.

let diagnosticEngine = driver.diagnosticEngine
// If we cannot compute this path, it means the build record path was not
// provided to the driver, which our caller detects for us.
let absPath = driver.driverDependencyGraphPath!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the past, we've run into trouble with disallowing relative paths.

///
/// FIXME: This is a little ridiculous. We could probably just replace the
/// build record outright with a serialized format.
var driverDependencyGraphPath: AbsolutePath? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect that this has to be a VirtualPath for the tests to run.

Comment on lines 163 to 165
let compileGroups =
Dictionary(uniqueKeysWithValues:
jobsInPhases.compileGroups.map { ($0.primaryInput, $0) })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to move this computation to just before using compileGroups

Comment on lines +179 to +186
// We failed to read the components we need for the incremental build -
// just schedule every file to build!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some reason not to try a build reading the swiftDeps?
This raises another question: as it is, the code has two paths:

  1. status quo - don't try to read the saved graph, don't try to exploit incrementalExternalDeps
  2. cross module - do both

In other words, maybe there are four logical possibilities--I haven't thought about if it's best to collapse it to these two.

Comment on lines 193 to 198
let mandatoryJobsInOrder = try
jobsInPhases.beforeCompiles +
driver.formBatchedJobs(
mandatoryCompileGroupsInOrder.flatMap {$0.allJobs()},
showJobLifecycle: driver.showJobLifecycle)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this computation is submerged in the other case, I'm not sure about seeing it here; need to think on it. Oh, I see, this is in an error-recovery path.

Comment on lines 220 to 274
private static func computeInitialStateForStatusQuoBuild(
_ options: Options,
_ jobsInPhases: JobsInPhases,
_ outputFileMap: OutputFileMap,
_ buildRecordInfo: BuildRecordInfo,
_ driver: inout Driver,
_ reporter: Reporter?
) throws -> InitialState? {
precondition(!options.contains(.enableCrossModuleIncrementalBuild))
let diagnosticEngine = driver.diagnosticEngine

// FIXME: This should work without an output file map. We should have
// another way to specify a build record and where to put intermediates.
guard let outOfDateBuildRecord = buildRecordInfo.populateOutOfDateBuildRecord(
inputFiles: driver.inputFiles, reporter: reporter)
else {
return nil
}

let missingInputs = Set(outOfDateBuildRecord.inputInfos.keys).subtracting(driver.inputFiles.map {$0.file})
guard missingInputs.isEmpty else {
if let reporter = reporter {
reporter.report(
"Incremental compilation has been disabled, " +
" because the following inputs were used in the previous compilation but not in this one: "
+ missingInputs.map {$0.basename} .joined(separator: ", "))
}
return nil
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I wrote above, I'm not sure about this factoring. I'd be tempted to:
Read the serialized Mod dep graph in any case,
and go from there. But maybe that's too much instability??

Comment on lines 176 to 173
let outOfDateBuildRecord = buildRecordInfo.populateOutOfDateBuildRecord(
inputFiles: driver.inputFiles, reporter: reporter)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This same computation and the aborting incremental building appears in both the statusQuo and crossModule code. Feels like it should be in a caller and passed in if non-null.

// MARK: - Serialization

extension IncrementalCompilationState {
@_spi(Testing) public func writeDependencyGraph() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other places the _spi is commented out. We went through some kerfuffle wrt testable vs _spi. I'm not sure what the current state is.

Comment on lines +857 to +879
// If the cross-module build is not enabled, the status quo dictates we
// not emit this file.
guard self.options.contains(.enableCrossModuleIncrementalBuild) else {
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be tempted to do so anyway, speeding up the non-cross-module case.

Comment on lines 59 to 88
/// Builds an empty `ModuleDependencyGraph` for a compilation with the
/// given input files.
static func buildEmptyGraph<Inputs: Sequence>(
for inputFiles: Inputs,
diagnosticEngine: DiagnosticsEngine,
outputFileMap: OutputFileMap,
options: IncrementalCompilationState.Options,
reporter: IncrementalCompilationState.Reporter?
) -> ModuleDependencyGraph
where Inputs.Element == TypedVirtualPath
{
let emptyGraph = ModuleDependencyGraph(diagnosticEngine: diagnosticEngine,
reporter: reporter,
options: options)

for input in inputFiles {
guard let swiftDepsFile = outputFileMap.existingOutput(inputFile: input.file,
outputType: .swiftDeps) else {
continue
}

assert(swiftDepsFile.extension == FileType.swiftDeps.rawValue)
let typedSwiftDepsFile = TypedVirtualPath(file: swiftDepsFile, type: .swiftDeps)
let dependencySource = ModuleDependencyGraph.DependencySource(typedSwiftDepsFile)
emptyGraph.inputDependencySourceMap[input] = dependencySource
}
return emptyGraph
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we ought to need to do this; just fall back to the buildInitialGraph path. Am I missing something?

@CodaFi CodaFi force-pushed the reader-writer-locks branch 2 times, most recently from 010901a to 1ecfe52 Compare February 5, 2021 02:30
The refactoring of the incremental compilation state class is in three pieces:

1) Formalize the type of the InitialState computed by the initializer
2) Internalize computeModuleDependencyGraph and instead make a unified computeInitialState entrypoint (we'll need this for testing later)
3) Divide the status quo and cross-module initial state computations. There is no change to the status quo outside of the breakup of Driver.getBuildInfo

The cross-module path is slightly different than the status quo. For one, the incremental build need not fail entirely when either the build record or dependency graph cannot be read. In fact, it is a requirement that the incremental build proceed but schedule every single input file to rebuild, otherwise we won't have a module dependency graph to serialize!

As an aside, we ought to consider unifying the build record with the module dependency graph state we're writing here. They're read and written in lock-step when cross-module builds are turned on.
@CodaFi CodaFi force-pushed the reader-writer-locks branch from 1ecfe52 to 93d710a Compare February 5, 2021 02:37
Copy link
Contributor

@davidungar davidungar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@CodaFi
Copy link
Contributor Author

CodaFi commented Feb 5, 2021

@swift-ci test

@CodaFi CodaFi merged commit a0fa5b5 into swiftlang:main Feb 5, 2021
@CodaFi CodaFi deleted the reader-writer-locks branch February 5, 2021 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants