From 2f15788e3ceebae76ff57b1035bf12ed63aa5ce9 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Wed, 9 Sep 2026 22:11:53 +0200 Subject: [PATCH 1/3] Sort the merged property definitions in one array HashRangeSorted built three intermediate lists - one from the comprehension, one from List.sortBy, one from List.map - plus the sort's own array, and ran for every type definition the code generator emits. Read out of FSharp.Compiler.Service.dll and FSharp.Core.dll, 80% of type definitions carry no properties at all and 94% carry at most three, so the empty case is the one worth being cheap. It now returns immediately when there is nothing to sort, and otherwise fills one exact-size array through Seq.toArray's ICollection fast path and sorts it in place. List.sortBy is stable where Array.sortInPlaceBy is not, but the sort keys are the insertion indices AddPropertyDefToHash assigns via ht[nm] <- (ht.Count, pdef), which are never duplicated, so the ordering that reaches mkILProperties is unchanged. Co-Authored-By: Claude Opus 5 --- .../release-notes/.FSharp.Compiler.Service/11.0.100.md | 1 + src/Compiler/CodeGen/IlxGen.fs | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index ed1e382d6c7..87cf3f3db02 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -194,6 +194,7 @@ * Make Entity's adhoc members list lazy ([PR #20286](https://github.com/dotnet/fsharp/pull/20286/changes)) * Constraint solver: `TryD` is now `inline` with `[]` on its always-run continuation, so the argument closures are no longer allocated at the (very hot) constraint-solver call sites; `IgnoreFailedMemberConstraintResolution` is `inline` so its forwarded continuation stays a literal. ([PR #20367](https://github.com/dotnet/fsharp/pull/20367)) * `DelayedILModuleReader` no longer boxes its cached `ILModuleReader` on every read: the field is typed `ILModuleReader | null` and matched directly. ([PR #20413](https://github.com/dotnet/fsharp/pull/20413)) +* IlxGen: `HashRangeSorted` returns immediately for an empty dictionary and otherwise sorts one exact-size array in place, rather than building three intermediate lists. Most type definitions carry no properties, so this takes the whole pipeline off the common path. ([PR #20456](https://github.com/dotnet/fsharp/pull/20456)) ### Changed * The `--warnaserror` option now ignores unrecognized diagnostic identifiers in warning lists while still applying recognized F# warning codes. ([PR #20246](https://github.com/dotnet/fsharp/pull/20246)) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index ceeec0a69ec..212d6342653 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -1971,8 +1971,16 @@ let GenPossibleILDebugRange (cenv: cenv) m = // Helpers for merging property definitions //-------------------------------------------------------------------------- +// Most type definitions carry no properties at all, so the empty case is the one worth being cheap. +// The sort keys are the insertion indices AddPropertyDefToHash hands out, which are unique, so an +// unstable sort orders these exactly as the stable one did. let HashRangeSorted (ht: IDictionary<_, int * _>) = - [ for KeyValue(_k, v) in ht -> v ] |> List.sortBy fst |> List.map snd + if ht.Count = 0 then + [] + else + let entries = Array.ofSeq ht.Values + Array.sortInPlaceBy fst entries + [ for _, v in entries -> v ] let MergeOptions m o1 o2 = match o1, o2 with From a4b071dce3224bc8cbddf5ef7e903882ddcde960 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Wed, 9 Sep 2026 22:16:18 +0200 Subject: [PATCH 2/3] Drop the HashRangeSorted entries from the ILVerify baselines The two StackUnexpected entries came from the closures the List pipeline emitted where FSharpFunc was expected. Sorting the array in place no longer produces them. Verified by running dotnet ilverify over both Release targets of the built FSharp.Compiler.Service: ten errors each, none in HashRangeSorted, matching these baselines exactly. The Debug baselines never carried the entries. Co-Authored-By: Claude Opus 5 --- .../ilverify_FSharp.Compiler.Service_Release_netcoreapp.bsl | 2 -- .../ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netcoreapp.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netcoreapp.bsl index 75176d28029..984349b58e0 100644 --- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netcoreapp.bsl +++ b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netcoreapp.bsl @@ -8,5 +8,3 @@ [IL]: Error [ReturnPtrToStack]: : FSharp.Compiler.CodeAnalysis.ItemKeyStore::ReadFirstKeyString()][offset 0x00000064] Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.ItemKeyStoreBuilder::writeRange([FSharp.Compiler.Service]FSharp.Compiler.Text.Range)][offset 0x00000019][found address of '[FSharp.Compiler.Service]FSharp.Compiler.Text.Range'][expected Native Int] Unexpected type on the stack. [IL]: Error [ExpectedNumericType]: : FSharp.Compiler.EditorServices.SemanticClassificationKeyStoreBuilder::WriteAll([FSharp.Compiler.Service]FSharp.Compiler.EditorServices.SemanticClassificationItem[])][offset 0x0000001A][found address of '[FSharp.Compiler.Service]FSharp.Compiler.EditorServices.SemanticClassificationItem'] Expected numeric type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.IlxGen::HashRangeSorted([S.P.CoreLib]System.Collections.Generic.IDictionary`2>)][offset 0x0000000A][found ref '[FSharp.Compiler.Service]FSharp.Compiler.IlxGen+HashRangeSorted'][expected ref '[FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,int32>'] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.IlxGen::HashRangeSorted([S.P.CoreLib]System.Collections.Generic.IDictionary`2>)][offset 0x0000000B][found ref '[FSharp.Compiler.Service]FSharp.Compiler.IlxGen+HashRangeSorted'][expected ref '[FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,T0>'] Unexpected type on the stack. diff --git a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl index 75176d28029..984349b58e0 100644 --- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl +++ b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl @@ -8,5 +8,3 @@ [IL]: Error [ReturnPtrToStack]: : FSharp.Compiler.CodeAnalysis.ItemKeyStore::ReadFirstKeyString()][offset 0x00000064] Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.ItemKeyStoreBuilder::writeRange([FSharp.Compiler.Service]FSharp.Compiler.Text.Range)][offset 0x00000019][found address of '[FSharp.Compiler.Service]FSharp.Compiler.Text.Range'][expected Native Int] Unexpected type on the stack. [IL]: Error [ExpectedNumericType]: : FSharp.Compiler.EditorServices.SemanticClassificationKeyStoreBuilder::WriteAll([FSharp.Compiler.Service]FSharp.Compiler.EditorServices.SemanticClassificationItem[])][offset 0x0000001A][found address of '[FSharp.Compiler.Service]FSharp.Compiler.EditorServices.SemanticClassificationItem'] Expected numeric type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.IlxGen::HashRangeSorted([S.P.CoreLib]System.Collections.Generic.IDictionary`2>)][offset 0x0000000A][found ref '[FSharp.Compiler.Service]FSharp.Compiler.IlxGen+HashRangeSorted'][expected ref '[FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,int32>'] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.IlxGen::HashRangeSorted([S.P.CoreLib]System.Collections.Generic.IDictionary`2>)][offset 0x0000000B][found ref '[FSharp.Compiler.Service]FSharp.Compiler.IlxGen+HashRangeSorted'][expected ref '[FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,T0>'] Unexpected type on the stack. From d04d68c3da09d99f9fb9655016cadfa756662561 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Wed, 9 Sep 2026 22:32:14 +0200 Subject: [PATCH 3/3] Document HashRangeSorted with an XML comment Co-Authored-By: Claude Opus 5 --- src/Compiler/CodeGen/IlxGen.fs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 212d6342653..b582b524e09 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -1971,9 +1971,14 @@ let GenPossibleILDebugRange (cenv: cenv) m = // Helpers for merging property definitions //-------------------------------------------------------------------------- -// Most type definitions carry no properties at all, so the empty case is the one worth being cheap. -// The sort keys are the insertion indices AddPropertyDefToHash hands out, which are unique, so an -// unstable sort orders these exactly as the stable one did. +/// +/// Returns the merged property definitions ordered by the index they were added with. +/// +/// +/// Most type definitions carry no properties at all, so the empty case is the one worth being cheap. +/// The sort keys are the indices hands out, which are never +/// duplicated, so an unstable sort orders these exactly as the stable one did. +/// let HashRangeSorted (ht: IDictionary<_, int * _>) = if ht.Count = 0 then []