From 7c8dc942c3ad41f6a78f3017aa30387350c5ef02 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 26 Aug 2026 16:44:27 +0200 Subject: [PATCH 1/2] Perf: eliminate per-call closure in StackGuard.Guard via InlineIfLambda StackGuard.Guard was called at 18 hot compiler sites as `guard.Guard <| fun () -> body`, allocating a heap closure for the `fun () -> body` on EVERY call even though the common path just runs `f ()` inline. This was the #1 allocator in the compiler (remapExprImpl guard closure alone ~312 MB/compile of a 65k-LOC input). Make Guard an `inline` member with `[] f`, so the fast path inlines the body (zero closure). The rare stack-insufficient path (metrics + async SwitchToNewThread) moves to a non-inline `RunOnNewStack` member so the heavy code isn't duplicated at call sites. Depth accounting moves to non-inline `EnterGuard`/`ExitGuard` members so the inlined body touches no private field (realsig-safe; no FS1113). Since the `<|` operator defeats InlineIfLambda, the 12 `<|` call sites are converted to direct-call `Guard(fun () -> ...)` syntax; the 4 existing direct-call sites benefit unchanged. Result on a 65k-LOC compile (Release, net11): total allocation 10630.7 -> 9979.0 MB/compile (~651 MB, 6.1% saved); the guard closures remapExprImpl@1749 (312 MB), accFreeInExprNonLinear@1099 (105 MB), OptimizeExpr@2519 (59 MB), accExpr@45 (40 MB), TransExpr@1130 (20 MB) all drop to 0. Emitted output is byte-identical (IL disassembly 0 diffs; single-file outputs hash-identical). Deep-recursion behavior preserved: 200000-deep guarded recursion still jumps threads (73 jumps) and completes with no Field/MethodAccessException under --realsig+. IL size delta +85.5 KB (+0.41%). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../CheckComputationExpressions.fs | 10 ++--- .../Checking/Expressions/CheckExpressions.fs | 4 +- src/Compiler/Checking/FindUnsolved.fs | 4 +- src/Compiler/Checking/PostInferenceChecks.fs | 4 +- src/Compiler/Checking/TailCallChecks.fs | 5 +-- src/Compiler/CodeGen/IlxGen.fs | 5 +-- src/Compiler/Facilities/DiagnosticsLogger.fs | 42 ++++++++++++------- src/Compiler/Facilities/DiagnosticsLogger.fsi | 11 ++++- .../Optimize/InnerLambdasToTopLevelFuncs.fs | 4 +- src/Compiler/Optimize/Optimizer.fs | 4 +- .../TypedTree/TypedTreeOps.ExprOps.fs | 3 +- .../TypedTree/TypedTreeOps.Remapping.fs | 5 +-- .../TypedTree/TypedTreeOps.Transforms.fs | 5 +-- 13 files changed, 58 insertions(+), 48 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index c83a6a2e827..4e6735bc5cf 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -1171,8 +1171,7 @@ let rec TryTranslateComputationExpression let cenv = ceenv.cenv - cenv.stackGuard.Guard - <| fun () -> + cenv.stackGuard.Guard(fun () -> match comp with @@ -2488,7 +2487,7 @@ let rec TryTranslateComputationExpression Some(translatedCtxt yieldOrReturnCall) - | _ -> None + | _ -> None) and ConsumeCustomOpClauses (ceenv: ComputationExpressionContext<'a>) @@ -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 -> @@ -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) = diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 02fe8f3bd29..611305e7882 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -5522,7 +5522,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... @@ -5535,7 +5535,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) = diff --git a/src/Compiler/Checking/FindUnsolved.fs b/src/Compiler/Checking/FindUnsolved.fs index 675cc5b7fd4..59c0723221d 100644 --- a/src/Compiler/Checking/FindUnsolved.fs +++ b/src/Compiler/Checking/FindUnsolved.fs @@ -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 @@ -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 = diff --git a/src/Compiler/Checking/PostInferenceChecks.fs b/src/Compiler/Checking/PostInferenceChecks.fs index 6e0b46c8fa4..3f3c79dd973 100644 --- a/src/Compiler/Checking/PostInferenceChecks.fs +++ b/src/Compiler/Checking/PostInferenceChecks.fs @@ -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 @@ -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 diff --git a/src/Compiler/Checking/TailCallChecks.fs b/src/Compiler/Checking/TailCallChecks.fs index b58ba59797a..e8154cd9fa6 100644 --- a/src/Compiler/Checking/TailCallChecks.fs +++ b/src/Compiler/Checking/TailCallChecks.fs @@ -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 @@ -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 = diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 24ecf10b6be..5abfe827aa1 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -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. diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 77351b7dd89..3b7d1bc0603 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -995,33 +995,43 @@ type StackGuard(name: string) = #endif [] - member _.Guard + member _.EnterGuard() = depth.Value <- depth.Value + 1 + + [] + member _.ExitGuard() = depth.Value <- depth.Value - 1 + + [] + 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 + + [] + member inline this.Guard ( - f, + [] f: unit -> 'T, [] memberName: string, [] path: string, [] 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() [] member x.GuardCancellable(original: Cancellable<'T>) = diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index 66377aac861..43c7682a86a 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -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: + [] f: (unit -> 'T) * [] memberName: string * [] path: string * [] line: int -> diff --git a/src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs b/src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs index 24174f11a45..06db48c164c 100644 --- a/src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs +++ b/src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs @@ -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 @@ -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 diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 3ecedc13c63..2d0a0a6e111 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -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 @@ -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) = diff --git a/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs b/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs index 36fb37643f1..3c00c0ee66d 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs @@ -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 diff --git a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs index 00dbae22c43..d6801f4b3b3 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs @@ -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 @@ -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 = diff --git a/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs b/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs index 55e8e71b3f2..c8635c0d197 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs @@ -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 _ @@ -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 From 1aec61ae8f789d5ced111de112be216d2c642e85 Mon Sep 17 00:00:00 2001 From: perf-bundle Date: Thu, 27 Aug 2026 10:24:09 +0200 Subject: [PATCH 2/2] Add regression test: StackGuard inline Guard closure placement (IL baseline) EmittedIL/Inlining baseline (StackGuardInlineIfLambda.fs + .il.bsl) over a vanilla StackGuard emulation: inline Guard + [] + a separate non-inline RunOnNewStack the lambda is handed to, guarded by RuntimeHelpers.TryEnsureSufficientExecutionStack (as in the real StackGuard). The baseline pins that `callDirect` (direct application) inlines the lambda on the common path with the closure `newobj` confined to the cold else-branch, while `callPiped` (`<|`) hoists the `newobj` to method entry. A regression that reintroduces a common-path allocation changes callDirect's IL and fails the baseline. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4862d2e9-7740-4d0b-ba96-113e99466711 --- .../EmittedIL/Inlining/Inlining.fs | 11 + .../Inlining/StackGuardInlineIfLambda.fs | 28 +++ .../StackGuardInlineIfLambda.fs.il.bsl | 213 ++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/StackGuardInlineIfLambda.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/StackGuardInlineIfLambda.fs.il.bsl diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Inlining.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Inlining.fs index 29e8a9b40bb..87e2c744510 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Inlining.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Inlining.fs @@ -28,6 +28,17 @@ module Inlining = |> getCompilation |> verifyCompilation + // Regression baseline for dotnet/fsharp#20368: the inline StackGuard.Guard shape must inline the + // [] 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. + [] + let ``StackGuardInlineIfLambda_fs`` compilation = + compilation + |> getCompilation + |> verifyCompilation + [] let ``Regression_TLR_MutualInnerRec_fs`` compilation = compilation diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/StackGuardInlineIfLambda.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/StackGuardInlineIfLambda.fs new file mode 100644 index 00000000000..a2102efd9e9 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/StackGuardInlineIfLambda.fs @@ -0,0 +1,28 @@ +// #NoMono #CodeGen #Optimizations +// Regression baseline for the StackGuard.Guard closure-elimination shape (dotnet/fsharp#20368): +// an inline Guard with an [] 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 [] 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. + [] + member _.RunOnNewStack(f: unit -> 'T) : 'T = f () + + member inline this.Guard([] 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 diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/StackGuardInlineIfLambda.fs.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/StackGuardInlineIfLambda.fs.il.bsl new file mode 100644 index 00000000000..fac279c2289 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/StackGuardInlineIfLambda.fs.il.bsl @@ -0,0 +1,213 @@ + + + + + +.assembly extern runtime { } +.assembly extern FSharp.Core { } +.assembly assembly +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32, + int32, + int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 ) + + + + + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module assembly.exe + +.imagebase {value} +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 +.corflags 0x00000001 + + + + + +.class public abstract auto ansi sealed assembly + extends [runtime]System.Object +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) + .class auto ansi serializable nested public StackGuard + extends [runtime]System.Object + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .method public specialname rtspecialname instance void .ctor() cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: callvirt instance void [runtime]System.Object::.ctor() + IL_0006: ldarg.0 + IL_0007: pop + IL_0008: ret + } + + .method public hidebysig instance !!T RunOnNewStack(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 f) cil managed noinlining + { + + .maxstack 8 + IL_0000: ldarg.1 + IL_0001: ldnull + IL_0002: tail. + IL_0004: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2::Invoke(!0) + IL_0009: ret + } + + .method public hidebysig instance !!T Guard(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 f) cil managed + { + .param [1] + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.InlineIfLambdaAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: call bool [runtime]System.Runtime.CompilerServices.RuntimeHelpers::TryEnsureSufficientExecutionStack() + IL_0005: brfalse.s IL_0011 + + IL_0007: ldarg.1 + IL_0008: ldnull + IL_0009: tail. + IL_000b: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2::Invoke(!0) + IL_0010: ret + + IL_0011: ldarg.0 + IL_0012: ldarg.1 + IL_0013: tail. + IL_0015: callvirt instance !!0 assembly/StackGuard::RunOnNewStack(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2) + IL_001a: ret + } + + } + + .class auto ansi serializable sealed nested assembly beforefieldinit callDirect@26 + extends class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 + { + .field public int32 env + .method assembly specialname rtspecialname instance void .ctor(int32 env) cil managed + { + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2::.ctor() + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: stfld int32 assembly/callDirect@26::env + IL_000d: ret + } + + .method public strict virtual instance int32 Invoke(class [FSharp.Core]Microsoft.FSharp.Core.Unit unitVar0) cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld int32 assembly/callDirect@26::env + IL_0006: ldc.i4.1 + IL_0007: add + IL_0008: ret + } + + } + + .class auto ansi serializable sealed nested assembly beforefieldinit callPiped@28 + extends class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 + { + .field public int32 env + .method assembly specialname rtspecialname instance void .ctor(int32 env) cil managed + { + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2::.ctor() + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: stfld int32 assembly/callPiped@28::env + IL_000d: ret + } + + .method public strict virtual instance int32 Invoke(class [FSharp.Core]Microsoft.FSharp.Core.Unit unitVar0) cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld int32 assembly/callPiped@28::env + IL_0006: ldc.i4.1 + IL_0007: add + IL_0008: ret + } + + } + + .method public static int32 callDirect(class assembly/StackGuard sg, + int32 env) cil managed + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) + + .maxstack 8 + IL_0000: call bool [runtime]System.Runtime.CompilerServices.RuntimeHelpers::TryEnsureSufficientExecutionStack() + IL_0005: brfalse.s IL_000b + + IL_0007: ldarg.1 + IL_0008: ldc.i4.1 + IL_0009: add + IL_000a: ret + + IL_000b: ldarg.0 + IL_000c: ldarg.1 + IL_000d: newobj instance void assembly/callDirect@26::.ctor(int32) + IL_0012: tail. + IL_0014: callvirt instance !!0 assembly/StackGuard::RunOnNewStack(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2) + IL_0019: ret + } + + .method public static int32 callPiped(class assembly/StackGuard sg, + int32 env) cil managed + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) + + .maxstack 4 + .locals init (class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 V_0) + IL_0000: ldarg.1 + IL_0001: newobj instance void assembly/callPiped@28::.ctor(int32) + IL_0006: stloc.0 + IL_0007: call bool [runtime]System.Runtime.CompilerServices.RuntimeHelpers::TryEnsureSufficientExecutionStack() + IL_000c: brfalse.s IL_0012 + + IL_000e: ldarg.1 + IL_000f: ldc.i4.1 + IL_0010: add + IL_0011: ret + + IL_0012: ldarg.0 + IL_0013: ldloc.0 + IL_0014: tail. + IL_0016: callvirt instance !!0 assembly/StackGuard::RunOnNewStack(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2) + IL_001b: ret + } + +} + +.class private abstract auto ansi sealed ''.$assembly + extends [runtime]System.Object +{ + .method public static void main@() cil managed + { + .entrypoint + + .maxstack 8 + IL_0000: ret + } + +} + + + + +