Enable globopt for functions with try finally - #2954
Conversation
67a652d to
ea97909
Compare
|
|
||
| if (this->catchOffsetStack && !this->catchOffsetStack->Empty()) | ||
| if ((this->handlerOffsetStack && !this->handlerOffsetStack->Empty()) || | ||
| inFinallyBlock > 0) |
There was a problem hiding this comment.
inFinallyBlock [](start = 16, length = 14)
inFinallyBlock [](start = 16, length = 14)
nit: for better readability, change name to finallyBlockLevel or something or have an API InFinallyBlock. #Resolved
e292898 to
bc15c38
Compare
|
@dotnet-bot test OSX static_osx_osx_debug |
| this->catchLabelStack = JitAnew(this->alloc, SList<IR::LabelInstr*>, this->alloc); | ||
| } | ||
| if (this->func->HasFinally() && (this->func->DoOptimizeTryFinally() || | ||
| (this->func->IsSimpleJit() && this->func->GetJITFunctionBody()->DoJITLoopBody()))) |
There was a problem hiding this comment.
this->func->IsSimpleJit() && this->func->GetJITFunctionBody()->DoJITLoopBody()))) [](start = 9, length = 81)
Take this condition out and check for DoOptimizeTryCatch and DoOptimizeTryFinally inside it. Actually, this makes me think - we can't turn off the opitimizer for try-catch and have it on for try-finally, or vice-versa, right?
There was a problem hiding this comment.
Oh, I was looking at the 2nd iteration - second issue seems fixed in 3rd iteration :). We should still hoist the common conditions though
In reply to: 116533348 [](ancestors = 116533348)
There was a problem hiding this comment.
Will do. thanks. We can turn off try finally opt without affecting try catch opt, but if we turn off try catch opt, try finally is turned off too.
There was a problem hiding this comment.
"We can turn off try finally opt without affecting try catch opt" - how? If we're not optimizing try-finally but are try-catch, and the trycatch is inside try-finally, wouldn't that be a problem?
In reply to: 116535802 [](ancestors = 116535802)
| } | ||
| if (instr->IsLabelInstr() && instr->AsLabelInstr()->m_isLoopTop && | ||
| this->finallyLabelStack && !this->finallyLabelStack->Empty() && | ||
| instr->m_next->m_opcode != Js::OpCode::BrOnException) |
There was a problem hiding this comment.
hoist the common conditions #Resolved
| // LeaveNull is needed to get the end of the finally block, this is important when adding edge from the finally block to the early exit block | ||
| if (instr->IsLabelInstr() && instr->AsLabelInstr()->m_isLoopTop && | ||
| this->leaveNullLabelStack && !this->leaveNullLabelStack->Empty() && | ||
| instr->m_next->m_opcode != Js::OpCode::BrOnException) |
| // Create a new label before LeaveNull | ||
| IR::LabelInstr *leaveNullLabel = IR::LabelInstr::New(Js::OpCode::Label, this->func); | ||
| instr->InsertBefore(leaveNullLabel); | ||
| this->leaveNullLabelStack->Push(leaveNullLabel); |
There was a problem hiding this comment.
can there be multiple LeaveNulls for a finally? #Resolved
There was a problem hiding this comment.
No, only one at the end of the finally #Resolved
There was a problem hiding this comment.
then it might be worth asserting after the flowgraph is built that the leaveNullLabelStack is null or empty (similar to catchLabelStack and finallyLabelStack) #Resolved
|
|
||
| FOREACH_BLOCK_ALL(block, this) | ||
| { | ||
| block->SetBlockNum(blockNum++); |
There was a problem hiding this comment.
can you do this in the following loop? #Resolved
| } | ||
| } | ||
|
|
||
| if (instr->IsLabelInstr() && instr->m_next->m_opcode == Js::OpCode::Finally) |
There was a problem hiding this comment.
Most of the code in this block is the same as in the previous one. Please refactor into one if block. #Resolved
| ProcessTryCatch(instr); | ||
| ProcessTryCatchOrTryFinally(instr); | ||
| } | ||
| else if (instr->m_opcode == Js::OpCode::TryFinally && this->func->DoOptimizeTry()) |
There was a problem hiding this comment.
Please merge into the if block above with doing an || of the opcodes condition #Resolved
| Region *targetRegion = instr->AsBranchInstr()->GetTarget()->GetRegion(); | ||
| Assert((targetRegion->GetType() == RegionType::RegionTypeFinally && | ||
| targetRegion->GetMatchingTryRegion()->GetMatchingFinallyRegion(true) == targetRegion) || | ||
| true); |
There was a problem hiding this comment.
'true' left over? #Resolved
| IR::Instr* LowerTry(IR::Instr* instr, bool tryCatch); | ||
| void EnsureBailoutReturnValueSym(); | ||
| void EnsureHasBailedOutSym(); | ||
| Region * GetNonNonExceptingFinallyGrandParent(Region *region); |
There was a problem hiding this comment.
nit: double 'non' #Resolved
There was a problem hiding this comment.
also, this should be on the Region class #Resolved
There was a problem hiding this comment.
And rename to GetFirstNonExceptingAncestor, and refactor code in Lowerer::InsertReturnThunkForRegion as discussed offline
|
|
||
| IR::LabelOpnd * continuationAddr; | ||
| if (region->GetParent()->GetType() != RegionTypeRoot) | ||
| if (region->IsNonExceptingFinally()) |
There was a problem hiding this comment.
Add lots of comments and asserts to explain the mechanics here and to enforce your invariants
| exception = err.GetAndClear(); | ||
| } | ||
|
|
||
| if (exception) |
There was a problem hiding this comment.
you need to check for hasBailedOutOffset in this function and also generate code in LowerMD.cpp to pass the param to this helper #Resolved
| BasicBlock * nextBlock = nextLabel->GetBasicBlock(); | ||
| IR::BranchInstr * branchTofinallyBlockOrEarlyExit = nextLabel->m_next->AsBranchInstr(); | ||
| IR::LabelInstr * finallyBlockLabelOrEarlyExitLabel = branchTofinallyBlockOrEarlyExit->GetTarget(); | ||
| if (finallyBlockLabelOrEarlyExitLabel->GetRegion() == this->currentRegion) |
There was a problem hiding this comment.
Did not understand this condition
| // TryFinally L1 | ||
| // <try code> | ||
| // BrOnException L1 | ||
| // Leave L1' |
There was a problem hiding this comment.
It appears to me that L1' is really L2 from before the transformation. If so, lets keep L2 only after the transformation. Makes understanding the code below easier #Resolved
|
|
||
| while (leaveChain->m_opcode != Js::OpCode::Br) | ||
| { | ||
| IR::LabelInstr * nextLabel = leaveChain->m_next->AsLabelInstr(); |
There was a problem hiding this comment.
leaveChain->m_next guaranteed to always be a labelInstr?
|
|
||
| IR::LabelInstr * currentLabel = nullptr; | ||
| // look for early exits from a try, and insert bailout | ||
| FOREACH_INSTR_IN_FUNC_EDITING(instr, instrPrev, func) |
There was a problem hiding this comment.
please call it instrNext #Resolved
| branchTargetRegion != currentRegion->GetMatchingFinallyRegion(true)); | ||
| if (!isEarly) return false; | ||
|
|
||
| if (DoesExitLabelDominate(leaveInstr)) return false; |
There was a problem hiding this comment.
we could probably cache the result of this call before region condition checks
|
Looked at most of the changes and gave some comments. Flowgraph changes are definitely complicated. Looks good overall. |
9f6f38b to
020b17f
Compare
|
@dotnet-bot test Ubuntu shared_ubuntu_linux_test |
|
Oh, and yes, add asserts for opcodes, for example, at places you are looping while the instruction is not a Br, and it seems that you expect the instr to be Leave. |
This change enables globopt on functions with try finally. We transform the flowgraph such that we have 2 flow edges - try to a excepting finally region and try to non excepting finally region. This enables us to optimize the function on the non exception path. We bailout on the exception path. Special handling is needed when there are early exits (break, continue, return) within the tryfinally. We need to execute the finally block on early exit, currently we bailout on early exit. We transform the flow graph from (eh region -> early exit) to have an edge from (eh region -> finally) and (finally -> early exit) This transformation can be done only after the regions are assigned in FlowGraph. So the flowgraph builder now has the following order of phase: - build flow graph -> add the excepting and non excepting finallys alongside - remove unreachable blocks - assign regions - identify early exits and add edges (does region info update when required) - break blocks removal (does region info update when required)
Merge pull request #2954 from meg-gupta:tryfinallypr This change enables globopt on functions with try finally. We transform the flowgraph such that we have 2 flow edges - try to a excepting finally region and try to non excepting finally region. This enables us to optimize the function on the non exception path. We bailout on the exception path. Special handling is needed when there are early exits (break, continue, return) within the tryfinally. We need to execute the finally block on early exit, currently we bailout on early exit. We transform the flow graph from (eh region -> early exit) to have an edge from (eh region -> finally) and (finally -> early exit) This transformation can be done only after the regions are assigned in FlowGraph. So the flowgraph builder now has the following order of phase: - build flow graph -> add the excepting and non excepting finallys alongside - remove unreachable blocks - assign regions - identify early exits and add edges (does region info update when required) - break blocks removal (does region info update when required)
This change enables globopt on functions with try finally.
We transform the flowgraph such that we have 2 flow edges -
try to a excepting finally region and try to non excepting finally region.
This enables us to optimize the function on the non exception path.
We bailout on the exception path.
Special handling is needed when there are early exits (break, continue, return) within the tryfinally.
We need to execute the finally block on early exit, currently we bailout on early exit.
We transform the flow graph from (eh region -> early exit) to have an edge from (eh region -> finally) and (finally -> early exit)
This transformation can be done only after the regions are assigned in FlowGraph.
So the flowgraph builder now has the following order of phase: