@@ -9,24 +9,18 @@ open Internal.Utilities.Collections
99open Internal.Utilities .Library
1010open Internal.Utilities .Library .Extras
1111open FSharp.Compiler
12- open FSharp.Compiler .AbstractIL .Diagnostics
1312open FSharp.Compiler .AbstractIL .IL
1413open FSharp.Compiler .AttributeChecking
1514open FSharp.Compiler .CompilerGlobalState
1615open FSharp.Compiler .DiagnosticsLogger
1716open FSharp.Compiler .Text .Range
1817open FSharp.Compiler .Syntax .PrettyNaming
1918open FSharp.Compiler .Syntax
20- open FSharp.Compiler .SyntaxTreeOps
2119open FSharp.Compiler .TcGlobals
2220open FSharp.Compiler .Text
23- open FSharp.Compiler .Text .Layout
24- open FSharp.Compiler .Text .LayoutRender
25- open FSharp.Compiler .Text .TaggedText
2621open FSharp.Compiler .TypedTree
2722open FSharp.Compiler .TypedTreeBasics
2823open FSharp.Compiler .TypedTreeOps
29- open FSharp.Compiler .TypedTreeOps .DebugPrint
3024open FSharp.Compiler .TypedTreePickle
3125open FSharp.Compiler .TypeHierarchy
3226open FSharp.Compiler .TypeRelations
@@ -36,6 +30,61 @@ open System.Collections.ObjectModel
3630
3731let OptimizerStackGuardDepth = GetEnvInteger " FSHARP_Optimizer" 50
3832
33+ //-------------------------------------------------------------------------
34+ // Cheap "occurs" checks (avoid full free-variable set construction)
35+ //-------------------------------------------------------------------------
36+
37+ let rec private ExprUsesLocal ( v : Val ) ( expr : Expr ) =
38+ // Be robust against reclinks and wrappers
39+ let expr = stripExpr expr
40+ match expr with
41+ | Expr.Val ( VRefLocal v2, _, _) -> valEq v v2
42+ | Expr.App ( f, _, _, args, _) ->
43+ ExprUsesLocal v f || List.exists ( ExprUsesLocal v) args
44+ | Expr.Lambda (_, _, _, _, body, _, _)
45+ | Expr.TyLambda (_, _, body, _, _) ->
46+ ExprUsesLocal v body
47+ | Expr.StaticOptimization (_, e2, e3, _) ->
48+ ExprUsesLocal v e2 || ExprUsesLocal v e3
49+ | Expr.TyChoose _ ->
50+ // Conservative: free-choice typars inside; assume potential use
51+ true
52+ | Expr.Quote _ ->
53+ // Conservative: quotations can capture locals
54+ true
55+ | Expr.Let ( TBind (_, e1, _), e2, _, _) ->
56+ ExprUsesLocal v e1 || ExprUsesLocal v e2
57+ | Expr.LetRec ( binds, body, _, _) ->
58+ List.exists ( fun ( TBind ( _ , e , _ )) -> ExprUsesLocal v e) binds || ExprUsesLocal v body
59+ | Expr.Sequential ( e1, e2, _, _) ->
60+ ExprUsesLocal v e1 || ExprUsesLocal v e2
61+ | Expr.Match (_, _, dtree, targets, _, _) ->
62+ DecisionTreeUsesLocal v dtree ||
63+ Array.exists ( fun ( TTarget ( _ , e , _ )) -> ExprUsesLocal v e) targets
64+ // IMPORTANT: account for address-of locals (e.g., passing out-byref) which carry the ValRef in the op payload
65+ | Expr.Op ( TOp.LValueOp ( LAddrOf _, lv), _, _, _) ->
66+ valEq v lv.Deref
67+ | Expr.Op (_, _, args, _) ->
68+ List.exists ( ExprUsesLocal v) args
69+ | Expr.Obj (_, _, _, ctorCall, overrides, iimpls, _) ->
70+ ExprUsesLocal v ctorCall ||
71+ List.exists ( fun ( TObjExprMethod ( _ , _ , _ , _ , e , _ )) -> ExprUsesLocal v e) overrides ||
72+ List.exists ( fun ( _ , ms ) -> List.exists ( fun ( TObjExprMethod ( _ , _ , _ , _ , e , _ )) -> ExprUsesLocal v e) ms) iimpls
73+ | Expr.DebugPoint (_, e) ->
74+ ExprUsesLocal v e
75+ | _ -> false
76+
77+ and private DecisionTreeUsesLocal ( v : Val ) ( dt : DecisionTree ) =
78+ match dt with
79+ | TDSuccess ( es, _) ->
80+ List.exists ( ExprUsesLocal v) es
81+ | TDBind ( TBind (_, e1, _), rest) ->
82+ ExprUsesLocal v e1 || DecisionTreeUsesLocal v rest
83+ | TDSwitch ( e, cases, dflt, _) ->
84+ ExprUsesLocal v e ||
85+ List.exists ( fun ( TCase ( _ , t )) -> DecisionTreeUsesLocal v t) cases ||
86+ Option.exists ( DecisionTreeUsesLocal v) dflt
87+
3988let i_ldlen = [ I_ ldlen; ( AI_ conv DT_ I4) ]
4089
4190/// size of a function call
@@ -1706,9 +1755,7 @@ let TryEliminateBinding cenv _env bind e2 _m =
17061755 // But note the cases below cover some instances of side-effecting expressions as well....
17071756 let IsUniqueUse vspec2 args =
17081757 valEq vspec1 vspec2
1709- // REVIEW: this looks slow. Look only for one variable instead
1710- && ( let fvs = accFreeInExprs ( CollectLocalsWithStackGuard()) args emptyFreeVars
1711- not ( Zset.contains vspec1 fvs.FreeLocals))
1758+ && not ( List.exists ( ExprUsesLocal vspec1) args)
17121759
17131760 // Immediate consumption of value as 2nd or subsequent argument to a construction or projection operation
17141761 let rec GetImmediateUseContext rargsl argsr =
@@ -1742,8 +1789,12 @@ let TryEliminateBinding cenv _env bind e2 _m =
17421789 // Immediate consumption of value by a pattern match 'let x = e in match x with ...'
17431790 | Expr.Match ( spMatch, _ exprm, TDSwitch( DebugPoints( Expr.Val ( VRefLocal vspec2, _, _), recreate1), cases, dflt, _), targets, m, ty2)
17441791 when ( valEq vspec1 vspec2 &&
1745- let fvs = accFreeInTargets CollectLocals targets ( accFreeInSwitchCases CollectLocals cases dflt emptyFreeVars)
1746- not ( Zset.contains vspec1 fvs.FreeLocals)) ->
1792+ let fvsContains =
1793+ let fvTargets = accFreeInTargets CollectLocals targets emptyFreeVars
1794+ let fvCases = accFreeInSwitchCases CollectLocals cases dflt emptyFreeVars
1795+ let fvs = unionFreeVars fvTargets fvCases
1796+ Zset.contains vspec1 fvs.FreeLocals
1797+ not fvsContains) ->
17471798
17481799 let spMatch = spBind.Combine spMatch
17491800 Some ( Expr.Match ( spMatch, e1.Range, TDSwitch( recreate1 e1, cases, dflt, m), targets, m, ty2) |> recreate0)
@@ -2896,12 +2947,25 @@ and OptimizeLinearExpr cenv env expr contf =
28962947
28972948 | Expr.Let ( bind, body, m, _) ->
28982949
2899- let ( bindR , bindingInfo ), env = OptimizeBinding cenv false env bind
2950+ let ( bindR : Binding , bindingInfo ), env = OptimizeBinding cenv false env bind
29002951
29012952 OptimizeLinearExpr cenv env body ( contf << ( fun ( bodyR , bodyInfo ) ->
2902- // PERF: This call to ValueIsUsedOrHasEffect/freeInExpr amounts to 9% of all optimization time.
2903- // Is it quadratic or quasi-quadratic?
2904- if ValueIsUsedOrHasEffect cenv ( fun () -> ( freeInExpr ( CollectLocalsWithStackGuard()) bodyR) .FreeLocals) ( bindR, bindingInfo) then
2953+ // Cheap occurrence check + precise fallback before eliminating
2954+ let v = bindR.Var
2955+ let usedQuick = ExprUsesLocal v bodyR
2956+ let usedFull =
2957+ if usedQuick then true
2958+ else
2959+ let fvs = freeInExpr CollectLocals bodyR
2960+ Zset.contains v fvs.FreeLocals
2961+ let keepBinding =
2962+ ( not cenv.settings.EliminateUnusedBindings && not v.InlineIfLambda) ||
2963+ Option.isSome v.MemberInfo ||
2964+ ( bindingInfo.HasEffect && not ( IsDiscardableEffectExpr bindR.Expr)) ||
2965+ v.IsFixed ||
2966+ usedFull
2967+
2968+ if keepBinding then
29052969 // Eliminate let bindings on the way back up
29062970 let exprR , adjust = TryEliminateLet cenv env bindR bodyR m
29072971 exprR,
@@ -4040,8 +4104,23 @@ and OptimizeDecisionTree cenv env m x =
40404104 let ( bind , binfo ), envinner = OptimizeBinding cenv false env bind
40414105 let rest , rinfo = OptimizeDecisionTree cenv envinner m rest
40424106
4043- if ValueIsUsedOrHasEffect cenv ( fun () -> ( accFreeInDecisionTree CollectLocals rest emptyFreeVars) .FreeLocals) ( bind, binfo) then
4107+ // Quick occurrence check with precise fallback
4108+ let v = bind.Var
4109+ let usedQuick = DecisionTreeUsesLocal v rest
4110+ let usedFull =
4111+ if usedQuick then true
4112+ else
4113+ let fvs = accFreeInDecisionTree CollectLocals rest emptyFreeVars
4114+ Zset.contains v fvs.FreeLocals
4115+
4116+ let keepBinding =
4117+ ( not cenv.settings.EliminateUnusedBindings && not v.InlineIfLambda) ||
4118+ Option.isSome v.MemberInfo ||
4119+ ( binfo.HasEffect && not ( IsDiscardableEffectExpr bind.Expr)) ||
4120+ v.IsFixed ||
4121+ usedFull
40444122
4123+ if keepBinding then
40454124 let info = CombineValueInfosUnknown [ rinfo; binfo]
40464125 // try to fold the let-binding into a single result expression
40474126 match rest with
0 commit comments