Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1c2d63d
Fix TLR under --realsig+ and closure constraint stripping
T-Gro Jun 1, 2026
f38d45b
Fix verifyILContains silent failure, add checkILPresent/checkILNotPre…
T-Gro Jun 1, 2026
739cf42
Add tests and regenerate IL baselines for TLR/constraint fixes
T-Gro Jun 1, 2026
5c08b88
Release notes for TLR realsig fix (#17607) and constraint stripping f…
T-Gro Jun 1, 2026
b87e6bc
Merge branch 'main' into fix/realsign-codegen
T-Gro Jun 3, 2026
2ccbbb1
Add PR link to release notes and fix code formatting
Jun 3, 2026
4ca4bec
Merge remote-tracking branch 'origin/main' into fix/realsign-codegen
Jun 3, 2026
7757717
Fix FS2014 duplicate-name when TLR routes namespace-level vals to mod…
T-Gro Jun 4, 2026
8a69d8d
Update stale IL baselines surfaced by CI after the moduleCloc routing…
T-Gro Jun 4, 2026
821ee31
Re-extract Point2D baselines from Linux CI log (use Entire actual: ma…
T-Gro Jun 4, 2026
c99bad2
Normalize ldc.r8 N vs ldc.r8 N. in ILChecker for shared .bsl files
T-Gro Jun 4, 2026
4eecf2c
Merge remote-tracking branch 'origin/main' into fix/realsign-codegen
T-Gro Jun 5, 2026
e2ed10f
Address adversarial review feedback (opus 4.8, opus 4.7 xhigh, gpt 5.5)
T-Gro Jun 5, 2026
f933f83
Address production-grade reuse review (opus 4.8, opus 4.7 xhigh, gpt …
T-Gro Jun 5, 2026
34f8107
Merge remote-tracking branch 'origin/main' into fix/realsign-codegen
T-Gro Jun 8, 2026
c0e9693
Address expert-review feedback (post-merge from origin/main)
T-Gro Jun 8, 2026
02ecec2
Collapse byte-identical Off/On TLR baselines into shared single .bsl
T-Gro Jun 8, 2026
945e9f2
Add Regression_TLR_RealsigPrivate.fs — 4 granular AV-class regressions
T-Gro Jun 8, 2026
c76accc
Bump ILVerify job timeout to 120 min (was AzDo default 60 min)
T-Gro Jun 9, 2026
0731fec
Refuse TLR when body references private member under --realsig+
T-Gro Jun 10, 2026
5bc6501
Tighten TLR private-ref guard to type-scoped members only
T-Gro Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions azure-pipelines-PR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ stages:
continueOnError: true
condition: always()
- job: ILVerify
timeoutInMinutes: 120
pool:
name: $(DncEngPublicBuildPool)
demands: ImageOverride -equals $(_WindowsMachineQueueName)
Expand Down
4 changes: 4 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
### Fixed

* 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))

* 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))
* Reject non-function bindings for single-case and partial active pattern names with FS1209, matching the existing multi-case behavior. ([PR #19763](https://github.com/dotnet/fsharp/pull/19763))
* Fix FS0421 "The address of the variable cannot be used at this point" incorrectly raised for the discard pattern `let _ = &expr` when `let x = &expr` compiles. ([Issue #18841](https://github.com/dotnet/fsharp/issues/18841), [PR #19811](https://github.com/dotnet/fsharp/pull/19811))
* Honor `--nowarn` and `--warnaserror` for warnings emitted during command-line option parsing ([Issue #19576](https://github.com/dotnet/fsharp/issues/19576), [PR #19776](https://github.com/dotnet/fsharp/pull/19776))
* Fix `[<return: X>]` prefix attributes being silently dropped on class members, and fix false-positive `AllowMultiple=false` errors when `[<X>]` and `[<return: X>]` are applied to the same binding. ([Issue #17904](https://github.com/dotnet/fsharp/issues/17904), [Issue #19020](https://github.com/dotnet/fsharp/issues/19020), [PR #19738](https://github.com/dotnet/fsharp/pull/19738))

* Preserve type abbreviations (`string`, user-defined aliases) in the refined type of bindings introduced after a `| null` pattern in a `match` expression. ([Issue #19646](https://github.com/dotnet/fsharp/issues/19646), [PR #19745](https://github.com/dotnet/fsharp/pull/19745))
* Fix attributes on return type of unparenthesized tuple methods being silently dropped from IL. ([Issue #462](https://github.com/dotnet/fsharp/issues/462), [PR #19714](https://github.com/dotnet/fsharp/pull/19714))
* Fix false-positive nullness warning (FS3261) when pattern matching narrows nullness inside seq/list/array comprehensions. ([Issue #19644](https://github.com/dotnet/fsharp/issues/19644), [PR #19743](https://github.com/dotnet/fsharp/pull/19743))
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/AbstractIL/il.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3375,13 +3375,16 @@ let mkILSimpleTypar nm =
MetadataIndex = NoMetadataIdx
}

/// Returns gp with all constraint state cleared. CustomAttrsStored is also reset because
/// some constraints (notably IsUnmanagedAttribute) are encoded there.
let stripILGenericParamConstraints (gp: ILGenericParameterDef) =
{ gp with
Constraints = []
HasReferenceTypeConstraint = false
HasNotNullableValueTypeConstraint = false
HasDefaultConstructorConstraint = false
HasAllowsRefStruct = false
CustomAttrsStored = storeILCustomAttrs emptyILCustomAttrs
}

let genericParamOfGenericActual (_ga: ILType) = mkILSimpleTypar "T"
Expand Down
10 changes: 5 additions & 5 deletions src/Compiler/CodeGen/EraseClosures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -484,9 +484,11 @@ let rec convIlxClosureDef cenv encl (td: ILTypeDef) clo =
match tyargsl, tmargsl, laterStruct with
// CASE 1 - Type abstraction
| _ :: _, [], _ ->
let addedGenParams = tyargsl
let nowReturnTy = (mkTyOfLambdas cenv laterStruct)

// Both Specialize<> and the T-suffixed closure type must be unconstrained (#14492).
let unconstrainedGenParams = tyargsl |> List.map stripILGenericParamConstraints

// CASE 1a. Split a type abstraction.
// Adjust all the argument and environment accesses
// Actually that special to do here in the type abstraction case
Expand All @@ -504,7 +506,7 @@ let rec convIlxClosureDef cenv encl (td: ILTypeDef) clo =

let laterTypeName = td.Name + "T"
let laterTypeRef = mkILNestedTyRef (ILScopeRef.Local, encl, laterTypeName)
let laterGenericParams = td.GenericParams @ addedGenParams
let laterGenericParams = td.GenericParams @ unconstrainedGenParams

let selfFreeVar =
let baseName = CompilerGeneratedName("self" + string nowFields.Length)
Expand Down Expand Up @@ -564,14 +566,12 @@ let rec convIlxClosureDef cenv encl (td: ILTypeDef) clo =

let convil = convILMethodBody (Some nowCloSpec, boxReturnTy) clo.cloCode.Value

let specializeGenParams = addedGenParams |> List.map stripILGenericParamConstraints

let nowApplyMethDef =
mkILGenericVirtualMethod (
"Specialize",
ILCallingConv.Instance,
ILMemberAccess.Public,
specializeGenParams,
unconstrainedGenParams,
[],
mkILReturn cenv.ilg.typ_Object,
MethodBody.IL(notlazy convil)
Expand Down
48 changes: 39 additions & 9 deletions src/Compiler/CodeGen/IlxGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,9 @@ and IlxGenEnv =
/// Indicates the default "place" for stuff we're currently generating
cloc: CompileLocation

/// Non-generic enclosing module (never narrowed by AddEnclosingToEnv). Routing target for TLR lifts.
moduleCloc: CompileLocation

/// Indicates the default "place" for initialization stuff we're currently generating
initClassCompLoc: CompileLocation option

Expand Down Expand Up @@ -10336,7 +10339,22 @@ and AllocValReprWithinExpr cenv cgbuf endMark cloc v eenv =
else
NoShadowLocal, eenv

ComputeAndAddStorageForLocalValWithValReprInfo (cenv, eenv.intraAssemblyInfo, cenv.options.isInteractive, optShadowLocal) cloc v eenv
// TLR lifts avoid generic enclosing scopes (#17607); namespace-root lifts use the per-file
// init class to avoid generated-name collisions in the shared <PrivateImplementationDetails$Asm>.
let effectiveCloc =
if v.IsCompiledAsTopLevel && not v.IsMemberOrModuleBinding then
if eenv.moduleCloc.Enclosing.IsEmpty then
CompLocForInitClass eenv.moduleCloc
else
eenv.moduleCloc
else
cloc

ComputeAndAddStorageForLocalValWithValReprInfo
(cenv, eenv.intraAssemblyInfo, cenv.options.isInteractive, optShadowLocal)
effectiveCloc
v
eenv

//--------------------------------------------------------------------------
// Generate stack save/restore and assertions - pulled into letrec by alloc*
Expand Down Expand Up @@ -10759,9 +10777,12 @@ and GenModuleBinding cenv (cgbuf: CodeGenBuffer) (qname: QualifiedNameOfFile) la
// Evaluate bindings for module
let hidden = IsHiddenTycon eenv.sigToImplRemapInfo mspec

let moduleLoc = CompLocForFixedModule cenv.options.fragName qname.Text mspec

let eenvinner =
{ eenv with
cloc = CompLocForFixedModule cenv.options.fragName qname.Text mspec
cloc = moduleLoc
moduleCloc = moduleLoc
initLocals =
eenv.initLocals
&& not (EntityHasWellKnownAttribute cenv.g WellKnownEntityAttributes.SkipLocalsInitAttribute mspec)
Expand Down Expand Up @@ -10827,13 +10848,16 @@ and GenImplFile cenv (mgbuf: AssemblyBuilder) mainInfoOpt eenv (implFile: Checke
for anonInfo in anonRecdTypes.Values do
mgbuf.GenerateAnonType((fun ilThisTy -> GenToStringMethod cenv eenv ilThisTy m), anonInfo)

let withQName (loc: CompileLocation) =
{ loc with
TopImplQualifiedName = qname.Text
Range = m
}

let eenv =
{ eenv with
cloc =
{ eenv.cloc with
TopImplQualifiedName = qname.Text
Range = m
}
cloc = withQName eenv.cloc
moduleCloc = withQName eenv.moduleCloc
}

cenv.optimizeDuringCodeGen <- optimizeDuringCodeGen
Expand Down Expand Up @@ -12569,9 +12593,12 @@ let CodegenAssembly cenv eenv mgbuf implFiles =
//-------------------------------------------------------------------------

let GetEmptyIlxGenEnv (g: TcGlobals) ccu =
let ccuLoc = CompLocForCcu ccu

{
tyenv = TypeReprEnv.Empty
cloc = CompLocForCcu ccu
cloc = ccuLoc
moduleCloc = ccuLoc
initClassCompLoc = None
initFieldName = CompilerGeneratedName "init"
staticInitializationName = CompilerGeneratedName "staticInitialization"
Expand Down Expand Up @@ -12654,8 +12681,11 @@ let GenerateCode (cenv, anonTypeTable, eenv, CheckedAssemblyAfterOptimization im
let mgbuf = AssemblyBuilder(cenv, anonTypeTable)

let eenv =
let fragLoc = CompLocForFragment cenv.options.fragName cenv.viewCcu

{ eenv with
cloc = CompLocForFragment cenv.options.fragName cenv.viewCcu
cloc = fragLoc
moduleCloc = fragLoc
delayCodeGen = cenv.options.parallelIlxGenEnabled
}

Expand Down
37 changes: 32 additions & 5 deletions src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,32 @@ let IsRefusedTLR g (f: Val) =
let refuseTest = alreadyChosen || mutableVal || byrefVal || specialVal || dllImportStubOrOtherNeverInline || isResumableCode || isInlineIfLambda
refuseTest

/// Under --realsig+, a TLR-lifted helper is emitted at module scope (outside its declaring
/// type when that type is a class). If the helper's body invokes a source-`private` member
/// of a class/struct, the CLR raises MethodAccessException at runtime because IL `private`
/// is type-scoped.
///
/// Private members of MODULES are not at risk: the lifted helper lands in the same module
/// IL class as the private val. F# RecdFields always compile to IL `assembly` or wider, so
/// field access is safe; only val/method references whose declaring entity is a class need
/// to be checked.
let BodyReferencesTypeScopedPrivate e =
let mutable found = false
let folder =
{ ExprFolder0 with
exprIntercept = fun _recurseF noInterceptF z expr ->
if found then z
else
match expr with
| Expr.Val (vref, _, _) when vref.Accessibility.IsPrivate ->
match vref.TryDeclaringEntity with
| Parent eref when not eref.IsModuleOrNamespace -> found <- true
| _ -> ()
| _ -> ()
noInterceptF z expr }
FoldExpr folder () e |> ignore
found

let IsMandatoryTopLevel (f: Val) =
let specialVal = f.MemberInfo.IsSome
let isModulBinding = f.IsMemberOrModuleBinding
Expand Down Expand Up @@ -185,18 +211,19 @@ module Pass1_DetermineTLRAndArities =
// Exclude values bound in a decision tree
elif Zset.contains f xinfo.DecisionTreeBindings then
None

// Under --realsig+, lifting a helper out of its declaring type would lose access to
// any source-`private` members it references, producing MethodAccessException at runtime.
elif g.realsig && BodyReferencesTypeScopedPrivate e then
None
else
// Could the binding be TLR? with what arity?
let atTopLevel = Zset.contains f xinfo.TopLevelBindings
let tps, vss, _b, _rty = stripTopLambda (e, f.Type)
let nFormals = vss.Length
let nMaxApplied = GetMaxNumArgsAtUses xinfo f
let arity = min nFormals nMaxApplied
if atTopLevel then
Some (f, arity)
elif g.realsig then
None
else if arity<>0 || not (isNil tps) then
if atTopLevel || arity <> 0 || not (isNil tps) then
Some (f, arity)
else
None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,15 +526,17 @@ Main()
|> compile
|> shouldSucceed
|> verifyIL ["""
.method assembly strict virtual instance class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32,!!T> DirectInvoke<valuetype (class [runtime]System.ValueType modreq([runtime]System.Runtime.InteropServices.UnmanagedType)) T>() cil managed
{
.param type T
.custom instance void [runtime]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = ( 01 00 00 00 )

.maxstack 8
IL_0000: ldsfld class Test/'func@3-1'<!0> class Test/'func@3-1'<!!T>::@_instance
IL_0005: ret
} """]
.method assembly static class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32,!!T>
func@3<valuetype (class [runtime]System.ValueType modreq([runtime]System.Runtime.InteropServices.UnmanagedType)) T>() cil managed
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 09 00 00 00 00 00 )
.param type T
.custom instance void [runtime]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = ( 01 00 00 00 )

.maxstack 8
IL_0000: ldsfld class Test/'func@3-1'<!0> class Test/'func@3-1'<!!T>::@_instance
IL_0005: ret
} """]


[<Fact>]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,30 @@ module Inlining =
|> getCompilation
|> verifyCompilation

[<Theory; FileInlineData("Regression_TLR_MutualInnerRec.fs", Realsig=BooleanOptions.Both)>]
let ``Regression_TLR_MutualInnerRec_fs`` compilation =
compilation
|> getCompilation
|> verifyCompilation

[<Theory; FileInlineData("Regression_TLR_MutualInnerRec_Point2D.fs", Realsig=BooleanOptions.Both)>]
let ``Regression_TLR_MutualInnerRec_Point2D_fs`` compilation =
compilation
|> getCompilation
|> verifyCompilation

[<Theory; FileInlineData("Regression_TLR_MutualInnerRec_Generic.fs", Realsig=BooleanOptions.Both)>]
let ``Regression_TLR_MutualInnerRec_Generic_fs`` compilation =
compilation
|> getCompilation
|> verifyCompilation

[<Theory; FileInlineData("Regression_TLR_MutualInnerRec_CapturedEnv.fs", Realsig=BooleanOptions.Both)>]
let ``Regression_TLR_MutualInnerRec_CapturedEnv_fs`` compilation =
compilation
|> getCompilation
|> verifyCompilation

// SOURCE=Match02.fs SCFLAGS="-a --optimize+" COMPILE_ONLY=1 POSTCMD="..\\CompareIL.cmd Match02.dll" # Match02.fs
[<Theory; FileInlineData("Match02.fs")>]
let ``Match02_fs`` compilation =
Expand Down
Loading
Loading