Skip to content

Commit 995b687

Browse files
authored
Enable graph-based type checking, parallel optimizations and ILX gen in deterministic build (#19028)
1 parent 079fda9 commit 995b687

File tree

13 files changed

+217
-50
lines changed

13 files changed

+217
-50
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
### Changed
2424

25-
* Parallel compilation stabilised and enabled by default ([PR #18998](https://github.com/dotnet/fsharp/pull/18998))
25+
* Parallel compilation features: ref resolution, graph based checking, ILXGen and optimization enabled by default ([PR #18998](https://github.com/dotnet/fsharp/pull/18998))
26+
* Make graph based type checking and parallel optimizations deterministic ([PR #19028](https://github.com/dotnet/fsharp/pull/19028))
27+
2628

2729
### Breaking Changes

src/Compiler/AbstractIL/ilwritepdb.fs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,12 @@ let scopeSorter (scope1: PdbMethodScope) (scope2: PdbMethodScope) =
339339
type PortablePdbGenerator
340340
(embedAllSource: bool, embedSourceList: string list, sourceLink: string, checksumAlgorithm, info: PdbData, pathMap: PathMap) =
341341

342-
let docs = info.Documents
342+
// Deterministic: build the Document table in a stable order by mapped file path,
343+
// but preserve the original-document-index -> handle mapping by filename.
344+
let originalDocFiles = info.Documents |> Array.map (fun d -> d.File)
343345

344-
// The metadata to wite to the PortablePDB (Roslyn = _debugMetadataOpt)
346+
let docsSorted =
347+
info.Documents |> Array.sortBy (fun d -> PathMap.apply pathMap d.File)
345348

346349
let metadata = MetadataBuilder()
347350

@@ -414,15 +417,16 @@ type PortablePdbGenerator
414417

415418
Some(builder.ToImmutableArray())
416419

420+
// Build Document table in deterministic order
417421
let documentIndex =
418-
let mutable index = Dictionary<string, DocumentHandle>(docs.Length)
422+
let mutable index = Dictionary<string, DocumentHandle>(docsSorted.Length)
419423

420-
let docLength = docs.Length + if String.IsNullOrEmpty sourceLink then 1 else 0
424+
let docLength =
425+
docsSorted.Length + (if String.IsNullOrWhiteSpace sourceLink then 0 else 1)
421426

422427
metadata.SetCapacity(TableIndex.Document, docLength)
423428

424-
for doc in docs do
425-
// For F# Interactive, file name 'stdin' gets generated for interactive inputs
429+
for doc in docsSorted do
426430
let handle =
427431
match checkSum doc.File checksumAlgorithm with
428432
| Some(hashAlg, checkSum) ->
@@ -472,11 +476,12 @@ type PortablePdbGenerator
472476

473477
let mutable lastLocalVariableHandle = Unchecked.defaultof<LocalVariableHandle>
474478

479+
// IMPORTANT: map original document index -> filename -> handle
475480
let getDocumentHandle d =
476-
if docs.Length = 0 || d < 0 || d > docs.Length then
481+
if info.Documents.Length = 0 || d < 0 || d >= info.Documents.Length then
477482
Unchecked.defaultof<DocumentHandle>
478483
else
479-
match documentIndex.TryGetValue(docs[d].File) with
484+
match documentIndex.TryGetValue(originalDocFiles[d]) with
480485
| false, _ -> Unchecked.defaultof<DocumentHandle>
481486
| true, h -> h
482487

@@ -559,7 +564,16 @@ type PortablePdbGenerator
559564
let serializeImportsBlob (imports: PdbImport[]) =
560565
let writer = BlobBuilder()
561566

562-
for import in imports do
567+
let importsSorted =
568+
imports
569+
|> Array.sortWith (fun a b ->
570+
match a, b with
571+
| ImportType t1, ImportType t2 -> compare t1 t2
572+
| ImportNamespace n1, ImportNamespace n2 -> compare n1 n2
573+
| ImportType _, ImportNamespace _ -> -1
574+
| ImportNamespace _, ImportType _ -> 1)
575+
576+
for import in importsSorted do
563577
serializeImport writer import
564578

565579
metadata.GetOrAddBlob(writer)
@@ -636,7 +650,8 @@ type PortablePdbGenerator
636650
)
637651
|> ignore
638652

639-
for localVariable in scope.Locals do
653+
// Deterministic: write locals by stable index
654+
for localVariable in scope.Locals |> Array.sortBy (fun l -> l.Index) do
640655
lastLocalVariableHandle <-
641656
metadata.AddLocalVariable(
642657
LocalVariableAttributes.None,
@@ -649,7 +664,7 @@ type PortablePdbGenerator
649664
let sps =
650665
match minfo.DebugRange with
651666
| None -> Array.empty
652-
| Some _ -> minfo.DebugPoints
667+
| Some _ -> minfo.DebugPoints |> Array.sortWith SequencePoint.orderByOffset
653668

654669
let builder = BlobBuilder()
655670
builder.WriteCompressedInteger(minfo.LocalSignatureToken)

src/Compiler/CodeGen/IlxGen.fs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,8 @@ type CompileLocation =
368368
Enclosing: string list
369369

370370
QualifiedNameOfFile: string
371+
372+
Range: range
371373
}
372374

373375
//--------------------------------------------------------------------------
@@ -388,6 +390,7 @@ let CompLocForFragment fragName (ccu: CcuThunk) =
388390
Scope = ccu.ILScopeRef
389391
Namespace = None
390392
Enclosing = []
393+
Range = range0
391394
}
392395

393396
let CompLocForCcu (ccu: CcuThunk) = CompLocForFragment ccu.AssemblyName ccu
@@ -406,7 +409,7 @@ let CompLocForSubModuleOrNamespace cloc (submod: ModuleOrNamespace) =
406409
Namespace = Some(mkTopName cloc.Namespace n)
407410
}
408411

409-
let CompLocForFixedPath fragName qname (CompPath(sref, _, cpath)) =
412+
let CompLocForFixedPath fragName qname m (CompPath(sref, _, cpath)) =
410413
let ns, t =
411414
cpath
412415
|> List.takeUntil (fun (_, mkind) ->
@@ -425,10 +428,11 @@ let CompLocForFixedPath fragName qname (CompPath(sref, _, cpath)) =
425428
Scope = sref
426429
Namespace = ns
427430
Enclosing = encl
431+
Range = m
428432
}
429433

430434
let CompLocForFixedModule fragName qname (mspec: ModuleOrNamespace) =
431-
let cloc = CompLocForFixedPath fragName qname mspec.CompilationPath
435+
let cloc = CompLocForFixedPath fragName qname mspec.Range mspec.CompilationPath
432436
let cloc = CompLocForSubModuleOrNamespace cloc mspec
433437
cloc
434438

@@ -2333,8 +2337,11 @@ and AssemblyBuilder(cenv: cenv, anonTypeTable: AnonTypeGenerationTable) as mgbuf
23332337
MemoizationTable<CompileLocation * int, ILTypeSpec>(
23342338
"rawDataValueTypeGenerator",
23352339
(fun (cloc, size) ->
2336-
let name =
2337-
CompilerGeneratedName("T" + string (newUnique ()) + "_" + string size + "Bytes") // Type names ending ...$T<unique>_37Bytes
2340+
2341+
let unique =
2342+
g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.IncrementOnly("@T", cloc.Range)
2343+
2344+
let name = CompilerGeneratedName $"T{unique}_{size}Bytes" // Type names ending ...$T<unique>_37Bytes
23382345

23392346
let vtdef = mkRawDataValueTypeDef g.iltyp_ValueType (name, size, 0us)
23402347
let vtref = NestedTypeRefForCompLoc cloc vtdef.Name
@@ -2390,7 +2397,12 @@ and AssemblyBuilder(cenv: cenv, anonTypeTable: AnonTypeGenerationTable) as mgbuf
23902397
// Byte array literals require a ValueType of size the required number of bytes.
23912398
// With fsi.exe, S.R.Emit TypeBuilder CreateType has restrictions when a ValueType VT is nested inside a type T, and T has a field of type VT.
23922399
// To avoid this situation, these ValueTypes are generated under the private implementation rather than in the current cloc. [was bug 1532].
2393-
let cloc = CompLocForPrivateImplementationDetails cloc
2400+
let cloc =
2401+
if cenv.options.isInteractive then
2402+
CompLocForPrivateImplementationDetails cloc
2403+
else
2404+
cloc
2405+
23942406
rawDataValueTypeGenerator.Apply((cloc, size))
23952407

23962408
member _.GenerateAnonType(genToStringMethod, anonInfo: AnonRecdTypeInfo) =
@@ -2754,7 +2766,11 @@ let GenConstArray cenv (cgbuf: CodeGenBuffer) eenv ilElementType (data: 'a[]) (w
27542766
CG.EmitInstrs cgbuf (pop 0) (Push [ ilArrayType ]) [ mkLdcInt32 0; I_newarr(ILArrayShape.SingleDimensional, ilElementType) ]
27552767
else
27562768
let vtspec = cgbuf.mgbuf.GenerateRawDataValueType(eenv.cloc, bytes.Length)
2757-
let ilFieldName = CompilerGeneratedName("field" + string (newUnique ()))
2769+
2770+
let unique =
2771+
g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.IncrementOnly("@field", eenv.cloc.Range)
2772+
2773+
let ilFieldName = CompilerGeneratedName $"field{unique}"
27582774
let fty = ILType.Value vtspec
27592775

27602776
let ilFieldDef =
@@ -10413,6 +10429,7 @@ and GenImplFile cenv (mgbuf: AssemblyBuilder) mainInfoOpt eenv (implFile: Checke
1041310429
cloc =
1041410430
{ eenv.cloc with
1041510431
TopImplQualifiedName = qname.Text
10432+
Range = m
1041610433
}
1041710434
}
1041810435

src/Compiler/Driver/GraphChecking/DependencyResolution.fs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,10 @@ let mkGraph (filePairs: FilePairMap) (files: FileInProject array) : Graph<FileIn
236236
| None -> Array.empty
237237
| Some sigIdx -> Array.singleton sigIdx
238238

239-
let wrongOrderSignature =
240-
match filePairs.TryGetWrongOrderSignatureToImplementationIndex file.Idx with
239+
// Add a link from signature files to their implementation files, if the implementation file comes before the signature file.
240+
// This allows us to emit FS0238 (implementation already given).
241+
let implementationGivenBeforeSignature =
242+
match filePairs.TryGetOutOfOrderImplementationIndex file.Idx with
241243
| None -> Array.empty
242244
| Some idx -> Array.singleton idx
243245

@@ -246,7 +248,7 @@ let mkGraph (filePairs: FilePairMap) (files: FileInProject array) : Graph<FileIn
246248
yield! depsResult.FoundDependencies
247249
yield! ghostDependencies
248250
yield! signatureDependency
249-
yield! wrongOrderSignature
251+
yield! implementationGivenBeforeSignature
250252
|]
251253
|> Array.distinct
252254

src/Compiler/Driver/GraphChecking/Types.fs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,14 @@ type internal FilePairMap(files: FileInProject array) =
173173
|> Option.map (fun (implFile: FileInProject) -> (sigFile.Idx, implFile.Idx)))
174174
|> Array.choose id
175175

176-
let goodPairs, wrongOrderPairs =
176+
let goodPairs, misorderedPairs =
177177
pairs |> Array.partition (fun (sigIdx, implIdx) -> sigIdx < implIdx)
178178

179179
let sigToImpl, implToSig = buildBiDirectionalMaps goodPairs
180180

181-
// Pairs where the signature file comes after the implementation file in the project order. We need to track them to report such errors.
182-
let wrongOrder = wrongOrderPairs |> Map.ofArray
181+
// Pairs where the signature file comes after the implementation file in the project order.
182+
// We need to track them to report FS0238 (implementation already given).
183+
let misordered = misorderedPairs |> Map.ofArray
183184

184185
member x.GetSignatureIndex(implementationIndex: FileIndex) = Map.find implementationIndex implToSig
185186
member x.GetImplementationIndex(signatureIndex: FileIndex) = Map.find signatureIndex sigToImpl
@@ -195,7 +196,8 @@ type internal FilePairMap(files: FileInProject array) =
195196

196197
member x.IsSignature(index: FileIndex) = Map.containsKey index sigToImpl
197198

198-
member x.TryGetWrongOrderSignatureToImplementationIndex(index: FileIndex) = wrongOrder |> Map.tryFind index
199+
member x.TryGetOutOfOrderImplementationIndex(signatureIndex: FileIndex) =
200+
misordered |> Map.tryFind signatureIndex
199201

200202
/// Callback that returns a previously calculated 'Result and updates 'State accordingly.
201203
type internal Finisher<'Node, 'State, 'Result> = Finisher of node: 'Node * finisher: ('State -> 'Result * 'State)

src/Compiler/Driver/GraphChecking/Types.fsi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ type internal FilePairMap =
117117
member HasSignature: implementationIndex: FileIndex -> bool
118118
member TryGetSignatureIndex: implementationIndex: FileIndex -> FileIndex option
119119
member IsSignature: index: FileIndex -> bool
120-
member TryGetWrongOrderSignatureToImplementationIndex: index: FileIndex -> FileIndex option
120+
/// Covers the case where the implementation file appears before the signature file in the project.
121+
/// This is needed only to correctly trigger FS0238 (implementation already given).
122+
member TryGetOutOfOrderImplementationIndex: signatureIndex: FileIndex -> FileIndex option
121123

122124
/// Callback that returns a previously calculated 'Result and updates 'State accordingly.
123125
type internal Finisher<'Node, 'State, 'Result> = Finisher of node: 'Node * finisher: ('State -> 'Result * 'State)

src/Compiler/Driver/OptimizeInputs.fs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -512,12 +512,11 @@ let ApplyAllOptimizations
512512
let results, optEnvFirstLoop =
513513
match tcConfig.optSettings.processingMode with
514514
// Parallel optimization breaks determinism - turn it off in deterministic builds.
515-
| Optimizer.OptimizationProcessingMode.Parallel when (not tcConfig.deterministic) ->
515+
| Optimizer.OptimizationProcessingMode.Parallel ->
516516
let results, optEnvFirstPhase =
517517
ParallelOptimization.optimizeFilesInParallel optEnv phases implFiles
518518

519519
results |> Array.toList, optEnvFirstPhase
520-
| Optimizer.OptimizationProcessingMode.Parallel
521520
| Optimizer.OptimizationProcessingMode.Sequential -> optimizeFilesSequentially optEnv phases implFiles
522521

523522
#if DEBUG
@@ -578,7 +577,7 @@ let GenerateIlxCode
578577
isInteractive = tcConfig.isInteractive
579578
isInteractiveItExpr = isInteractiveItExpr
580579
alwaysCallVirt = tcConfig.alwaysCallVirt
581-
parallelIlxGenEnabled = tcConfig.parallelIlxGen && not tcConfig.deterministic
580+
parallelIlxGenEnabled = tcConfig.parallelIlxGen
582581
}
583582

584583
ilxGenerator.GenerateCode(ilxGenOpts, optimizedImpls, topAttrs.assemblyAttrs, topAttrs.netModuleAttrs)

src/Compiler/Driver/ParseAndCheckInputs.fs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,11 +1881,7 @@ let CheckClosedInputSet (ctok, checkForErrors, tcConfig: TcConfig, tcImports, tc
18811881
// tcEnvAtEndOfLastFile is the environment required by fsi.exe when incrementally adding definitions
18821882
let results, tcState =
18831883
match tcConfig.typeCheckingConfig.Mode with
1884-
| TypeCheckingMode.Graph when
1885-
(not tcConfig.isInteractive
1886-
&& not tcConfig.compilingFSharpCore
1887-
&& not tcConfig.deterministic)
1888-
->
1884+
| TypeCheckingMode.Graph when (not tcConfig.isInteractive && not tcConfig.compilingFSharpCore) ->
18891885
CheckMultipleInputsUsingGraphMode(
18901886
ctok,
18911887
checkForErrors,

src/Compiler/TypedTree/CompilerGlobalState.fs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,24 @@ open FSharp.Compiler.Text
1818
/// policy to make all globally-allocated objects concurrency safe in case future versions of the compiler
1919
/// are used to host multiple concurrent instances of compilation.
2020
type NiceNameGenerator() =
21-
let basicNameCounts = ConcurrentDictionary<string, int ref>(max Environment.ProcessorCount 1, 127)
21+
let basicNameCounts = ConcurrentDictionary<struct (string * int), int ref>(max Environment.ProcessorCount 1, 127)
2222
// Cache this as a delegate.
23-
let basicNameCountsAddDelegate = Func<string, int ref>(fun _ -> ref 0)
23+
let basicNameCountsAddDelegate = Func<struct (string * int), int ref>(fun _ -> ref 0)
24+
25+
let increment basicName (m: range) =
26+
let key = struct (basicName, m.FileIndex)
27+
let countCell = basicNameCounts.GetOrAdd(key, basicNameCountsAddDelegate)
28+
Interlocked.Increment(countCell)
2429

2530
member _.FreshCompilerGeneratedNameOfBasicName (basicName, m: range) =
26-
let countCell = basicNameCounts.GetOrAdd(basicName, basicNameCountsAddDelegate)
27-
let count = Interlocked.Increment(countCell)
28-
29-
CompilerGeneratedNameSuffix basicName (string m.StartLine + (match (count-1) with 0 -> "" | n -> "-" + string n))
31+
let count = increment basicName m
32+
CompilerGeneratedNameSuffix basicName (string m.StartLine + (match (count - 1) with 0 -> "" | n -> "-" + string n))
3033

3134
member this.FreshCompilerGeneratedName (name, m: range) =
3235
this.FreshCompilerGeneratedNameOfBasicName (GetBasicNameOfPossibleCompilerGeneratedName name, m)
3336

37+
member _.IncrementOnly(name: string, m: range) = increment name m
38+
3439
/// Generates compiler-generated names marked up with a source code location, but if given the same unique value then
3540
/// return precisely the same name. Each name generated also includes the StartLine number of the range passed in
3641
/// at the point of first generation.

src/Compiler/TypedTree/CompilerGlobalState.fsi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type NiceNameGenerator =
1717

1818
new: unit -> NiceNameGenerator
1919
member FreshCompilerGeneratedName: name: string * m: range -> string
20+
member IncrementOnly: name: string * m: range -> int
2021

2122
/// Generates compiler-generated names marked up with a source code location, but if given the same unique value then
2223
/// return precisely the same name. Each name generated also includes the StartLine number of the range passed in

0 commit comments

Comments
 (0)