Skip to content

Commit d344ce4

Browse files
TIHanbaronfel
authored andcommitted
Stack overflow fixes (#7151)
* Trying to fix the stack overflow case in ilxgen * Added stack guard to GenExpr * Still trying to resolve this * This resolves the stack overflow, but need to review * Adding better tests * Fixing compile and run successfully assert * One test passes * Fixing a few things * Delete fieldNames.txt * Update IlxGen.fs * Update IlxGen.fs * Update IlxGen.fs * More work on CompilerAssert * Got verifying IL working on method * Always delay gen methods when already gen'ing in one. Some small refactoring, created IlxMethodInfo type instead of using large tuples. * Better error message for stack overflow * small msg change * Simplifying some tests * Removing attempt at bringing a FSharpQA test to nunit * Fixing up seq points * More seq point tweaks * Trying to fix ilxgen * Fixing ilxgen * Using RunScript * Fix test * removed IlxMethodInfo * trivial refactor * another quick refactor
1 parent d6ad536 commit d344ce4

File tree

3 files changed

+130
-93
lines changed

3 files changed

+130
-93
lines changed

src/fsharp/IlxGen.fs

Lines changed: 119 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@ type cenv =
245245

246246
/// Used to apply forced inlining optimizations to witnesses generated late during codegen
247247
mutable optimizeDuringCodeGen: (Expr -> Expr)
248+
249+
/// What depth are we at when generating an expression?
250+
mutable exprRecursionDepth: int
251+
252+
/// Delayed Method Generation - prevents stack overflows when we need to generate methods that are split into many methods by the optimizer.
253+
delayedGenMethods: Queue<cenv -> unit>
248254
}
249255

250256

@@ -2130,20 +2136,53 @@ let DoesGenExprStartWithSequencePoint g sp expr =
21302136
FirstEmittedCodeWillBeSequencePoint g sp expr ||
21312137
EmitSequencePointForWholeExpr g sp expr
21322138

2139+
let ProcessSequencePointForExpr (cenv: cenv) (cgbuf: CodeGenBuffer) sp expr =
2140+
let g = cenv.g
2141+
if not (FirstEmittedCodeWillBeSequencePoint g sp expr) then
2142+
if EmitSequencePointForWholeExpr g sp expr then
2143+
CG.EmitSeqPoint cgbuf (RangeOfSequencePointForWholeExpr g expr)
2144+
elif EmitHiddenCodeMarkerForWholeExpr g sp expr then
2145+
cgbuf.EmitStartOfHiddenCode()
2146+
21332147
//-------------------------------------------------------------------------
21342148
// Generate expressions
21352149
//-------------------------------------------------------------------------
21362150

2137-
let rec GenExpr (cenv: cenv) (cgbuf: CodeGenBuffer) eenv sp expr sequel =
2151+
let rec GenExpr cenv cgbuf eenv sp (expr: Expr) sequel =
2152+
cenv.exprRecursionDepth <- cenv.exprRecursionDepth + 1
2153+
2154+
if cenv.exprRecursionDepth > 1 then
2155+
StackGuard.EnsureSufficientExecutionStack cenv.exprRecursionDepth
2156+
GenExprAux cenv cgbuf eenv sp expr sequel
2157+
else
2158+
GenExprWithStackGuard cenv cgbuf eenv sp expr sequel
2159+
2160+
cenv.exprRecursionDepth <- cenv.exprRecursionDepth - 1
2161+
2162+
if cenv.exprRecursionDepth = 0 then
2163+
ProcessDelayedGenMethods cenv
2164+
2165+
and ProcessDelayedGenMethods cenv =
2166+
while cenv.delayedGenMethods.Count > 0 do
2167+
let gen = cenv.delayedGenMethods.Dequeue ()
2168+
gen cenv
2169+
2170+
and GenExprWithStackGuard cenv cgbuf eenv sp expr sequel =
2171+
assert (cenv.exprRecursionDepth = 1)
2172+
try
2173+
GenExprAux cenv cgbuf eenv sp expr sequel
2174+
assert (cenv.exprRecursionDepth = 1)
2175+
with
2176+
| :? System.InsufficientExecutionStackException ->
2177+
error(InternalError("Expression is too large and/or complex to emit.", expr.Range))
2178+
2179+
and GenExprAux (cenv: cenv) (cgbuf: CodeGenBuffer) eenv sp expr sequel =
21382180
let g = cenv.g
21392181
let expr = stripExpr expr
21402182

2141-
if not (FirstEmittedCodeWillBeSequencePoint g sp expr) then
2142-
if EmitSequencePointForWholeExpr g sp expr then
2143-
CG.EmitSeqPoint cgbuf (RangeOfSequencePointForWholeExpr g expr)
2144-
elif EmitHiddenCodeMarkerForWholeExpr g sp expr then
2145-
cgbuf.EmitStartOfHiddenCode()
2183+
ProcessSequencePointForExpr cenv cgbuf sp expr
21462184

2185+
// A sequence expression will always match Expr.App.
21472186
match (if compileSequenceExpressions then LowerCallsAndSeqs.LowerSeqExpr g cenv.amap expr else None) with
21482187
| Some info ->
21492188
GenSequenceExpr cenv cgbuf eenv info sequel
@@ -2154,32 +2193,8 @@ let rec GenExpr (cenv: cenv) (cgbuf: CodeGenBuffer) eenv sp expr sequel =
21542193
GenConstant cenv cgbuf eenv (c, m, ty) sequel
21552194
| Expr.Match (spBind, exprm, tree, targets, m, ty) ->
21562195
GenMatch cenv cgbuf eenv (spBind, exprm, tree, targets, m, ty) sequel
2157-
| Expr.Sequential (e1, e2, dir, spSeq, m) ->
2158-
GenSequential cenv cgbuf eenv sp (e1, e2, dir, spSeq, m) sequel
21592196
| Expr.LetRec (binds, body, m, _) ->
21602197
GenLetRec cenv cgbuf eenv (binds, body, m) sequel
2161-
| Expr.Let (bind, body, _, _) ->
2162-
// This case implemented here to get a guaranteed tailcall
2163-
// Make sure we generate the sequence point outside the scope of the variable
2164-
let startScope, endScope as scopeMarks = StartDelayedLocalScope "let" cgbuf
2165-
let eenv = AllocStorageForBind cenv cgbuf scopeMarks eenv bind
2166-
let spBind = GenSequencePointForBind cenv cgbuf bind
2167-
GenBindingAfterSequencePoint cenv cgbuf eenv spBind bind (Some startScope)
2168-
2169-
// Work out if we need a sequence point for the body. For any "user" binding then the body gets SPAlways.
2170-
// For invisible compiler-generated bindings we just use "sp", unless its body is another invisible binding
2171-
// For sticky bindings arising from inlining we suppress any immediate sequence point in the body
2172-
let spBody =
2173-
match bind.SequencePointInfo with
2174-
| SequencePointAtBinding _
2175-
| NoSequencePointAtLetBinding
2176-
| NoSequencePointAtDoBinding -> SPAlways
2177-
| NoSequencePointAtInvisibleBinding -> sp
2178-
| NoSequencePointAtStickyBinding -> SPSuppress
2179-
2180-
// Generate the body
2181-
GenExpr cenv cgbuf eenv spBody body (EndLocalScope(sequel, endScope))
2182-
21832198
| Expr.Lambda _ | Expr.TyLambda _ ->
21842199
GenLambda cenv cgbuf eenv false None expr sequel
21852200
| Expr.App (Expr.Val (vref, _, m) as v, _, tyargs, [], _) when
@@ -2200,8 +2215,10 @@ let rec GenExpr (cenv: cenv) (cgbuf: CodeGenBuffer) eenv sp expr sequel =
22002215
// Most generation of linear expressions is implemented routinely using tailcalls and the correct sequels.
22012216
// This is because the element of expansion happens to be the final thing generated in most cases. However
22022217
// for large lists we have to process the linearity separately
2218+
| Expr.Sequential _
2219+
| Expr.Let _
22032220
| LinearOpExpr _ ->
2204-
GenLinearExpr cenv cgbuf eenv expr sequel id |> ignore<FakeUnit>
2221+
GenLinearExpr cenv cgbuf eenv sp expr sequel (* canProcessSequencePoint *) false id |> ignore<FakeUnit>
22052222

22062223
| Expr.Op (op, tyargs, args, m) ->
22072224
match op, args, tyargs with
@@ -2515,16 +2532,63 @@ and GenAllocUnionCase cenv cgbuf eenv (c,tyargs,args,m) sequel =
25152532
GenAllocUnionCaseCore cenv cgbuf eenv (c,tyargs,args.Length,m)
25162533
GenSequel cenv eenv.cloc cgbuf sequel
25172534

2518-
and GenLinearExpr cenv cgbuf eenv expr sequel (contf: FakeUnit -> FakeUnit) =
2519-
match expr with
2520-
| LinearOpExpr (TOp.UnionCase c, tyargs, argsFront, argLast, m) ->
2535+
and GenLinearExpr cenv cgbuf eenv sp expr sequel canProcessSequencePoint (contf: FakeUnit -> FakeUnit) =
2536+
match stripExpr expr with
2537+
| LinearOpExpr (TOp.UnionCase c, tyargs, argsFront, argLast, m) ->
25212538
GenExprs cenv cgbuf eenv argsFront
2522-
GenLinearExpr cenv cgbuf eenv argLast Continue (contf << (fun Fake ->
2539+
GenLinearExpr cenv cgbuf eenv SPSuppress argLast Continue (* canProcessSequencePoint *) true (contf << (fun Fake ->
25232540
GenAllocUnionCaseCore cenv cgbuf eenv (c, tyargs, argsFront.Length + 1, m)
25242541
GenSequel cenv eenv.cloc cgbuf sequel
25252542
Fake))
2543+
2544+
| Expr.Sequential (e1, e2, specialSeqFlag, spSeq, _) ->
2545+
if canProcessSequencePoint then
2546+
ProcessSequencePointForExpr cenv cgbuf sp expr
2547+
2548+
// Compiler generated sequential executions result in suppressions of sequence points on both
2549+
// left and right of the sequence
2550+
let spAction, spExpr =
2551+
(match spSeq with
2552+
| SequencePointsAtSeq -> SPAlways, SPAlways
2553+
| SuppressSequencePointOnExprOfSequential -> SPSuppress, sp
2554+
| SuppressSequencePointOnStmtOfSequential -> sp, SPSuppress)
2555+
match specialSeqFlag with
2556+
| NormalSeq ->
2557+
GenExpr cenv cgbuf eenv spAction e1 discard
2558+
GenLinearExpr cenv cgbuf eenv spExpr e2 sequel (* canProcessSequencePoint *) true contf
2559+
| ThenDoSeq ->
2560+
GenExpr cenv cgbuf eenv spExpr e1 Continue
2561+
GenExpr cenv cgbuf eenv spAction e2 discard
2562+
GenSequel cenv eenv.cloc cgbuf sequel
2563+
contf Fake
2564+
2565+
| Expr.Let (bind, body, _, _) ->
2566+
if canProcessSequencePoint then
2567+
ProcessSequencePointForExpr cenv cgbuf sp expr
2568+
2569+
// This case implemented here to get a guaranteed tailcall
2570+
// Make sure we generate the sequence point outside the scope of the variable
2571+
let startScope, endScope as scopeMarks = StartDelayedLocalScope "let" cgbuf
2572+
let eenv = AllocStorageForBind cenv cgbuf scopeMarks eenv bind
2573+
let spBind = GenSequencePointForBind cenv cgbuf bind
2574+
GenBindingAfterSequencePoint cenv cgbuf eenv spBind bind (Some startScope)
2575+
2576+
// Work out if we need a sequence point for the body. For any "user" binding then the body gets SPAlways.
2577+
// For invisible compiler-generated bindings we just use "sp", unless its body is another invisible binding
2578+
// For sticky bindings arising from inlining we suppress any immediate sequence point in the body
2579+
let spBody =
2580+
match bind.SequencePointInfo with
2581+
| SequencePointAtBinding _
2582+
| NoSequencePointAtLetBinding
2583+
| NoSequencePointAtDoBinding -> SPAlways
2584+
| NoSequencePointAtInvisibleBinding -> sp
2585+
| NoSequencePointAtStickyBinding -> SPSuppress
2586+
2587+
// Generate the body
2588+
GenLinearExpr cenv cgbuf eenv spBody body (EndLocalScope(sequel, endScope)) (* canProcessSequencePoint *) true contf
2589+
25262590
| _ ->
2527-
GenExpr cenv cgbuf eenv SPSuppress expr sequel
2591+
GenExpr cenv cgbuf eenv sp expr sequel
25282592
contf Fake
25292593

25302594
and GenAllocRecd cenv cgbuf eenv ctorInfo (tcref,argtys,args,m) sequel =
@@ -3475,28 +3539,6 @@ and GenWhileLoop cenv cgbuf eenv (spWhile, e1, e2, m) sequel =
34753539
// SEQUENCE POINTS: Emit a sequence point to cover 'done' if present
34763540
GenUnitThenSequel cenv eenv m eenv.cloc cgbuf sequel
34773541

3478-
//--------------------------------------------------------------------------
3479-
// Generate seq
3480-
//--------------------------------------------------------------------------
3481-
3482-
and GenSequential cenv cgbuf eenv spIn (e1, e2, specialSeqFlag, spSeq, _m) sequel =
3483-
3484-
// Compiler generated sequential executions result in suppressions of sequence points on both
3485-
// left and right of the sequence
3486-
let spAction, spExpr =
3487-
(match spSeq with
3488-
| SequencePointsAtSeq -> SPAlways, SPAlways
3489-
| SuppressSequencePointOnExprOfSequential -> SPSuppress, spIn
3490-
| SuppressSequencePointOnStmtOfSequential -> spIn, SPSuppress)
3491-
match specialSeqFlag with
3492-
| NormalSeq ->
3493-
GenExpr cenv cgbuf eenv spAction e1 discard
3494-
GenExpr cenv cgbuf eenv spExpr e2 sequel
3495-
| ThenDoSeq ->
3496-
GenExpr cenv cgbuf eenv spExpr e1 Continue
3497-
GenExpr cenv cgbuf eenv spAction e2 discard
3498-
GenSequel cenv eenv.cloc cgbuf sequel
3499-
35003542
//--------------------------------------------------------------------------
35013543
// Generate IL assembly code.
35023544
// Polymorphic IL/ILX instructions may be instantiated when polymorphic code is inlined.
@@ -5210,7 +5252,14 @@ and GenBindingAfterSequencePoint cenv cgbuf eenv sp (TBind(vspec, rhsExpr, _)) s
52105252
let tps, ctorThisValOpt, baseValOpt, vsl, body', bodyty = IteratedAdjustArityOfLambda g cenv.amap topValInfo rhsExpr
52115253
let methodVars = List.concat vsl
52125254
CommitStartScope cgbuf startScopeMarkOpt
5213-
GenMethodForBinding cenv cgbuf eenv (vspec, mspec, access, paramInfos, retInfo) (topValInfo, ctorThisValOpt, baseValOpt, tps, methodVars, methodArgTys, body', bodyty)
5255+
5256+
let ilxMethInfoArgs =
5257+
(vspec, mspec, access, paramInfos, retInfo, topValInfo, ctorThisValOpt, baseValOpt, tps, methodVars, methodArgTys, body', bodyty)
5258+
// if we have any expression recursion depth, we should delay the generation of a method to prevent stack overflows
5259+
if cenv.exprRecursionDepth > 0 then
5260+
DelayGenMethodForBinding cenv cgbuf.mgbuf eenv ilxMethInfoArgs
5261+
else
5262+
GenMethodForBinding cenv cgbuf.mgbuf eenv ilxMethInfoArgs
52145263

52155264
| StaticProperty (ilGetterMethSpec, optShadowLocal) ->
52165265

@@ -5649,11 +5698,10 @@ and ComputeMethodImplAttribs cenv (_v: Val) attrs =
56495698
let hasAggressiveInliningImplFlag = (implflags &&& 0x0100) <> 0x0
56505699
hasPreserveSigImplFlag, hasSynchronizedImplFlag, hasNoInliningImplFlag, hasAggressiveInliningImplFlag, attrs
56515700

5652-
and GenMethodForBinding
5653-
cenv cgbuf eenv
5654-
(v: Val, mspec, access, paramInfos, retInfo)
5655-
(topValInfo, ctorThisValOpt, baseValOpt, tps, methodVars, methodArgTys, body, returnTy) =
5701+
and DelayGenMethodForBinding cenv mgbuf eenv ilxMethInfoArgs =
5702+
cenv.delayedGenMethods.Enqueue (fun cenv -> GenMethodForBinding cenv mgbuf eenv ilxMethInfoArgs)
56565703

5704+
and GenMethodForBinding cenv mgbuf eenv (v, mspec, access, paramInfos, retInfo, topValInfo, ctorThisValOpt, baseValOpt, tps, methodVars, methodArgTys, body, returnTy) =
56575705
let g = cenv.g
56585706
let m = v.Range
56595707
let selfMethodVars, nonSelfMethodVars, compileAsInstance =
@@ -5714,7 +5762,7 @@ and GenMethodForBinding
57145762
else
57155763
body
57165764

5717-
let ilCode = CodeGenMethodForExpr cenv cgbuf.mgbuf (SPAlways, tailCallInfo, mspec.Name, eenvForMeth, 0, bodyExpr, sequel)
5765+
let ilCode = CodeGenMethodForExpr cenv mgbuf (SPAlways, tailCallInfo, mspec.Name, eenvForMeth, 0, bodyExpr, sequel)
57185766

57195767
// This is the main code generation for most methods
57205768
false, MethodBody.IL ilCode, false
@@ -5780,7 +5828,7 @@ and GenMethodForBinding
57805828
else
57815829
mdef
57825830
CountMethodDef()
5783-
cgbuf.mgbuf.AddMethodDef(tref, mdef)
5831+
mgbuf.AddMethodDef(tref, mdef)
57845832

57855833

57865834
match v.MemberInfo with
@@ -5817,7 +5865,7 @@ and GenMethodForBinding
58175865
let mdef = List.fold (fun mdef f -> f mdef) mdef flagFixups
58185866

58195867
// fixup can potentially change name of reflected definition that was already recorded - patch it if necessary
5820-
cgbuf.mgbuf.ReplaceNameOfReflectedDefinition(v, mdef.Name)
5868+
mgbuf.ReplaceNameOfReflectedDefinition(v, mdef.Name)
58215869
mdef
58225870
else
58235871
mkILGenericNonVirtualMethod (v.CompiledName g.CompilerGlobalState, access, ilMethTypars, ilParams, ilReturn, ilMethodBody)
@@ -5844,7 +5892,7 @@ and GenMethodForBinding
58445892
// Emit the pseudo-property as an event, but not if its a private method impl
58455893
if mdef.Access <> ILMemberAccess.Private then
58465894
let edef = GenEventForProperty cenv eenvForMeth mspec v ilAttrsThatGoOnPrimaryItem m returnTy
5847-
cgbuf.mgbuf.AddEventDef(tref, edef)
5895+
mgbuf.AddEventDef(tref, edef)
58485896
// The method def is dropped on the floor here
58495897

58505898
else
@@ -5854,7 +5902,7 @@ and GenMethodForBinding
58545902
let ilPropTy = GenType cenv.amap m eenvUnderMethTypeTypars.tyenv vtyp
58555903
let ilArgTys = v |> ArgInfosOfPropertyVal g |> List.map fst |> GenTypes cenv.amap m eenvUnderMethTypeTypars.tyenv
58565904
let ilPropDef = GenPropertyForMethodDef compileAsInstance tref mdef v memberInfo ilArgTys ilPropTy (mkILCustomAttrs ilAttrsThatGoOnPrimaryItem) compiledName
5857-
cgbuf.mgbuf.AddOrMergePropertyDef(tref, ilPropDef, m)
5905+
mgbuf.AddOrMergePropertyDef(tref, ilPropDef, m)
58585906

58595907
// Add the special name flag for all properties
58605908
let mdef = mdef.WithSpecialName.With(customAttrs= mkILCustomAttrs ((GenAttrs cenv eenv attrsAppliedToGetterOrSetter) @ sourceNameAttribs @ ilAttrsCompilerGenerated))
@@ -7668,7 +7716,9 @@ type IlxAssemblyGenerator(amap: ImportMap, tcGlobals: TcGlobals, tcVal: Constrai
76687716
casApplied = casApplied
76697717
intraAssemblyInfo = intraAssemblyInfo
76707718
opts = codeGenOpts
7671-
optimizeDuringCodeGen = (fun x -> x) }
7719+
optimizeDuringCodeGen = (fun x -> x)
7720+
exprRecursionDepth = 0
7721+
delayedGenMethods = Queue () }
76727722
GenerateCode (cenv, anonTypeTable, ilxGenEnv, typedAssembly, assemAttribs, moduleAttribs)
76737723

76747724
/// Invert the compilation of the given value and clear the storage of the value

src/fsharp/lib.fs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,3 +536,14 @@ module UnmanagedProcessExecutionOptions =
536536
"HeapSetInformation() returned FALSE; LastError = 0x" +
537537
GetLastError().ToString("X").PadLeft(8, '0') + "."))
538538

539+
[<RequireQualifiedAccess>]
540+
module StackGuard =
541+
542+
open System.Runtime.CompilerServices
543+
544+
[<Literal>]
545+
let private MaxUncheckedRecursionDepth = 20
546+
547+
let EnsureSufficientExecutionStack recursionDepth =
548+
if recursionDepth > MaxUncheckedRecursionDepth then
549+
RuntimeHelpers.EnsureSufficientExecutionStack ()

tests/fsharp/core/members/ops/THIRD-PARTY-NOTICES.TXT

Lines changed: 0 additions & 24 deletions
This file was deleted.

0 commit comments

Comments
 (0)