Skip to content
Merged
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@

### Changed

* Parallel compilation stabilised and enabled by default ([PR #18998](https://github.com/dotnet/fsharp/pull/18998))
Copy link
Contributor

Choose a reason for hiding this comment

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

This is quite vague about what is actually changed here. Anyone reading this, will need to scour through the PR to know what is actual up.


### Breaking Changes
19 changes: 4 additions & 15 deletions src/Compiler/Driver/CompilerConfig.fs
Original file line number Diff line number Diff line change
Expand Up @@ -805,18 +805,11 @@ type TcConfigBuilder =
doTLR = false
doFinalSimplify = false
optsOn = false
optSettings =
{ OptimizationSettings.Defaults with
processingMode =
if FSharpExperimentalFeaturesEnabledAutomatically then
OptimizationProcessingMode.Parallel
else
OptimizationProcessingMode.Sequential
}
optSettings = OptimizationSettings.Defaults
emitTailcalls = true
deterministic = false
parallelParsing = true
parallelIlxGen = FSharpExperimentalFeaturesEnabledAutomatically
parallelIlxGen = true
emitMetadataAssembly = MetadataAssemblyGeneration.None
preferredUiLang = None
lcid = None
Expand Down Expand Up @@ -858,15 +851,11 @@ type TcConfigBuilder =
sdkDirOverride = sdkDirOverride
xmlDocInfoLoader = None
exiter = QuitProcessExiter
parallelReferenceResolution = ParallelReferenceResolution.Off
parallelReferenceResolution = ParallelReferenceResolution.On
captureIdentifiersWhenParsing = false
typeCheckingConfig =
{
TypeCheckingConfig.Mode =
if FSharpExperimentalFeaturesEnabledAutomatically then
TypeCheckingMode.Graph
else
TypeCheckingMode.Sequential
TypeCheckingConfig.Mode = TypeCheckingMode.Graph
DumpGraph = false
}
dumpSignatureData = false
Expand Down
158 changes: 78 additions & 80 deletions src/Compiler/Driver/CompilerImports.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1586,8 +1586,10 @@ and [<Sealed>] TcImports

#if !NO_TYPEPROVIDERS
member private tcImports.AttachDisposeTypeProviderAction action =
CheckDisposed()
disposeTypeProviderActions.Add action
tciLock.AcquireLock(fun tcitok ->
CheckDisposed()
RequireTcImportsLock(tcitok, disposeTypeProviderActions)
disposeTypeProviderActions.Add action)
#endif

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

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

let! contentsOpt =
async {
match r.ProjectReference with
| Some ilb -> return! ilb.EvaluateRawContents()
| None -> return ProjectAssemblyDataResult.Unavailable true
}
try

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

let assemblyData =
// If we have a project reference but did not get any valid contents,
// just return None and do not attempt to read elsewhere.
match contentsOpt with
| ProjectAssemblyDataResult.Available ilb -> ilb
| ProjectAssemblyDataResult.Unavailable _ ->
let ilModule, ilAssemblyRefs = tcImports.OpenILBinaryModule(ctok, fileName, m)
RawFSharpAssemblyDataBackedByFileOnDisk(ilModule, ilAssemblyRefs) :> IRawFSharpAssemblyData
| ProjectAssemblyDataResult.Unavailable false -> return None
| _ ->

match contentsOpt with
| ProjectAssemblyDataResult.Available ilb -> return Some(r, ilb)
| ProjectAssemblyDataResult.Unavailable _ ->
let ilModule, ilAssemblyRefs = tcImports.OpenILBinaryModule(ctok, fileName, m)
return Some(r, RawFSharpAssemblyDataBackedByFileOnDisk(ilModule, ilAssemblyRefs))

with e ->
errorR (Error(FSComp.SR.buildProblemReadingAssembly (fileName, e.Message), m))
return None
}

let ilShortAssemName = assemblyData.ShortAssemblyName
let ilScopeRef = assemblyData.ILScopeRef
let registerDll (r: AssemblyResolution, assemblyData: IRawFSharpAssemblyData) =
let m = r.originalReference.Range
let fileName = r.resolvedPath
let ilShortAssemName = assemblyData.ShortAssemblyName
let ilScopeRef = assemblyData.ILScopeRef

if tcImports.IsAlreadyRegistered ilShortAssemName then
let dllinfo = tcImports.FindDllInfo(ctok, m, ilShortAssemName)
if tcImports.IsAlreadyRegistered ilShortAssemName then

let phase2 () =
[ tcImports.FindCcuInfo(ctok, m, ilShortAssemName, lookupOnly = true) ]
let phase2 () =
[ tcImports.FindCcuInfo(ctok, m, ilShortAssemName, lookupOnly = true) ]

return Some(dllinfo, phase2)
else
let dllinfo =
{
RawMetadata = assemblyData
FileName = fileName
async { return phase2 () }
else
let dllinfo =
{
RawMetadata = assemblyData
FileName = fileName
#if !NO_TYPEPROVIDERS
ProviderGeneratedAssembly = None
IsProviderGenerated = false
ProviderGeneratedStaticLinkMap = None
ProviderGeneratedAssembly = None
IsProviderGenerated = false
ProviderGeneratedStaticLinkMap = None
#endif
ILScopeRef = ilScopeRef
ILAssemblyRefs = assemblyData.ILAssemblyRefs
}
ILScopeRef = ilScopeRef
ILAssemblyRefs = assemblyData.ILAssemblyRefs
}

tcImports.RegisterDll dllinfo
tcImports.RegisterDll dllinfo

let phase2 =
if assemblyData.HasAnyFSharpSignatureDataAttribute then
if not assemblyData.HasMatchingFSharpSignatureDataAttribute then
errorR (Error(FSComp.SR.buildDifferentVersionMustRecompile fileName, m))
tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo)
else
try
tcImports.PrepareToImportReferencedFSharpAssembly(ctok, m, fileName, dllinfo)
with e ->
error (Error(FSComp.SR.buildErrorOpeningBinaryFile (fileName, e.Message), m))
else
let phase2 =
if assemblyData.HasAnyFSharpSignatureDataAttribute then
if not assemblyData.HasMatchingFSharpSignatureDataAttribute then
errorR (Error(FSComp.SR.buildDifferentVersionMustRecompile fileName, m))
tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo)
else
try
tcImports.PrepareToImportReferencedFSharpAssembly(ctok, m, fileName, dllinfo)
with e ->
error (Error(FSComp.SR.buildErrorOpeningBinaryFile (fileName, e.Message), m))
else
tcImports.PrepareToImportReferencedILAssembly(ctok, m, fileName, dllinfo)

return Some(dllinfo, phase2)
}
async { return phase2 () }

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

let tcConfig = tcConfigP.Get ctok

let runMethod =
let runMethod computations =
match tcConfig.parallelReferenceResolution with
| ParallelReferenceResolution.On -> MultipleDiagnosticsLoggers.Parallel
| ParallelReferenceResolution.Off -> MultipleDiagnosticsLoggers.Sequential
| ParallelReferenceResolution.On -> MultipleDiagnosticsLoggers.Parallel computations
| ParallelReferenceResolution.Off -> MultipleDiagnosticsLoggers.Sequential computations

let! assemblyData = nms |> List.map tryGetAssemblyData |> runMethod

// Preserve determinicstic order of references, because types from later assemblies may shadow earlier ones.
let phase2s = assemblyData |> Seq.choose id |> Seq.map registerDll |> List.ofSeq

let! results =
nms
|> List.map (fun nm ->
async {
try
use _ = new CompilationGlobalsScope()
return! tcImports.TryRegisterAndPrepareToImportReferencedDll(ctok, nm)
with e ->
errorR (Error(FSComp.SR.buildProblemReadingAssembly (nm.resolvedPath, e.Message), nm.originalReference.Range))
return None
})
|> runMethod

let _dllinfos, phase2s = results |> Array.choose id |> List.ofArray |> List.unzip
fixupOrphanCcus ()
let ccuinfos = List.collect (fun phase2 -> phase2 ()) phase2s

let! ccuinfos = phase2s |> runMethod

if importsBase.IsSome then
importsBase.Value.CcuTable.Values |> Seq.iter addConstraintSources
ccuTable.Values |> Seq.iter addConstraintSources

return ccuinfos
return ccuinfos |> List.concat
}

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

tcImports.RegisterAndImportReferencedAssemblies(ctok, res)
|> Async.RunImmediate
|> Async.RunSynchronously
|> ignore

true
Expand Down Expand Up @@ -2684,7 +2682,7 @@ let RequireReferences (ctok, tcImports: TcImports, tcEnv, thisAssemblyName, reso

let ccuinfos =
tcImports.RegisterAndImportReferencedAssemblies(ctok, resolutions)
|> Async.RunImmediate
|> Async.RunSynchronously

let asms =
ccuinfos
Expand Down
8 changes: 5 additions & 3 deletions src/Compiler/Driver/CompilerOptions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -625,10 +625,12 @@ let callVirtSwitch (tcConfigB: TcConfigBuilder) switch =
let callParallelCompilationSwitch (tcConfigB: TcConfigBuilder) switch =
tcConfigB.parallelIlxGen <- switch = OptionSwitch.On

let (graphCheckingMode, optMode) =
let (graphCheckingMode, optMode, parallelReferenceResolution) =
match switch with
| OptionSwitch.On -> TypeCheckingMode.Graph, OptimizationProcessingMode.Parallel
| OptionSwitch.Off -> TypeCheckingMode.Sequential, OptimizationProcessingMode.Sequential
| OptionSwitch.On -> TypeCheckingMode.Graph, OptimizationProcessingMode.Parallel, ParallelReferenceResolution.On
| OptionSwitch.Off -> TypeCheckingMode.Sequential, OptimizationProcessingMode.Sequential, ParallelReferenceResolution.Off

tcConfigB.parallelReferenceResolution <- parallelReferenceResolution

if tcConfigB.typeCheckingConfig.Mode <> graphCheckingMode then
tcConfigB.typeCheckingConfig <-
Expand Down
32 changes: 30 additions & 2 deletions src/Compiler/Driver/GraphChecking/DependencyResolution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ let mkGraph (filePairs: FilePairMap) (files: FileInProject array) : Graph<FileIn
if file.Idx = 0 then
// First file cannot have any dependencies.
Array.empty

else
let fileContent = fileContents[file.Idx]

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

let wrongOrderSignature =
match filePairs.TryGetWrongOrderSignatureToImplementationIndex file.Idx with
| None -> Array.empty
| Some idx -> Array.singleton idx

let allDependencies =
[|
yield! depsResult.FoundDependencies
yield! ghostDependencies
yield! signatureDependency
yield! wrongOrderSignature
|]
|> Array.distinct

allDependencies

let graph =
// 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.
// That means all files up to the last script file.
let scriptCompilationLength =
files
|> Array.tryFindIndexBack (fun f -> f.IsScript)
|> Option.map (fun idx -> idx + 1)
|> Option.defaultValue 0

let sequentialPartForScriptCompilation =
files
|> Array.take scriptCompilationLength
|> Array.map (fun file ->
file.Idx,
[|
if file.Idx > 0 then
file.Idx - 1
|])

let normalPart =
files
|> Array.skip scriptCompilationLength
|> Array.Parallel.map (fun file -> file.Idx, findDependencies file)
|> readOnlyDict

let graph =
Array.append sequentialPartForScriptCompilation normalPart |> readOnlyDict

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

Expand Down
Loading
Loading