Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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,7 +1,11 @@
### 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 — a ≈23× perf regression for struct-typed mutual recursion. ([Issue #17607](https://github.com/dotnet/fsharp/issues/17607))
* Fix `TypeLoadException` for multi-level closures from constrained inline generics by stripping constraints from added generic params in `EraseClosures.convIlxClosureDef`. ([Issue #14492](https://github.com/dotnet/fsharp/issues/14492))

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

* 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))
* Fix internal error FS0073 "Undefined or unsolved type variable" in IlxGen when nested inline SRTP functions with multiple overloads leave unsolved typars in the non-witness codegen path. ([Issue #19709](https://github.com/dotnet/fsharp/issues/19709), [PR #19710](https://github.com/dotnet/fsharp/pull/19710))
Expand Down
9 changes: 2 additions & 7 deletions src/Compiler/AbstractIL/il.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3375,14 +3375,9 @@ let mkILSimpleTypar nm =
MetadataIndex = NoMetadataIdx
}

/// Returns gp with all constraints cleared, including IsUnmanagedAttribute (carried via CustomAttrsStored).
let stripILGenericParamConstraints (gp: ILGenericParameterDef) =
{ gp with
Constraints = []
HasReferenceTypeConstraint = false
HasNotNullableValueTypeConstraint = false
HasDefaultConstructorConstraint = false
HasAllowsRefStruct = false
}
{ mkILSimpleTypar gp.Name with Variance = gp.Variance; MetadataIndex = gp.MetadataIndex }

let genericParamOfGenericActual (_ga: ILType) = mkILSimpleTypar "T"

Expand Down
12 changes: 7 additions & 5 deletions src/Compiler/CodeGen/EraseClosures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -484,9 +484,13 @@ 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)

// Strip constraints: both the Specialize override method-typars (CASE 1b) and the
// later closure class-typars (CASE 1a) must be unconstrained — the override must match
// its base signature, and the class must accept any T that Specialize passes. See #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 +508,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 +568,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
30 changes: 23 additions & 7 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

/// Always points to the enclosing module (non-generic). Used for TLR-lifted vals.
moduleCloc: CompileLocation

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

Expand Down Expand Up @@ -1299,6 +1302,8 @@ let EnvForTycon tps eenv =
tyenv = eenv.tyenv.ForTycon tps
}

/// Narrows cloc into a nested type scope. moduleCloc is intentionally NOT updated:
/// TLR-lifted vals must stay in the module class, not in a generic enclosing type.
let AddEnclosingToEnv eenv enclosing name ns =
{ eenv with
cloc =
Expand Down Expand Up @@ -10269,7 +10274,13 @@ and AllocValReprWithinExpr cenv cgbuf endMark cloc v eenv =
else
NoShadowLocal, eenv

ComputeAndAddStorageForLocalValWithValReprInfo (cenv, eenv.intraAssemblyInfo, cenv.options.isInteractive, optShadowLocal) cloc v eenv
// TLR-lifted inner functions must go in the module class — if emitted inside a generic
// enclosing type they inherit its class typar, conflicting with their own method typars. See #17607.
// Note: also matches `do let x = …` and `copyOfStruct` vals — safe because at module scope cloc == moduleCloc.
let effectiveCloc =
if v.IsCompiledAsTopLevel && not v.IsMemberOrModuleBinding then 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 @@ -10692,9 +10703,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 @@ -10760,13 +10774,13 @@ 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 @@ -12505,6 +12519,7 @@ let GetEmptyIlxGenEnv (g: TcGlobals) ccu =
{
tyenv = TypeReprEnv.Empty
cloc = CompLocForCcu ccu
moduleCloc = CompLocForCcu ccu
initClassCompLoc = None
initFieldName = CompilerGeneratedName "init"
staticInitializationName = CompilerGeneratedName "staticInitialization"
Expand Down Expand Up @@ -12589,6 +12604,7 @@ let GenerateCode (cenv, anonTypeTable, eenv, CheckedAssemblyAfterOptimization im
let eenv =
{ eenv with
cloc = CompLocForFragment cenv.options.fragName cenv.viewCcu
moduleCloc = CompLocForFragment cenv.options.fragName cenv.viewCcu
delayCodeGen = cenv.options.parallelIlxGenEnabled
}

Expand Down
4 changes: 1 addition & 3 deletions src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,7 @@ module Pass1_DetermineTLRAndArities =
let arity = min nFormals nMaxApplied
if atTopLevel then
Some (f, arity)
elif g.realsig then
None
else if arity<>0 || not (isNil tps) then
elif 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