Skip to content

Commit 36645e1

Browse files
authored
All parallel optimizations enabled (#18998)
1 parent e434841 commit 36645e1

24 files changed

+993
-938
lines changed

docs/release-notes/.FSharp.Compiler.Service/11.0.0.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@
1111

1212
### Changed
1313

14+
* Parallel compilation stabilised and enabled by default ([PR #18998](https://github.com/dotnet/fsharp/pull/18998))
15+
1416
### Breaking Changes

src/Compiler/Driver/CompilerConfig.fs

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -805,18 +805,11 @@ type TcConfigBuilder =
805805
doTLR = false
806806
doFinalSimplify = false
807807
optsOn = false
808-
optSettings =
809-
{ OptimizationSettings.Defaults with
810-
processingMode =
811-
if FSharpExperimentalFeaturesEnabledAutomatically then
812-
OptimizationProcessingMode.Parallel
813-
else
814-
OptimizationProcessingMode.Sequential
815-
}
808+
optSettings = OptimizationSettings.Defaults
816809
emitTailcalls = true
817810
deterministic = false
818811
parallelParsing = true
819-
parallelIlxGen = FSharpExperimentalFeaturesEnabledAutomatically
812+
parallelIlxGen = true
820813
emitMetadataAssembly = MetadataAssemblyGeneration.None
821814
preferredUiLang = None
822815
lcid = None
@@ -858,15 +851,11 @@ type TcConfigBuilder =
858851
sdkDirOverride = sdkDirOverride
859852
xmlDocInfoLoader = None
860853
exiter = QuitProcessExiter
861-
parallelReferenceResolution = ParallelReferenceResolution.Off
854+
parallelReferenceResolution = ParallelReferenceResolution.On
862855
captureIdentifiersWhenParsing = false
863856
typeCheckingConfig =
864857
{
865-
TypeCheckingConfig.Mode =
866-
if FSharpExperimentalFeaturesEnabledAutomatically then
867-
TypeCheckingMode.Graph
868-
else
869-
TypeCheckingMode.Sequential
858+
TypeCheckingConfig.Mode = TypeCheckingMode.Graph
870859
DumpGraph = false
871860
}
872861
dumpSignatureData = false

src/Compiler/Driver/CompilerImports.fs

Lines changed: 78 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,8 +1586,10 @@ and [<Sealed>] TcImports
15861586

15871587
#if !NO_TYPEPROVIDERS
15881588
member private tcImports.AttachDisposeTypeProviderAction action =
1589-
CheckDisposed()
1590-
disposeTypeProviderActions.Add action
1589+
tciLock.AcquireLock(fun tcitok ->
1590+
CheckDisposed()
1591+
RequireTcImportsLock(tcitok, disposeTypeProviderActions)
1592+
disposeTypeProviderActions.Add action)
15911593
#endif
15921594

15931595
// Note: the returned binary reader is associated with the tcImports, i.e. when the tcImports are closed
@@ -2247,110 +2249,106 @@ and [<Sealed>] TcImports
22472249
phase2
22482250

22492251
// NOTE: When used in the Language Service this can cause the transitive checking of projects. Hence it must be cancellable.
2250-
member tcImports.TryRegisterAndPrepareToImportReferencedDll
2251-
(ctok, r: AssemblyResolution)
2252-
: Async<(_ * (unit -> AvailableImportedAssembly list)) option> =
2253-
async {
2254-
CheckDisposed()
2255-
let m = r.originalReference.Range
2256-
let fileName = r.resolvedPath
2252+
member tcImports.RegisterAndImportReferencedAssemblies(ctok, nms: AssemblyResolution list) =
2253+
let tryGetAssemblyData (r: AssemblyResolution) =
2254+
async {
2255+
CheckDisposed()
2256+
let m = r.originalReference.Range
2257+
let fileName = r.resolvedPath
22572258

2258-
let! contentsOpt =
2259-
async {
2260-
match r.ProjectReference with
2261-
| Some ilb -> return! ilb.EvaluateRawContents()
2262-
| None -> return ProjectAssemblyDataResult.Unavailable true
2263-
}
2259+
try
22642260

2265-
// If we have a project reference but did not get any valid contents,
2266-
// just return None and do not attempt to read elsewhere.
2267-
match contentsOpt with
2268-
| ProjectAssemblyDataResult.Unavailable false -> return None
2269-
| _ ->
2261+
let! contentsOpt =
2262+
async {
2263+
match r.ProjectReference with
2264+
| Some ilb -> return! ilb.EvaluateRawContents()
2265+
| None -> return ProjectAssemblyDataResult.Unavailable true
2266+
}
22702267

2271-
let assemblyData =
2268+
// If we have a project reference but did not get any valid contents,
2269+
// just return None and do not attempt to read elsewhere.
22722270
match contentsOpt with
2273-
| ProjectAssemblyDataResult.Available ilb -> ilb
2274-
| ProjectAssemblyDataResult.Unavailable _ ->
2275-
let ilModule, ilAssemblyRefs = tcImports.OpenILBinaryModule(ctok, fileName, m)
2276-
RawFSharpAssemblyDataBackedByFileOnDisk(ilModule, ilAssemblyRefs) :> IRawFSharpAssemblyData
2271+
| ProjectAssemblyDataResult.Unavailable false -> return None
2272+
| _ ->
2273+
2274+
match contentsOpt with
2275+
| ProjectAssemblyDataResult.Available ilb -> return Some(r, ilb)
2276+
| ProjectAssemblyDataResult.Unavailable _ ->
2277+
let ilModule, ilAssemblyRefs = tcImports.OpenILBinaryModule(ctok, fileName, m)
2278+
return Some(r, RawFSharpAssemblyDataBackedByFileOnDisk(ilModule, ilAssemblyRefs))
2279+
2280+
with e ->
2281+
errorR (Error(FSComp.SR.buildProblemReadingAssembly (fileName, e.Message), m))
2282+
return None
2283+
}
22772284

2278-
let ilShortAssemName = assemblyData.ShortAssemblyName
2279-
let ilScopeRef = assemblyData.ILScopeRef
2285+
let registerDll (r: AssemblyResolution, assemblyData: IRawFSharpAssemblyData) =
2286+
let m = r.originalReference.Range
2287+
let fileName = r.resolvedPath
2288+
let ilShortAssemName = assemblyData.ShortAssemblyName
2289+
let ilScopeRef = assemblyData.ILScopeRef
22802290

2281-
if tcImports.IsAlreadyRegistered ilShortAssemName then
2282-
let dllinfo = tcImports.FindDllInfo(ctok, m, ilShortAssemName)
2291+
if tcImports.IsAlreadyRegistered ilShortAssemName then
22832292

2284-
let phase2 () =
2285-
[ tcImports.FindCcuInfo(ctok, m, ilShortAssemName, lookupOnly = true) ]
2293+
let phase2 () =
2294+
[ tcImports.FindCcuInfo(ctok, m, ilShortAssemName, lookupOnly = true) ]
22862295

2287-
return Some(dllinfo, phase2)
2288-
else
2289-
let dllinfo =
2290-
{
2291-
RawMetadata = assemblyData
2292-
FileName = fileName
2296+
async { return phase2 () }
2297+
else
2298+
let dllinfo =
2299+
{
2300+
RawMetadata = assemblyData
2301+
FileName = fileName
22932302
#if !NO_TYPEPROVIDERS
2294-
ProviderGeneratedAssembly = None
2295-
IsProviderGenerated = false
2296-
ProviderGeneratedStaticLinkMap = None
2303+
ProviderGeneratedAssembly = None
2304+
IsProviderGenerated = false
2305+
ProviderGeneratedStaticLinkMap = None
22972306
#endif
2298-
ILScopeRef = ilScopeRef
2299-
ILAssemblyRefs = assemblyData.ILAssemblyRefs
2300-
}
2307+
ILScopeRef = ilScopeRef
2308+
ILAssemblyRefs = assemblyData.ILAssemblyRefs
2309+
}
23012310

2302-
tcImports.RegisterDll dllinfo
2311+
tcImports.RegisterDll dllinfo
23032312

2304-
let phase2 =
2305-
if assemblyData.HasAnyFSharpSignatureDataAttribute then
2306-
if not assemblyData.HasMatchingFSharpSignatureDataAttribute then
2307-
errorR (Error(FSComp.SR.buildDifferentVersionMustRecompile fileName, m))
2308-
tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo)
2309-
else
2310-
try
2311-
tcImports.PrepareToImportReferencedFSharpAssembly(ctok, m, fileName, dllinfo)
2312-
with e ->
2313-
error (Error(FSComp.SR.buildErrorOpeningBinaryFile (fileName, e.Message), m))
2314-
else
2313+
let phase2 =
2314+
if assemblyData.HasAnyFSharpSignatureDataAttribute then
2315+
if not assemblyData.HasMatchingFSharpSignatureDataAttribute then
2316+
errorR (Error(FSComp.SR.buildDifferentVersionMustRecompile fileName, m))
23152317
tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo)
2318+
else
2319+
try
2320+
tcImports.PrepareToImportReferencedFSharpAssembly(ctok, m, fileName, dllinfo)
2321+
with e ->
2322+
error (Error(FSComp.SR.buildErrorOpeningBinaryFile (fileName, e.Message), m))
2323+
else
2324+
tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo)
23162325

2317-
return Some(dllinfo, phase2)
2318-
}
2326+
async { return phase2 () }
23192327

2320-
// NOTE: When used in the Language Service this can cause the transitive checking of projects. Hence it must be cancellable.
2321-
member tcImports.RegisterAndImportReferencedAssemblies(ctok, nms: AssemblyResolution list) =
23222328
async {
23232329
CheckDisposed()
23242330

23252331
let tcConfig = tcConfigP.Get ctok
23262332

2327-
let runMethod =
2333+
let runMethod computations =
23282334
match tcConfig.parallelReferenceResolution with
2329-
| ParallelReferenceResolution.On -> MultipleDiagnosticsLoggers.Parallel
2330-
| ParallelReferenceResolution.Off -> MultipleDiagnosticsLoggers.Sequential
2335+
| ParallelReferenceResolution.On -> MultipleDiagnosticsLoggers.Parallel computations
2336+
| ParallelReferenceResolution.Off -> MultipleDiagnosticsLoggers.Sequential computations
2337+
2338+
let! assemblyData = nms |> List.map tryGetAssemblyData |> runMethod
2339+
2340+
// Preserve determinicstic order of references, because types from later assemblies may shadow earlier ones.
2341+
let phase2s = assemblyData |> Seq.choose id |> Seq.map registerDll |> List.ofSeq
23312342

2332-
let! results =
2333-
nms
2334-
|> List.map (fun nm ->
2335-
async {
2336-
try
2337-
use _ = new CompilationGlobalsScope()
2338-
return! tcImports.TryRegisterAndPrepareToImportReferencedDll(ctok, nm)
2339-
with e ->
2340-
errorR (Error(FSComp.SR.buildProblemReadingAssembly (nm.resolvedPath, e.Message), nm.originalReference.Range))
2341-
return None
2342-
})
2343-
|> runMethod
2344-
2345-
let _dllinfos, phase2s = results |> Array.choose id |> List.ofArray |> List.unzip
23462343
fixupOrphanCcus ()
2347-
let ccuinfos = List.collect (fun phase2 -> phase2 ()) phase2s
2344+
2345+
let! ccuinfos = phase2s |> runMethod
23482346

23492347
if importsBase.IsSome then
23502348
importsBase.Value.CcuTable.Values |> Seq.iter addConstraintSources
23512349
ccuTable.Values |> Seq.iter addConstraintSources
23522350

2353-
return ccuinfos
2351+
return ccuinfos |> List.concat
23542352
}
23552353

23562354
/// Note that implicit loading is not used for compilations from MSBuild, which passes ``--noframework``
@@ -2376,7 +2374,7 @@ and [<Sealed>] TcImports
23762374
ReportWarnings warns
23772375

23782376
tcImports.RegisterAndImportReferencedAssemblies(ctok, res)
2379-
|> Async.RunImmediate
2377+
|> Async.RunSynchronously
23802378
|> ignore
23812379

23822380
true
@@ -2684,7 +2682,7 @@ let RequireReferences (ctok, tcImports: TcImports, tcEnv, thisAssemblyName, reso
26842682

26852683
let ccuinfos =
26862684
tcImports.RegisterAndImportReferencedAssemblies(ctok, resolutions)
2687-
|> Async.RunImmediate
2685+
|> Async.RunSynchronously
26882686

26892687
let asms =
26902688
ccuinfos

src/Compiler/Driver/CompilerOptions.fs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -625,10 +625,12 @@ let callVirtSwitch (tcConfigB: TcConfigBuilder) switch =
625625
let callParallelCompilationSwitch (tcConfigB: TcConfigBuilder) switch =
626626
tcConfigB.parallelIlxGen <- switch = OptionSwitch.On
627627

628-
let (graphCheckingMode, optMode) =
628+
let (graphCheckingMode, optMode, parallelReferenceResolution) =
629629
match switch with
630-
| OptionSwitch.On -> TypeCheckingMode.Graph, OptimizationProcessingMode.Parallel
631-
| OptionSwitch.Off -> TypeCheckingMode.Sequential, OptimizationProcessingMode.Sequential
630+
| OptionSwitch.On -> TypeCheckingMode.Graph, OptimizationProcessingMode.Parallel, ParallelReferenceResolution.On
631+
| OptionSwitch.Off -> TypeCheckingMode.Sequential, OptimizationProcessingMode.Sequential, ParallelReferenceResolution.Off
632+
633+
tcConfigB.parallelReferenceResolution <- parallelReferenceResolution
632634

633635
if tcConfigB.typeCheckingConfig.Mode <> graphCheckingMode then
634636
tcConfigB.typeCheckingConfig <-

src/Compiler/Driver/GraphChecking/DependencyResolution.fs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ let mkGraph (filePairs: FilePairMap) (files: FileInProject array) : Graph<FileIn
206206
if file.Idx = 0 then
207207
// First file cannot have any dependencies.
208208
Array.empty
209+
209210
else
210211
let fileContent = fileContents[file.Idx]
211212

@@ -235,20 +236,47 @@ let mkGraph (filePairs: FilePairMap) (files: FileInProject array) : Graph<FileIn
235236
| None -> Array.empty
236237
| Some sigIdx -> Array.singleton sigIdx
237238

239+
let wrongOrderSignature =
240+
match filePairs.TryGetWrongOrderSignatureToImplementationIndex file.Idx with
241+
| None -> Array.empty
242+
| Some idx -> Array.singleton idx
243+
238244
let allDependencies =
239245
[|
240246
yield! depsResult.FoundDependencies
241247
yield! ghostDependencies
242248
yield! signatureDependency
249+
yield! wrongOrderSignature
243250
|]
244251
|> Array.distinct
245252

246253
allDependencies
247254

248-
let graph =
255+
// If there is a script in the project, we just process sequentially all the files that may have been added as part of the script closure.
256+
// That means all files up to the last script file.
257+
let scriptCompilationLength =
249258
files
259+
|> Array.tryFindIndexBack (fun f -> f.IsScript)
260+
|> Option.map (fun idx -> idx + 1)
261+
|> Option.defaultValue 0
262+
263+
let sequentialPartForScriptCompilation =
264+
files
265+
|> Array.take scriptCompilationLength
266+
|> Array.map (fun file ->
267+
file.Idx,
268+
[|
269+
if file.Idx > 0 then
270+
file.Idx - 1
271+
|])
272+
273+
let normalPart =
274+
files
275+
|> Array.skip scriptCompilationLength
250276
|> Array.Parallel.map (fun file -> file.Idx, findDependencies file)
251-
|> readOnlyDict
277+
278+
let graph =
279+
Array.append sequentialPartForScriptCompilation normalPart |> readOnlyDict
252280

253281
let trie = trie |> Array.last |> snd
254282

0 commit comments

Comments
 (0)