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 8669e7142f0..73971b6a385 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -20,6 +20,7 @@ * Fixed how the source ranges of warn directives are reported (as trivia) in the parser output (by not reporting leading spaces). ([Issue #19405](https://github.com/dotnet/fsharp/issues/19405), [PR #19408]((https://github.com/dotnet/fsharp/pull/19408))) * Fix UoM value type `ToString()` returning garbage values when `--checknulls+` is enabled, caused by double address-taking in codegen. ([Issue #19435](https://github.com/dotnet/fsharp/issues/19435), [PR #19440](https://github.com/dotnet/fsharp/pull/19440)) * Fix completion inconsistently showing some obsolete members (fields and events) while hiding others (methods and properties). All obsolete members are now consistently hidden by default. ([Issue #13512](https://github.com/dotnet/fsharp/issues/13512), [PR #19506](https://github.com/dotnet/fsharp/pull/19506)) +* Fix O(n) `TypeStructure.GetHashCode` performance regression causing sustained high CPU in IDE mode with generative type providers. ([Issue #18925](https://github.com/dotnet/fsharp/issues/18925), [PR #19369](https://github.com/dotnet/fsharp/pull/19369)) * Fix TypeLoadException when creating delegate with voidptr parameter. (Issue [#11132](https://github.com/dotnet/fsharp/issues/11132), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) * Suppress tail calls when localloc (NativePtr.stackalloc) is used. (Issue [#13447](https://github.com/dotnet/fsharp/issues/13447), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) * Fix TypeLoadException in Release builds with inline constraints. (Issue [#14492](https://github.com/dotnet/fsharp/issues/14492), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) @@ -32,3 +33,4 @@ * Added warning FS3884 when a function or delegate value is used as an interpolated string argument. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289)) * Add `#version;;` directive to F# Interactive to display version and environment information. ([Issue #13307](https://github.com/dotnet/fsharp/issues/13307), [PR #19332](https://github.com/dotnet/fsharp/pull/19332)) + diff --git a/src/Compiler/Checking/OverloadResolutionCache.fs b/src/Compiler/Checking/OverloadResolutionCache.fs index cbf6b1aca03..aae0a99bc2f 100644 --- a/src/Compiler/Checking/OverloadResolutionCache.fs +++ b/src/Compiler/Checking/OverloadResolutionCache.fs @@ -83,12 +83,12 @@ let tryGetTypeStructureForOverloadCache (g: TcGlobals) (ty: TType) : TypeStructu let ty = stripTyEqns g ty match tryGetTypeStructureOfStrippedType ty with - | ValueSome(Stable tokens) -> ValueSome(Stable tokens) - | ValueSome(Unstable tokens) -> + | ValueSome(Stable(h, tokens)) -> ValueSome(Stable(h, tokens)) + | ValueSome(Unstable(h, tokens)) -> if hasUnsolvedTokens tokens then ValueNone else - ValueSome(Stable tokens) + ValueSome(Stable(h, tokens)) | ValueSome PossiblyInfinite -> ValueNone | ValueNone -> ValueNone @@ -162,8 +162,12 @@ let tryComputeOverloadCacheKey | Some retTy -> match tryGetTypeStructureForOverloadCache g retTy with | ValueSome ts -> ValueSome ts - | ValueNone -> if anyHasOutArgs then ValueNone else ValueSome(Stable [||]) - | None -> ValueSome(Stable [||]) + | ValueNone -> + if anyHasOutArgs then + ValueNone + else + ValueSome(Stable(0, [||])) + | None -> ValueSome(Stable(0, [||])) match retTyStructure with | ValueNone -> ValueNone diff --git a/src/Compiler/Utilities/TypeHashing.fs b/src/Compiler/Utilities/TypeHashing.fs index 7056e4c229c..5ada7732fe4 100644 --- a/src/Compiler/Utilities/TypeHashing.fs +++ b/src/Compiler/Utilities/TypeHashing.fs @@ -407,12 +407,37 @@ module StructuralUtilities = | Unsolved of int | Rigid of int + let private hashTokenArray (tokens: TypeToken[]) = + let mutable acc = 0 + + for t in tokens do + acc <- combineHash acc (hash t) + + acc + + [] type TypeStructure = - | Stable of TypeToken[] + | Stable of hash: int * tokens: TypeToken[] // Unstable means that the type structure of a given TType may change because of constraint solving or Trace.Undo. - | Unstable of TypeToken[] + | Unstable of hash: int * tokens: TypeToken[] | PossiblyInfinite + override this.GetHashCode() = + match this with + | Stable(h, _) -> h + | Unstable(h, _) -> h ||| 0x40000000 + | PossiblyInfinite -> 0 + + override this.Equals(obj) = + match obj with + | :? TypeStructure as other -> + match this, other with + | Stable(h1, a), Stable(h2, b) -> h1 = h2 && a = b + | Unstable(h1, a), Unstable(h2, b) -> h1 = h2 && a = b + | PossiblyInfinite, PossiblyInfinite -> true + | _ -> false + | _ -> false + type private GenerationContext() = member val TyparMap = System.Collections.Generic.Dictionary(4) member val Tokens = ResizeArray(MaxTokenCount) @@ -565,9 +590,16 @@ module StructuralUtilities = let out = ctx.Tokens // If the sequence got too long, just drop it, we could be dealing with an infinite type. - if out.Count >= MaxTokenCount then PossiblyInfinite - elif not ctx.Stable then Unstable(out.ToArray()) - else Stable(out.ToArray()) + if out.Count >= MaxTokenCount then + PossiblyInfinite + else + let tokens = out.ToArray() + let h = hashTokenArray tokens + + if not ctx.Stable then + Unstable(h, tokens) + else + Stable(h, tokens) // Speed up repeated calls by memoizing results for types that yield a stable structure. let private getTypeStructureOfStrippedType =