Skip to content

Refactor IncrementalCompilationState to compute initial state before job-generation. #621

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 1 commit into from
May 5, 2021

Conversation

artemcm
Copy link
Contributor

@artemcm artemcm commented Apr 30, 2021

In the near future, planning of PrecompileModuleDependenciesJobs, for explicit module dependencies' compilation jobs, will require access to the incremental state in order to incrementalize expensive actions such as dependency scanning.

This change refactors the creation of incremental compilation state in order to move computation of the initial state (dependency graph) to occur before job-generation. The first wave of jobs is then computed after job-generation, using the initial state, and the set of jobs in the plan, as input.

  • Separate InitialIncrementalStateComputer to run early, by the Driver, to compute the dependency graph.
  • Introduce FirstWaveComputer which the the constructor of the IncrementalCompilationContext uses to compute the mandatoryJobsInOrder set of jobs the executors must run.

@artemcm artemcm requested review from davidungar and CodaFi April 30, 2021 20:34
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.

Testing, one two three

) throws {
guard driver.shouldAttemptIncrementalCompilation else { 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.

Not your doing, but in a prior PR, someone else moved shouldAttemptIncrementalCompilation into the driver, which diffused the knowledge about incrementality. It would be great to find a better place for it.

Comment on lines 20 to 45
guard shouldAttemptIncrementalCompilation else { return nil }

guard let outputFileMap = outputFileMap else {
diagnosticEngine.emit(.warning_incremental_requires_output_file_map)
return nil
}

let reporter : IncrementalCompilationState.Reporter?
if options.contains(.showIncremental) {
reporter = IncrementalCompilationState.Reporter(diagnosticEngine: diagnosticEngine,
outputFileMap: outputFileMap)
} else {
reporter = nil
}

guard let buildRecordInfo = buildRecordInfo else {
reporter?.reportDisablingIncrementalBuild("no build record path")
return nil
}

// FIXME: This should work without an output file map. We should have
// another way to specify a build record and where to put intermediates.
let maybeBuildRecord =
buildRecordInfo.populateOutOfDateBuildRecord(inputFiles: 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 code encapsulates knowledge about what incremental builds need: an output file map, a build record info, etc. How about moving it (or much of it) into the InitialStateComputer, either the constructor, or in a static function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, it is now a static method on IncrementalCompilationState.

}

/// Builds the `InitialState`
/// Also bundles up an bunch of configuration info
extension IncrementalCompilationState {

public struct InitialStateComputer {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not your doing, at all, but I really want to rename the InitialStateComputer. It's my name and a horrible one. "Initial" and "state" are way too generic, and "computer" isn't much better. What is it really doing? Looking at the inputs, constructing a graph. Is it setting up for an incremental build? IncrementalBuildSetup? InputChangeAndDependencyGraphConstructor? Can you come up with something good?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tentatively going with IncrementalDependencyAndInputSetup, I think it better reflects what it is computing. 😬

Copy link
Contributor

Choose a reason for hiding this comment

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

yes!

@@ -140,7 +118,6 @@ public final class IncrementalCompilationState {
}

// MARK: - Initial State

extension IncrementalCompilationState {
/// The initial state of an incremental compilation plan.
@_spi(Testing) public struct InitialState {
Copy link
Contributor

Choose a reason for hiding this comment

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

While I'm kvetching, I'm wondering if this struct is pulling its own weight?

@@ -136,6 +141,7 @@ extension Driver {
)
}

// Extract options relevant to incremental builds
mutating func computeIncrementalOptions() -> IncrementalCompilationState.Options {
Copy link
Contributor

Choose a reason for hiding this comment

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

I hate to sound like a broken record, and not your fault, but computeIncrementalOptions is in the wrong place, too, wrt centralizing the knowledge about incremental builds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved. Thanks for pointing it out.

Copy link
Contributor

Choose a reason for hiding this comment

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

tx!

moduleDependencyGraph.phase = .updatingAfterCompilation


let skippedInputs = computeSkippedCompilationInputs(
Copy link
Contributor

Choose a reason for hiding this comment

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

It feels like this computation belongs in the other place, the initial-wobulator-computer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As we discussed in the chat: I very much would like to have moved this to the initial state setup, but the reliance of this code on the input->outputs mapping that is a part of compilation jobs prevents us from being able to do so with the current planning/jobs architecture.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed

return skippedInputs
}

private func sortByCommandLineOrder(_ inputs: Set<TypedVirtualPath>) -> [TypedVirtualPath] {
Copy link
Contributor

Choose a reason for hiding this comment

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

same here, feels like the wrong place.


/// Encapsulates information about an input the driver has determined has
/// changed in a way that requires an incremental rebuild.
struct ChangedInput {
Copy link
Contributor

Choose a reason for hiding this comment

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

wrong place?

}

// Find the inputs that have changed since last compilation, or were marked as needed a build
private func computeChangedInputs(
Copy link
Contributor

Choose a reason for hiding this comment

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

wrong place?

/// Also bundles up an bunch of configuration info
// Initial incremental state computation
extension Driver {
func computeInitialIncrementalState(options: IncrementalCompilationState.Options)
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't feel right to put this in the Driver, not your fault, I know.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, moved to IncrementalCompilationState as a static.

@artemcm artemcm force-pushed the IncrementalRefactorForExplicit branch from 259fc05 to c0e5cf3 Compare May 4, 2021 18:51
@artemcm
Copy link
Contributor Author

artemcm commented May 4, 2021

@swift-ci please test

@artemcm
Copy link
Contributor Author

artemcm commented May 5, 2021

@davidungar, the current revision addresses the above comments.
Thanks for the feedback.

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.

Thank you. Overall much much better, IMO. Just a few small thoughts; see what you think.

@@ -69,81 +69,48 @@ public final class IncrementalCompilationState {
/// be protected by the confinement queue.
private var skippedCompileGroups = [TypedVirtualPath: CompileJobGroup]()

// MARK: - Creating IncrementalCompilationState if possible
// MARK: - Creating IncrementalCompilationState if possible
/// Return nil if not compiling incrementally
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this comment still correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope it got stale. Thanks for the catch.

Comment on lines +97 to +103
if let initialState = initialIncrementalState {
incrementalCompilationState =
try IncrementalCompilationState(driver: &self, jobsInPhases: jobsInPhases,
initialState: initialState)
} else {
incrementalCompilationState = nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! Here's an interesting Swift-style question that I wonder about myself:
Is the "if" construction more or less preferable than the "map" construction:
let incrementalCompilationState = initialIncrementalState.map {...}

I think it depends on whether the reader is comfortable/familiar with "map", but I don't know. Coming from C++, one would favor "if", coming from Haskel (which I don't know), one might favor "map"?

Copy link
Contributor

Choose a reason for hiding this comment

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

Feel free to choose either--just mentioning it.

@@ -104,7 +137,8 @@ extension Driver {

try addPrecompileModuleDependenciesJobs(addJob: addJobBeforeCompiles)
Copy link
Contributor

Choose a reason for hiding this comment

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

BTW, nice name!

try IncrementalCompilationState.computeIncrementalStateForPlanning(driver: &self)

// Compute all jobs required for this module
let jobsInPhases = try computeBuildJobsInPhases()
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: computeBuildJobsInPhases can be misread as "compute and build the jobs (in phases)".
Maybe something like "computeJobsForPhasedStandardBuild" ??

Copy link
Contributor

Choose a reason for hiding this comment

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

BTW, I like how this reads with the middle factored out. Thank you!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made the name more explanatory and updated the comment.

}

/// Consturct all jobs required for this module
mutating func computeBuildJobsInPhases() throws -> JobsInPhases {
Copy link
Contributor

Choose a reason for hiding this comment

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

private?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, changed.

)
}

/// Consturct all jobs required for this module
Copy link
Contributor

Choose a reason for hiding this comment

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

What does the comment say that the name of the function does not? I would either put more info in the comment, or eliminate it.
Why is this function here? The comment could be:
"After computing the basic incremental state, plan a build as if it were not incremental, so that the plan can be modified for incrementality"
or some such.

Comment on lines +126 to +128
return Self(options, outputFileMap,
BuildRecordInfo.mock(diagnosticsEngine, outputFileMap),
nil, nil, [], fileSystem, diagnosticsEngine)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice

@davidungar
Copy link
Contributor

@davidungar, the current revision addresses the above comments.
Thanks for the feedback.

My pleasure!

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.

Much better! Just a few nits you can take or leave.

…e job-generation.

In the near future, planning of `PrecompileModuleDependenciesJobs`, for explicit module dependencies, will require access to the incremental state in order to incrementalize expensive actions such as dependency scanning.

This change refactors the creation of incremental compilation state in order to move computation of the initial state (dependency graph) to occur before job-generation.
The first wave of jobs is then computed after job-generation, using the initial state, and the set of jobs in the plan, as input.

- Separate `InitialIncrementalStateComputer` to run early, by the `Driver`, to compute the dependency graph and the set of changed inputs. Rename `InitialIncrementalStateComputer` into `IncrementalDependencyAndInputSetup`.
- Introduce `FirstWaveComputer` which the constructor of the `IncrementalCompilationContext` uses to compute the `mandatoryJobsInOrder` set of jobs the executors *must* run.
@artemcm artemcm force-pushed the IncrementalRefactorForExplicit branch from c0e5cf3 to ccc4024 Compare May 5, 2021 17:09
@artemcm
Copy link
Contributor Author

artemcm commented May 5, 2021

@swift-ci please test

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