Skip to content

Incremental: Don't rebuild everything when only emitting the module#2102

Open
benb wants to merge 1 commit intoswiftlang:mainfrom
benb:swiftmodule-only-incremental
Open

Incremental: Don't rebuild everything when only emitting the module#2102
benb wants to merge 1 commit intoswiftlang:mainfrom
benb:swiftmodule-only-incremental

Conversation

@benb
Copy link
Copy Markdown
Contributor

@benb benb commented Mar 11, 2026

Currently, if we want to emit a swiftmodule / header only in incremental mode, swift-driver will schedule the work for rebuilding the object outputs regardless (although it won't actually produce that output).

This PR makes these changes:

  1. Planning.swift: Changes logic when deciding when we can skip object file jobs. Explicit module builds can be scheduled without passing this .driverExplicitModuleBuild option (i.e. explicitSwiftModuleMap), and as far as I can tell checking for explicit module builds is overly conservative here anyway.
  2. EmitModuleJob.swift: makes a check stricter so .d files can still be generated even though we won't now do the rest of the work
  3. Updates tests to reflect the reduced work scheduled.

I might be barking up the wrong tree here, if so I'd appreciate any guidance on how else we can resolve this.

XCTAssertEqual(plannedJobs.count, 3)
XCTAssertEqual(Set(plannedJobs.map { $0.kind }), Set([.emitModule, .compile]))
XCTAssertEqual(plannedJobs.count, 1)
XCTAssertEqual(Set(plannedJobs.map { $0.kind }), Set([.emitModule]))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not simplify this?

Suggested change
XCTAssertEqual(Set(plannedJobs.map { $0.kind }), Set([.emitModule]))
XCTAssertEqual(plannedJobs[0].kind, .emitModule)

Comment thread Sources/SwiftDriver/Jobs/Planning.swift Outdated
// built separately, unless we need per-file outputs like const values
// that only compile jobs can produce.
let canSkipIfOnlyModule = compilerOutputType == .swiftModule && emitModuleSeparately
&& !parsedOptions.hasArgument(.emitConstValues)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there some classification that we can use? "like const values" ... are there other features would also require compile jobs?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1 to this, this doesn't seem like a general approach, there are many other kinds of outputs which may be requested on a driver invocation which are only produced by object compilation (not emit-module) tasks.

I think we should trust the compilerOutputType and for the case you are trying to catch here perhaps we instead have the driver diagnose when the invocation specifies a module output compiler "mode" but also specifies requiring some outputs which require compilation tasks which produce object files.

We select the compiler output type here:

private static func determinePrimaryOutputs(

Based on which compilation "mode" flag is used. Admittedly this part of the driver logic seems quite messy because -emit-module seems to be considered a mode only if no other .modes flag was specified.

But, if we are in a compilerOutputType == .swiftModule scenario, -emit-module doesn't mean "among other things, emit a module", but rather "this swiftc invocation's intent is to emit a module". It's reasonable that some outputs will not be compatible with this mode.

An alternative would be to extend determinePrimaryOutputs to select a different compilerOutputType when things like .emitConstValues are specified.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Let me paraphrase you, and you can tell me if I have this right:

Given the current implementation of determinePrimaryOutputs, if we have compilerOutputType == .swiftModule then we must be in an de facto emit module 'mode' (albeit this is not an actual Option.Group.Modes mode like .emitExecutable).

Therefore one way of looking at is: When only specifying emit-module you are not really expecting to receive other outputs here, so we don't need to worry about them here.

Option two would be to catch other outputs (supplementaryOutputs?) in determinePrimaryOutputs and change the compilerOutputType to reflect this.

My opinion here is that it seems weird to make a change so that Swiftmodule is not be the 'primary output' in these cases - it really is the primary output! So I would be minded to 'trust' the compilerOutputType and remove this change.

Does this make sense?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah that's a good summary and I agree with your conclusion. 👍🏼

Comment on lines 60 to 62
if emitModuleSeparately && compilerOutputType != .swiftModule {
return
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't understand this - how is this different from L45-47?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess this is meant to relax the check on this line, but it does make it fully redundant in the process.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ach, I forgot about this weirdness. Yes, it's redundant this way. Kicking myself.

Comment on lines 60 to 62
if emitModuleSeparately && compilerOutputType != .swiftModule {
return
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess this is meant to relax the check on this line, but it does make it fully redundant in the process.

Comment thread Sources/SwiftDriver/Jobs/Planning.swift Outdated
// built separately, unless we need per-file outputs like const values
// that only compile jobs can produce.
let canSkipIfOnlyModule = compilerOutputType == .swiftModule && emitModuleSeparately
&& !parsedOptions.hasArgument(.emitConstValues)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1 to this, this doesn't seem like a general approach, there are many other kinds of outputs which may be requested on a driver invocation which are only produced by object compilation (not emit-module) tasks.

I think we should trust the compilerOutputType and for the case you are trying to catch here perhaps we instead have the driver diagnose when the invocation specifies a module output compiler "mode" but also specifies requiring some outputs which require compilation tasks which produce object files.

We select the compiler output type here:

private static func determinePrimaryOutputs(

Based on which compilation "mode" flag is used. Admittedly this part of the driver logic seems quite messy because -emit-module seems to be considered a mode only if no other .modes flag was specified.

But, if we are in a compilerOutputType == .swiftModule scenario, -emit-module doesn't mean "among other things, emit a module", but rather "this swiftc invocation's intent is to emit a module". It's reasonable that some outputs will not be compatible with this mode.

An alternative would be to extend determinePrimaryOutputs to select a different compilerOutputType when things like .emitConstValues are specified.

// We can skip the compile jobs if all we want is a module when it's
// built separately.
if parsedOptions.hasArgument(.driverExplicitModuleBuild), canSkipIfOnlyModule { return }
if canSkipIfOnlyModule { return }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a change I've wanted to make for a while since it is rather superfluous and leads to an odd difference between implicit and explicit builds.

While I think we should do it, I am having a hard time remembering if this is still an assumption some part of swift-build relies on. @owenv do you recall?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This might break some assumptions but now that we've open sourced swift-build it's a lot easier to test. I'll try to take a closer look soon but I might not get to it until next week

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@owenv Any more thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

sorry, I haven't had a chance to look yet. I'll try to investigate asap

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I ran this through some basic testing and didn't encounter any issues, so I don't think there are any concerns on my end. However, I think I'd recommend running cross-repo testing from the main swiftlang/swift repo before merging this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@owenv Automated testing, or manual? Can you point me at some docs or give me more info?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's automated testing, I kicked it off in swiftlang/swift#79684. That'll run the full compiler test suite on macOS/Linux/Windows which might catch issues the CI in this repo would not

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What can we do to push this forward - is there anything I can do?
Afraid I didn't understand the output you posted at the time, and now it has apparently expired.

@benb benb force-pushed the swiftmodule-only-incremental branch from d6d8c4e to 84410d0 Compare March 13, 2026 17:26
@benb benb force-pushed the swiftmodule-only-incremental branch from 84410d0 to 34af4f3 Compare March 13, 2026 17:28
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.

4 participants