Skip to content

Optimizer: treat EI_ilzero (Unchecked.defaultof) as effect-free to enable dead binding elimination#19758

Open
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-unchecked-defaultof-optimization
Open

Optimizer: treat EI_ilzero (Unchecked.defaultof) as effect-free to enable dead binding elimination#19758
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-unchecked-defaultof-optimization

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 18, 2026

Unchecked.defaultof<T> unused bindings were not eliminated even with optimizations enabled, because the optimizer's effect analysis incorrectly classified the underlying EI_ilzero IL instruction as having side effects.

Root cause

Unchecked.defaultof<T> inlines to (# "ilzero !0" type ('T) : 'T #), which the optimizer represents as TOp.ILAsm([EI_ilzero ty], ...). The IlAssemblyCodeInstrHasEffect function in Optimizer.fs did not recognize EI_ilzero as effect-free, so it fell through to the conservative _ -> true default, preventing dead binding elimination.

Change

src/Compiler/Optimize/Optimizer.fs — add EI_ilzero _ to the no-effect branch of IlAssemblyCodeInstrHasEffect. The instruction only produces a zero/default value (mapping to ldnull, ldc.i4.0, or initobj depending on type), with no observable side effects.

// Before: initobj instructions emitted for all three unused bindings
let f (n: float32) =
    Console.WriteLine n
    let _ = Unchecked.defaultof<decimal>   // initobj Decimal
    let _ = Unchecked.defaultof<decimal>   // initobj Decimal
    let _ = Unchecked.defaultof<decimal>   // initobj Decimal
    let n' = n * 2.f
    Console.WriteLine n'

// After: all three bindings eliminated; IL only contains the two Console.WriteLine calls

This is particularly relevant for SRTP-based patterns (common in libraries like FSharpPlus) where Unchecked.defaultof<'T> is used as a witness/dummy argument to drive overload resolution and ends up as unused bindings at call sites.

Copilot AI linked an issue May 18, 2026 that may be closed by this pull request
Copilot AI changed the title [WIP] Fix unchecked.defaultof preventing other optimizations Optimizer: treat EI_ilzero (Unchecked.defaultof) as effect-free to enable dead binding elimination May 18, 2026
Copilot finished work on behalf of T-Gro May 18, 2026 11:41
Copilot AI requested a review from T-Gro May 18, 2026 11:41
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

❗ Release notes required


✅ Found changes and release notes in following paths:

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

…for concrete types

Unchecked.defaultof<'T> (compiled as EI_ilzero) was unconditionally treated
as having an effect, preventing the optimizer from eliminating unused bindings
like `let _ = Unchecked.defaultof<decimal>`.

Fix: In ExprHasEffect and OptimizeExprOpFallback, EI_ilzero is now considered
effect-free when all type args are concrete (not type parameters). When type
variables are present (e.g. SRTP ^T), it is conservatively treated as having
an effect to prevent orphaned type variables during IL generation (FS0073).

Fixes #17775

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@T-Gro T-Gro force-pushed the copilot/fix-unchecked-defaultof-optimization branch from 8b99fdc to 7ad68b7 Compare May 20, 2026 06:46
isTyparTy only detects direct type parameters (e.g. ^T) but not type
variables nested inside constructed types (e.g. SomeType<^T>). Replace
with freeInTypes CollectTyparsNoCaching which recursively checks for any
free type parameters in the type structure.

Also add release notes entry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@T-Gro T-Gro marked this pull request as ready for review May 20, 2026 08:32
@T-Gro T-Gro requested a review from a team as a code owner May 20, 2026 08:32
@github-actions github-actions Bot added the AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed label May 20, 2026
Broaden the type-variable check to cover ALL effect-free Expr.Op
expressions, not just EI_ilzero. When any operation would be effect-free
but its F# 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.

Also add EI_ilzero to the instruction-level no-effect list (its safety
is ensured by the tyargs check above it in the pipeline).

Also add release notes entry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@T-Gro T-Gro force-pushed the copilot/fix-unchecked-defaultof-optimization branch from 17e17e9 to ccefa82 Compare May 20, 2026 10:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

Unchecked.defaultof prevents other optimizations from working

2 participants