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 4a6110cf514..a592b2dd52f 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -1,5 +1,6 @@ ### Fixed +* Optimizer: treat `Unchecked.defaultof<'T>` (`EI_ilzero`) as effect-free for concrete types, enabling dead binding elimination. ([Issue #17775](https://github.com/dotnet/fsharp/issues/17775), [PR #19758](https://github.com/dotnet/fsharp/pull/19758)) * 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 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)) * Fix NRE when calling virtual Object methods on value types through inline SRTP functions. ([Issue #8098](https://github.com/dotnet/fsharp/issues/8098), [PR #19511](https://github.com/dotnet/fsharp/pull/19511)) diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 3cbb574598c..e52880f880c 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -1615,7 +1615,8 @@ let IlAssemblyCodeInstrHasEffect i = | ( AI_nop | AI_ldc _ | AI_add | AI_sub | AI_mul | AI_xor | AI_and | AI_or | AI_ceq | AI_cgt | AI_cgt_un | AI_clt | AI_clt_un | AI_conv _ | AI_shl | AI_shr | AI_shr_un | AI_neg | AI_not | AI_ldnull ) - | I_ldstr _ | I_ldtoken _ -> false + | I_ldstr _ | I_ldtoken _ + | EI_ilzero _ -> false | _ -> true let IlAssemblyCodeHasEffect instrs = List.exists IlAssemblyCodeInstrHasEffect instrs @@ -1629,7 +1630,16 @@ let rec ExprHasEffect g expr = | Expr.Const _ -> false // type applications do not have effects, with the exception of type functions | Expr.App (f0, _, _, [], _) -> IsTyFuncValRefExpr f0 || ExprHasEffect g f0 - | Expr.Op (op, _, args, m) -> ExprsHaveEffect g args || OpHasEffect g m op + // An Expr.Op is effect-free when its op is effect-free, its args are effect-free, AND its type args + // don't contain free type parameters. Type variables in tyargs indicate the expression may serve as a + // witness/dummy for SRTP resolution; eliminating it can orphan those type vars causing FS0073 in IlxGen. + | Expr.Op (op, tyargs, args, m) -> + if ExprsHaveEffect g args || OpHasEffect g m op then + true + elif List.isEmpty tyargs then + false + else + not (Zset.isEmpty (freeInTypes CollectTyparsNoCaching tyargs).FreeTypars) | Expr.LetRec (binds, body, _, _) -> BindingsHaveEffect g binds || ExprHasEffect g body | Expr.Let (bind, body, _, _) -> BindingHasEffect g bind || ExprHasEffect g body // REVIEW: could add Expr.Obj on an interface type - these are similar to records of lambda expressions @@ -2641,7 +2651,18 @@ and OptimizeExprOpFallback cenv env (op, tyargs, argsR, m) arginfos value_ = let argsFSize = AddFunctionSizes arginfos let argEffects = OrEffects arginfos let argValues = List.map (fun x -> x.Info) arginfos - let effect = OpHasEffect g m op + + let effect = + let opEffect = OpHasEffect g m op + + // If an operation would be effect-free, but its type args contain free type parameters, + // conservatively treat it as having an effect. This prevents dead binding/sequential + // elimination from orphaning type variables that are only referenced through the eliminated + // expression, which would cause FS0073 during IL generation (common with SRTP patterns). + if not opEffect && not argEffects && not (List.isEmpty tyargs) then + not (Zset.isEmpty (freeInTypes CollectTyparsNoCaching tyargs).FreeTypars) + else + opEffect let cost, value_ = match op with | TOp.UnionCase c -> 2, MakeValueInfoForUnionCase c (Array.ofList argValues) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Observations.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Observations.fs index 4eb3dc57b98..18a797344e3 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Observations.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Observations.fs @@ -218,3 +218,47 @@ let empty<'T> = Seq.empty<'T> .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 ) """ ] + + // https://github.com/dotnet/fsharp/issues/17775 + [] + let ``Unchecked_defaultof_unused_bindings_eliminated_when_optimized`` () = + FSharp """ +module Test + +open System + +let f (n: float32) = + Console.WriteLine n + let _ = Unchecked.defaultof + let _ = Unchecked.defaultof + let _ = Unchecked.defaultof + let n' = n * 2.f + Console.WriteLine n' +""" + |> asLibrary + |> withOptimize + |> compile + |> shouldSucceed + |> verifyILNotPresent [ "initobj" ] + |> ignore + + // https://github.com/dotnet/fsharp/issues/17775 + // Regression: Unchecked.defaultof with type variables (including nested) must not be eliminated, + // otherwise orphaned type variables cause FS0073 during IL generation. + [] + let ``Unchecked_defaultof_with_type_variables_compiles_with_optimization`` () = + FSharp """ +module Test + +type Wrapper<'T> = { Value: 'T } + +let inline f< ^T when ^T : (static member op_Explicit: ^T -> int)> (x: ^T) = + let _ = Unchecked.defaultof< ^T > + let _ = Unchecked.defaultof> + int x +""" + |> asLibrary + |> withOptimize + |> compile + |> shouldSucceed + |> ignore