From b0bfff80086b0b988b541249920306e7f0e8ef3c Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Fri, 29 May 2026 13:20:43 +0200 Subject: [PATCH 1/2] PROOF DRAFT: Release determinism CI x8 iterations, no product changes This draft demonstrates that the current main branch is non-deterministic in Release configuration. 8 parallel iterations maximize the probability that pre-existing races (TypeDefsBuilder counter, ConcurrentStack drain, parallel optimizer Val.Stamp iteration order) trigger at least once. DO NOT MERGE. Evidence-only PR for #19732 / #19810. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- azure-pipelines-PR.yml | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/azure-pipelines-PR.yml b/azure-pipelines-PR.yml index b627fab985..feeace8a09 100644 --- a/azure-pipelines-PR.yml +++ b/azure-pipelines-PR.yml @@ -100,7 +100,7 @@ stages: helixRepo: dotnet/fsharp jobs: # Determinism, we want to run it only in PR builds - - job: Determinism_Debug + - job: Determinism_Release condition: eq(variables['Build.Reason'], 'PullRequest') variables: - name: _SignType @@ -110,11 +110,23 @@ stages: demands: ImageOverride -equals $(_WindowsMachineQueueName) timeoutInMinutes: 90 strategy: - maxParallel: 2 + maxParallel: 8 matrix: - regular: + iter1: + _experimental_flag: '' + iter2: + _experimental_flag: '' + iter3: + _experimental_flag: '' + iter4: + _experimental_flag: '' + iter5: + _experimental_flag: '' + iter6: + _experimental_flag: '' + iter7: _experimental_flag: '' - experimental_features: + iter8: _experimental_flag: '' steps: - checkout: self @@ -129,15 +141,15 @@ stages: workingDirectory: $(Build.SourcesDirectory) installationPath: $(Build.SourcesDirectory)/.dotnet - script: .\eng\common\dotnet.cmd - - script: .\eng\test-determinism.cmd -configuration Debug + - script: .\eng\test-determinism.cmd -configuration Release env: FSHARP_EXPERIMENTAL_FEATURES: $(_experimental_flag) - displayName: Determinism tests with Debug configuration + displayName: Determinism tests with Release configuration - task: PublishPipelineArtifact@1 displayName: Publish Determinism Logs inputs: - targetPath: '$(Build.SourcesDirectory)/artifacts/log/Debug' - artifactName: 'Determinism_Debug Attempt $(System.JobAttempt) Logs' + targetPath: '$(Build.SourcesDirectory)/artifacts/log/Release' + artifactName: 'Determinism_Release Attempt $(System.JobAttempt) Logs' continueOnError: true condition: not(succeeded()) From 8132d02fb129178d8000eb9b14dd1b6ebbfcfcce Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Mon, 1 Jun 2026 12:27:44 +0200 Subject: [PATCH 2/2] PROOF: add seq-vs-par diff test (must FAIL on main without IlxGen fixes) --- .../CodeGen/EmittedIL/DeterministicTests.fs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/tests/fsharp/Compiler/CodeGen/EmittedIL/DeterministicTests.fs b/tests/fsharp/Compiler/CodeGen/EmittedIL/DeterministicTests.fs index ad43e5b7bf..29ced81f28 100644 --- a/tests/fsharp/Compiler/CodeGen/EmittedIL/DeterministicTests.fs +++ b/tests/fsharp/Compiler/CodeGen/EmittedIL/DeterministicTests.fs @@ -334,6 +334,97 @@ let inline myFunc x y = x - y""" else Assert.NotEqual(mvid1,mvid2) + // https://github.com/dotnet/fsharp/issues/19732 + // Differential test: compile the same multi-file project once fully sequentially + // and once fully parallel, with --deterministic. If the MVIDs differ, the + // compiler is non-deterministic with respect to its own internal parallelism. + // This is a hard, repeatable signal — no need to retry across runs. + [] + let ``Parallel and sequential compilation must produce identical assemblies`` () = + let outputDir = DirectoryInfo(Path.Combine(Path.GetTempPath(), "fsharp-determinism-seqpar")) + if outputDir.Exists then outputDir.Delete(true) + outputDir.Create() + + let makeFile i = + let src = + (sprintf + """ +module File%d + +let processTuple%d (a: int, b: string) = + let inner x = x + a + let nested () = inner 42 + (nested (), b.Length) + +let anon%d () = + {| Name = "f%d"; Index = %d; Children = [| 1; 2; 3 |] |} + +let anon%db () = + {| Tag = "T%d"; Value = %d * 7; Extras = "x" |} + +let useAnon%d () = + let r = anon%d () + let r2 = anon%db () + let composed (k: int) = + let h x = x + r.Index + r2.Value + k + h 100 + composed 0 + r.Name.Length + +[] +type Rec%d = { X: int; Y: string } + +let mkRec%d () = { X = %d; Y = "rec%d" } +""" + i i i i i i i i i i i i i i i) + + FsSourceWithFileName $"File%d{i}.fs" src + + let additionalFiles = [ for i in 2..12 -> makeFile i ] + + let compileWith parallelism = + FSharp + """ +module File1 + +let processTuple1 (a: int, b: string) = + let inner x = x + a + let nested () = inner 42 + (nested (), b.Length) + +let anon1 () = + {| Name = "f1"; Index = 1; Children = [| 1; 2; 3 |] |} + +let anon1b () = + {| Tag = "T1"; Value = 7; Extras = "x" |} + +let useAnon1 () = + let r = anon1 () + let r2 = anon1b () + let composed (k: int) = + let h x = x + r.Index + r2.Value + k + h 100 + composed 0 + r.Name.Length + +[] +type Rec1 = { X: int; Y: string } + +let mkRec1 () = { X = 1; Y = "rec1" } +""" + |> withAdditionalSourceFiles additionalFiles + |> asLibrary + |> withOptimize + |> withName "DetTest" + |> withOutputDirectory (Some outputDir) + |> withOptions ("--deterministic" :: "--nowarn:75" :: parallelism) + |> compileGuid + + let seqMvid = compileWith [ "--parallelcompilation-"; "--test:ParallelOff" ] + let parMvid = compileWith [ "--parallelcompilation+" ] + + outputDir.Delete(true) + + Assert.Equal(seqMvid, parMvid) + [] let ``Reference assemblies MVID must change when literal constant value changes`` () = let codeWithLiteral42 = """