|  | 
|  | 1 | +# Compiling Equality | 
|  | 2 | + | 
|  | 3 | +This spec covers how equality is compiled and executed by the F# compiler and library, based mainly on the types involved in the equality operation after all inlining, type specialization and other optimizations have been applied. | 
|  | 4 | + | 
|  | 5 | +## What do we mean by an equality operation? | 
|  | 6 | + | 
|  | 7 | +This spec is about the semantics and performance of the following coding constructs | 
|  | 8 | + | 
|  | 9 | +* `a = b` | 
|  | 10 | +* `a <> b` | 
|  | 11 | + | 
|  | 12 | +It is also about the semantics and performance of uses of the following `FSharp.Core` constructs which, after inlining, generate code that contains an equality check at the specific `EQTYPE` | 
|  | 13 | +* `HashIdentity.Structural<'T>` | 
|  | 14 | +* `{Array,Seq,List}.contains`  | 
|  | 15 | +* `{Array,Seq,List}.countBy`  | 
|  | 16 | +* `{Array,Seq,List}.groupBy` | 
|  | 17 | +* `{Array,Seq,List}.distinct`  | 
|  | 18 | +* `{Array,Seq,List}.distinctBy`  | 
|  | 19 | +* `{Array,Seq,List}.except`  | 
|  | 20 | + | 
|  | 21 | +All of which have implied equality checks. Some of these operations are inlined, see below, which in turn affects the semantics and performance of the overall operation. | 
|  | 22 | + | 
|  | 23 | +## ER vs PER equality | 
|  | 24 | + | 
|  | 25 | +In math, a (binary) relation is a way to describe a relationship between the elements of sets. "Greater than" is a relation for numbers, "Subset of" is a relation for sets. | 
|  | 26 | + | 
|  | 27 | +Here we talk about 3 particular relations: | 
|  | 28 | +1) **Reflexivity** - every element is related to itself | 
|  | 29 | +  - For integers, `=` is reflexive (`a = a` is always true) and `>` is not (`a > a` is never true) | 
|  | 30 | +2) **Symmetry** - if `a` is related to `b`, then `b` is related to `a` | 
|  | 31 | +  - For integers, `=` is symmetric (`a = b` -> `b = a`) and `>` is not (if `a > b` then `b > a` is false) | 
|  | 32 | +3) **Transitivity** -  if `a` is related to `b`, and `b` is related to `c`, then `a` is also related `c` | 
|  | 33 | +  - For integers, `>` is transitive (`a > b` && `b > c` -> `a > c`) and `√` is not (`a = √b` && `b = √c` doesn't mean `a = √c`) | 
|  | 34 | + | 
|  | 35 | +If a relation has 1, 2, and 3, we talk about **Equivalence Relation (ER)**. If a relation only has 2 and 3, we talk about **Partial Equivalence Relation (PER)**. | 
|  | 36 | + | 
|  | 37 | +This matters in comparing floats since they include [NaN](https://en.wikipedia.org/wiki/NaN). Depending on if we consider `NaN = NaN` true or false, we talk about ER or PER comparison respectively.  | 
|  | 38 | + | 
|  | 39 | +## What is the type known to the compiler and library for an equality operation? | 
|  | 40 | + | 
|  | 41 | +The static type known to the F# compiler is crucial to determining the performance of the operation. The runtime type of the equality check is also significant in some situations. | 
|  | 42 | + | 
|  | 43 | +Here we define the relevant static type `EQTYPE` for the different constructs above: | 
|  | 44 | + | 
|  | 45 | +### Basics | 
|  | 46 | + | 
|  | 47 | +* `a = b`:  `EQTYPE` is the statically known type of `a` or `b` | 
|  | 48 | +* `a <> b`: `EQTYPE` is the statically known type of `a` or `b` | 
|  | 49 | + | 
|  | 50 | +### Inlined constructs | 
|  | 51 | + | 
|  | 52 | +* `HashIdentity.Structural<'T>`, `EQTYPE` is the **inlined** `'T` (results in specialized equality) | 
|  | 53 | +* `Array.contains<'T>`, `EQTYPE` is the **inlined** `'T` (results in specialized equality) | 
|  | 54 | +* `List.contains<T>` likewise | 
|  | 55 | +* `Seq.contains<T>` likewise | 
|  | 56 | + | 
|  | 57 | +These only result in naked generic equality if themselves used from a non-inlined generic context. | 
|  | 58 | + | 
|  | 59 | +### Non-inlined constructs always resulting in naked generic equality | 
|  | 60 | + | 
|  | 61 | +* `Array.groupBy<'Key, 'T> f array`, `EQTYPE` is non-inlined `'Key`, results in naked generic equality | 
|  | 62 | +* `Array.countBy array` likewise for `'T` | 
|  | 63 | +* `Array.distinct<'T> array` likewise | 
|  | 64 | +* `Array.distinctBy array` likewise | 
|  | 65 | +* `Array.except array` likewise | 
|  | 66 | +* `List.groupBy` likewise | 
|  | 67 | +* `List.countBy` likewise | 
|  | 68 | +* `List.distinct` likewise | 
|  | 69 | +* `List.distinctBy` likewise | 
|  | 70 | +* `List.except` likewise | 
|  | 71 | +* `Seq.groupBy` likewise | 
|  | 72 | +* `Seq.countBy` likewise | 
|  | 73 | +* `Seq.distinct` likewise | 
|  | 74 | +* `Seq.distinctBy` likewise | 
|  | 75 | +* `Seq.except` likewise | 
|  | 76 | + | 
|  | 77 | +These **always** result in naked generic equality checks. | 
|  | 78 | + | 
|  | 79 | +Example 1: | 
|  | 80 | + | 
|  | 81 | +```fsharp | 
|  | 82 | +let x = HashIdentity.Structural<byte>  // EQTYPE known to compiler is `byte` | 
|  | 83 | +``` | 
|  | 84 | + | 
|  | 85 | +Example 2 (a non-inlined "naked" generic context): | 
|  | 86 | + | 
|  | 87 | +```fsharp | 
|  | 88 | +let f2<'T> () = | 
|  | 89 | +   ... some long code | 
|  | 90 | +   // EQTYPE known to the compiler is `'T` | 
|  | 91 | +   // RUNTIME-EQTYPE known to the library is `byte` | 
|  | 92 | +   let x = HashIdentity.Structural<'T> | 
|  | 93 | +   ... some long code | 
|  | 94 | +
 | 
|  | 95 | +f2<byte>() // performance of this is determined by EQTYPE<'T> and RUNTIME-EQTYPE<byte> | 
|  | 96 | +``` | 
|  | 97 | + | 
|  | 98 | +Example 3 (an inlined generic context): | 
|  | 99 | + | 
|  | 100 | +```fsharp | 
|  | 101 | +let f3<'T> () = | 
|  | 102 | +   ... some long code | 
|  | 103 | +   // EQTYPE known to the compiler is `byte` | 
|  | 104 | +   // RUNTIME-EQTYPE known to the library is `byte` | 
|  | 105 | +   let x = HashIdentity.Structural<'T> | 
|  | 106 | +   ... some long code | 
|  | 107 | +
 | 
|  | 108 | +f3<byte>() // performance of this is determined by EQTYPE<byte> and RUNTIME-EQTYPE<byte> | 
|  | 109 | +``` | 
|  | 110 | + | 
|  | 111 | +Example 4 (a generic struct type in a non-inline generic context): | 
|  | 112 | + | 
|  | 113 | +```fsharp | 
|  | 114 | +let f4<'T> () = | 
|  | 115 | +   ... some long code | 
|  | 116 | +   // EQTYPE known to the compiler is `SomeStructType<'T>` | 
|  | 117 | +   // RUNTIME-EQTYPE known to the library is `SomeStructType<byte>` | 
|  | 118 | +   let x = HashIdentity.Structural<SomeStructType<'T>> | 
|  | 119 | +   ... some long code | 
|  | 120 | +
 | 
|  | 121 | +f4<byte>() // performance of this determined by EQTYPE<SomeStructType<'T>> and RUNTIME-EQTYPE<SomeStructType<byte>> | 
|  | 122 | +``` | 
|  | 123 | + | 
|  | 124 | +## How we compile equality "a = b" | 
|  | 125 | + | 
|  | 126 | +This very much depends on the `EQTYPE` involved in the equality as known by the compiler | 
|  | 127 | + | 
|  | 128 | +Aim here is to flesh these all out with: | 
|  | 129 | +* **Semantics**: what semantics the user expects, and what the semantics actually is | 
|  | 130 | +* **Perf expectation**: what perf the user expects | 
|  | 131 | +* **Compilation today**: How we actually compile today | 
|  | 132 | +* **Perf today**: What is the perf we achieve today | 
|  | 133 | +* (Optional) sharplab.io link to how things are in whatever version is selected in sharplab | 
|  | 134 | +* (Optional) notes | 
|  | 135 | + | 
|  | 136 | +### primitive integer types (`int32`, `int64`, ...) | 
|  | 137 | + | 
|  | 138 | +```fsharp | 
|  | 139 | +let f (x: int) (y: int) = (x = y) | 
|  | 140 | +``` | 
|  | 141 | + | 
|  | 142 | +* Semantics: equality on primitive | 
|  | 143 | +* Perf: User expects full performance down to native | 
|  | 144 | +* Compilation today: compiles to IL instruction ✅ | 
|  | 145 | +* Perf today: good ✅ | 
|  | 146 | +* [sharplab int32](https://sharplab.io/#v2:DYLgZgzgNAJiDUAfYBTALgAjBgFADxAwEsA7NASlwE9DSKMBeXPRjK8gWACgg===) | 
|  | 147 | + | 
|  | 148 | +### primitive floating point types (`float32`, `float64`) | 
|  | 149 | + | 
|  | 150 | +```fsharp | 
|  | 151 | +let f (x: float32) (y: float32) = (x = y) | 
|  | 152 | +``` | 
|  | 153 | + | 
|  | 154 | +* Semantics: IEEE floating point equality (respecting NaN etc.) | 
|  | 155 | +* Perf: User expects full performance down to native | 
|  | 156 | +* Compilation today: compiles to IL instruction ✅ | 
|  | 157 | +* Perf today: good ✅ | 
|  | 158 | +* [sharplab float32](https://sharplab.io/#v2:DYLgZgzgNAJiDUAfYBTALgAjBgFADxC2AHsBDNAZgCYBKXAT0LBPOroF5c8NP6aBYAFBA===) | 
|  | 159 | + | 
|  | 160 | +### primitive `string`, `decimal` | 
|  | 161 | + | 
|  | 162 | +* Semantics: .NET equivalent equality, non-localized for strings | 
|  | 163 | +* Perf: User expects full performance down to native | 
|  | 164 | +* Compilation today: compiles to `String.Equals` or `Decimal.op_Equality` call ✅ | 
|  | 165 | +* Perf today: good ✅ | 
|  | 166 | +* [sharplab decimal](https://sharplab.io/#v2:DYLgZgzgNALiCWwoBMQGoA+wCmMAEYeAFAB4h7LYDG8AtgIbACUxAnuZTQ83gLzEk+eVkwCwAKCA) | 
|  | 167 | +* [sharplab string](https://sharplab.io/#v2:DYLgZgzgNALiCWwoBMQGoA+wCmMAEYeAFAB4h4QwBO8AdgOYCUxAnuZTQ8wLzEl68WjALAAoIA==) | 
|  | 168 | + | 
|  | 169 | +### reference tuple type (size <= 5) | 
|  | 170 | + | 
|  | 171 | +* Semantics: User expects structural | 
|  | 172 | +* Perf: User expects flattening to constituent checks | 
|  | 173 | +* Compilation today: tuple equality is flattened to constituent checks ✅ | 
|  | 174 | +* Perf today: good ✅ | 
|  | 175 | +* [sharplab (int * double * 'T), with example reductions/optimizations noted](https://sharplab.io/#v2:DYLgZgzgPgsAUMApgFwARlQCgB4iwSwDs0AqVAEwHsBXAIyVTIHIAVASjdQE9UBeLbH25t48TCVFxB/LpIC0cosCJEA5goB8kgOKJCiAE74AxgFEAjtQCGy5D0Gy48BUpWF1crU7gAJKxAALAGFKAFsABysDRAA6XX0jM0sbfDsAMX80B1R5RUJlQjVNHT1DEwtrWy4ASWIjQggTAB4WAEZGVBYAJg6WAGYNVAdcgHlw5HxQ/AAvQ00sckQAN3wDNHiypMrUmrqiRuMRbwyIZAqbCBZqcKQ+1AAZK3drVUQABSMpiaXECDjSxIhCJRQwCVoAGmwXUhfU4mC4EK40K4sNyrkK7mK3iQaGMYUi0QMQkezysrw+k1S+B+fw2gPxIIM8Dp5WSVQA6qlggzCSdcTzQdh2gjUAAyUXMgGs7Z2TnIbnA3mZVB4xWCnpIsUSuAsrYpWVcoEEwx8lUConYO4o3KDSQ4s1qon8EmqF7vT5Umn/BImI2M+DGRDmIbC9rigNBoYanrhnVSvUcw3m2rIeoHB3Gi1WvqSEhHeBAA==) | 
|  | 176 | + | 
|  | 177 | +### reference tuple type (size > 5) | 
|  | 178 | + | 
|  | 179 | +* Semantics: User expects structural | 
|  | 180 | +* Perf: User expects flattening to constituent checks | 
|  | 181 | +* Compilation today: not flattened, compiled to `GenericEqualityIntrinsic` | 
|  | 182 | +* Perf today: the check does type tests, does virtual calls via `IStructuralEqualityComparer`, boxes etc. ❌(Problem1) | 
|  | 183 | +* [sharplab for size 6](https://sharplab.io/#v2:DYLgZgzgPgsAUMApgFwARlQCgB4iwSwDs0AqVI0841MimqyigSidQE9UBeLbL9p+EA==) | 
|  | 184 | + | 
|  | 185 | +### struct tuple type | 
|  | 186 | + | 
|  | 187 | +* Semantics: User expects structural | 
|  | 188 | +* Perf: User expects flattening to constituent checks or at least the same optimizations as tuples | 
|  | 189 | +* Compilation today: compiled to `GenericEqualityIntrinsic` | 
|  | 190 | +* Perf today: boxes, does type tests, does virtual calls via `IStructuralEqualityComparer` etc. ❌(Problem2) | 
|  | 191 | +* [sharplab for size 3](https://sharplab.io/#v2:DYLgZgzgPgsAUMApgFwARlQCgB4lRZAJwFcBjNTASwDs0AqVG+x2gSldQE9UBeLbXl1bwgA=) | 
|  | 192 | + | 
|  | 193 | +### C# or F# enum type | 
|  | 194 | + | 
|  | 195 | +* Semantics: User expects identical to equality on the underlying type | 
|  | 196 | +* Perf: User expects same perf as flattening to underlying type | 
|  | 197 | +* Compilation today: flattens to underlying type | 
|  | 198 | +* Perf today: good ✅ | 
|  | 199 | +* [sharplab for C# enum int](https://sharplab.io/#v2:DYLgZgzgNALiCWwA+BYAUMApjABGHAFAB4g4DKAnhDJgLYB0AIgIYUDyYA6ppgNYCUOCjgC8hIqKH90QA===) | 
|  | 200 | +* [sharplab for F# enum int](https://sharplab.io/#v2:DYLgZgzgNALiCWwA+BYAUDAngBwKYAIBRfAXn3X0qXwEFT8BGCq/AIXoCZ11hcZ8w+ABQAPEEQCU+TPVH1ME9EA=) | 
|  | 201 | + | 
|  | 202 | +### C# struct type | 
|  | 203 | + | 
|  | 204 | +* Semantics: User expects call to `IEquatable<T>` if present, but F# spec says call `this.Equals(box that)`, in practice these are the same | 
|  | 205 | +* Perf expected: no boxing | 
|  | 206 | +* Compilation today: `GenericEqualityIntrinsic<SomeStructType>` | 
|  | 207 | +* Perf today: always boxes (Problem3 ❌) | 
|  | 208 | +* [sharplab](https://sharplab.io/#v2:DYLgZgzgNALiCWwA+BYAUMApjABGHAFAB4g4DKAnhDJgLYB0AIgIY0Aq8tmA8mJNgEocFHAF5CRMcIHogA==) | 
|  | 209 | +* Note: [#16615](https://github.com/dotnet/fsharp/pull/16615) will improve things here since we'll start avoiding boxing | 
|  | 210 | + | 
|  | 211 | +### F# struct type (records, tuples - with compiler-generated structural equality) | 
|  | 212 | + | 
|  | 213 | +* Semantics: User expects field-by-field structural equality with no boxing | 
|  | 214 | +* Perf expected: no boxing | 
|  | 215 | +* Compilation today: `GenericEqualityIntrinsic<SomeStructType>` | 
|  | 216 | +* Perf today: always boxes (Problem3 ❌) | 
|  | 217 | +* [sharplab](https://sharplab.io/#v2:DYLgZgzgNALiCWwA+BYAUAbQDwGUYCcBXAYxgD4BddGATwAcBTAAhwHsBbBvI0gCgDcQTeADsYUJoSGiYASiYBedExVNO7AEYN8TAPoA6AGqKm/ZavVadBgKonC6dMAYwmYJrwAeQtp24k5JhoTLxMaWXQgA) | 
|  | 218 | +* Note: the optimization path is a bit strange here, see the reductions below | 
|  | 219 | + | 
|  | 220 | +<details> | 
|  | 221 | + | 
|  | 222 | +<summary>Details</summary> | 
|  | 223 | + | 
|  | 224 | +```fsharp | 
|  | 225 | +(x = y)  | 
|  | 226 | +
 | 
|  | 227 | +--inline-->  | 
|  | 228 | +
 | 
|  | 229 | +GenericEquality x y  | 
|  | 230 | +
 | 
|  | 231 | +--inline-->  | 
|  | 232 | +
 | 
|  | 233 | +GenericEqualityFast x y  | 
|  | 234 | +
 | 
|  | 235 | +--inline-->  | 
|  | 236 | +
 | 
|  | 237 | +GenericEqualityIntrinsic x y | 
|  | 238 | +
 | 
|  | 239 | +--devirtualize--> | 
|  | 240 | +
 | 
|  | 241 | +x.Equals(box y, LanguagePrimitives.GenericEqualityComparer); | 
|  | 242 | +``` | 
|  | 243 | + | 
|  | 244 | +The struct type has these generated methods: | 
|  | 245 | +```csharp | 
|  | 246 | +    override bool Equals(object y)  | 
|  | 247 | +    override bool Equals(SomeStruct obj) | 
|  | 248 | +    override bool Equals(object obj, IEqualityComparer comp) //with EqualsVal | 
|  | 249 | +``` | 
|  | 250 | + | 
|  | 251 | +These call each other in sequence, boxing then unboxing then boxing. We do NOT generate this method, we probably should: | 
|  | 252 | + | 
|  | 253 | +```csharp | 
|  | 254 | +    override bool Equals(SomeStruct obj, IEqualityComparer comp) //with EqualsValUnboxed | 
|  | 255 | +``` | 
|  | 256 | + | 
|  | 257 | +If we did, the devirtualizing optimization should reduce to this directly, which would result in no boxing. | 
|  | 258 | + | 
|  | 259 | +</details> | 
|  | 260 | + | 
|  | 261 | +### array type (byte[], int[], some-struct-type[],  ...) | 
|  | 262 | + | 
|  | 263 | +* Semantics: User expects structural | 
|  | 264 | +* Perf expected: User expects perf is sum of constituent parts | 
|  | 265 | +* Compilation today: `GenericEqualityIntrinsic<uint8[]>` | 
|  | 266 | +* Perf today: hand-optimized ([here](https://github.com/dotnet/fsharp/blob/611e4f350e119a4173a2b235eac65539ac2b61b6/src/FSharp.Core/prim-types.fs#L1562)) for some primitive element types ✅ but boxes each element if "other" is struct or generic, see Problem3 ❌, Problem4 ❌  | 
|  | 267 | +* [sharplab for `byte[]`](https://sharplab.io/#v2:DYLgZgzgPgsAUMApgFwARlQCgB4lQIwE9lEBtAXQEpVDUBeLbemy+IA=) | 
|  | 268 | +* Note: ([#16615](https://github.com/dotnet/fsharp/pull/16615)) will improve this compiling to either ``FSharpEqualityComparer_PER`1<uint8[]>::get_EqualityComparer().Equals(...)`` or ``FSharpEqualityComparer_PER`1<T[]>::get_EqualityComparer().Equals(...)`` | 
|  | 269 | + | 
|  | 270 | +### F# large reference record/union type | 
|  | 271 | + | 
|  | 272 | +Here "large" means the compiler-generated structural equality is NOT inlined. | 
|  | 273 | + | 
|  | 274 | +* Semantics: User expects structural by default | 
|  | 275 | +* Perf expected: User expects perf is sum of constituent parts, type-specialized if generic | 
|  | 276 | +* Compilation today: direct call to `Equals(T)` | 
|  | 277 | +* Perf today: the call to `Equals(T)` has specialized code but boxes fields if struct or generic, see Problem3 ❌, Problem4 ❌ | 
|  | 278 | + | 
|  | 279 | +### F# tiny reference (anonymous) record or union type | 
|  | 280 | + | 
|  | 281 | +Here "tiny" means the compiler-generated structural equality IS inlined. | 
|  | 282 | + | 
|  | 283 | +* Semantics: User expects structural by default | 
|  | 284 | +* Perf expected: User expects perf is sum of constituent parts, type-specialized if generic | 
|  | 285 | +* Compilation today: flattened, calling `GenericEqualityERIntrinsic` on struct and generic fields | 
|  | 286 | +* Perf today: boxes on struct and generic fields, see Problem3 ❌, Problem4 ❌ | 
|  | 287 | +* Note: [#16615](https://github.com/dotnet/fsharp/pull/16615) will help, compiling to ``FSharpEqualityComparer_ER`1<!a>::get_EqualityComparer().Equals(...)`` on struct and generic fields | 
|  | 288 | + | 
|  | 289 | +### Generic `'T` in non-inlined generic code | 
|  | 290 | + | 
|  | 291 | +* Semantics: User expects the PER equality semantics of whatever `'T` actually is   | 
|  | 292 | +* Perf expected: User expects no boxing | 
|  | 293 | +* Compilation today: `GenericEqualityERIntrinsic`  | 
|  | 294 | +* Perf today: boxes if `'T` is any non-reference type (Problem4 ❌) | 
|  | 295 | +* Note:  [#16615](https://github.com/dotnet/fsharp/pull/16615) will improve this compiling to ``FSharpEqualityComparer_ER`1<!a>::get_EqualityComparer().Equals(...)`` | 
|  | 296 | + | 
|  | 297 | +### Generic `'T` in recursive position in structural comparison | 
|  | 298 | + | 
|  | 299 | +This case happens in structural equality for tuple types and other structural types | 
|  | 300 | + | 
|  | 301 | +* Semantics: User expects the PER equality semantics of whatever `'T` actually is   | 
|  | 302 | +* Perf: User expects no boxing | 
|  | 303 | +* Compilation today: `GenericEqualityWithComparerIntrinsic LanguagePrimitives.GenericComparer`  | 
|  | 304 | +* Perf today: boxes for if `'T` is any non-reference type (Problem4 ❌) | 
|  | 305 | +* [Sharplab](https://sharplab.io/#v2:DYLgZgzgPgsAUMApgFwARlQCgB4iwSwDs0AqVAEwHsBXAIyVTIHIAVASjdQE9UBeLbH25t48TCVFxB/LpIC0cosCJEA5goB8kgOKJCiAE74AxgFEAjtQCGy5D0Gy48BUpWF1crU7gAJKxAALAGFKAFsABysDRAA6XX0jM0sbfDsAMX80B1R5RUJlQjVNHT1DEwtrWy4ASWIjQggTAB4WAEZGVBYAJg6WAGYNVAdcgHlw5HxQ/AAvQ00sckQAN3wDNHiypMrUmrqiRuMRbwyIZAqbCBZqcKQ+1AAZK3drVUQABSMpiaXECDjSxIhCJRQwCVoAGmwXUhfU4mC4EK40K4sNyrkK7mK3iQaGMYUi0QMQkezysrw+k1S+B+fw2gPxIIM8Dp5WSVQA6qlggzCSdcTzQdh2gjUAAyUXMgGs7Z2TnIbnA3mZVB4xWCnpIsUSuAsrYpWVcoEEwx8lUConYO4o3KDSQ4s1qon8EmqF7vT5Umn/BImI2M+DGRDmIbC9rigNBoYanrhnVSvUcw3m2rIeoHB3Gi1WvqSEhHeBAA==) | 
|  | 306 | +* Note: [#16615](https://github.com/dotnet/fsharp/pull/16615) will compile to ``FSharpEqualityComparer_ER`1<!a>::get_EqualityComparer().Equals(...)`` and avoid boxing in many cases | 
|  | 307 | + | 
|  | 308 | +## Techniques available to us | 
|  | 309 | +  | 
|  | 310 | +1. Flatten and inline | 
|  | 311 | +2. RCG: Use reflective code generation internally in FSharp.Core | 
|  | 312 | +3. KFS: Rely on known semantics of F# structural types and treat those as special | 
|  | 313 | +4. TS: Hand-code type-specializations using static optimization conditions in FSharp.Core | 
|  | 314 | +5. TT: Type-indexed tables of baked (poss by reflection) equality comparers and functions, where some pre-computation is done  | 
|  | 315 | +6. DV: De-virtualization | 
|  | 316 | +7. DEQ: Use `EqualityComparer<'T>.Default` where possible | 
|  | 317 | + | 
|  | 318 | +## Notes on previous attempts to improve things | 
|  | 319 | + | 
|  | 320 | +### [#5112](https://github.com/dotnet/fsharp/pull/5112) | 
|  | 321 | + | 
|  | 322 | +* Uses TT, DEQ, KFS, DV | 
|  | 323 | +* Focuses on solving Problem4 | 
|  | 324 | +* 99% not breaking, apart from the case of value types with custom equality implemented differently than the `EqualityComparer.Default` - the change would lead to the usage of the custom implementation which is reasonable | 
|  | 325 | + | 
|  | 326 | +Note: this included [changes to the optimizer to reduce GenericEqualityIntrinsic](https://github.com/dotnet/fsharp/pull/5112/files#diff-be48dbef2f0baca27a783ac4a31ec0aedb2704c7f42ea3a2b8228513f9904cfbR2360-R2363) down to a type-indexed table lookup fetching an `IEqualityComparer` and calling it. These hand-coded reductions appear unnecessary as the reduction doesn't open up any further optimizations. | 
0 commit comments