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
Original file line number Diff line number Diff line change
Expand Up @@ -1171,8 +1171,7 @@ let rec TryTranslateComputationExpression

let cenv = ceenv.cenv

cenv.stackGuard.Guard
<| fun () ->
cenv.stackGuard.Guard(fun () ->

match comp with

Expand Down Expand Up @@ -2488,7 +2487,7 @@ let rec TryTranslateComputationExpression

Some(translatedCtxt yieldOrReturnCall)

| _ -> None
| _ -> None)

and ConsumeCustomOpClauses
(ceenv: ComputationExpressionContext<'a>)
Expand Down Expand Up @@ -2896,8 +2895,7 @@ and isSimpleExpr ceenv comp =

and TranslateComputationExpression (ceenv: ComputationExpressionContext<'a>) firstTry q varSpace comp translatedCtxt =

ceenv.cenv.stackGuard.Guard
<| fun () ->
ceenv.cenv.stackGuard.Guard(fun () ->
match TryTranslateComputationExpression ceenv firstTry q varSpace comp translatedCtxt with
| Some e -> e
| None ->
Expand Down Expand Up @@ -3000,7 +2998,7 @@ and TranslateComputationExpression (ceenv: ComputationExpressionContext<'a>) fir
SynExprSequentialTrivia.Zero
)

translatedCtxt fillExpr)
translatedCtxt fillExpr))

/// Used for all computation expressions except sequence expressions
let TcComputationExpression (cenv: TcFileState) env (overallTy: OverallTy) tpenv (mWhole, interpExpr: Expr, builderTy, comp: SynExpr) =
Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5519,7 +5519,7 @@ and TcExpr (cenv: cenv) ty (env: TcEnv) tpenv (synExpr: SynExpr) =
let g = cenv.g

// Guard the stack for deeply nested expressions
cenv.stackGuard.Guard <| fun () ->
cenv.stackGuard.Guard(fun () ->

// Start an error recovery handler, and check for stack recursion depth, moving to a new stack if necessary.
// Note the try/with can lead to tail-recursion problems for iterated constructs, e.g. let... in...
Expand All @@ -5532,7 +5532,7 @@ and TcExpr (cenv: cenv) ty (env: TcEnv) tpenv (synExpr: SynExpr) =
// the type of the current expression with a type variable that indicates an error
errorRecovery exn m
SolveTypeAsError env.DisplayEnv cenv.css m ty.Commit
mkThrow m ty.Commit (mkOne g m), tpenv
mkThrow m ty.Commit (mkOne g m), tpenv)

and TcExprNoRecover (cenv: cenv) (ty: OverallTy) (env: TcEnv) tpenv (synExpr: SynExpr) =

Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/Checking/FindUnsolved.fs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ let accTypeInst cenv env mFallback tyargs =

/// Walk expressions, collecting type variables
let rec accExpr (cenv: cenv) (env: env) expr =
cenv.stackGuard.Guard <| fun () ->
cenv.stackGuard.Guard(fun () ->

let expr = stripExpr expr
match expr with
Expand Down Expand Up @@ -125,7 +125,7 @@ let rec accExpr (cenv: cenv) (env: env) expr =
accExpr cenv env eref.Value

| Expr.DebugPoint (_, innerExpr) ->
accExpr cenv env innerExpr
accExpr cenv env innerExpr)

/// Walk methods, collecting type variables
and accMethods cenv env baseValOpt l =
Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/Checking/PostInferenceChecks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1169,7 +1169,7 @@ and TryCheckResumableCodeConstructs cenv env expr : bool =
and CheckExpr (cenv: cenv) (env: env) origExpr (ctxt: PermitByRefExpr) : Limit =

// Guard the stack for deeply nested expressions
cenv.stackGuard.Guard <| fun () ->
cenv.stackGuard.Guard(fun () ->

let g = cenv.g

Expand Down Expand Up @@ -1274,7 +1274,7 @@ and CheckExpr (cenv: cenv) (env: env) origExpr (ctxt: PermitByRefExpr) : Limit =
NoLimit

| Expr.Link _ ->
failwith "Unexpected reclink"
failwith "Unexpected reclink")

and CheckQuoteExpr cenv env (ast, savedConv, m, ty) =
let g = cenv.g
Expand Down
5 changes: 2 additions & 3 deletions src/Compiler/Checking/TailCallChecks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,7 @@ and CheckExprLinear (cenv: cenv) expr (ctxt: PermitByRefExpr) (tailCall: TailCal
and CheckExpr (cenv: cenv) origExpr (ctxt: PermitByRefExpr) (tailCall: TailCall) : unit =

// Guard the stack for deeply nested expressions
cenv.stackGuard.Guard
<| fun () ->
cenv.stackGuard.Guard(fun () ->

let g = cenv.g

Expand Down Expand Up @@ -408,7 +407,7 @@ and CheckExpr (cenv: cenv) origExpr (ctxt: PermitByRefExpr) (tailCall: TailCall)

| Expr.WitnessArg _ -> ()

| Expr.Link _ -> failwith "Unexpected reclink"
| Expr.Link _ -> failwith "Unexpected reclink")

and CheckStructStateMachineExpr cenv info =

Expand Down
5 changes: 2 additions & 3 deletions src/Compiler/CodeGen/IlxGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3167,10 +3167,9 @@ let ComputeDebugPointForBinding g bind =
//-------------------------------------------------------------------------

let rec GenExpr cenv cgbuf eenv (expr: Expr) sequel =
cenv.stackGuard.Guard
<| fun () ->
cenv.stackGuard.Guard(fun () ->

GenExprAux cenv cgbuf eenv expr sequel
GenExprAux cenv cgbuf eenv expr sequel)

/// Process the debug point and check for alternative ways to generate this expression.
/// Returns 'true' if the expression was processed by alternative means.
Expand Down
42 changes: 26 additions & 16 deletions src/Compiler/Facilities/DiagnosticsLogger.fs
Original file line number Diff line number Diff line change
Expand Up @@ -995,33 +995,43 @@ type StackGuard(name: string) =
#endif

[<DebuggerHidden; DebuggerStepThrough>]
member _.Guard
member _.EnterGuard() = depth.Value <- depth.Value + 1

[<DebuggerHidden; DebuggerStepThrough>]
member _.ExitGuard() = depth.Value <- depth.Value - 1

[<DebuggerHidden; DebuggerStepThrough>]
member _.RunOnNewStack(f: unit -> 'T, memberName: string, path: string, line: int) : 'T =
let fileName = System.IO.Path.GetFileName(path)
let depthWhenJump = depth.Value

StackGuardMetrics.countJump memberName $"{fileName}:{line}" depthWhenJump

async {
do! Async.SwitchToNewThread()
Thread.CurrentThread.Name <- $"F# Extra Compilation Thread for {name} (depth {depthWhenJump})"
return f ()
}
|> Async.RunSynchronouslyImmediate

[<DebuggerHidden; DebuggerStepThrough>]
member inline this.Guard
(
f,
[<InlineIfLambda>] f: unit -> 'T,
[<CallerMemberName; Optional; DefaultParameterValue("")>] memberName: string,
[<CallerFilePath; Optional; DefaultParameterValue("")>] path: string,
[<CallerLineNumber; Optional; DefaultParameterValue(0)>] line: int
) =
) : 'T =

depth.Value <- depth.Value + 1
this.EnterGuard()

try
if StackGuard.IsStackSufficient() then
f ()
else
let fileName = System.IO.Path.GetFileName(path)
let depthWhenJump = depth.Value

StackGuardMetrics.countJump memberName $"{fileName}:{line}" depthWhenJump

async {
do! Async.SwitchToNewThread()
Thread.CurrentThread.Name <- $"F# Extra Compilation Thread for {name} (depth {depthWhenJump})"
return f ()
}
|> Async.RunSynchronouslyImmediate
this.RunOnNewStack(f, memberName, path, line)
finally
depth.Value <- depth.Value - 1
this.ExitGuard()

[<DebuggerHidden; DebuggerStepThrough>]
member x.GuardCancellable(original: Cancellable<'T>) =
Expand Down
11 changes: 9 additions & 2 deletions src/Compiler/Facilities/DiagnosticsLogger.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,16 @@ module internal StackGuardMetrics =
type StackGuard =
new: name: string -> StackGuard

member EnterGuard: unit -> unit

member ExitGuard: unit -> unit

/// The rare slow path: run the continuation on a fresh thread with a bigger stack.
member RunOnNewStack: f: (unit -> 'T) * memberName: string * path: string * line: int -> 'T

/// Execute the new function, on a new thread if necessary
member Guard:
f: (unit -> 'T) *
member inline Guard:
[<InlineIfLambda>] f: (unit -> 'T) *
[<CallerMemberName; Optional; DefaultParameterValue("")>] memberName: string *
[<CallerFilePath; Optional; DefaultParameterValue("")>] path: string *
[<CallerLineNumber; Optional; DefaultParameterValue(0)>] line: int ->
Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1127,7 +1127,7 @@ module Pass4_RewriteAssembly =
/// At free vals, fixup 0-call if it is an arity-met constant.
/// Other cases rewrite structurally.
let rec TransExpr (penv: RewriteContext) (z: RewriteState) expr: Expr * RewriteState =
penv.stackGuard.Guard <| fun () ->
penv.stackGuard.Guard(fun () ->

match expr with
// Use TransLinearExpr with a rebuild-continuation for some forms to avoid stack overflows on large terms
Expand Down Expand Up @@ -1230,7 +1230,7 @@ module Pass4_RewriteAssembly =
error(Error(FSComp.SR.tlrUnexpectedTExpr(),m))

| Expr.WitnessArg (_witnessInfo, _m) ->
expr, z
expr, z)

/// Walk over linear structured terms in tail-recursive loop, using a continuation
/// to represent the rebuild-the-term stack
Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/Optimize/Optimizer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2516,7 +2516,7 @@ let shouldForceInlineInDebug cenv env (vref: ValRef) : bool =

/// Optimize/analyze an expression
let rec OptimizeExpr cenv (env: IncrementalOptimizationEnv) expr =
cenv.stackGuard.Guard <| fun () ->
cenv.stackGuard.Guard(fun () ->

let g = cenv.g

Expand Down Expand Up @@ -2636,7 +2636,7 @@ let rec OptimizeExpr cenv (env: IncrementalOptimizationEnv) expr =
FunctionSize = 1
HasEffect = false
MightMakeCriticalTailcall=false
Info=UnknownValue }
Info=UnknownValue })

/// Optimize/analyze an object expression
and OptimizeObjectExpr cenv env (ty, baseValOpt, basecall, overrides, iimpls, m) =
Expand Down
3 changes: 1 addition & 2 deletions src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,7 @@ module internal ExprFolding =
let rec exprsF z xs = List.fold exprFClosure z xs

and exprF (z: 'State) (x: Expr) =
stackGuard.Guard
<| fun () -> folders.exprIntercept exprFClosure exprNoInterceptFClosure z x
stackGuard.Guard(fun () -> folders.exprIntercept exprFClosure exprNoInterceptFClosure z x)

and exprNoInterceptF (z: 'State) (x: Expr) =
match x with
Expand Down
5 changes: 2 additions & 3 deletions src/Compiler/TypedTree/TypedTreeOps.Remapping.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1745,8 +1745,7 @@ module internal ExprRemapping =
and remapExprImpl (ctxt: RemapContext) (compgen: ValCopyFlag) (tmenv: Remap) expr =

// Guard against stack overflow, moving to a whole new stack if necessary
ctxt.stackGuard.Guard
<| fun () ->
ctxt.stackGuard.Guard(fun () ->

match expr with

Expand Down Expand Up @@ -1865,7 +1864,7 @@ module internal ExprRemapping =

| Expr.WitnessArg(traitInfo, m) ->
let traitInfoR = remapTraitInfo tmenv traitInfo
Expr.WitnessArg(traitInfoR, m)
Expr.WitnessArg(traitInfoR, m))

and remapLambaExpr (ctxt: RemapContext) (compgen: ValCopyFlag) (tmenv: Remap) (ctorThisValOpt, baseValOpt, vs, body, m, bodyTy) =
let ctorThisValOptR, tmenv =
Expand Down
5 changes: 2 additions & 3 deletions src/Compiler/TypedTree/TypedTreeOps.Transforms.fs
Original file line number Diff line number Diff line change
Expand Up @@ -746,8 +746,7 @@ module internal Rewriting =
and rewriteBinds env binds = List.map (rewriteBind env) binds

and RewriteExpr env expr =
env.StackGuard.Guard
<| fun () ->
env.StackGuard.Guard(fun () ->
match expr with
| LinearOpExpr _
| LinearMatchExpr _
Expand All @@ -760,7 +759,7 @@ module internal Rewriting =
| Some expr -> expr
| None -> rewriteExprStructure env expr

postRewriteExpr env expr
postRewriteExpr env expr)

and preRewriteExpr env expr =
match env.PreIntercept with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ module Inlining =
|> getCompilation
|> verifyCompilation

// Regression baseline for dotnet/fsharp#20368: the inline StackGuard.Guard shape must inline the
// [<InlineIfLambda>] lambda on the common path. In `callDirect` the closure `newobj` is confined to
// the cold else-branch (common path allocates nothing); `callPiped` shows the `<|` form hoisting it
// to method entry. A regression that reintroduces a common-path allocation changes this baseline.
// Realsig has no effect on this IL, so a single variant is snapshotted.
[<Theory; FileInlineData("StackGuardInlineIfLambda.fs")>]
let ``StackGuardInlineIfLambda_fs`` compilation =
compilation
|> getCompilation
|> verifyCompilation

[<Theory; FileInlineData("Regression_TLR_MutualInnerRec.fs", Realsig=BooleanOptions.Both)>]
let ``Regression_TLR_MutualInnerRec_fs`` compilation =
compilation
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// #NoMono #CodeGen #Optimizations
// Regression baseline for the StackGuard.Guard closure-elimination shape (dotnet/fsharp#20368):
// an inline Guard with an [<InlineIfLambda>] function whose rare slow path (RunOnNewStack) is a
// separate, non-inline method the function is handed to.
//
// callDirect: direct application. The lambda body is inlined on the common path; the closure
// 'newobj' appears only in the cold else-branch (reached only when the stack is
// insufficient), so the common path allocates nothing.
// callPiped: '<|' eta-expands the member operand, which defeats [<InlineIfLambda>] and hoists
// the closure 'newobj' to method entry -> allocated on every call. Kept as a contrast
// so a regression that makes callDirect look like callPiped shows up in the baseline.
module StackGuardInlineIfLambda

open System.Runtime.CompilerServices

type StackGuard() =
// The lambda escapes into this non-inline method, so it must be materialized as a closure -
// but only where the call happens (the cold branch), never on the common path.
[<MethodImpl(MethodImplOptions.NoInlining)>]
member _.RunOnNewStack(f: unit -> 'T) : 'T = f ()

member inline this.Guard([<InlineIfLambda>] f: unit -> 'T) : 'T =
if RuntimeHelpers.TryEnsureSufficientExecutionStack() then f ()
else this.RunOnNewStack f

let callDirect (sg: StackGuard) (env: int) : int = sg.Guard(fun () -> env + 1)

let callPiped (sg: StackGuard) (env: int) : int = sg.Guard <| fun () -> env + 1
Loading
Loading