Skip to content

Commit 226338e

Browse files
authored
Type subsumption cache, fix keys to reduce memory use (#18875)
1 parent 71167fb commit 226338e

File tree

13 files changed

+167
-165
lines changed

13 files changed

+167
-165
lines changed

azure-pipelines-PR.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,13 +457,11 @@ stages:
457457
_configuration: Release
458458
_testKind: testFSharpQA
459459
transparentCompiler:
460-
FSHARP_CACHE_OVERRIDE: 256
461460
vs_release:
462461
_configuration: Release
463462
_testKind: testVs
464463
setupVsHive: true
465464
transparentCompiler:
466-
FSHARP_CACHE_OVERRIDE: 256
467465
transparent_compiler_release:
468466
_configuration: Release
469467
_testKind: testCoreclr

src/Compiler/Checking/TypeRelations.fs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module internal FSharp.Compiler.TypeRelations
77
open FSharp.Compiler.Features
88
open Internal.Utilities.Collections
99
open Internal.Utilities.Library
10+
open Internal.Utilities.TypeHashing.StructuralUtilities
1011

1112
open FSharp.Compiler.DiagnosticsLogger
1213
open FSharp.Compiler.TcGlobals
@@ -19,6 +20,26 @@ open Import
1920

2021
#nowarn "3391"
2122

23+
[<Struct; NoComparison>]
24+
type CanCoerce =
25+
| CanCoerce
26+
| NoCoerce
27+
28+
[<Struct; NoComparison>]
29+
type TTypeCacheKey =
30+
| TTypeCacheKey of TypeStructure * TypeStructure * CanCoerce
31+
static member FromStrippedTypes(ty1, ty2, canCoerce) =
32+
TTypeCacheKey(getTypeStructure ty1, getTypeStructure ty2, canCoerce)
33+
34+
let getTypeSubsumptionCache =
35+
let factory (g: TcGlobals) =
36+
let options =
37+
match g.compilationMode with
38+
| CompilationMode.OneOff -> Caches.CacheOptions.getDefault() |> Caches.CacheOptions.withNoEviction
39+
| _ -> { Caches.CacheOptions.getDefault() with TotalCapacity = 65536; HeadroomPercentage = 75 }
40+
new Caches.Cache<TTypeCacheKey, bool>(options, "typeSubsumptionCache")
41+
Extras.WeakMap.getOrCreate factory
42+
2243
/// Implements a :> b without coercion based on finalized (no type variable) types
2344
// Note: This relation is approximate and not part of the language specification.
2445
//
@@ -136,14 +157,8 @@ let rec TypeFeasiblySubsumesType ndeep (g: TcGlobals) (amap: ImportMap) m (ty1:
136157
List.exists (TypeFeasiblySubsumesType (ndeep + 1) g amap m ty1 NoCoerce) interfaces
137158

138159
if g.langVersion.SupportsFeature LanguageFeature.UseTypeSubsumptionCache then
139-
let key = TTypeCacheKey.FromStrippedTypes (ty1, ty2, canCoerce)
140-
141-
match amap.TypeSubsumptionCache.TryGetValue(key) with
142-
| true, subsumes -> subsumes
143-
| false, _ ->
144-
let subsumes = checkSubsumes ty1 ty2
145-
amap.TypeSubsumptionCache.TryAdd(key, subsumes) |> ignore
146-
subsumes
160+
let key = TTypeCacheKey.FromStrippedTypes(ty1, ty2, canCoerce)
161+
(getTypeSubsumptionCache g).GetOrAdd(key, fun _ -> checkSubsumes ty1 ty2)
147162
else
148163
checkSubsumes ty1 ty2
149164

src/Compiler/Checking/TypeRelations.fsi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ open FSharp.Compiler.TcGlobals
99
open FSharp.Compiler.Text
1010
open FSharp.Compiler.TypedTree
1111

12+
[<Struct; NoComparison>]
13+
type CanCoerce =
14+
| CanCoerce
15+
| NoCoerce
16+
1217
/// Implements a :> b without coercion based on finalized (no type variable) types
1318
val TypeDefinitelySubsumesTypeNoCoercion:
1419
ndeep: int -> g: TcGlobals -> amap: ImportMap -> m: range -> ty1: TType -> ty2: TType -> bool

src/Compiler/Checking/import.fs

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -50,51 +50,6 @@ type AssemblyLoader =
5050
abstract RecordGeneratedTypeRoot : ProviderGeneratedType -> unit
5151
#endif
5252

53-
[<Struct; NoComparison>]
54-
type CanCoerce =
55-
| CanCoerce
56-
| NoCoerce
57-
58-
[<Struct; NoComparison; CustomEquality; DebuggerDisplay("{ToString()}")>]
59-
type TTypeCacheKey =
60-
61-
val ty1: TType
62-
val ty2: TType
63-
val canCoerce: CanCoerce
64-
65-
private new (ty1, ty2, canCoerce) =
66-
{ ty1 = ty1; ty2 = ty2; canCoerce = canCoerce }
67-
68-
static member FromStrippedTypes (ty1, ty2, canCoerce) =
69-
TTypeCacheKey(ty1, ty2, canCoerce)
70-
71-
interface System.IEquatable<TTypeCacheKey> with
72-
member this.Equals other =
73-
if this.canCoerce <> other.canCoerce then
74-
false
75-
elif this.ty1 === other.ty1 && this.ty2 === other.ty2 then
76-
true
77-
else
78-
HashStamps.stampEquals this.ty1 other.ty1
79-
&& HashStamps.stampEquals this.ty2 other.ty2
80-
81-
override this.Equals(other:objnull) =
82-
match other with
83-
| :? TTypeCacheKey as p -> (this :> System.IEquatable<TTypeCacheKey>).Equals p
84-
| _ -> false
85-
86-
override this.GetHashCode () : int =
87-
HashStamps.hashTType this.ty1
88-
|> pipeToHash (HashStamps.hashTType this.ty2)
89-
|> pipeToHash (hash this.canCoerce)
90-
91-
override this.ToString () = $"{this.ty1.DebugText}-{this.ty2.DebugText}"
92-
93-
let typeSubsumptionCache =
94-
// Leave most of the capacity in reserve for bursts.
95-
let options = { CacheOptions.getDefault() with TotalCapacity = 131072; HeadroomPercentage = 75 }
96-
lazy new Cache<TTypeCacheKey, bool>(options, "typeSubsumptionCache")
97-
9853
//-------------------------------------------------------------------------
9954
// Import an IL types as F# types.
10055
//-------------------------------------------------------------------------
@@ -117,8 +72,6 @@ type ImportMap(g: TcGlobals, assemblyLoader: AssemblyLoader) =
11772

11873
member _.ILTypeRefToTyconRefCache = typeRefToTyconRefCache
11974

120-
member val TypeSubsumptionCache: Cache<TTypeCacheKey, bool> = typeSubsumptionCache.Value
121-
12275
let CanImportILScopeRef (env: ImportMap) m scoref =
12376

12477
let isResolved assemblyRef =

src/Compiler/Checking/import.fsi

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,6 @@ type AssemblyLoader =
3636
abstract RecordGeneratedTypeRoot: ProviderGeneratedType -> unit
3737
#endif
3838

39-
[<Struct; NoComparison>]
40-
type CanCoerce =
41-
| CanCoerce
42-
| NoCoerce
43-
44-
[<Struct; NoComparison; CustomEquality>]
45-
type TTypeCacheKey =
46-
interface System.IEquatable<TTypeCacheKey>
47-
private new: ty1: TType * ty2: TType * canCoerce: CanCoerce -> TTypeCacheKey
48-
49-
static member FromStrippedTypes: ty1: TType * ty2: TType * canCoerce: CanCoerce -> TTypeCacheKey
50-
51-
val ty1: TType
52-
val ty2: TType
53-
val canCoerce: CanCoerce
54-
55-
override GetHashCode: unit -> int
56-
5739
/// Represents a context used for converting AbstractIL .NET and provided types to F# internal compiler data structures.
5840
/// Also cache the conversion of AbstractIL ILTypeRef nodes, based on hashes of these.
5941
///
@@ -70,9 +52,6 @@ type ImportMap =
7052
/// The TcGlobals for the import context
7153
member g: TcGlobals
7254

73-
/// Type subsumption cache
74-
member TypeSubsumptionCache: Cache<TTypeCacheKey, bool>
75-
7655
module Nullness =
7756

7857
[<Struct; NoEquality; NoComparison>]

src/Compiler/Service/FSharpCheckerResults.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ type internal TypeCheckInfo
538538
// check that type of value is the same or subtype of tcref
539539
// yes - allow access to protected members
540540
// no - strip ability to access protected members
541-
if TypeRelations.TypeFeasiblySubsumesType 0 g amap m thisTy Import.CanCoerce ty then
541+
if TypeRelations.TypeFeasiblySubsumesType 0 g amap m thisTy TypeRelations.CanCoerce ty then
542542
ad
543543
else
544544
AccessibleFrom(paths, None)

src/Compiler/Utilities/Caches.fs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -118,21 +118,6 @@ module CacheOptions =
118118
CacheOptions.EvictionMode = EvictionMode.NoEviction
119119
}
120120

121-
module Cache =
122-
// During testing a lot of compilations are started in app domains and subprocesses.
123-
// This is a reliable way to pass the override to all of them.
124-
[<Literal>]
125-
let private overrideVariable = "FSHARP_CACHE_OVERRIDE"
126-
127-
/// Use for testing purposes to reduce memory consumption in testhost and its subprocesses.
128-
let OverrideCapacityForTesting () =
129-
Environment.SetEnvironmentVariable(overrideVariable, "4096", EnvironmentVariableTarget.Process)
130-
131-
let applyOverride (options: CacheOptions<_>) =
132-
match Int32.TryParse(Environment.GetEnvironmentVariable(overrideVariable)) with
133-
| true, n when options.TotalCapacity > n -> { options with TotalCapacity = n }
134-
| _ -> options
135-
136121
// It is important that this is not a struct, because LinkedListNode holds a reference to it,
137122
// and it holds the reference to that Node, in a circular way.
138123
[<Sealed; NoComparison; NoEquality>]
@@ -176,8 +161,6 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
176161
if options.HeadroomPercentage < 0 then
177162
invalidArg "HeadroomPercentage" "HeadroomPercentage must be positive"
178163

179-
let options = Cache.applyOverride options
180-
181164
let name = defaultArg name (Guid.NewGuid().ToString())
182165

183166
// Determine evictable headroom as the percentage of total capcity, since we want to not resize the dictionary.

src/Compiler/Utilities/Caches.fsi

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,6 @@ module internal CacheOptions =
4949
/// Set eviction mode to NoEviction.
5050
val withNoEviction: CacheOptions<'Key> -> CacheOptions<'Key>
5151

52-
module internal Cache =
53-
/// Use for testing purposes to reduce memory consumption in testhost and its subprocesses.
54-
val OverrideCapacityForTesting: unit -> unit
55-
5652
[<Sealed; NoComparison; NoEquality>]
5753
type internal Cache<'Key, 'Value when 'Key: not null> =
5854
new: options: CacheOptions<'Key> * ?name: string -> Cache<'Key, 'Value>

0 commit comments

Comments
 (0)