From 7679c374c71954ab3fe3e8a99c211e965ea14e07 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 26 Aug 2026 19:40:08 +0200 Subject: [PATCH] Fuse the optimizer inlining copy + type-instantiation passes When inlining a generic function saturated by its type arguments, the optimizer cloned the whole body twice: copyExpr (clone + fresh vals), then a separate type-application beta reduction to substitute the type args. remapExpr already applies a type instantiation at every type position, so the generic-inline site now carries the instantiation via mkInstRemap in the same copy pass - one traversal, one val-clone - and applies the (already type-instantiated) body with an empty tyarg list. The range-remark stays a separate pass, unchanged. Non-generic inline sites are untouched, and TypedTreeOps.Remapping is unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 69e8646f-b2d9-4852-af5c-5f96dc2d36a3 --- .../.FSharp.Compiler.Service/11.0.100.md | 1 + src/Compiler/Optimize/Optimizer.fs | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 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 562632e460d..93ed2d7111d 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -191,6 +191,7 @@ ### Changed * The `--warnaserror` option now ignores unrecognized diagnostic identifiers in warning lists while still applying recognized F# warning codes. ([PR #20246](https://github.com/dotnet/fsharp/pull/20246)) +* Reduce allocations when inlining a generic function by fusing the optimizer's copy and type-instantiation passes into a single tree traversal instead of cloning the body twice. ([PR #20363](https://github.com/dotnet/fsharp/pull/20363)) * Improvements in error and warning messages: new error FS3885 when `let!`/`use!` is the final expression in a computation expression; new warning FS3886 when a list literal contains a single tuple element (likely missing `;` separator); improved wording for FS0003, FS0025, FS0039, FS0072, FS0247, FS0597, FS0670, FS3082, and SRTP operator-not-in-scope hints. ([PR #19398](https://github.com/dotnet/fsharp/pull/19398)) * Exception field serialization (`GetObjectData` and field-restoring constructor) is now gated behind `langversion:11` (`LanguageFeature.ExceptionFieldSerializationSupport`). With langversion ≤10, exception codegen is unchanged from pre-#19342 behavior. ([PR #19746](https://github.com/dotnet/fsharp/pull/19746)) * field serialization (`GetObjectData` and field-restoring constructor) is now gated behind `langversion:11` (`LanguageFeature.ExceptionFieldSerializationSupport`). With langversion ≤10, exception codegen is unchanged from pre-#19342 behavior. ([PR #19746](https://github.com/dotnet/fsharp/pull/19746)) diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 3ecedc13c63..d390696aa96 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -3889,9 +3889,6 @@ and TryInlineApplication cenv env finfo (valExpr: Expr) (tyargs: TType list, arg if isApplicationPartialExpr then None else - // Inlining lambda - let f2R = CopyExprForInlining cenv false f2 m - // Optimizing arguments after inlining // REVIEW: this is a cheapshot way of optimizing the arg expressions as well without the restriction of recursive @@ -3900,7 +3897,17 @@ and TryInlineApplication cenv env finfo (valExpr: Expr) (tyargs: TType list, arg // Beta reduce. MakeApplicationAndBetaReduce g does all the hard work. // Inlining: beta reducing - let exprR = MakeApplicationAndBetaReduce g (f2R, f2ty, [tyargs], argsR, m) + // Generic lambda saturated by its type args: fuse the inlining copy with the type + // instantiation into one traversal (one val-clone) instead of copyExpr then instExpr. + let exprR = + match f2 with + | Expr.TyLambda(_, tyvs, body, _, bodyTy) when tyvs.Length = tyargs.Length -> + let tpinst = bindTypars tyvs tyargs emptyTyparInst + let bodyR = remapExpr g CloneAllAndMarkExprValsAsCompilerGenerated (mkInstRemap tpinst) body |> remarkExpr m + MakeApplicationAndBetaReduce g (bodyR, instType tpinst bodyTy, [], argsR, m) + | _ -> + let f2R = CopyExprForInlining cenv false f2 m + MakeApplicationAndBetaReduce g (f2R, f2ty, [tyargs], argsR, m) // Inlining: reoptimizing Some(OptimizeExpr cenv {env with dontInline = Map.add lambdaId [] env.dontInline} exprR)