Skip to content

Commit fec0f3e

Browse files
authored
Merge pull request #1166 from rjmansfield/fix-shared-pch-staleness
Fix shared PCH staleness issues due to missing target ordering.
2 parents dfb0982 + c7293ec commit fec0f3e

12 files changed

Lines changed: 169 additions & 6 deletions

File tree

Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,17 +1611,24 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
16111611
let (precompFile, pchInfoAny) = delegate.createOrReuseSharedNodeWithIdentifier(sharingIdentString) { () -> (any PlannedNode, any Sendable) in
16121612
// This block is invoked only when we need to create a task to precompile a header.
16131613

1614+
// Ordering gate: a workspace level task will wire together all sharing targets' startCompilingNode.
1615+
let orderingNode = delegate.createVirtualNode("shared-pch-ordering-\(sharingIdentHashValue)")
1616+
16141617
// Construct the full path of the precomp file, which includes the hash to make it unique.
16151618
// FIXME: This needs to actually include a hash code, and it should ideally (for comparison reasons) be the same as Xcode.
16161619
let precompPath = baseCachePath.join("SharedPrecompiledHeaders").join("\(sharingIdentHashValue)").join(prefixHeader.basename + ".gch")
16171620

16181621
// Start by invoking our logic to create a task to precompile the prefix header.
1619-
let pchInfo = self.precompile(cbc, delegate, headerPath: prefixHeader, language: language, inputFileType: inputFileType, extraArgs: perFileFlags, precompPath: precompPath, clangInfo: clangInfo)
1622+
let pchInfo = self.precompile(cbc, delegate, headerPath: prefixHeader, language: language, inputFileType: inputFileType, extraArgs: perFileFlags, precompPath: precompPath, clangInfo: clangInfo, additionalOrderingInputs: [orderingNode])
16201623

16211624
// Return the output node for the precomp file.
16221625
return (delegate.createNode(precompPath), pchInfo)
16231626
}
16241627

1628+
// Ensure shared ProcessPCH depends on all consuming targets upstream headers.
1629+
let orderingNode = delegate.createVirtualNode("shared-pch-ordering-\(sharingIdentHashValue)")
1630+
delegate.registerSharedPCHOrderingDependency(sharingIdentString, orderingNode: orderingNode)
1631+
16251632
let pchInfo = pchInfoAny as! ClangPrefixInfo.PCHInfo
16261633

16271634
// Include the precomp file.
@@ -1659,7 +1666,7 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
16591666
}
16601667

16611668
/// Specialized function that creates a task for precompiling a particular header.
1662-
private func precompile(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, headerPath: Path, language: GCCCompatibleLanguageDialect, inputFileType: FileTypeSpec, extraArgs: [String], precompPath: Path, clangInfo: DiscoveredClangToolSpecInfo?) -> ClangPrefixInfo.PCHInfo {
1669+
private func precompile(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, headerPath: Path, language: GCCCompatibleLanguageDialect, inputFileType: FileTypeSpec, extraArgs: [String], precompPath: Path, clangInfo: DiscoveredClangToolSpecInfo?, additionalOrderingInputs: [any PlannedNode] = []) -> ClangPrefixInfo.PCHInfo {
16631670

16641671
// FIXME: Disabled for now because some projects manages to end up getting here with multiple files. <rdar://problem/23682348> Project groups multiple .c files together for C compiler?
16651672
//let input = cbc.input
@@ -1773,7 +1780,8 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
17731780
// If explicit modules are enabled we need to create the scan task first
17741781
let extraInputs: [Path]
17751782
if action != nil {
1776-
delegate.createTask(type: self, payload: payload, ruleInfo: ["ScanDependencies"] + ruleInfo.dropFirst(), additionalSignatureData: additionalSignatureData, commandLine: ["builtin-ScanDependencies", "-o", scanningOutput.str, "--"] + commandLine, additionalOutput: responseFileAdditionalOutput, environment: environmentBindings, workingDirectory: compilerWorkingDirectory(cbc), inputs: inputPaths, outputs: [scanningOutput], action: delegate.taskActionCreationDelegate.createClangScanTaskAction(), execDescription: "Scan dependencies of \(headerPath.basename)", preparesForIndexing: true, enableSandboxing: enableSandboxing, additionalTaskOrderingOptions: [.compilationForIndexableSourceFile], usesExecutionInputs: usesExecutionInputs)
1783+
let scanInputNodes: [any PlannedNode] = inputPaths.map(delegate.createNode) + additionalOrderingInputs
1784+
delegate.createTask(type: self, payload: payload, ruleInfo: ["ScanDependencies"] + ruleInfo.dropFirst(), additionalSignatureData: additionalSignatureData, commandLine: ["builtin-ScanDependencies", "-o", scanningOutput.str, "--"] + commandLine, additionalOutput: responseFileAdditionalOutput, environment: environmentBindings, workingDirectory: compilerWorkingDirectory(cbc), inputs: scanInputNodes, outputs: [delegate.createNode(scanningOutput)], action: delegate.taskActionCreationDelegate.createClangScanTaskAction(), execDescription: "Scan dependencies of \(headerPath.basename)", preparesForIndexing: true, enableSandboxing: enableSandboxing, additionalTaskOrderingOptions: [.compilationForIndexableSourceFile], usesExecutionInputs: usesExecutionInputs)
17771785

17781786
extraInputs = [scanningOutput]
17791787
} else {
@@ -1782,7 +1790,8 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
17821790

17831791
// Finally, create the task.
17841792
let execDescription = archSpecificExecutionDescription(cbc.scope.namespace.parseString("Precompile \(headerPath.basename)"), cbc, delegate)
1785-
delegate.createTask(type: self, dependencyData: dependencyData, payload: payload, ruleInfo: ruleInfo, additionalSignatureData: additionalSignatureData, commandLine: byteStringCommandLine, additionalOutput: additionalOutput, environment: environmentBindings, workingDirectory: compilerWorkingDirectory(cbc), inputs: inputPaths + extraInputs, outputs: outputPaths, action: action ?? delegate.taskActionCreationDelegate.createDeferredExecutionTaskActionIfRequested(userPreferences: cbc.producer.userPreferences), execDescription: execDescription, enableSandboxing: enableSandboxing, usesExecutionInputs: usesExecutionInputs, showEnvironment: true)
1793+
let allInputNodes: [any PlannedNode] = (inputPaths + extraInputs).map(delegate.createNode) + additionalOrderingInputs
1794+
delegate.createTask(type: self, dependencyData: dependencyData, payload: payload, ruleInfo: ruleInfo, additionalSignatureData: additionalSignatureData, commandLine: byteStringCommandLine, additionalOutput: additionalOutput, environment: environmentBindings, workingDirectory: compilerWorkingDirectory(cbc), inputs: allInputNodes, outputs: outputPaths.map(delegate.createNode), mustPrecede: [], action: action ?? delegate.taskActionCreationDelegate.createDeferredExecutionTaskActionIfRequested(userPreferences: cbc.producer.userPreferences), execDescription: execDescription, preparesForIndexing: false, enableSandboxing: enableSandboxing, llbuildControlDisabled: false, additionalTaskOrderingOptions: [], usesExecutionInputs: usesExecutionInputs, showEnvironment: true)
17861795

17871796
// If the object file verifier is enabled and we are building with explicit modules, also create a job to produce an adjacent PCH using implicit modules.
17881797
if cbc.scope.evaluate(BuiltinMacros.CLANG_ENABLE_EXPLICIT_MODULES_OBJECT_FILE_VERIFIER) && action != nil {

Sources/SWBCore/TaskGeneration.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,9 @@ public protocol TaskGenerationDelegate: AnyObject, TargetDiagnosticProducingDele
820820
/// FIXME: The name of this method is also a bit awkward. We could probably do better.
821821
func createOrReuseSharedNodeWithIdentifier(_ ident: String, creator: () -> (any PlannedNode, any Sendable)) -> (any PlannedNode, any Sendable)
822822

823+
/// Register the current target as a consumer of the shared PCH.
824+
func registerSharedPCHOrderingDependency(_ ident: String, orderingNode: any PlannedNode)
825+
823826
/// Adds the accessed path to the list of paths which invalidate the build description.
824827
func access(path: Path)
825828

Sources/SWBTaskConstruction/ProductPlanning/BuildPlan.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
package import SWBUtil
1414
package import SWBCore
15+
import Synchronization
1516

1617
/// Information describing a complete build plan request.
1718
package struct BuildPlanRequest: Sendable {
@@ -187,6 +188,24 @@ package final class BuildPlan: StaleFileRemovalContext {
187188
}
188189

189190
await aggregationQueue.sync{ }
191+
192+
// Create shared PCH ordering gate tasks. This must happen after all per target
193+
// generateTasks() calls complete since those calls populate sharedPCHOrdering.
194+
if !globalProductPlan.sharedPCHOrdering.isEmpty {
195+
let workspaceResultContext = productPlanResultContexts.last!
196+
globalProductPlan.sharedPCHOrdering.forEach { (_, ordering) in
197+
let inputs = ordering.inputs.withLock { Array($0) }
198+
guard !inputs.isEmpty else {
199+
return
200+
}
201+
let gateTask = delegate.createGateTask(inputs, output: ordering.orderingNode, name: ordering.orderingNode.name, mustPrecede: []) { _ in }
202+
aggregationQueue.async {
203+
workspaceResultContext.addPlannedTasks([gateTask])
204+
}
205+
}
206+
await aggregationQueue.sync{ }
207+
}
208+
190209
if delegate.cancelled {
191210
// Reset any deferred producers, which may participate in cycles.
192211
for context in productPlanResultContexts {

Sources/SWBTaskConstruction/ProductPlanning/ProductPlan.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider
4747
/// This is a limited mechanism for concurrent task construction processes to coordinate on the production of a shared node.
4848
let sharedIntermediateNodes = Registry<String, (any PlannedNode, any Sendable)>()
4949

50+
/// Tracks ordering dependencies for shared PCHs.
51+
final class SharedPCHOrdering: Sendable {
52+
let orderingNode: any PlannedNode
53+
let inputs: SWBMutex<[any PlannedNode]>
54+
init(orderingNode: any PlannedNode, inputs: consuming SWBMutex<[any PlannedNode]>) {
55+
self.orderingNode = orderingNode
56+
self.inputs = inputs
57+
}
58+
}
59+
let sharedPCHOrdering = Registry<String, SharedPCHOrdering>()
60+
5061
let delegate: any GlobalProductPlanDelegate
5162

5263
struct VFSContentsKey: Hashable {

Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ private final class SourcesPhaseBasedTaskGenerationDelegate: TaskGenerationDeleg
180180
return delegate.createOrReuseSharedNodeWithIdentifier(ident, creator: creator)
181181
}
182182

183+
func registerSharedPCHOrderingDependency(_ ident: String, orderingNode: any PlannedNode) {
184+
delegate.registerSharedPCHOrderingDependency(ident, orderingNode: orderingNode)
185+
}
186+
183187
func access(path: Path) {
184188
producer.access(path: path)
185189
}

Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/ModuleMapTaskProducer.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ private final class ModuleMapPhaseBasedTaskGenerationDelegate: TaskGenerationDel
127127
return delegate.createOrReuseSharedNodeWithIdentifier(ident, creator: creator)
128128
}
129129

130+
func registerSharedPCHOrderingDependency(_ ident: String, orderingNode: any PlannedNode) {
131+
delegate.registerSharedPCHOrderingDependency(ident, orderingNode: orderingNode)
132+
}
133+
130134
func access(path: Path) {
131135
delegate.access(path: path)
132136
}

Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,6 +1713,17 @@ class ProducerBasedTaskGenerationDelegate: TaskGenerationDelegate {
17131713
return context.globalProductPlan.sharedIntermediateNodes.getOrInsert(ident, creator)
17141714
}
17151715

1716+
func registerSharedPCHOrderingDependency(_ ident: String, orderingNode: any PlannedNode) {
1717+
guard let configuredTarget = context.configuredTarget,
1718+
let targetGateNodes = context.globalProductPlan.targetGateNodes[configuredTarget] else {
1719+
return
1720+
}
1721+
let ordering = context.globalProductPlan.sharedPCHOrdering.getOrInsert(ident) {
1722+
GlobalProductPlan.SharedPCHOrdering(orderingNode: orderingNode, inputs: SWBMutex([]))
1723+
}
1724+
ordering.inputs.withLock { $0.append(targetGateNodes.startCompilingNode) }
1725+
}
1726+
17161727
func access(path: Path) {
17171728
producer.access(path: path)
17181729
}

Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ package class CapturingTaskGenerationDelegate: TaskGenerationDelegate, CoreClien
101101
package func createOrReuseSharedNodeWithIdentifier(_ ident: String, creator: () -> (any PlannedNode, any Sendable)) -> (any PlannedNode, any Sendable) {
102102
return sharedIntermediateNodes.getOrInsert(ident, creator)
103103
}
104+
package func registerSharedPCHOrderingDependency(_ ident: String, orderingNode: any PlannedNode) {}
104105
package func access(path: Path) {}
105106
package func readFileContents(_ path: Path) throws -> ByteString { return ByteString() }
106107
package func fileExists(at path: Path) -> Bool { return true }

Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ private final class CapturingTaskGenerationDelegate: TaskGenerationDelegate {
9696
{
9797
return sharedIntermediateNodes.getOrInsert(ident, creator)
9898
}
99+
func registerSharedPCHOrderingDependency(_ ident: String, orderingNode: any PlannedNode) {}
99100
func access(path: Path) {}
100101
func readFileContents(_ path: Path) throws -> ByteString { return ByteString() }
101102
func recordAttachment(contents: SWBUtil.ByteString) -> SWBUtil.Path {

Tests/SWBCoreTests/CommandLineSpecTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,8 @@ import SWBMacro
959959
#expect(precompileTask.ruleInfo == ["ProcessPCH", Path.root.join("tmp/precomps/SharedPrecompiledHeaders/\(pchHash)/prefix.h.gch").str, Path.root.join("tmp/prefix.h").str, "normal", "x86_64", "c", "com.apple.compilers.llvm.clang.1_0"])
960960
#expect(precompileTask.execDescription == "Precompile prefix.h (x86_64)")
961961
precompileTask.checkInputs([
962-
.path(Path.root.join("tmp/prefix.h").str)
962+
.path(Path.root.join("tmp/prefix.h").str),
963+
.any,
963964
])
964965
precompileTask.checkOutputs([
965966
.path(Path.root.join("tmp/precomps/SharedPrecompiledHeaders/\(pchHash)/prefix.h.gch").str)

0 commit comments

Comments
 (0)