From 0297b6b4e4511674719937909e74bf2430fc02ea Mon Sep 17 00:00:00 2001 From: T-Gro Date: Wed, 17 Jun 2026 14:47:38 +0200 Subject: [PATCH 1/4] Fix FieldAccessException when optimizer relocates protected base-field access (#19963) The optimizer inlines/splits a member that reads a protected (family) base-class field into a method outside the field's family (e.g. a trivial member inlined into module/startup code under --optimize+), copying the ldfld/stfld there and producing illegal IL that throws FieldAccessException at runtime. Protected *method*/base calls were already pinned via FreeVars.UsesMethodLocalConstructs (set for TOp.ILCall isProtected) and the optimizer's relocation guards. Field loads were invisible to that machinery. Add a FreeVars.ContainsILFieldAccess flag set for any IL field instruction (TOp.ILAsm ldfld/stfld/...) and OR it into the five optimizer relocation guards. It is deliberately NOT read by the FS0405 escape check, so it can never reject otherwise-valid code; field accessibility is unavailable where free vars are summarised, so the flag is conservative over all IL fields (F# fields use TOp.ValFieldGet and are unaffected). Measured zero EmittedIL baseline churn. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.FSharp.Compiler.Service/11.0.100.md | 1 + src/Compiler/Optimize/Optimizer.fs | 9 ++-- src/Compiler/TypedTree/TypedTree.fs | 8 +++ src/Compiler/TypedTree/TypedTree.fsi | 8 +++ .../TypedTree/TypedTreeOps.Remapping.fs | 42 ++++++++++++++- .../OnTypeMembers/BaseClass.cs | 2 + .../OnTypeMembers/OnTypeMembers.fs | 53 +++++++++++++++++++ 7 files changed, 117 insertions(+), 6 deletions(-) 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 b89cf11dc7e..e6f85d9fcc7 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -5,6 +5,7 @@ * Fix missing FS1182 ("unused binding") warning for unused `let` function bindings inside class types. ([Issue #13849](https://github.com/dotnet/fsharp/issues/13849), [PR #19805](https://github.com/dotnet/fsharp/pull/19805)) * Fix inner mutually-recursive `let rec ... and ...` functions under `--realsig+` not being lifted to top-level static methods (TLR), causing `FSharpFunc` closure allocations and loss of `tail.` opcodes — the large struct-mutual-recursion perf regression reported in [Issue #17607](https://github.com/dotnet/fsharp/issues/17607). ([PR #19882](https://github.com/dotnet/fsharp/pull/19882)) * Fix `TypeLoadException` ("Specialize tried to implicitly override a method with weaker type parameter constraints") and the related CLR crash with constrained inline calls by stripping constraints from closure-class typars in `EraseClosures.convIlxClosureDef`. ([Issue #14492](https://github.com/dotnet/fsharp/issues/14492), [Issue #19075](https://github.com/dotnet/fsharp/issues/19075), [PR #19882](https://github.com/dotnet/fsharp/pull/19882)) +* Fix `FieldAccessException` at runtime when the optimizer relocates a read of a `protected` (family) base-class field into a method outside the field's family (e.g. a trivial member inlined into module/startup code under `--optimize+`). IL field access is no longer hoisted out of its declaring family. ([Issue #19963](https://github.com/dotnet/fsharp/issues/19963)) * Suppress hover/symbol resolution for wildcard `_` patterns inside `member _.…` bodies that incorrectly showed `val _: T` tooltip. ([PR #19760](https://github.com/dotnet/fsharp/pull/19760)) * Deduplicate format specifier locations in computation expressions so editor tooling no longer reports duplicate entries for the same `%` specifier. ([Issue #16419](https://github.com/dotnet/fsharp/issues/16419), [PR #19791](https://github.com/dotnet/fsharp/pull/19791)) diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 3a9856d7912..0d062615e1d 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -1447,7 +1447,7 @@ let AbstractExprInfoByVars (boundVars: Val list, boundTyVars) ivalue = (let fvs = freeInExpr (if isNil boundTyVars then CollectLocalsWithStackGuard() else CollectTyparsAndLocals) expr (not (isNil boundVars) && List.exists (Zset.memberOf fvs.FreeLocals) boundVars) || (not (isNil boundTyVars) && List.exists (Zset.memberOf fvs.FreeTyvars.FreeTypars) boundTyVars) || - fvs.UsesMethodLocalConstructs) -> + fvs.UsesMethodLocalConstructs || fvs.ContainsILFieldAccess) -> // Trimming lambda UnknownValue @@ -3094,7 +3094,7 @@ and TryOptimizeVal cenv env (vOpt: ValRef option, shouldInline, inlineIfLambda, | CurriedLambdaValue (_, _, _, expr, _) when shouldInline || inlineIfLambda -> let fvs = freeInExpr CollectLocals expr - if fvs.UsesMethodLocalConstructs then + if fvs.UsesMethodLocalConstructs || fvs.ContainsILFieldAccess then // Discarding lambda for binding because uses protected members --- TBD: Should we warn or error here None else @@ -3893,7 +3893,7 @@ and OptimizeLambdas (vspec: Val option) cenv env valReprInfo expr exprTy = | None -> CurriedLambdaValue (lambdaId, arities, bsize, exprR, exprTy) | Some baseVal -> let fvs = freeInExpr CollectLocals bodyR - if fvs.UsesMethodLocalConstructs || fvs.FreeLocals.Contains baseVal then + if fvs.UsesMethodLocalConstructs || fvs.ContainsILFieldAccess || fvs.FreeLocals.Contains baseVal then UnknownValue else let expr2 = mkMemberLambdas g m tps ctorThisValOpt None vsl (bodyR, bodyTy) @@ -3976,6 +3976,7 @@ and ComputeSplitToMethodCondition flag threshold cenv env (e: Expr, einfo) = (let fvs = freeInExpr (CollectLocalsWithStackGuard()) e not fvs.UsesUnboundRethrow && not fvs.UsesMethodLocalConstructs && + not fvs.ContainsILFieldAccess && fvs.FreeLocals |> Zset.forall (fun v -> // no direct-self-recursive references not (env.dontSplitVars.ContainsVal v) && @@ -4168,7 +4169,7 @@ and OptimizeBinding cenv isRec env (TBind(vref, expr, spBind)) = UnknownValue else let fvs = freeInExpr CollectLocals body - if fvs.UsesMethodLocalConstructs then + if fvs.UsesMethodLocalConstructs || fvs.ContainsILFieldAccess then // Discarding lambda for binding because uses protected members UnknownValue elif fvs.FreeLocals.ToArray() |> Seq.fold(fun acc v -> if not acc then v.Accessibility.IsPrivate else acc) false then diff --git a/src/Compiler/TypedTree/TypedTree.fs b/src/Compiler/TypedTree/TypedTree.fs index dac2e23c10c..bdb89ca99ed 100644 --- a/src/Compiler/TypedTree/TypedTree.fs +++ b/src/Compiler/TypedTree/TypedTree.fs @@ -6086,6 +6086,14 @@ type FreeVars = /// Rethrow may only occur in such locations. UsesUnboundRethrow: bool + /// Indicates if the expression contains a direct IL field load/store. Such an access to a + /// protected (family) field is only verifiable inside a method whose declaring type is within + /// the field's family, so the optimizer must not relocate it (by inlining or splitting) into a + /// method outside that family (issue #19963). Field accessibility is not available where free + /// variables are summarised, so this is set for any IL field access and is consumed only by the + /// optimizer's relocation guards, never by escape/accessibility checks. + ContainsILFieldAccess: bool + /// The summary of locally defined tycon representations used in the expression. These may be made private by a signature /// or marked 'internal' or 'private' and we have to check various conditions associated with that. FreeLocalTyconReprs: FreeTycons diff --git a/src/Compiler/TypedTree/TypedTree.fsi b/src/Compiler/TypedTree/TypedTree.fsi index 7fbe446641a..78a492d091a 100644 --- a/src/Compiler/TypedTree/TypedTree.fsi +++ b/src/Compiler/TypedTree/TypedTree.fsi @@ -4411,6 +4411,14 @@ type FreeVars = /// Rethrow may only occur in such locations. UsesUnboundRethrow: bool + /// Indicates if the expression contains a direct IL field load/store. Such an access to a + /// protected (family) field is only verifiable inside a method whose declaring type is within + /// the field's family, so the optimizer must not relocate it (by inlining or splitting) into a + /// method outside that family (issue #19963). Field accessibility is not available where free + /// variables are summarised, so this is set for any IL field access and is consumed only by the + /// optimizer's relocation guards, never by escape/accessibility checks. + ContainsILFieldAccess: bool + /// The summary of locally defined tycon representations used in the expression. These may be made private by a signature /// or marked 'internal' or 'private' type we have to check various conditions associated with that. FreeLocalTyconReprs: FreeTycons diff --git a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs index 7401ed40b14..859ded5a10a 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs @@ -798,6 +798,7 @@ module internal ExprFreeVars = { UsesMethodLocalConstructs = false UsesUnboundRethrow = false + ContainsILFieldAccess = false FreeLocalTyconReprs = emptyFreeTycons FreeLocals = emptyFreeLocals FreeTyvars = emptyFreeTyvars @@ -816,6 +817,7 @@ module internal ExprFreeVars = FreeTyvars = unionFreeTyvars fvs1.FreeTyvars fvs2.FreeTyvars UsesMethodLocalConstructs = fvs1.UsesMethodLocalConstructs || fvs2.UsesMethodLocalConstructs UsesUnboundRethrow = fvs1.UsesUnboundRethrow || fvs2.UsesUnboundRethrow + ContainsILFieldAccess = fvs1.ContainsILFieldAccess || fvs2.ContainsILFieldAccess FreeLocalTyconReprs = unionFreeTycons fvs1.FreeLocalTyconReprs fvs2.FreeLocalTyconReprs FreeRecdFields = unionFreeRecdFields fvs1.FreeRecdFields fvs2.FreeRecdFields FreeUnionCases = unionFreeUnionCases fvs1.FreeUnionCases fvs2.FreeUnionCases @@ -866,9 +868,10 @@ module internal ExprFreeVars = } let boundProtect fvs = - if fvs.UsesMethodLocalConstructs then + if fvs.UsesMethodLocalConstructs || fvs.ContainsILFieldAccess then { fvs with UsesMethodLocalConstructs = false + ContainsILFieldAccess = false } else fvs @@ -881,6 +884,14 @@ module internal ExprFreeVars = else fvs + let accContainsILFieldAccess fvs = + if fvs.ContainsILFieldAccess then + fvs + else + { fvs with + ContainsILFieldAccess = true + } + let bound_rethrow fvs = if fvs.UsesUnboundRethrow then { fvs with UsesUnboundRethrow = false } @@ -1199,7 +1210,34 @@ module internal ExprFreeVars = let acc = accUsesFunctionLocalConstructs (kind = RecdExprIsObjInit) acc (accUsedRecdOrUnionTyconRepr opts tcref.Deref (accFreeTyvars opts accFreeTycon tcref acc)) - | TOp.ILAsm(_, retTypes) -> accFreeVarsInTys opts retTypes acc + | TOp.ILAsm(instrs, retTypes) -> + // A protected (family) IL field load/store is only verifiable inside a method whose + // declaring type lies within the field's family. If the optimizer relocates such an + // access (by inlining or lambda/method splitting) into a method outside that family it + // emits an illegal ldfld/stfld that throws FieldAccessException at runtime (issue + // #19963). Field accessibility is not carried on the ILFieldSpec here, so we + // conservatively flag any IL field access; the flag (ContainsILFieldAccess) is consumed + // only by the optimizer's relocation guards, never by the escape/accessibility checks, + // so this can never reject otherwise-valid code. F#-defined fields use TOp.ValFieldGet + // and are unaffected. + let usesFieldAccess = + instrs + |> List.exists (function + | I_ldfld _ + | I_ldflda _ + | I_stfld _ + | I_ldsfld _ + | I_ldsflda _ + | I_stsfld _ -> true + | _ -> false) + + let acc = + if usesFieldAccess then + accContainsILFieldAccess acc + else + acc + + accFreeVarsInTys opts retTypes acc | TOp.Reraise -> accUsesRethrow true acc diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/BaseClass.cs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/BaseClass.cs index 0166573b291..6ea3b5a9269 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/BaseClass.cs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/BaseClass.cs @@ -4,5 +4,7 @@ public class BaseClass { protected static int ProtectedStatic() { return 3; } protected int ProtectedInstance() { return 4; } + protected string ProtectedField = "protected-field"; + protected static string ProtectedStaticField = "protected-static-field"; } } \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/OnTypeMembers.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/OnTypeMembers.fs index 49c79c2600d..667202fd4d2 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/OnTypeMembers.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/OnTypeMembers.fs @@ -99,3 +99,56 @@ module AccessibilityAnnotations_OnTypeMembers = compilation |> verifyCompileAndRun |> shouldSucceed + + // Regression for https://github.com/dotnet/fsharp/issues/19963: the optimizer must not relocate + // a protected base-field load into a method outside the family. A trivial member returning a C# + // protected field gets inlined into module/startup code under --optimize+; before the fix the + // relocated `ldfld` threw System.FieldAccessException at runtime. Protected *method* access was + // already safe (it sets UsesMethodLocalConstructs); this locks the *field* path to the same. + [] + let ``Protected base field read via optimized member does not crash (issue 19963)`` () = + let lib = + CSharpFromPath (__SOURCE_DIRECTORY__ ++ "BaseClass.cs") + |> withName "BaseClassLib" + + FSharp """ +open TestBaseClass + +type DerivedClass() = + inherit BaseClass() + member x.GetField() = x.ProtectedField + +[] +let main _ = + if DerivedClass().GetField() = "protected-field" then 0 else 1 +""" + |> withReferences [lib] + |> withOptimize + |> asExe + |> compileAndRun + |> shouldSucceed + + // Companion to the above exercising a protected *static* field (the I_ldsfld branch of the + // optimizer relocation guard) for issue https://github.com/dotnet/fsharp/issues/19963. + [] + let ``Protected static base field read via optimized member does not crash (issue 19963)`` () = + let lib = + CSharpFromPath (__SOURCE_DIRECTORY__ ++ "BaseClass.cs") + |> withName "BaseClassLib" + + FSharp """ +open TestBaseClass + +type DerivedClass() = + inherit BaseClass() + member x.GetStaticField() = BaseClass.ProtectedStaticField + +[] +let main _ = + if DerivedClass().GetStaticField() = "protected-static-field" then 0 else 1 +""" + |> withReferences [lib] + |> withOptimize + |> asExe + |> compileAndRun + |> shouldSucceed From ecc50257027b0e070fc9314c6c2df49a6a886495 Mon Sep 17 00:00:00 2001 From: T-Gro Date: Wed, 17 Jun 2026 14:48:43 +0200 Subject: [PATCH 2/4] Add PR link to #19963 release note Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 2 +- 1 file changed, 1 insertion(+), 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 e6f85d9fcc7..7c53a0cd56b 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -5,7 +5,7 @@ * Fix missing FS1182 ("unused binding") warning for unused `let` function bindings inside class types. ([Issue #13849](https://github.com/dotnet/fsharp/issues/13849), [PR #19805](https://github.com/dotnet/fsharp/pull/19805)) * Fix inner mutually-recursive `let rec ... and ...` functions under `--realsig+` not being lifted to top-level static methods (TLR), causing `FSharpFunc` closure allocations and loss of `tail.` opcodes — the large struct-mutual-recursion perf regression reported in [Issue #17607](https://github.com/dotnet/fsharp/issues/17607). ([PR #19882](https://github.com/dotnet/fsharp/pull/19882)) * Fix `TypeLoadException` ("Specialize tried to implicitly override a method with weaker type parameter constraints") and the related CLR crash with constrained inline calls by stripping constraints from closure-class typars in `EraseClosures.convIlxClosureDef`. ([Issue #14492](https://github.com/dotnet/fsharp/issues/14492), [Issue #19075](https://github.com/dotnet/fsharp/issues/19075), [PR #19882](https://github.com/dotnet/fsharp/pull/19882)) -* Fix `FieldAccessException` at runtime when the optimizer relocates a read of a `protected` (family) base-class field into a method outside the field's family (e.g. a trivial member inlined into module/startup code under `--optimize+`). IL field access is no longer hoisted out of its declaring family. ([Issue #19963](https://github.com/dotnet/fsharp/issues/19963)) +* Fix `FieldAccessException` at runtime when the optimizer relocates a read of a `protected` (family) base-class field into a method outside the field's family (e.g. a trivial member inlined into module/startup code under `--optimize+`). IL field access is no longer hoisted out of its declaring family. ([Issue #19963](https://github.com/dotnet/fsharp/issues/19963), [PR #19964](https://github.com/dotnet/fsharp/pull/19964)) * Suppress hover/symbol resolution for wildcard `_` patterns inside `member _.…` bodies that incorrectly showed `val _: T` tooltip. ([PR #19760](https://github.com/dotnet/fsharp/pull/19760)) * Deduplicate format specifier locations in computation expressions so editor tooling no longer reports duplicate entries for the same `%` specifier. ([Issue #16419](https://github.com/dotnet/fsharp/issues/16419), [PR #19791](https://github.com/dotnet/fsharp/pull/19791)) From 329d387a3483f99d8c5bc9fcb72513ff0b3f6ef0 Mon Sep 17 00:00:00 2001 From: T-Gro Date: Thu, 18 Jun 2026 12:52:46 +0200 Subject: [PATCH 3/4] Scope optimizer protected-field relocation barrier to protected (family) fields only The #19963 fix flagged every IL field access as non-relocatable, so the optimizer refused to inline `inline` values reading public IL fields - e.g. FSharp.Core's `inline GetStringSlice` (which reads String.Empty) - failing the FSharp.Core bootstrap with FS1118 across all build legs. Refine the five optimizer relocation guards to pin only protected/family IL field access: keep the cheap FreeVars.ContainsILFieldAccess gate, then resolve accessibility via ImportILTypeRef/ILFieldDef.Access in the optimizer (where the import map is available) through a single usesMethodLocalConstructsOrProtectedField helper. Public IL field access is freely optimized again, matching the existing protected-method treatment. Adds an (|ILFieldInstr|_|) active pattern and a positive regression test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.FSharp.Compiler.Service/11.0.100.md | 2 +- src/Compiler/AbstractIL/il.fs | 12 ++++ src/Compiler/AbstractIL/il.fsi | 5 ++ src/Compiler/Optimize/Optimizer.fs | 66 +++++++++++++++---- src/Compiler/TypedTree/TypedTree.fs | 9 +-- src/Compiler/TypedTree/TypedTree.fsi | 9 +-- .../TypedTree/TypedTreeOps.Remapping.fs | 29 +++----- .../OnTypeMembers/OnTypeMembers.fs | 21 ++++++ 8 files changed, 107 insertions(+), 46 deletions(-) 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 7c53a0cd56b..4f0da209f23 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -5,7 +5,7 @@ * Fix missing FS1182 ("unused binding") warning for unused `let` function bindings inside class types. ([Issue #13849](https://github.com/dotnet/fsharp/issues/13849), [PR #19805](https://github.com/dotnet/fsharp/pull/19805)) * Fix inner mutually-recursive `let rec ... and ...` functions under `--realsig+` not being lifted to top-level static methods (TLR), causing `FSharpFunc` closure allocations and loss of `tail.` opcodes — the large struct-mutual-recursion perf regression reported in [Issue #17607](https://github.com/dotnet/fsharp/issues/17607). ([PR #19882](https://github.com/dotnet/fsharp/pull/19882)) * Fix `TypeLoadException` ("Specialize tried to implicitly override a method with weaker type parameter constraints") and the related CLR crash with constrained inline calls by stripping constraints from closure-class typars in `EraseClosures.convIlxClosureDef`. ([Issue #14492](https://github.com/dotnet/fsharp/issues/14492), [Issue #19075](https://github.com/dotnet/fsharp/issues/19075), [PR #19882](https://github.com/dotnet/fsharp/pull/19882)) -* Fix `FieldAccessException` at runtime when the optimizer relocates a read of a `protected` (family) base-class field into a method outside the field's family (e.g. a trivial member inlined into module/startup code under `--optimize+`). IL field access is no longer hoisted out of its declaring family. ([Issue #19963](https://github.com/dotnet/fsharp/issues/19963), [PR #19964](https://github.com/dotnet/fsharp/pull/19964)) +* Fix `FieldAccessException` at runtime when the optimizer relocates a read of a `protected` (family) base-class field into a method outside the field's family (e.g. a trivial member inlined into module/startup code under `--optimize+`). Protected (family) IL field access is no longer hoisted out of its declaring family by inlining or method-splitting. ([Issue #19963](https://github.com/dotnet/fsharp/issues/19963), [PR #19964](https://github.com/dotnet/fsharp/pull/19964)) * Suppress hover/symbol resolution for wildcard `_` patterns inside `member _.…` bodies that incorrectly showed `val _: T` tooltip. ([PR #19760](https://github.com/dotnet/fsharp/pull/19760)) * Deduplicate format specifier locations in computation expressions so editor tooling no longer reports duplicate entries for the same `%` specifier. ([Issue #16419](https://github.com/dotnet/fsharp/issues/16419), [PR #19791](https://github.com/dotnet/fsharp/pull/19791)) diff --git a/src/Compiler/AbstractIL/il.fs b/src/Compiler/AbstractIL/il.fs index 629a3d260bb..95bb99d33e3 100644 --- a/src/Compiler/AbstractIL/il.fs +++ b/src/Compiler/AbstractIL/il.fs @@ -3980,6 +3980,18 @@ let mkNormalLdfld fspec = I_ldfld(Aligned, Nonvolatile, fspec) let mkNormalLdflda fspec = I_ldflda fspec +/// Matches an IL instruction that loads or stores a field, returning the referenced field spec. +[] +let (|ILFieldInstr|_|) instr = + match instr with + | I_ldsfld(_, fspec) + | I_ldfld(_, _, fspec) + | I_ldsflda fspec + | I_ldflda fspec + | I_stsfld(_, fspec) + | I_stfld(_, _, fspec) -> ValueSome fspec + | _ -> ValueNone + let mkNormalLdobj dt = I_ldobj(Aligned, Nonvolatile, dt) let mkNormalStobj dt = I_stobj(Aligned, Nonvolatile, dt) diff --git a/src/Compiler/AbstractIL/il.fsi b/src/Compiler/AbstractIL/il.fsi index dc22d5bc803..af41d86a6d2 100644 --- a/src/Compiler/AbstractIL/il.fsi +++ b/src/Compiler/AbstractIL/il.fsi @@ -2144,6 +2144,11 @@ val internal mkNormalStsfld: ILFieldSpec -> ILInstr val internal mkNormalLdsfld: ILFieldSpec -> ILInstr val internal mkNormalLdfld: ILFieldSpec -> ILInstr val internal mkNormalLdflda: ILFieldSpec -> ILInstr + +/// Matches an IL instruction that loads or stores a field, returning the referenced field spec. +[] +val internal (|ILFieldInstr|_|): instr: ILInstr -> ILFieldSpec voption + val internal mkNormalLdobj: ILType -> ILInstr val internal mkNormalStobj: ILType -> ILInstr val internal mkLdcInt32: int32 -> ILInstr diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 0d062615e1d..8d5b8d909da 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -1415,8 +1415,51 @@ let AbstractOptimizationInfoToEssentials = abstractLazyModulInfo +/// True if the IL field referenced by a load/store has protected (family) accessibility. Such an +/// access is only verifiable from within the field's family, so the optimizer must not relocate it +/// (by inlining or method-splitting) outside that family — doing so emits illegal IL that throws +/// FieldAccessException at runtime (issue #19963). +let private isProtectedILFieldSpec cenv m (fspec: ILFieldSpec) = + let tref = fspec.DeclaringTypeRef + + if Import.CanImportILTypeRef cenv.amap m tref then + let tcref = Import.ImportILTypeRef cenv.amap m tref + + tcref.IsILTycon + && tcref.ILTyconRawMetadata.Fields.LookupByName fspec.Name + |> List.exists (fun fdef -> fdef.Access = ILMemberAccess.Family || fdef.Access = ILMemberAccess.FamilyOrAssembly) + else + false + +/// True if the expression loads or stores a protected (family) IL field anywhere in its body. +let private exprReferencesProtectedILField cenv expr = + let mutable found = false + + let folder = + { ExprFolder0 with + exprIntercept = + fun _recurseF noInterceptF z e -> + if not found then + match e with + | Expr.Op(TOp.ILAsm(instrs, _), _, _, m) -> + if instrs |> List.exists (function ILFieldInstr fspec -> isProtectedILFieldSpec cenv m fspec | _ -> false) then + found <- true + | _ -> () + + noInterceptF z e } + + FoldExpr folder () expr |> ignore + found + +/// True if the expression references constructs that are only valid within their defining method or +/// family, and so must not be relocated by inlining or method-splitting: a protected/base call +/// (UsesMethodLocalConstructs) or a protected (family) IL field access (issue #19963). +let usesMethodLocalConstructsOrProtectedField cenv (fvs: FreeVars) expr = + fvs.UsesMethodLocalConstructs + || (fvs.ContainsILFieldAccess && exprReferencesProtectedILField cenv expr) + /// Hide information because of a "let ... in ..." or "let rec ... in ... " -let AbstractExprInfoByVars (boundVars: Val list, boundTyVars) ivalue = +let AbstractExprInfoByVars cenv (boundVars: Val list, boundTyVars) ivalue = // Module and member bindings can be skipped when checking abstraction, since abstraction of these values has already been done when // we hit the end of the module and called AbstractLazyModulInfoByHiding. If we don't skip these then we end up quadratically retraversing // the inferred optimization data, i.e. at each binding all the way up a sequences of 'lets' in a module. @@ -1447,7 +1490,7 @@ let AbstractExprInfoByVars (boundVars: Val list, boundTyVars) ivalue = (let fvs = freeInExpr (if isNil boundTyVars then CollectLocalsWithStackGuard() else CollectTyparsAndLocals) expr (not (isNil boundVars) && List.exists (Zset.memberOf fvs.FreeLocals) boundVars) || (not (isNil boundTyVars) && List.exists (Zset.memberOf fvs.FreeTyvars.FreeTypars) boundTyVars) || - fvs.UsesMethodLocalConstructs || fvs.ContainsILFieldAccess) -> + usesMethodLocalConstructsOrProtectedField cenv fvs expr) -> // Trimming lambda UnknownValue @@ -2851,7 +2894,7 @@ and OptimizeLetRec cenv env (binds, bodyExpr, m) = let fvs = List.fold (fun acc x -> unionFreeVars acc (fst x |> freeInBindingRhs CollectLocals)) fvs0 bindsR SplitValuesByIsUsedOrHasEffect cenv (fun () -> fvs.FreeLocals) bindsR // Trim out any optimization info that involves escaping values - let evalueR = AbstractExprInfoByVars (vs, []) einfo.Info + let evalueR = AbstractExprInfoByVars cenv (vs, []) einfo.Info // REVIEW: size of constructing new closures - should probably add #freevars + #recfixups here let bodyExprR = Expr.LetRec (bindsRR, bodyExprR, m, Construct.NewFreeVarsCache()) let info = CombineValueInfos (einfo :: bindinfos) evalueR @@ -2926,7 +2969,7 @@ and OptimizeLinearExpr cenv env expr contf = Info = UnknownValue } else // On the way back up: Trim out any optimization info that involves escaping values on the way back up - let evalueR = AbstractExprInfoByVars ([bindR.Var], []) bodyInfo.Info + let evalueR = AbstractExprInfoByVars cenv ([bindR.Var], []) bodyInfo.Info // Preserve the debug points for eliminated bindings that have debug points. let bodyR = @@ -3094,8 +3137,8 @@ and TryOptimizeVal cenv env (vOpt: ValRef option, shouldInline, inlineIfLambda, | CurriedLambdaValue (_, _, _, expr, _) when shouldInline || inlineIfLambda -> let fvs = freeInExpr CollectLocals expr - if fvs.UsesMethodLocalConstructs || fvs.ContainsILFieldAccess then - // Discarding lambda for binding because uses protected members --- TBD: Should we warn or error here + if usesMethodLocalConstructsOrProtectedField cenv fvs expr then + // Don't inline: the body references method-local or protected (family) constructs that must not be relocated None else let exprCopy = CopyExprForInlining cenv inlineIfLambda expr m @@ -3893,7 +3936,7 @@ and OptimizeLambdas (vspec: Val option) cenv env valReprInfo expr exprTy = | None -> CurriedLambdaValue (lambdaId, arities, bsize, exprR, exprTy) | Some baseVal -> let fvs = freeInExpr CollectLocals bodyR - if fvs.UsesMethodLocalConstructs || fvs.ContainsILFieldAccess || fvs.FreeLocals.Contains baseVal then + if usesMethodLocalConstructsOrProtectedField cenv fvs bodyR || fvs.FreeLocals.Contains baseVal then UnknownValue else let expr2 = mkMemberLambdas g m tps ctorThisValOpt None vsl (bodyR, bodyTy) @@ -3975,8 +4018,7 @@ and ComputeSplitToMethodCondition flag threshold cenv env (e: Expr, einfo) = let m = e.Range (let fvs = freeInExpr (CollectLocalsWithStackGuard()) e not fvs.UsesUnboundRethrow && - not fvs.UsesMethodLocalConstructs && - not fvs.ContainsILFieldAccess && + not (usesMethodLocalConstructsOrProtectedField cenv fvs e) && fvs.FreeLocals |> Zset.forall (fun v -> // no direct-self-recursive references not (env.dontSplitVars.ContainsVal v) && @@ -4038,7 +4080,7 @@ and OptimizeDecisionTreeTarget cenv env _m (TTarget(vs, expr, flags)) = let env = BindInternalValsToUnknown cenv vs env let exprR, einfo = OptimizeExpr cenv env expr let exprR, einfo = ConsiderSplitToMethod cenv.settings.abstractBigTargets cenv.settings.bigTargetSize cenv env (exprR, einfo) - let evalueR = AbstractExprInfoByVars (vs, []) einfo.Info + let evalueR = AbstractExprInfoByVars cenv (vs, []) einfo.Info TTarget(vs, exprR, flags), { TotalSize=einfo.TotalSize FunctionSize=einfo.FunctionSize @@ -4169,8 +4211,8 @@ and OptimizeBinding cenv isRec env (TBind(vref, expr, spBind)) = UnknownValue else let fvs = freeInExpr CollectLocals body - if fvs.UsesMethodLocalConstructs || fvs.ContainsILFieldAccess then - // Discarding lambda for binding because uses protected members + if usesMethodLocalConstructsOrProtectedField cenv fvs body then + // Don't keep optimization info: body references method-local or protected (family) constructs UnknownValue elif fvs.FreeLocals.ToArray() |> Seq.fold(fun acc v -> if not acc then v.Accessibility.IsPrivate else acc) false then // Discarding lambda for binding because uses private members diff --git a/src/Compiler/TypedTree/TypedTree.fs b/src/Compiler/TypedTree/TypedTree.fs index bdb89ca99ed..2e0d7017f06 100644 --- a/src/Compiler/TypedTree/TypedTree.fs +++ b/src/Compiler/TypedTree/TypedTree.fs @@ -6086,12 +6086,9 @@ type FreeVars = /// Rethrow may only occur in such locations. UsesUnboundRethrow: bool - /// Indicates if the expression contains a direct IL field load/store. Such an access to a - /// protected (family) field is only verifiable inside a method whose declaring type is within - /// the field's family, so the optimizer must not relocate it (by inlining or splitting) into a - /// method outside that family (issue #19963). Field accessibility is not available where free - /// variables are summarised, so this is set for any IL field access and is consumed only by the - /// optimizer's relocation guards, never by escape/accessibility checks. + /// Indicates if the expression contains a direct IL field load/store. This is a cheap, + /// over-approximate gate: the optimizer refines it to protected (family) fields, which must + /// not be relocated out of their family scope (issue #19963). Never read by escape checks. ContainsILFieldAccess: bool /// The summary of locally defined tycon representations used in the expression. These may be made private by a signature diff --git a/src/Compiler/TypedTree/TypedTree.fsi b/src/Compiler/TypedTree/TypedTree.fsi index 78a492d091a..452ddffd4e7 100644 --- a/src/Compiler/TypedTree/TypedTree.fsi +++ b/src/Compiler/TypedTree/TypedTree.fsi @@ -4411,12 +4411,9 @@ type FreeVars = /// Rethrow may only occur in such locations. UsesUnboundRethrow: bool - /// Indicates if the expression contains a direct IL field load/store. Such an access to a - /// protected (family) field is only verifiable inside a method whose declaring type is within - /// the field's family, so the optimizer must not relocate it (by inlining or splitting) into a - /// method outside that family (issue #19963). Field accessibility is not available where free - /// variables are summarised, so this is set for any IL field access and is consumed only by the - /// optimizer's relocation guards, never by escape/accessibility checks. + /// Indicates if the expression contains a direct IL field load/store. This is a cheap, + /// over-approximate gate: the optimizer refines it to protected (family) fields, which must + /// not be relocated out of their family scope (issue #19963). Never read by escape checks. ContainsILFieldAccess: bool /// The summary of locally defined tycon representations used in the expression. These may be made private by a signature diff --git a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs index 859ded5a10a..6de4f309cb4 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs @@ -1211,28 +1211,15 @@ module internal ExprFreeVars = (accUsedRecdOrUnionTyconRepr opts tcref.Deref (accFreeTyvars opts accFreeTycon tcref acc)) | TOp.ILAsm(instrs, retTypes) -> - // A protected (family) IL field load/store is only verifiable inside a method whose - // declaring type lies within the field's family. If the optimizer relocates such an - // access (by inlining or lambda/method splitting) into a method outside that family it - // emits an illegal ldfld/stfld that throws FieldAccessException at runtime (issue - // #19963). Field accessibility is not carried on the ILFieldSpec here, so we - // conservatively flag any IL field access; the flag (ContainsILFieldAccess) is consumed - // only by the optimizer's relocation guards, never by the escape/accessibility checks, - // so this can never reject otherwise-valid code. F#-defined fields use TOp.ValFieldGet - // and are unaffected. - let usesFieldAccess = - instrs - |> List.exists (function - | I_ldfld _ - | I_ldflda _ - | I_stfld _ - | I_ldsfld _ - | I_ldsflda _ - | I_stsfld _ -> true - | _ -> false) - + // Flag any IL field load/store (cheap, over-approximate). The optimizer refines this to + // protected (family) fields, which must not be relocated out of their family (issue #19963). let acc = - if usesFieldAccess then + if + instrs + |> List.exists (function + | ILFieldInstr _ -> true + | _ -> false) + then accContainsILFieldAccess acc else acc diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/OnTypeMembers.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/OnTypeMembers.fs index 667202fd4d2..f1a0c488791 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/OnTypeMembers.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/OnTypeMembers/OnTypeMembers.fs @@ -152,3 +152,24 @@ let main _ = |> asExe |> compileAndRun |> shouldSucceed + + // Positive companion to the issue 19963 regressions above: the relocation barrier must fire ONLY + // for protected (family) fields. A PUBLIC IL field (System.String.Empty, an I_ldsfld) inside an + // `inline` value must still be inlined under --optimize+. The over-broad form pinned every IL + // field, so this failed with FS1118 while bootstrapping FSharp.Core (whose `inline GetStringSlice` + // reads String.Empty). FS1118 is escalated to an error so the test actually guards the regression. + [] + let ``Public IL field access inside an inline value is still optimized away (issue 19963)`` () = + FSharp """ +module Test +let inline emptyOr (s: string) = if s.Length = 0 then System.String.Empty else s + +[] +let main _ = + if emptyOr "" = "" && emptyOr "x" = "x" then 0 else 1 +""" + |> withOptimize + |> withOptions ["--warnaserror:1118"] + |> asExe + |> compileAndRun + |> shouldSucceed From 658a05b15e40309cc286fa4e3d49c74760489050 Mon Sep 17 00:00:00 2001 From: T-Gro Date: Thu, 18 Jun 2026 13:56:04 +0200 Subject: [PATCH 4/4] Fuse check-then-extract idioms into active patterns; consolidate comments isProtectedILFieldSpec is now a flat pattern match over two reusable active patterns instead of nested if/extract: (|TryImportILTypeRef|_|) in import.fs (CanImportILTypeRef + ImportILTypeRef) and (|ILTyconRawMetadata|_|) in TypedTreeBasics.fs (IsILTycon + ILTyconRawMetadata, a widespread idiom). Also collapse the repeated #19963 rationale to a single place. No behaviour change (build 0/0, 3/3 tests, repros unchanged). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/import.fs | 8 +++++++ src/Compiler/Checking/import.fsi | 4 ++++ src/Compiler/Optimize/Optimizer.fs | 22 +++++-------------- src/Compiler/TypedTree/TypedTree.fs | 5 ++--- src/Compiler/TypedTree/TypedTree.fsi | 5 ++--- src/Compiler/TypedTree/TypedTreeBasics.fs | 5 +++++ src/Compiler/TypedTree/TypedTreeBasics.fsi | 5 +++++ .../TypedTree/TypedTreeOps.Remapping.fs | 3 +-- 8 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/Compiler/Checking/import.fs b/src/Compiler/Checking/import.fs index 8979bab5e77..e6f03e11639 100644 --- a/src/Compiler/Checking/import.fs +++ b/src/Compiler/Checking/import.fs @@ -159,6 +159,14 @@ let ImportILTypeRef (env: ImportMap) m (tref: ILTypeRef) = let CanImportILTypeRef (env: ImportMap) m (tref: ILTypeRef) = env.ILTypeRefToTyconRefCache.ContainsKey(tref) || CanImportILScopeRef env m tref.Scope +/// Imports a reference to a type definition (ILTypeRef) as a TyconRef when it can be imported. +[] +let (|TryImportILTypeRef|_|) (env: ImportMap) m (tref: ILTypeRef) = + if CanImportILTypeRef env m tref then + ValueSome(ImportILTypeRef env m tref) + else + ValueNone + /// Import a type, given an AbstractIL ILTypeRef and an F# type instantiation. /// /// Prefer the F# abbreviation for some built-in types, e.g. 'string' rather than diff --git a/src/Compiler/Checking/import.fsi b/src/Compiler/Checking/import.fsi index f51f4d6208f..55ce752e886 100644 --- a/src/Compiler/Checking/import.fsi +++ b/src/Compiler/Checking/import.fsi @@ -77,6 +77,10 @@ val internal ImportILTypeRef: ImportMap -> range -> ILTypeRef -> TyconRef /// Pre-check for ability to import a reference to a type definition, given an AbstractIL ILTypeRef, with caching val internal CanImportILTypeRef: ImportMap -> range -> ILTypeRef -> bool +/// Imports a reference to a type definition (ILTypeRef) as a TyconRef when it can be imported. +[] +val internal (|TryImportILTypeRef|_|): ImportMap -> range -> ILTypeRef -> TyconRef voption + /// Import an IL type as an F# type. val internal ImportILType: ImportMap -> range -> TType list -> ILType -> TType diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 8d5b8d909da..ae71bf0811f 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -1415,21 +1415,13 @@ let AbstractOptimizationInfoToEssentials = abstractLazyModulInfo -/// True if the IL field referenced by a load/store has protected (family) accessibility. Such an -/// access is only verifiable from within the field's family, so the optimizer must not relocate it -/// (by inlining or method-splitting) outside that family — doing so emits illegal IL that throws -/// FieldAccessException at runtime (issue #19963). +/// True if the IL field has protected (family) accessibility. let private isProtectedILFieldSpec cenv m (fspec: ILFieldSpec) = - let tref = fspec.DeclaringTypeRef - - if Import.CanImportILTypeRef cenv.amap m tref then - let tcref = Import.ImportILTypeRef cenv.amap m tref - - tcref.IsILTycon - && tcref.ILTyconRawMetadata.Fields.LookupByName fspec.Name - |> List.exists (fun fdef -> fdef.Access = ILMemberAccess.Family || fdef.Access = ILMemberAccess.FamilyOrAssembly) - else - false + match fspec.DeclaringTypeRef with + | Import.TryImportILTypeRef cenv.amap m (ILTyconRawMetadata tdef) -> + tdef.Fields.LookupByName fspec.Name + |> List.exists (fun fdef -> fdef.Access = ILMemberAccess.Family || fdef.Access = ILMemberAccess.FamilyOrAssembly) + | _ -> false /// True if the expression loads or stores a protected (family) IL field anywhere in its body. let private exprReferencesProtectedILField cenv expr = @@ -3138,7 +3130,6 @@ and TryOptimizeVal cenv env (vOpt: ValRef option, shouldInline, inlineIfLambda, | CurriedLambdaValue (_, _, _, expr, _) when shouldInline || inlineIfLambda -> let fvs = freeInExpr CollectLocals expr if usesMethodLocalConstructsOrProtectedField cenv fvs expr then - // Don't inline: the body references method-local or protected (family) constructs that must not be relocated None else let exprCopy = CopyExprForInlining cenv inlineIfLambda expr m @@ -4212,7 +4203,6 @@ and OptimizeBinding cenv isRec env (TBind(vref, expr, spBind)) = else let fvs = freeInExpr CollectLocals body if usesMethodLocalConstructsOrProtectedField cenv fvs body then - // Don't keep optimization info: body references method-local or protected (family) constructs UnknownValue elif fvs.FreeLocals.ToArray() |> Seq.fold(fun acc v -> if not acc then v.Accessibility.IsPrivate else acc) false then // Discarding lambda for binding because uses private members diff --git a/src/Compiler/TypedTree/TypedTree.fs b/src/Compiler/TypedTree/TypedTree.fs index 2e0d7017f06..eb279378053 100644 --- a/src/Compiler/TypedTree/TypedTree.fs +++ b/src/Compiler/TypedTree/TypedTree.fs @@ -6086,9 +6086,8 @@ type FreeVars = /// Rethrow may only occur in such locations. UsesUnboundRethrow: bool - /// Indicates if the expression contains a direct IL field load/store. This is a cheap, - /// over-approximate gate: the optimizer refines it to protected (family) fields, which must - /// not be relocated out of their family scope (issue #19963). Never read by escape checks. + /// Indicates if the expression contains a direct IL field load/store — a cheap over-approximate + /// gate the optimizer refines to protected (family) fields (issue #19963). Never read by escape checks. ContainsILFieldAccess: bool /// The summary of locally defined tycon representations used in the expression. These may be made private by a signature diff --git a/src/Compiler/TypedTree/TypedTree.fsi b/src/Compiler/TypedTree/TypedTree.fsi index 452ddffd4e7..d4fae73fb92 100644 --- a/src/Compiler/TypedTree/TypedTree.fsi +++ b/src/Compiler/TypedTree/TypedTree.fsi @@ -4411,9 +4411,8 @@ type FreeVars = /// Rethrow may only occur in such locations. UsesUnboundRethrow: bool - /// Indicates if the expression contains a direct IL field load/store. This is a cheap, - /// over-approximate gate: the optimizer refines it to protected (family) fields, which must - /// not be relocated out of their family scope (issue #19963). Never read by escape checks. + /// Indicates if the expression contains a direct IL field load/store — a cheap over-approximate + /// gate the optimizer refines to protected (family) fields (issue #19963). Never read by escape checks. ContainsILFieldAccess: bool /// The summary of locally defined tycon representations used in the expression. These may be made private by a signature diff --git a/src/Compiler/TypedTree/TypedTreeBasics.fs b/src/Compiler/TypedTree/TypedTreeBasics.fs index 1445fce7b8c..6c14530109a 100644 --- a/src/Compiler/TypedTree/TypedTreeBasics.fs +++ b/src/Compiler/TypedTree/TypedTreeBasics.fs @@ -342,6 +342,11 @@ let (|AbbrevOrAppTy|_|) (ty: TType) = | TType_app (tcref, tinst, _) -> ValueSome(tcref, tinst) | _ -> ValueNone +/// Matches a type definition reference backed by Abstract IL metadata, returning that metadata. +[] +let (|ILTyconRawMetadata|_|) (tcref: TyconRef) = + if tcref.IsILTycon then ValueSome tcref.ILTyconRawMetadata else ValueNone + //--------------------------------------------------------------------------- // These make local/non-local references to values according to whether // the item is globally stable ("published") or not. diff --git a/src/Compiler/TypedTree/TypedTreeBasics.fsi b/src/Compiler/TypedTree/TypedTreeBasics.fsi index 4f67c7aa377..4c8f62cb8fd 100644 --- a/src/Compiler/TypedTree/TypedTreeBasics.fsi +++ b/src/Compiler/TypedTree/TypedTreeBasics.fsi @@ -7,6 +7,7 @@ module internal FSharp.Compiler.TypedTreeBasics open Internal.Utilities.Library.Extras +open FSharp.Compiler.AbstractIL.IL open FSharp.Compiler.Syntax open FSharp.Compiler.Text open FSharp.Compiler.TypedTree @@ -155,6 +156,10 @@ val stripUnitEqns: unt: Measure -> Measure [] val (|AbbrevOrAppTy|_|): ty: TType -> (TyconRef * TypeInst) voption +/// Matches a type definition reference backed by Abstract IL metadata, returning that metadata. +[] +val (|ILTyconRawMetadata|_|): tcref: TyconRef -> ILTypeDef voption + val mkLocalValRef: v: Val -> ValRef val mkLocalModuleRef: v: ModuleOrNamespace -> EntityRef diff --git a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs index 6de4f309cb4..d95f124815e 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs @@ -1211,8 +1211,7 @@ module internal ExprFreeVars = (accUsedRecdOrUnionTyconRepr opts tcref.Deref (accFreeTyvars opts accFreeTycon tcref acc)) | TOp.ILAsm(instrs, retTypes) -> - // Flag any IL field load/store (cheap, over-approximate). The optimizer refines this to - // protected (family) fields, which must not be relocated out of their family (issue #19963). + // Cheap over-approximate gate; the optimizer refines this to protected (family) fields (issue #19963). let acc = if instrs