-
Notifications
You must be signed in to change notification settings - Fork 790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
More stack overflow fixes #7678
More stack overflow fixes #7678
Conversation
83bc428
to
0a6764a
Compare
I've taken the approach of linearizing Expr.Match as a whole in IlxGen by using a queue and continuations. This is quite different from how Expr.Match is handled in other parts of the compiler, so we'll see how it works out. This could technically increase allocations when compiling to an assembly, but it may not be as bad as we think. If this approach fails, the fallback is to mitigate the original user issue is: split non-user defined methods even when optimizations are turned off. Though, we will still have an issue with large structs as their generated methods do not get split due to byref free vars. |
src/fsharp/IlxGen.fs
Outdated
// Since code is branching and joining, the cgbuf stack is maintained manually. | ||
let targetQueue = GenDecisionTreeAndTargets cenv cgbuf stackAtTargets eenv tree targets repeatSP sequelOnBranches | ||
|
||
let rec popTargetQueue () = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should add a comment describing why we are doing this.
Ok, CI passed. Surprising, also great. |
src/fsharp/IlxGen.fs
Outdated
CountClosure() | ||
for cloTypeDef in cloTypeDefs do | ||
cgbuf.mgbuf.AddTypeDef(ilCloTypeRef, cloTypeDef, false, false, None) | ||
GenMethodForLambda cenv cgbuf.mgbuf eenv (entryPointInfo, cloinfo, eenvinner, body, isLocalTypeFunc, m) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a refactoring. Will be useful if we want to delay generation of methods from lambdas.
@dsyme could you take a look at this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll need to look at this really, really carefully in daylight, but I've left a few initial comments.
src/fsharp/IlxGen.fs
Outdated
let targetInfos = GenDecisionTreeAndTargetsInner cenv cgbuf None stackAtTargets eenv tree targets repeatSP (IntMap.empty()) sequel | ||
GenPostponedDecisionTreeTargets cenv cgbuf stackAtTargets targetInfos sequel | ||
and GenDecisionTreeAndTargets cenv cgbuf stackAtTargets eenv tree targets repeatSP sequel : (Queue<unit -> (IlxGenEnv * EmitSequencePointState * Expr * sequel) option>) = | ||
let rec genDecisions targetInfos decisions (queue: Queue<_>) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't use any inner recursive functions in IlxGen.fs. They are always suspect w.r.t. readability, and IlxGen.fs is written without their use because there are enough side effects going on that it's plainer and simpler to do without them. Take it to the module scope and call it GenDecisionTreeAndQueuedTargets
or something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine, I can do that.
src/fsharp/IlxGen.fs
Outdated
|> Seq.filter (fun (KeyValue(_, (_, isTargetPostponed))) -> isTargetPostponed) | ||
|> List.ofSeq | ||
|
||
let rec genRemaining remaining (queue: Queue<_>) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise here - let rec
inside let rec
inside let
is just too much...
src/fsharp/IlxGen.fs
Outdated
GenDecisionTreeAndTargetsInner cenv cgbuf (Some caseLabel) stackAtTargets eenv caseTree targets repeatSP targetInfos sequel) | ||
targetInfos | ||
(decisions, caseLabels, cases) | ||
|||> List.fold2 (fun decisions caseLabel (TCase(_, caseTree)) -> decisions @ [(Some caseLabel, eenv, caseTree)]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This tail @
looks suspect (it is quadratic if decisions
is long)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I can see that. Actually, I should have just used List.map
here, and would append the 1 default decision right before this. I think that would be fine.
src/fsharp/IlxGen.fs
Outdated
@@ -5150,7 +5196,7 @@ and GenLetRecFixup cenv cgbuf eenv (ilxCloSpec: IlxClosureSpec, e, ilField: ILFi | |||
|
|||
/// Generate letrec bindings | |||
and GenLetRecBindings cenv (cgbuf: CodeGenBuffer) eenv (allBinds: Bindings, m) = | |||
let eenv = SetIsInLoop true eenv |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you undo the renaming here to minimize the diff please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that's fair. I'll do the same to the other changes.
src/fsharp/IlxGen.fs
Outdated
@@ -7535,7 +7581,7 @@ let GetEmptyIlxGenEnv (ilg: ILGlobals) ccu = | |||
innerVals = [] | |||
sigToImplRemapInfo = [] (* "module remap info" *) | |||
withinSEH = false | |||
isInLoop = false } | |||
inLoop = false } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise undo this renaming to minimize the diff
test997: string | ||
test998: string | ||
test999: string | ||
test1000: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
big :)
Thank you @dsyme . I made more adjustments. I also realized that I didn't need to use a queue... I could just keep using CPS on the decision tree functions. |
31ba8b1
to
5b9126e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work, much cleaner without the explicit stack!
To resolve this: #7673
What I'm curious about is the fact that the generated equality/comparison methods on the newly added tests are not broken apart into multiple methods. Effectively, these methods become very very large which probably isn't a good thing.
The initial fix addresses part of the issue with linear pattern matching combined with let bindings, but it still isn't enough. If these methods were broken apart, then the fix wouldn't need to be in IlxGen. But, this fix also breaks other stuff, so it isn't complete unfortunately.
Instead of trying to do this in IlxGen, we should break apart methods, which that is dictated by the optimizer I believe.
In regards to this specific issue, #7673, it can technically be resolved if we took out the stack guard. The stack guard will throw an exception earlier than an actual stack overflow, so the record type just happened to be in the sweet spot where the guard would throw but an overflow still wouldn't occur.