Uh oh!
There was an error while loading. Please reload this page.
Fix FieldAccessException when optimizer relocates protected base-field access (#19963) - #19964
Conversation
…d 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>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
❗ Release notes requiredYou can open this PR in browser to add release notes: open in github.dev
|
T-Gro
left a comment
There was a problem hiding this comment.
AI code review (expert-reviewer). The committed design fixes the crash but has an over-broad relocation guard that will block inlining of public IL field accesses - a regression that manifests as FS1118 when bootstrapping FSharp.Core.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…ly) 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>
…ents 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>
Collapse the repeated regression rationale to 1-2 lines per test and share the single BaseClass C# lib across the two protected-field tests instead of redefining it. Test-only; 3/3 still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
No code change. The prior run's FSharpPlus leg failed with 'job was abandoned / lost contact with the agent' (AzDO infra flake) while the build was succeeding (0 errors). Empty commit to request a fresh run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.
Resolved conflicts: - tests/AheadOfTime/Trimming/check.ps1: kept main's expected size (will re-measure after build) - 28 EmittedIL .bsl baselines: took PR side; all baselines to be regenerated against the merged compiler IlxGen.fs / Optimizer.fs auto-merged; verified main's #19955 (augmentation-member closure nesting) and #19964 (optimizer protected-field relocation) both present and composing with the determinism codegen changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes#19963.
Problem
Reading a
protected(family) base-class field from a derived F# member compiles fine, but the optimizer can relocate that field load — by inlining or method/lambda splitting — into a method outside the field's family. The relocatedldfld/stfldis then illegal IL and throwsSystem.FieldAccessExceptionat runtime. This happens under--optimize+, the default for Release builds, regardless of--realsig.--optimize-runs fine;--optimize+inlinesRunintomain(which is not inBase's family) and throws:Cause
Protected method/base calls are already pinned in-family: they set
FreeVars.UsesMethodLocalConstructs(viaTOp.ILCall isProtected), which the optimizer's relocation guards honour. Protected field loads were invisible to that machinery, so the optimizer freely relocated them. Contrast under--optimize+:Fix
Add a
FreeVars.ContainsILFieldAccessflag, set for any IL field instruction (TOp.ILAsmldfld/ldflda/stfld/ldsfld/ldsflda/stsfld), and OR it into the five optimizer relocation guards so such code is not inlined or split out of its declaring family.The flag is deliberately not read by the FS0405 escape check, so it can never reject otherwise-valid code (e.g. capturing a public field like
IntPtr.Zeroin a closure). Field accessibility is not available where free variables are summarised, so the flag is conservative over all IL field access; F#-defined fields useTOp.ValFieldGetand are unaffected.Validation
--optimize+(red before, green after).EmittedILsuite: 1307 tests, zero baseline churn.This is also a prerequisite for enabling protected-member access from closures (#5302): the same relocation otherwise reintroduces the runtime failure there.