Skip to content
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

Enable globopt for functions with try finally #2954

Merged
merged 1 commit into from
May 17, 2017

Conversation

meg-gupta
Copy link
Contributor

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)


if (this->catchOffsetStack && !this->catchOffsetStack->Empty())
if ((this->handlerOffsetStack && !this->handlerOffsetStack->Empty()) ||
inFinallyBlock > 0)
Copy link
Contributor

@rajatd rajatd May 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@meg-gupta meg-gupta force-pushed the tryfinallypr branch 4 times, most recently from e292898 to bc15c38 Compare May 12, 2017 03:05
@meg-gupta
Copy link
Contributor Author

@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())))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"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)

@@ -76,6 +208,27 @@ FlowGraph::Build(void)
instrPrev = brOnException; // process BrOnException before adding a new block for loop top label.
continue;
}
if (instr->IsLabelInstr() && instr->AsLabelInstr()->m_isLoopTop &&
this->finallyLabelStack && !this->finallyLabelStack->Empty() &&
instr->m_next->m_opcode != Js::OpCode::BrOnException)
Copy link
Contributor

@rajatd rajatd May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

@rajatd rajatd May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same #Resolved

// Create a new label before LeaveNull
IR::LabelInstr *leaveNullLabel = IR::LabelInstr::New(Js::OpCode::Label, this->func);
instr->InsertBefore(leaveNullLabel);
this->leaveNullLabelStack->Push(leaveNullLabel);
Copy link
Contributor

@rajatd rajatd May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can there be multiple LeaveNulls for a finally? #Resolved

Copy link
Contributor Author

@meg-gupta meg-gupta May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, only one at the end of the finally #Resolved

Copy link
Contributor

@rajatd rajatd May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then it might be worth asserting after the flowgraph is built that the leaveNullLabelStack is null or empty (similar to catchLabelStack and finallyLabelStack) #Resolved

}
bool assignRegionsBeforeGlobopt = this->func->HasTry() && (this->func->DoOptimizeTry() ||
(this->func->IsSimpleJit() && this->func->hasBailout));

FOREACH_BLOCK_ALL(block, this)
{
block->SetBlockNum(blockNum++);
Copy link
Contributor

@rajatd rajatd May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you do this in the following loop? #Resolved

@@ -2824,13 +2824,38 @@ BackwardPass::ProcessBlock(BasicBlock * block)
}
}
}

if (instr->IsLabelInstr() && instr->m_next->m_opcode == Js::OpCode::Finally)
Copy link
Contributor

@rajatd rajatd May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())
Copy link
Contributor

@rajatd rajatd May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Contributor

@rajatd rajatd May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'true' left over? #Resolved

@@ -663,6 +664,7 @@ class Lowerer
IR::Instr* LowerTry(IR::Instr* instr, bool tryCatch);
void EnsureBailoutReturnValueSym();
void EnsureHasBailedOutSym();
Region * GetNonNonExceptingFinallyGrandParent(Region *region);
Copy link
Contributor

@rajatd rajatd May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: double 'non' #Resolved

Copy link
Contributor

@rajatd rajatd May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, this should be on the Region class #Resolved

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And rename to GetFirstNonExceptingAncestor, and refactor code in Lowerer::InsertReturnThunkForRegion as discussed offline

@@ -24604,7 +24660,37 @@ Lowerer::InsertReturnThunkForRegion(Region* region, IR::LabelInstr* restoreLabel
}

IR::LabelOpnd * continuationAddr;
if (region->GetParent()->GetType() != RegionTypeRoot)
if (region->IsNonExceptingFinally())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add lots of comments and asserts to explain the mechanics here and to enforce your invariants

@@ -238,6 +280,53 @@ namespace Js
{
// Clone static exception object early in case finally block overwrites it
exception = exception->CloneIfStaticExceptionObject(scriptContext);

Copy link
Contributor

@rajatd rajatd May 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not understand this condition

// TryFinally L1
// <try code>
// BrOnException L1
// Leave L1'
Copy link
Contributor

@rajatd rajatd May 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

@rajatd rajatd May 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please call it instrNext #Resolved

branchTargetRegion != currentRegion->GetMatchingFinallyRegion(true));
if (!isEarly) return false;

if (DoesExitLabelDominate(leaveInstr)) return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could probably cache the result of this call before region condition checks

@rajatd
Copy link
Contributor

rajatd commented May 16, 2017

Looked at most of the changes and gave some comments. Flowgraph changes are definitely complicated. Looks good overall.

@meg-gupta meg-gupta force-pushed the tryfinallypr branch 3 times, most recently from 9f6f38b to 020b17f Compare May 16, 2017 19:41
@meg-gupta
Copy link
Contributor Author

@dotnet-bot test Ubuntu shared_ubuntu_linux_test
@dotnet-bot test Ubuntu shared_ubuntu_linux_debug
@dotnet-bot test OSX static_osx_osx_debug

@rajatd
Copy link
Contributor

rajatd commented May 17, 2017

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)
@chakrabot chakrabot merged commit 82ff317 into chakra-core:master May 17, 2017
chakrabot pushed a commit that referenced this pull request May 17, 2017
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants