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
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
27 changes: 24 additions & 3 deletions src/Compiler/Optimize/Optimizer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
[<Fact>]
let ``Unchecked_defaultof_unused_bindings_eliminated_when_optimized`` () =
FSharp """
module Test

open System

let f (n: float32) =
Console.WriteLine n
let _ = Unchecked.defaultof<decimal>
let _ = Unchecked.defaultof<decimal>
let _ = Unchecked.defaultof<decimal>
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.
[<Fact>]
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<Wrapper< ^T >>
int x
"""
|> asLibrary
|> withOptimize
|> compile
|> shouldSucceed
|> ignore
Loading