| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| #include "Backend.h" |
| |
| FlowGraph * |
| FlowGraph::New(Func * func, JitArenaAllocator * alloc) |
| { |
| FlowGraph * graph; |
| |
| graph = JitAnew(alloc, FlowGraph, func, alloc); |
| |
| return graph; |
| } |
| |
| |
| // When there is an early exit within an EH region, |
| // Leave instructions are inserted in the bytecode to jump up the region tree one by one |
| // We delete this Leave chain of instructions, and add an edge to Finally |
| IR::LabelInstr * FlowGraph::DeleteLeaveChainBlocks(IR::BranchInstr *leaveInstr, IR::Instr * &instrPrev) |
| { |
| // Cleanup Rest of the Leave chain |
| IR::LabelInstr * leaveTarget = leaveInstr->GetTarget(); |
| Assert(leaveTarget->GetNextRealInstr()->IsBranchInstr()); |
| IR::BranchInstr *leaveChain = leaveTarget->GetNextRealInstr()->AsBranchInstr(); |
| IR::LabelInstr * curLabel = leaveTarget->AsLabelInstr(); |
| |
| while (leaveChain->m_opcode != Js::OpCode::Br) |
| { |
| Assert(leaveChain->m_opcode == Js::OpCode::Leave || leaveChain->m_opcode == Js::OpCode::BrOnException); |
| IR::LabelInstr * nextLabel = leaveChain->m_next->AsLabelInstr(); |
| leaveChain = nextLabel->m_next->AsBranchInstr(); |
| BasicBlock *curBlock = curLabel->GetBasicBlock(); |
| FOREACH_SLISTBASECOUNTED_ENTRY_EDITING(FlowEdge*, edge, curBlock->GetPredList(), iter1) |
| { |
| BasicBlock * pred = edge->GetPred(); |
| pred->RemoveSucc(curBlock, this); |
| } NEXT_SLISTBASECOUNTED_ENTRY_EDITING; |
| this->RemoveBlock(curBlock); |
| curLabel = nextLabel; |
| } |
| |
| instrPrev = leaveChain->m_next; |
| IR::LabelInstr * exitLabel = leaveChain->GetTarget(); |
| BasicBlock * curBlock = curLabel->GetBasicBlock(); |
| FOREACH_SLISTBASECOUNTED_ENTRY_EDITING(FlowEdge*, edge, curBlock->GetPredList(), iter1) |
| { |
| BasicBlock * pred = edge->GetPred(); |
| pred->RemoveSucc(curBlock, this); |
| } NEXT_SLISTBASECOUNTED_ENTRY_EDITING; |
| this->RemoveBlock(curBlock); |
| return exitLabel; |
| } |
| |
| bool FlowGraph::Dominates(Region *region1, Region *region2) |
| { |
| Assert(region1); |
| Assert(region2); |
| Region *startR1 = region1; |
| Region *startR2 = region2; |
| int region1Depth = 0; |
| while (startR1 != nullptr) |
| { |
| region1Depth++; |
| startR1 = startR1->GetParent(); |
| } |
| int region2Depth = 0; |
| while (startR2 != nullptr) |
| { |
| region2Depth++; |
| startR2 = startR2->GetParent(); |
| } |
| return region1Depth > region2Depth; |
| } |
| |
| bool FlowGraph::DoesExitLabelDominate(IR::BranchInstr *leaveInstr) |
| { |
| IR::LabelInstr * leaveTarget = leaveInstr->GetTarget(); |
| Assert(leaveTarget->GetNextRealInstr()->IsBranchInstr()); |
| IR::BranchInstr *leaveChain = leaveTarget->GetNextRealInstr()->AsBranchInstr(); |
| |
| while (leaveChain->m_opcode != Js::OpCode::Br) |
| { |
| Assert(leaveChain->m_opcode == Js::OpCode::Leave || leaveChain->m_opcode == Js::OpCode::BrOnException); |
| IR::LabelInstr * nextLabel = leaveChain->m_next->AsLabelInstr(); |
| leaveChain = nextLabel->m_next->AsBranchInstr(); |
| } |
| IR::LabelInstr * exitLabel = leaveChain->GetTarget(); |
| return Dominates(exitLabel->GetRegion(), finallyLabelStack->Top()->GetRegion()); |
| } |
| |
| bool FlowGraph::IsEarlyExitFromFinally(IR::BranchInstr *leaveInstr, Region *currentRegion, Region *branchTargetRegion, IR::Instr * &instrPrev, IR::LabelInstr * &exitLabel) |
| { |
| if (finallyLabelStack->Empty()) |
| { |
| return false; |
| } |
| |
| if (currentRegion->GetType() == RegionTypeTry) |
| { |
| if (currentRegion->GetMatchingCatchRegion() == nullptr) |
| { |
| // try of try-finally |
| bool isEarly = |
| (branchTargetRegion != currentRegion->GetMatchingFinallyRegion(false) && |
| branchTargetRegion != currentRegion->GetMatchingFinallyRegion(true)); |
| if (!isEarly) return false; |
| |
| if (DoesExitLabelDominate(leaveInstr)) return false; |
| |
| // Cleanup Rest of the Leave chain |
| exitLabel = DeleteLeaveChainBlocks(leaveInstr, instrPrev); |
| return true; |
| } |
| // try of try-catch |
| |
| IR::BranchInstr *leaveChain = leaveInstr; |
| IR::LabelInstr * leaveTarget = leaveChain->GetTarget(); |
| IR::Instr *target = leaveTarget->GetNextRealInstr(); |
| if (target->m_opcode == Js::OpCode::Br) |
| { |
| IR::BranchInstr *tryExit = target->AsBranchInstr(); |
| instrPrev = tryExit; |
| return false; |
| } |
| |
| if (DoesExitLabelDominate(leaveInstr)) return false; |
| |
| // Cleanup Rest of the Leave chain |
| exitLabel = DeleteLeaveChainBlocks(leaveInstr, instrPrev); |
| return true; |
| } |
| if (currentRegion->GetType() == RegionTypeCatch) |
| { |
| // We don't care for early exits in catch blocks, because we bailout anyway |
| return false; |
| } |
| Assert(currentRegion->GetType() == RegionTypeFinally); |
| // All Leave's inside Finally region are early exits |
| // Since we execute non-excepting Finallys in JIT now, we should convert Leave to Br |
| if (DoesExitLabelDominate(leaveInstr)) return false; |
| exitLabel = DeleteLeaveChainBlocks(leaveInstr, instrPrev); |
| return true; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// FlowGraph::Build |
| /// |
| /// Construct flow graph and loop structures for the current state of the function. |
| /// |
| ///---------------------------------------------------------------------------- |
| void |
| FlowGraph::Build(void) |
| { |
| Func * func = this->func; |
| BEGIN_CODEGEN_PHASE(func, Js::FGPeepsPhase); |
| this->RunPeeps(); |
| END_CODEGEN_PHASE(func, Js::FGPeepsPhase); |
| |
| // We don't optimize fully with SimpleJit. But, when JIT loop body is enabled, we do support |
| // bailing out from a simple jitted function to do a full jit of a loop body in the function |
| // (BailOnSimpleJitToFullJitLoopBody). For that purpose, we need the flow from try to handler. |
| if (this->func->HasTry() && (this->func->DoOptimizeTry() || |
| (this->func->IsSimpleJit() && this->func->GetJITFunctionBody()->DoJITLoopBody()))) |
| { |
| this->catchLabelStack = JitAnew(this->alloc, SList<IR::LabelInstr*>, this->alloc); |
| } |
| if (this->func->HasFinally() && (this->func->DoOptimizeTry() || |
| (this->func->IsSimpleJit() && this->func->GetJITFunctionBody()->DoJITLoopBody()))) |
| { |
| this->finallyLabelStack = JitAnew(this->alloc, SList<IR::LabelInstr*>, this->alloc); |
| this->leaveNullLabelStack = JitAnew(this->alloc, SList<IR::LabelInstr*>, this->alloc); |
| this->regToFinallyEndMap = JitAnew(this->alloc, RegionToFinallyEndMapType, this->alloc, 0); |
| } |
| |
| IR::Instr * currLastInstr = nullptr; |
| BasicBlock * currBlock = nullptr; |
| BasicBlock * nextBlock = nullptr; |
| bool hasCall = false; |
| |
| FOREACH_INSTR_IN_FUNC_BACKWARD_EDITING(instr, instrPrev, func) |
| { |
| if (currLastInstr == nullptr || instr->EndsBasicBlock()) |
| { |
| // Start working on a new block. |
| // If we're currently processing a block, then wrap it up before beginning a new one. |
| if (currLastInstr != nullptr) |
| { |
| nextBlock = currBlock; |
| currBlock = this->AddBlock(instr->m_next, currLastInstr, nextBlock); |
| currBlock->hasCall = hasCall; |
| hasCall = false; |
| } |
| |
| currLastInstr = instr; |
| } |
| |
| if (instr->StartsBasicBlock()) |
| { |
| // Insert a BrOnException after the loop top if we are in a try-catch/try-finally. This is required to |
| // model flow from the loop to the catch/finally block for loops that don't have a break condition. |
| if (instr->IsLabelInstr() && instr->AsLabelInstr()->m_isLoopTop && instr->m_next->m_opcode != Js::OpCode::BrOnException) |
| { |
| IR::BranchInstr * brOnException = nullptr; |
| IR::Instr *instrNext = instr->m_next; |
| if (this->catchLabelStack && !this->catchLabelStack->Empty()) |
| { |
| brOnException = IR::BranchInstr::New(Js::OpCode::BrOnException, this->catchLabelStack->Top(), instr->m_func); |
| instr->InsertAfter(brOnException); |
| } |
| if (this->finallyLabelStack && !this->finallyLabelStack->Empty()) |
| { |
| brOnException = IR::BranchInstr::New(Js::OpCode::BrOnException, this->finallyLabelStack->Top(), instr->m_func); |
| instr->InsertAfter(brOnException); |
| } |
| // Insert a BrOnException after the loop top if we are in a finally block. This is required so |
| // that LeaveNull is not eliminated in the presence of loops with no exit condition |
| // 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 (this->leaveNullLabelStack && !this->leaveNullLabelStack->Empty()) |
| { |
| brOnException = IR::BranchInstr::New(Js::OpCode::BrOnException, this->leaveNullLabelStack->Top(), instr->m_func); |
| instr->InsertAfter(brOnException); |
| } |
| if (brOnException) |
| { |
| instrPrev = instrNext->m_prev; |
| continue; |
| } |
| } |
| |
| // Wrap up the current block and get ready to process a new one. |
| nextBlock = currBlock; |
| currBlock = this->AddBlock(instr, currLastInstr, nextBlock); |
| currBlock->hasCall = hasCall; |
| hasCall = false; |
| currLastInstr = nullptr; |
| } |
| |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::Catch: |
| Assert(instr->m_prev->IsLabelInstr()); |
| if (this->catchLabelStack) |
| { |
| this->catchLabelStack->Push(instr->m_prev->AsLabelInstr()); |
| } |
| break; |
| |
| case Js::OpCode::Finally: |
| { |
| if (!this->finallyLabelStack) |
| { |
| break; |
| } |
| |
| //To enable globopt on functions with try finallys we transform the flowgraph as below : |
| // TryFinally L1 |
| // <try code> |
| // Leave L2 |
| // L2 : Br L3 |
| // L1 : <finally code> |
| // LeaveNull |
| // L3 : <code after try finally> |
| // |
| //to: |
| // |
| // TryFinally L1 |
| // <try code> |
| // BrOnException L1 |
| // Leave L1' |
| // L1 : BailOnException |
| // L2 : Finally |
| // <finally code> |
| // LeaveNull |
| // L3: <code after try finally> |
| //We generate 2 flow edges from the try - an exception path and a non exception path. |
| //This transformation enables us to optimize on the non-excepting finally path |
| |
| Assert(instr->m_prev->IsLabelInstr()); |
| |
| IR::LabelInstr * finallyLabel = instr->m_prev->AsLabelInstr(); |
| |
| // Find leave label |
| |
| Assert(finallyLabel->m_prev->m_opcode == Js::OpCode::Br && finallyLabel->m_prev->m_prev->m_opcode == Js::OpCode::Label); |
| |
| IR::Instr * insertPoint = finallyLabel->m_prev; |
| IR::LabelInstr * leaveTarget = finallyLabel->m_prev->m_prev->AsLabelInstr(); |
| |
| leaveTarget->Unlink(); |
| finallyLabel->InsertBefore(leaveTarget); |
| finallyLabel->Unlink(); |
| insertPoint->InsertBefore(finallyLabel); |
| |
| // Bailout from the opcode following Finally |
| IR::Instr * bailOnException = IR::BailOutInstr::New(Js::OpCode::BailOnException, IR::BailOutOnException, instr->m_next, instr->m_func); |
| insertPoint->InsertBefore(bailOnException); |
| insertPoint->Remove(); |
| |
| this->finallyLabelStack->Push(finallyLabel); |
| Assert(!this->leaveNullLabelStack->Empty()); |
| this->leaveNullLabelStack->Pop(); |
| |
| Assert(leaveTarget->labelRefs.HasOne()); |
| IR::BranchInstr * brOnException = IR::BranchInstr::New(Js::OpCode::BrOnException, finallyLabel, instr->m_func); |
| leaveTarget->labelRefs.Head()->InsertBefore(brOnException); |
| |
| instrPrev = instr->m_prev; |
| } |
| break; |
| |
| case Js::OpCode::TryCatch: |
| if (this->catchLabelStack) |
| { |
| this->catchLabelStack->Pop(); |
| } |
| break; |
| |
| case Js::OpCode::TryFinally: |
| if (this->finallyLabelStack) |
| { |
| this->finallyLabelStack->Pop(); |
| } |
| break; |
| case Js::OpCode::LeaveNull: |
| { |
| if (!this->leaveNullLabelStack) |
| { |
| break; |
| } |
| IR::Instr * label = instr->GetPrevRealInstrOrLabel(); |
| if (label->IsLabelInstr()) |
| { |
| this->leaveNullLabelStack->Push(label->AsLabelInstr()); |
| break; |
| } |
| // Create a new label before LeaveNull |
| IR::LabelInstr *leaveNullLabel = IR::LabelInstr::New(Js::OpCode::Label, this->func); |
| instr->InsertBefore(leaveNullLabel); |
| this->leaveNullLabelStack->Push(leaveNullLabel); |
| leaveNullLabel->SetByteCodeOffset(instr); |
| |
| // Wrap up the current block and get ready to process a new one. |
| nextBlock = currBlock; |
| currBlock = this->AddBlock(leaveNullLabel, instr, nextBlock); |
| currBlock->hasCall = hasCall; |
| hasCall = false; |
| currLastInstr = nullptr; |
| } |
| break; |
| |
| case Js::OpCode::CloneBlockScope: |
| case Js::OpCode::CloneInnerScopeSlots: |
| // It would be nice to do this in IRBuilder, but doing so gives us |
| // trouble when doing the DoSlotArrayCheck since it assume single def |
| // of the sym to do its check properly. So instead we assign the dst |
| // here in FlowGraph. |
| instr->SetDst(instr->GetSrc1()); |
| break; |
| } |
| |
| if (OpCodeAttr::UseAllFields(instr->m_opcode)) |
| { |
| // UseAllFields opcode are call instruction or opcode that would call. |
| hasCall = true; |
| |
| if (OpCodeAttr::CallInstr(instr->m_opcode)) |
| { |
| if (!instr->isCallInstrProtectedByNoProfileBailout) |
| { |
| instr->m_func->SetHasCallsOnSelfAndParents(); |
| } |
| |
| // For ARM & X64 because of their register calling convention |
| // the ArgOuts need to be moved next to the call. |
| #if defined(_M_ARM) || defined(_M_X64) |
| |
| IR::Instr* argInsertInstr = instr; |
| instr->IterateArgInstrs([&](IR::Instr* argInstr) |
| { |
| if (argInstr->m_opcode != Js::OpCode::LdSpreadIndices && |
| argInstr->m_opcode != Js::OpCode::ArgOut_A_Dynamic && |
| argInstr->m_opcode != Js::OpCode::ArgOut_A_FromStackArgs && |
| argInstr->m_opcode != Js::OpCode::ArgOut_A_SpreadArg) |
| { |
| // don't have bailout in asm.js so we don't need BytecodeArgOutCapture |
| if (!argInstr->m_func->GetJITFunctionBody()->IsAsmJsMode()) |
| { |
| // Need to always generate byte code arg out capture, |
| // because bailout can't restore from the arg out as it is |
| // replaced by new sym for register calling convention in lower |
| argInstr->GenerateBytecodeArgOutCapture(); |
| } |
| // Check if the instruction is already next |
| if (argInstr != argInsertInstr->m_prev) |
| { |
| // It is not, move it. |
| argInstr->Move(argInsertInstr); |
| } |
| argInsertInstr = argInstr; |
| } |
| return false; |
| }); |
| #endif |
| } |
| } |
| } |
| NEXT_INSTR_IN_FUNC_BACKWARD_EDITING; |
| this->func->isFlowGraphValid = true; |
| Assert(!this->catchLabelStack || this->catchLabelStack->Empty()); |
| Assert(!this->finallyLabelStack || this->finallyLabelStack->Empty()); |
| Assert(!this->leaveNullLabelStack || this->leaveNullLabelStack->Empty()); |
| |
| // We've been walking backward so that edge lists would be in the right order. Now walk the blocks |
| // forward to number the blocks in lexical order. |
| unsigned int blockNum = 0; |
| FOREACH_BLOCK(block, this) |
| { |
| block->SetBlockNum(blockNum++); |
| }NEXT_BLOCK; |
| AssertMsg(blockNum == this->blockCount, "Block count is out of whack"); |
| |
| this->RemoveUnreachableBlocks(); |
| |
| // Regions need to be assigned before Globopt because: |
| // 1. FullJit: The Backward Pass will set the write-through symbols on the regions and the forward pass will |
| // use this information to insert ToVars for those symbols. Also, for a symbol determined as write-through |
| // in the try region to be restored correctly by the bailout, it should not be removed from the |
| // byteCodeUpwardExposedUsed upon a def in the try region (the def might be preempted by an exception). |
| // |
| // 2. SimpleJit: Same case of correct restoration as above applies in SimpleJit too. However, the only bailout |
| // we have in Simple Jitted code right now is BailOnSimpleJitToFullJitLoopBody, installed in IRBuilder. So, |
| // for now, we can just check if the func has a bailout to assign regions pre globopt while running SimpleJit. |
| |
| bool assignRegionsBeforeGlobopt = this->func->HasTry() && (this->func->DoOptimizeTry() || |
| (this->func->IsSimpleJit() && this->func->hasBailout)); |
| |
| blockNum = 0; |
| FOREACH_BLOCK_ALL(block, this) |
| { |
| block->SetBlockNum(blockNum++); |
| if (assignRegionsBeforeGlobopt) |
| { |
| if (block->isDeleted && !block->isDead) |
| { |
| continue; |
| } |
| this->UpdateRegionForBlock(block); |
| } |
| } NEXT_BLOCK_ALL; |
| AssertMsg(blockNum == this->blockCount, "Block count is out of whack"); |
| |
| if (this->finallyLabelStack) |
| { |
| Assert(this->finallyLabelStack->Empty()); |
| |
| // Add s0 definition at the beginning of the function |
| // We need this because - s0 symbol can get added to bytcodeUpwardExposed use when there are early returns, |
| // And globopt will complain that s0 is uninitialized, because we define it to undefined only at the end of the function |
| const auto addrOpnd = IR::AddrOpnd::New(this->func->GetScriptContextInfo()->GetUndefinedAddr(), IR::AddrOpndKindDynamicVar, this->func, true); |
| addrOpnd->SetValueType(ValueType::Undefined); |
| IR::RegOpnd *regOpnd = IR::RegOpnd::New(this->func->m_symTable->FindStackSym(0), TyVar, this->func); |
| IR::Instr *ldRet = IR::Instr::New(Js::OpCode::Ld_A, regOpnd, addrOpnd, this->func); |
| this->func->m_headInstr->GetNextRealInstr()->InsertBefore(ldRet); |
| |
| IR::LabelInstr * currentLabel = nullptr; |
| // look for early exits from a try, and insert bailout |
| FOREACH_INSTR_IN_FUNC_EDITING(instr, instrNext, func) |
| { |
| if (instr->IsLabelInstr()) |
| { |
| currentLabel = instr->AsLabelInstr(); |
| } |
| else if (instr->m_opcode == Js::OpCode::TryFinally) |
| { |
| Assert(instr->AsBranchInstr()->GetTarget()->GetRegion()->GetType() == RegionTypeFinally); |
| this->finallyLabelStack->Push(instr->AsBranchInstr()->GetTarget()); |
| } |
| else if (instr->m_opcode == Js::OpCode::Leave) |
| { |
| IR::LabelInstr *branchTarget = instr->AsBranchInstr()->GetTarget(); |
| IR::LabelInstr *exitLabel = nullptr; |
| // An early exit (break, continue, return) from an EH region appears as Leave opcode |
| // When there is an early exit within a try finally, we have to execute finally code |
| // Currently we bailout on early exits |
| // For all such edges add edge from eh region -> finally and finally -> earlyexit |
| Assert(currentLabel != nullptr); |
| if (currentLabel && IsEarlyExitFromFinally(instr->AsBranchInstr(), currentLabel->GetRegion(), branchTarget->GetRegion(), instrNext, exitLabel)) |
| { |
| Assert(exitLabel); |
| IR::Instr * bailOnEarlyExit = IR::BailOutInstr::New(Js::OpCode::BailOnEarlyExit, IR::BailOutOnEarlyExit, instr, instr->m_func); |
| instr->InsertBefore(bailOnEarlyExit); |
| IR::LabelInstr *exceptFinallyLabel = this->finallyLabelStack->Top(); |
| IR::LabelInstr *nonExceptFinallyLabel = exceptFinallyLabel->m_next->m_next->AsLabelInstr(); |
| BasicBlock *nonExceptFinallyBlock = nonExceptFinallyLabel->GetBasicBlock(); |
| |
| if (Dominates(nonExceptFinallyBlock->firstInstr->AsLabelInstr()->GetRegion(), exitLabel->GetRegion())) |
| { |
| IR::Instr * leaveToFinally = IR::BranchInstr::New(Js::OpCode::Leave, exceptFinallyLabel, this->func); |
| instr->InsertBefore(leaveToFinally); |
| instr->Remove(); |
| this->AddEdge(currentLabel->GetBasicBlock(), exceptFinallyLabel->GetBasicBlock()); |
| |
| Assert(this->regToFinallyEndMap->ContainsKey(nonExceptFinallyLabel->GetRegion())); |
| BasicBlock * finallyEndBlock = this->regToFinallyEndMap->Item(nonExceptFinallyLabel->GetRegion()); |
| Assert(finallyEndBlock); |
| Assert(finallyEndBlock->GetFirstInstr()->AsLabelInstr()->GetRegion() == nonExceptFinallyLabel->GetRegion()); |
| InsertEdgeFromFinallyToEarlyExit(finallyEndBlock, exitLabel); |
| } |
| } |
| else if (currentLabel && currentLabel->GetRegion()->GetType() == RegionTypeFinally) |
| { |
| Assert(currentLabel->GetRegion()->GetMatchingTryRegion()->GetMatchingFinallyRegion(false) == currentLabel->GetRegion()); |
| // Convert Leave to Br because we execute non-excepting Finally in native code |
| instr->m_opcode = Js::OpCode::Br; |
| } |
| } |
| else if (instr->m_opcode == Js::OpCode::Finally) |
| { |
| this->finallyLabelStack->Pop(); |
| } |
| } |
| NEXT_INSTR_IN_FUNC_EDITING; |
| } |
| |
| blockNum = 0; |
| FOREACH_BLOCK_ALL(block, this) |
| { |
| block->SetBlockNum(blockNum++); |
| } NEXT_BLOCK_ALL; |
| |
| this->FindLoops(); |
| #if DBG_DUMP |
| if (PHASE_DUMP(Js::FGBuildPhase, this->GetFunc())) |
| { |
| if (assignRegionsBeforeGlobopt) |
| { |
| Output::Print(_u("Before CanonicalizeLoops\n")); |
| FOREACH_BLOCK_ALL(block, this) |
| { |
| block->DumpHeader(true); |
| Region *region = block->GetFirstInstr()->AsLabelInstr()->GetRegion(); |
| if (region) { |
| const char16 * regMap[] = { _u("RegionTypeInvalid"), |
| _u("RegionTypeRoot"), |
| _u("RegionTypeTry"), |
| _u("RegionTypeCatch"), |
| _u("RegionTypeFinally") }; |
| Output::Print(_u("Region %p RegionParent %p RegionType %s\n"), region, region->GetParent(), regMap[region->GetType()]); |
| } |
| } NEXT_BLOCK_ALL; |
| } |
| } |
| #endif |
| bool breakBlocksRelocated = this->CanonicalizeLoops(); |
| |
| blockNum = 0; |
| FOREACH_BLOCK_ALL(block, this) |
| { |
| block->SetBlockNum(blockNum++); |
| } NEXT_BLOCK_ALL; |
| |
| #if DBG |
| FOREACH_BLOCK_ALL(block, this) |
| { |
| if (assignRegionsBeforeGlobopt) |
| { |
| if (block->GetFirstInstr()->AsLabelInstr()->GetRegion()) |
| { |
| Assert(block->GetFirstInstr()->AsLabelInstr()->GetRegion()->ehBailoutData); |
| } |
| } |
| } NEXT_BLOCK_ALL; |
| #endif |
| |
| #if DBG |
| FOREACH_BLOCK_ALL(block, this) |
| { |
| if (PHASE_DUMP(Js::FGBuildPhase, this->GetFunc())) |
| { |
| if (assignRegionsBeforeGlobopt) |
| { |
| block->DumpHeader(true); |
| Region *region = block->GetFirstInstr()->AsLabelInstr()->GetRegion(); |
| if (region) { |
| const char16 * regMap[] = { _u("RegionTypeInvalid"), |
| _u("RegionTypeRoot"), |
| _u("RegionTypeTry"), |
| _u("RegionTypeCatch"), |
| _u("RegionTypeFinally") }; |
| Output::Print(_u("Region %p RegionParent %p RegionType %s\n"), region, region->GetParent(), regMap[region->GetType()]); |
| } |
| } |
| } |
| } NEXT_BLOCK_ALL; |
| #endif |
| |
| if (breakBlocksRelocated) |
| { |
| // Sort loop lists only if there is break block removal. |
| SortLoopLists(); |
| } |
| |
| #if DBG |
| this->VerifyLoopGraph(); |
| #endif |
| |
| #if DBG_DUMP |
| this->Dump(false, nullptr); |
| #endif |
| } |
| |
| void FlowGraph::InsertEdgeFromFinallyToEarlyExit(BasicBlock *finallyEndBlock, IR::LabelInstr *exitLabel) |
| { |
| IR::Instr * lastInstr = finallyEndBlock->GetLastInstr(); |
| IR::LabelInstr * lastLabel = finallyEndBlock->GetFirstInstr()->AsLabelInstr(); |
| |
| Assert(lastInstr->m_opcode == Js::OpCode::LeaveNull || lastInstr->m_opcode == Js::OpCode::Leave || lastInstr->m_opcode == Js::OpCode::BrOnException); |
| // Add a new block, add BrOnException to earlyexit, assign region |
| // Finally |
| // ... |
| // L1: |
| // LeaveNull |
| // to |
| // Finally |
| // ... |
| // L1: |
| // BrOnException earlyExitLabel |
| // L1': |
| // LeaveNull |
| |
| BasicBlock *nextBB = finallyEndBlock->GetNext(); |
| |
| IR::LabelInstr *leaveLabel = IR::LabelInstr::New(Js::OpCode::Label, this->func); |
| lastInstr->InsertBefore(leaveLabel); |
| |
| this->AddBlock(leaveLabel, lastInstr, nextBB, finallyEndBlock /*prevBlock*/); |
| leaveLabel->SetRegion(lastLabel->GetRegion()); |
| |
| this->AddEdge(finallyEndBlock, leaveLabel->GetBasicBlock()); |
| |
| // If the Leave/LeaveNull at the end of finally was not preceeded by a Label, we have to create a new block with BrOnException to early exit |
| if (!lastInstr->GetPrevRealInstrOrLabel()->IsLabelInstr()) |
| { |
| IR::LabelInstr *brLabel = IR::LabelInstr::New(Js::OpCode::Label, this->func); |
| leaveLabel->InsertBefore(brLabel); |
| |
| IR::BranchInstr *brToExit = IR::BranchInstr::New(Js::OpCode::BrOnException, exitLabel, this->func); |
| leaveLabel->InsertBefore(brToExit); |
| |
| this->AddBlock(brLabel, brToExit, finallyEndBlock->GetNext(), finallyEndBlock /*prevBlock*/); |
| brLabel->SetRegion(lastLabel->GetRegion()); |
| |
| this->AddEdge(finallyEndBlock, brLabel->GetBasicBlock()); |
| } |
| else |
| { |
| // If the Leave/LeaveNull at the end of finally was preceeded by a Label, we reuse the block inserting BrOnException to early exit in it |
| IR::BranchInstr *brToExit = IR::BranchInstr::New(Js::OpCode::BrOnException, exitLabel, this->func); |
| leaveLabel->InsertBefore(brToExit); |
| this->AddEdge(finallyEndBlock, exitLabel->GetBasicBlock()); |
| } |
| |
| // In case of throw/non-terminating loop, there maybe no edge to the next block |
| if (this->FindEdge(finallyEndBlock, nextBB)) |
| { |
| finallyEndBlock->RemoveSucc(nextBB, this); |
| } |
| |
| this->regToFinallyEndMap->Item(lastLabel->GetRegion(), leaveLabel->GetBasicBlock()); |
| } |
| |
| void |
| FlowGraph::SortLoopLists() |
| { |
| // Sort the blocks in loopList |
| for (Loop *loop = this->loopList; loop; loop = loop->next) |
| { |
| unsigned int lastBlockNumber = loop->GetHeadBlock()->GetBlockNum(); |
| // Insertion sort as the blockList is almost sorted in the loop. |
| FOREACH_BLOCK_IN_LOOP_EDITING(block, loop, iter) |
| { |
| if (lastBlockNumber <= block->GetBlockNum()) |
| { |
| lastBlockNumber = block->GetBlockNum(); |
| } |
| else |
| { |
| iter.UnlinkCurrent(); |
| FOREACH_BLOCK_IN_LOOP_EDITING(insertBlock,loop,newIter) |
| { |
| if (insertBlock->GetBlockNum() > block->GetBlockNum()) |
| { |
| break; |
| } |
| }NEXT_BLOCK_IN_LOOP_EDITING; |
| newIter.InsertBefore(block); |
| } |
| }NEXT_BLOCK_IN_LOOP_EDITING; |
| } |
| } |
| |
| void |
| FlowGraph::RunPeeps() |
| { |
| if (this->func->HasTry()) |
| { |
| return; |
| } |
| |
| if (PHASE_OFF(Js::FGPeepsPhase, this->func)) |
| { |
| return; |
| } |
| |
| IR::Instr * instrCm = nullptr; |
| bool tryUnsignedCmpPeep = false; |
| |
| FOREACH_INSTR_IN_FUNC_EDITING(instr, instrNext, this->func) |
| { |
| switch(instr->m_opcode) |
| { |
| case Js::OpCode::Br: |
| case Js::OpCode::BrEq_I4: |
| case Js::OpCode::BrGe_I4: |
| case Js::OpCode::BrGt_I4: |
| case Js::OpCode::BrLt_I4: |
| case Js::OpCode::BrLe_I4: |
| case Js::OpCode::BrUnGe_I4: |
| case Js::OpCode::BrUnGt_I4: |
| case Js::OpCode::BrUnLt_I4: |
| case Js::OpCode::BrUnLe_I4: |
| case Js::OpCode::BrNeq_I4: |
| case Js::OpCode::BrEq_A: |
| case Js::OpCode::BrGe_A: |
| case Js::OpCode::BrGt_A: |
| case Js::OpCode::BrLt_A: |
| case Js::OpCode::BrLe_A: |
| case Js::OpCode::BrUnGe_A: |
| case Js::OpCode::BrUnGt_A: |
| case Js::OpCode::BrUnLt_A: |
| case Js::OpCode::BrUnLe_A: |
| case Js::OpCode::BrNotEq_A: |
| case Js::OpCode::BrNotNeq_A: |
| case Js::OpCode::BrSrNotEq_A: |
| case Js::OpCode::BrSrNotNeq_A: |
| case Js::OpCode::BrNotGe_A: |
| case Js::OpCode::BrNotGt_A: |
| case Js::OpCode::BrNotLt_A: |
| case Js::OpCode::BrNotLe_A: |
| case Js::OpCode::BrNeq_A: |
| case Js::OpCode::BrNotNull_A: |
| case Js::OpCode::BrNotAddr_A: |
| case Js::OpCode::BrAddr_A: |
| case Js::OpCode::BrSrEq_A: |
| case Js::OpCode::BrSrNeq_A: |
| case Js::OpCode::BrOnHasProperty: |
| case Js::OpCode::BrOnNoProperty: |
| case Js::OpCode::BrHasSideEffects: |
| case Js::OpCode::BrNotHasSideEffects: |
| case Js::OpCode::BrFncEqApply: |
| case Js::OpCode::BrFncNeqApply: |
| case Js::OpCode::BrOnEmpty: |
| case Js::OpCode::BrOnNotEmpty: |
| case Js::OpCode::BrFncCachedScopeEq: |
| case Js::OpCode::BrFncCachedScopeNeq: |
| case Js::OpCode::BrOnObject_A: |
| case Js::OpCode::BrOnClassConstructor: |
| case Js::OpCode::BrOnBaseConstructorKind: |
| if (tryUnsignedCmpPeep) |
| { |
| this->UnsignedCmpPeep(instr); |
| } |
| instrNext = Peeps::PeepBranch(instr->AsBranchInstr()); |
| break; |
| |
| case Js::OpCode::MultiBr: |
| // TODO: Run peeps on these as well... |
| break; |
| |
| case Js::OpCode::BrTrue_I4: |
| case Js::OpCode::BrFalse_I4: |
| case Js::OpCode::BrTrue_A: |
| case Js::OpCode::BrFalse_A: |
| if (instrCm) |
| { |
| if (instrCm->GetDst()->IsInt32()) |
| { |
| Assert(instr->m_opcode == Js::OpCode::BrTrue_I4 || instr->m_opcode == Js::OpCode::BrFalse_I4); |
| instrNext = this->PeepTypedCm(instrCm); |
| } |
| else |
| { |
| instrNext = this->PeepCm(instrCm); |
| } |
| instrCm = nullptr; |
| |
| if (instrNext == nullptr) |
| { |
| // Set instrNext back to the current instr. |
| instrNext = instr; |
| } |
| } |
| else |
| { |
| instrNext = Peeps::PeepBranch(instr->AsBranchInstr()); |
| } |
| break; |
| |
| case Js::OpCode::CmEq_I4: |
| case Js::OpCode::CmGe_I4: |
| case Js::OpCode::CmGt_I4: |
| case Js::OpCode::CmLt_I4: |
| case Js::OpCode::CmLe_I4: |
| case Js::OpCode::CmNeq_I4: |
| case Js::OpCode::CmEq_A: |
| case Js::OpCode::CmGe_A: |
| case Js::OpCode::CmGt_A: |
| case Js::OpCode::CmLt_A: |
| case Js::OpCode::CmLe_A: |
| case Js::OpCode::CmNeq_A: |
| case Js::OpCode::CmSrEq_A: |
| case Js::OpCode::CmSrNeq_A: |
| if (tryUnsignedCmpPeep) |
| { |
| this->UnsignedCmpPeep(instr); |
| } |
| case Js::OpCode::CmUnGe_I4: |
| case Js::OpCode::CmUnGt_I4: |
| case Js::OpCode::CmUnLt_I4: |
| case Js::OpCode::CmUnLe_I4: |
| case Js::OpCode::CmUnGe_A: |
| case Js::OpCode::CmUnGt_A: |
| case Js::OpCode::CmUnLt_A: |
| case Js::OpCode::CmUnLe_A: |
| // There may be useless branches between the Cm instr and the branch that uses the result. |
| // So save the last Cm instr seen, and trigger the peep on the next BrTrue/BrFalse. |
| instrCm = instr; |
| break; |
| |
| case Js::OpCode::Label: |
| if (instr->AsLabelInstr()->IsUnreferenced()) |
| { |
| instrNext = Peeps::PeepUnreachableLabel(instr->AsLabelInstr(), false); |
| } |
| break; |
| |
| case Js::OpCode::StatementBoundary: |
| instr->ClearByteCodeOffset(); |
| instr->SetByteCodeOffset(instr->GetNextRealInstrOrLabel()); |
| break; |
| |
| case Js::OpCode::ShrU_I4: |
| case Js::OpCode::ShrU_A: |
| if (tryUnsignedCmpPeep) |
| { |
| break; |
| } |
| if (instr->GetDst()->AsRegOpnd()->m_sym->IsSingleDef() |
| && instr->GetSrc2()->IsRegOpnd() && instr->GetSrc2()->AsRegOpnd()->m_sym->IsTaggableIntConst() |
| && instr->GetSrc2()->AsRegOpnd()->m_sym->GetIntConstValue() == 0) |
| { |
| tryUnsignedCmpPeep = true; |
| } |
| break; |
| default: |
| Assert(!instr->IsBranchInstr()); |
| } |
| } NEXT_INSTR_IN_FUNC_EDITING; |
| } |
| |
| void |
| Loop::InsertLandingPad(FlowGraph *fg) |
| { |
| BasicBlock *headBlock = this->GetHeadBlock(); |
| |
| // Always create a landing pad. This allows globopt to easily hoist instructions |
| // and re-optimize the block if needed. |
| BasicBlock *landingPad = BasicBlock::New(fg); |
| this->landingPad = landingPad; |
| IR::Instr * headInstr = headBlock->GetFirstInstr(); |
| IR::LabelInstr *landingPadLabel = IR::LabelInstr::New(Js::OpCode::Label, headInstr->m_func); |
| landingPadLabel->SetByteCodeOffset(headInstr); |
| headInstr->InsertBefore(landingPadLabel); |
| |
| landingPadLabel->SetBasicBlock(landingPad); |
| landingPadLabel->SetRegion(headBlock->GetFirstInstr()->AsLabelInstr()->GetRegion()); |
| landingPad->SetBlockNum(fg->blockCount++); |
| landingPad->SetFirstInstr(landingPadLabel); |
| landingPad->SetLastInstr(landingPadLabel); |
| |
| landingPad->prev = headBlock->prev; |
| landingPad->prev->next = landingPad; |
| landingPad->next = headBlock; |
| headBlock->prev = landingPad; |
| |
| Loop *parentLoop = this->parent; |
| landingPad->loop = parentLoop; |
| |
| // We need to add this block to the block list of the parent loops |
| while (parentLoop) |
| { |
| // Find the head block in the block list of the parent loop |
| FOREACH_BLOCK_IN_LOOP_EDITING(block, parentLoop, iter) |
| { |
| if (block == headBlock) |
| { |
| // Add the landing pad to the block list |
| iter.InsertBefore(landingPad); |
| break; |
| } |
| } NEXT_BLOCK_IN_LOOP_EDITING; |
| |
| parentLoop = parentLoop->parent; |
| } |
| |
| // Fix predecessor flow edges |
| FOREACH_PREDECESSOR_EDGE_EDITING(edge, headBlock, iter) |
| { |
| // Make sure it isn't a back-edge |
| if (edge->GetPred()->loop != this && !this->IsDescendentOrSelf(edge->GetPred()->loop)) |
| { |
| if (edge->GetPred()->GetLastInstr()->IsBranchInstr() && headBlock->GetFirstInstr()->IsLabelInstr()) |
| { |
| IR::BranchInstr *branch = edge->GetPred()->GetLastInstr()->AsBranchInstr(); |
| branch->ReplaceTarget(headBlock->GetFirstInstr()->AsLabelInstr(), landingPadLabel); |
| } |
| headBlock->UnlinkPred(edge->GetPred(), false); |
| landingPad->AddPred(edge, fg); |
| edge->SetSucc(landingPad); |
| } |
| } NEXT_PREDECESSOR_EDGE_EDITING; |
| |
| fg->AddEdge(landingPad, headBlock); |
| |
| if (headBlock->GetFirstInstr()->AsLabelInstr()->GetRegion() && headBlock->GetFirstInstr()->AsLabelInstr()->GetRegion()->GetType() != RegionTypeRoot) |
| { |
| landingPadLabel->m_hasNonBranchRef = true; |
| } |
| } |
| |
| bool |
| Loop::RemoveBreakBlocks(FlowGraph *fg) |
| { |
| bool breakBlockRelocated = false; |
| if (PHASE_OFF(Js::RemoveBreakBlockPhase, fg->GetFunc())) |
| { |
| return false; |
| } |
| |
| BasicBlock *loopTailBlock = nullptr; |
| FOREACH_BLOCK_IN_LOOP(block, this) |
| { |
| loopTailBlock = block; |
| }NEXT_BLOCK_IN_LOOP; |
| |
| AnalysisAssert(loopTailBlock); |
| |
| FOREACH_BLOCK_BACKWARD_IN_RANGE_EDITING(breakBlockEnd, loopTailBlock, this->GetHeadBlock(), blockPrev) |
| { |
| while (!this->IsDescendentOrSelf(breakBlockEnd->loop)) |
| { |
| // Found at least one break block; |
| breakBlockRelocated = true; |
| |
| #if DBG |
| breakBlockEnd->isBreakBlock = true; |
| #endif |
| // Find the first block in this break block sequence. |
| BasicBlock *breakBlockStart = breakBlockEnd; |
| BasicBlock *breakBlockStartPrev = breakBlockEnd->GetPrev(); |
| |
| // Walk back the blocks until we find a block which belongs to that block. |
| // Note: We don't really care if there are break blocks corresponding to different loops. We move the blocks conservatively to the end of the loop. |
| |
| // Algorithm works on one loop at a time. |
| while((breakBlockStartPrev->loop == breakBlockEnd->loop) || !this->IsDescendentOrSelf(breakBlockStartPrev->loop)) |
| { |
| breakBlockStart = breakBlockStartPrev; |
| breakBlockStartPrev = breakBlockStartPrev->GetPrev(); |
| } |
| |
| #if DBG |
| breakBlockStart->isBreakBlock = true; // Mark the first block as well. |
| #endif |
| |
| BasicBlock *exitLoopTail = loopTailBlock; |
| // Move these break blocks to the tail of the loop. |
| fg->MoveBlocksBefore(breakBlockStart, breakBlockEnd, exitLoopTail->next); |
| |
| #if DBG_DUMP |
| fg->Dump(true /*needs verbose flag*/, _u("\n After Each iteration of canonicalization \n")); |
| #endif |
| // Again be conservative, there are edits to the loop graph. Start fresh for this loop. |
| breakBlockEnd = loopTailBlock; |
| blockPrev = breakBlockEnd->prev; |
| } |
| } NEXT_BLOCK_BACKWARD_IN_RANGE_EDITING; |
| |
| return breakBlockRelocated; |
| } |
| |
| void |
| FlowGraph::MoveBlocksBefore(BasicBlock *blockStart, BasicBlock *blockEnd, BasicBlock *insertBlock) |
| { |
| BasicBlock *srcPredBlock = blockStart->prev; |
| BasicBlock *srcNextBlock = blockEnd->next; |
| BasicBlock *dstPredBlock = insertBlock->prev; |
| IR::Instr* dstPredBlockLastInstr = dstPredBlock->GetLastInstr(); |
| IR::Instr* blockEndLastInstr = blockEnd->GetLastInstr(); |
| |
| // Fix block linkage |
| srcPredBlock->next = srcNextBlock; |
| srcNextBlock->prev = srcPredBlock; |
| |
| dstPredBlock->next = blockStart; |
| insertBlock->prev = blockEnd; |
| |
| blockStart->prev = dstPredBlock; |
| blockEnd->next = insertBlock; |
| |
| // Fix instruction linkage |
| IR::Instr::MoveRangeAfter(blockStart->GetFirstInstr(), blockEndLastInstr, dstPredBlockLastInstr); |
| |
| // Fix instruction flow |
| IR::Instr *srcLastInstr = srcPredBlock->GetLastInstr(); |
| if (srcLastInstr->IsBranchInstr() && srcLastInstr->AsBranchInstr()->HasFallThrough()) |
| { |
| // There was a fallthrough in the break blocks original position. |
| IR::BranchInstr *srcBranch = srcLastInstr->AsBranchInstr(); |
| IR::Instr *srcBranchNextInstr = srcBranch->GetNextRealInstrOrLabel(); |
| |
| // Save the target and invert the branch. |
| IR::LabelInstr *srcBranchTarget = srcBranch->GetTarget(); |
| srcPredBlock->InvertBranch(srcBranch); |
| IR::LabelInstr *srcLabel = blockStart->GetFirstInstr()->AsLabelInstr(); |
| |
| // Point the inverted branch to break block. |
| srcBranch->SetTarget(srcLabel); |
| |
| if (srcBranchNextInstr != srcBranchTarget) |
| { |
| FlowEdge *srcEdge = this->FindEdge(srcPredBlock, srcBranchTarget->GetBasicBlock()); |
| Assert(srcEdge); |
| |
| BasicBlock *compensationBlock = this->InsertCompensationCodeForBlockMove(srcEdge, true /*insert compensation block to loop list*/, false /*At source*/); |
| Assert(compensationBlock); |
| } |
| } |
| |
| IR::Instr *dstLastInstr = dstPredBlockLastInstr; |
| |
| bool assignRegionsBeforeGlobopt = this->func->HasTry() && (this->func->DoOptimizeTry() || |
| (this->func->IsSimpleJit() && this->func->hasBailout)); |
| |
| if (dstLastInstr->IsBranchInstr() && dstLastInstr->AsBranchInstr()->HasFallThrough()) |
| { |
| //There is a fallthrough in the block after which break block is inserted. |
| FlowEdge *dstEdge = this->FindEdge(dstPredBlock, blockEnd->GetNext()); |
| Assert(dstEdge); |
| |
| BasicBlock *compensationBlock = this->InsertCompensationCodeForBlockMove(dstEdge, true /*insert compensation block to loop list*/, true /*At sink*/); |
| Assert(compensationBlock); |
| } |
| |
| // We have to update region info for blocks whose predecessors changed |
| if (assignRegionsBeforeGlobopt) |
| { |
| UpdateRegionForBlockFromEHPred(dstPredBlock, true); |
| UpdateRegionForBlockFromEHPred(blockStart, true); |
| UpdateRegionForBlockFromEHPred(srcNextBlock, true); |
| } |
| } |
| |
| FlowEdge * |
| FlowGraph::FindEdge(BasicBlock *predBlock, BasicBlock *succBlock) |
| { |
| FlowEdge *srcEdge = nullptr; |
| FOREACH_SUCCESSOR_EDGE(edge, predBlock) |
| { |
| if (edge->GetSucc() == succBlock) |
| { |
| srcEdge = edge; |
| break; |
| } |
| } NEXT_SUCCESSOR_EDGE; |
| |
| return srcEdge; |
| } |
| |
| void |
| BasicBlock::InvertBranch(IR::BranchInstr *branch) |
| { |
| Assert(this->GetLastInstr() == branch); |
| Assert(this->GetSuccList()->HasTwo()); |
| |
| branch->Invert(); |
| this->GetSuccList()->Reverse(); |
| } |
| |
| bool |
| FlowGraph::CanonicalizeLoops() |
| { |
| if (this->func->HasProfileInfo()) |
| { |
| this->implicitCallFlags = this->func->GetReadOnlyProfileInfo()->GetImplicitCallFlags(); |
| for (Loop *loop = this->loopList; loop; loop = loop->next) |
| { |
| this->implicitCallFlags = (Js::ImplicitCallFlags)(this->implicitCallFlags | loop->GetImplicitCallFlags()); |
| } |
| } |
| |
| #if DBG_DUMP |
| this->Dump(true, _u("\n Before canonicalizeLoops \n")); |
| #endif |
| |
| bool breakBlockRelocated = false; |
| |
| for (Loop *loop = this->loopList; loop; loop = loop->next) |
| { |
| loop->InsertLandingPad(this); |
| if (!this->func->HasTry() || this->func->DoOptimizeTry()) |
| { |
| bool relocated = loop->RemoveBreakBlocks(this); |
| if (!breakBlockRelocated && relocated) |
| { |
| breakBlockRelocated = true; |
| } |
| } |
| } |
| |
| #if DBG_DUMP |
| this->Dump(true, _u("\n After canonicalizeLoops \n")); |
| #endif |
| |
| return breakBlockRelocated; |
| } |
| |
| // Find the loops in this function, build the loop structure, and build a linked |
| // list of the basic blocks in this loop (including blocks of inner loops). The |
| // list preserves the reverse post-order of the blocks in the flowgraph block list. |
| void |
| FlowGraph::FindLoops() |
| { |
| if (!this->hasLoop) |
| { |
| return; |
| } |
| |
| Func * func = this->func; |
| |
| FOREACH_BLOCK_BACKWARD_IN_FUNC(block, func) |
| { |
| if (block->loop != nullptr) |
| { |
| // Block already visited |
| continue; |
| } |
| FOREACH_SUCCESSOR_BLOCK(succ, block) |
| { |
| if (succ->isLoopHeader && succ->loop == nullptr) |
| { |
| // Found a loop back-edge |
| BuildLoop(succ, block); |
| } |
| } NEXT_SUCCESSOR_BLOCK; |
| if (block->isLoopHeader && block->loop == nullptr) |
| { |
| // We would have built a loop for it if it was a loop... |
| block->isLoopHeader = false; |
| block->GetFirstInstr()->AsLabelInstr()->m_isLoopTop = false; |
| } |
| } NEXT_BLOCK_BACKWARD_IN_FUNC; |
| } |
| |
| void |
| FlowGraph::BuildLoop(BasicBlock *headBlock, BasicBlock *tailBlock, Loop *parentLoop) |
| { |
| // This function is recursive, so when jitting in the foreground, probe the stack |
| if(!func->IsBackgroundJIT()) |
| { |
| PROBE_STACK(func->GetScriptContext(), Js::Constants::MinStackDefault); |
| } |
| |
| if (tailBlock->number < headBlock->number) |
| { |
| // Not a loop. We didn't see any back-edge. |
| headBlock->isLoopHeader = false; |
| headBlock->GetFirstInstr()->AsLabelInstr()->m_isLoopTop = false; |
| return; |
| } |
| |
| Assert(headBlock->isLoopHeader); |
| Loop *loop = JitAnewZ(this->GetFunc()->m_alloc, Loop, this->GetFunc()->m_alloc, this->GetFunc()); |
| loop->next = this->loopList; |
| this->loopList = loop; |
| headBlock->loop = loop; |
| loop->headBlock = headBlock; |
| loop->int32SymsOnEntry = nullptr; |
| loop->lossyInt32SymsOnEntry = nullptr; |
| |
| // If parentLoop is a parent of loop, it's headBlock better appear first. |
| if (parentLoop && loop->headBlock->number > parentLoop->headBlock->number) |
| { |
| loop->parent = parentLoop; |
| parentLoop->isLeaf = false; |
| } |
| loop->hasDeadStoreCollectionPass = false; |
| loop->hasDeadStorePrepass = false; |
| loop->memOpInfo = nullptr; |
| loop->doMemOp = true; |
| |
| NoRecoverMemoryJitArenaAllocator tempAlloc(_u("BE-LoopBuilder"), this->func->m_alloc->GetPageAllocator(), Js::Throw::OutOfMemory); |
| |
| WalkLoopBlocks(tailBlock, loop, &tempAlloc); |
| |
| Assert(loop->GetHeadBlock() == headBlock); |
| |
| IR::LabelInstr * firstInstr = loop->GetLoopTopInstr(); |
| |
| firstInstr->SetLoop(loop); |
| |
| if (firstInstr->IsProfiledLabelInstr()) |
| { |
| loop->SetImplicitCallFlags(firstInstr->AsProfiledLabelInstr()->loopImplicitCallFlags); |
| if (this->func->HasProfileInfo() && this->func->GetReadOnlyProfileInfo()->IsLoopImplicitCallInfoDisabled()) |
| { |
| loop->SetImplicitCallFlags(this->func->GetReadOnlyProfileInfo()->GetImplicitCallFlags()); |
| } |
| loop->SetLoopFlags(firstInstr->AsProfiledLabelInstr()->loopFlags); |
| } |
| else |
| { |
| // Didn't collect profile information, don't do optimizations |
| loop->SetImplicitCallFlags(Js::ImplicitCall_All); |
| } |
| } |
| |
| Loop::MemCopyCandidate* Loop::MemOpCandidate::AsMemCopy() |
| { |
| Assert(this->IsMemCopy()); |
| return (Loop::MemCopyCandidate*)this; |
| } |
| |
| Loop::MemSetCandidate* Loop::MemOpCandidate::AsMemSet() |
| { |
| Assert(this->IsMemSet()); |
| return (Loop::MemSetCandidate*)this; |
| } |
| |
| void |
| Loop::EnsureMemOpVariablesInitialized() |
| { |
| Assert(this->doMemOp); |
| if (this->memOpInfo == nullptr) |
| { |
| JitArenaAllocator *allocator = this->GetFunc()->GetTopFunc()->m_fg->alloc; |
| this->memOpInfo = JitAnewStruct(allocator, Loop::MemOpInfo); |
| this->memOpInfo->inductionVariablesUsedAfterLoop = nullptr; |
| this->memOpInfo->startIndexOpndCache[0] = nullptr; |
| this->memOpInfo->startIndexOpndCache[1] = nullptr; |
| this->memOpInfo->startIndexOpndCache[2] = nullptr; |
| this->memOpInfo->startIndexOpndCache[3] = nullptr; |
| this->memOpInfo->inductionVariableChangeInfoMap = JitAnew(allocator, Loop::InductionVariableChangeInfoMap, allocator); |
| this->memOpInfo->inductionVariableOpndPerUnrollMap = JitAnew(allocator, Loop::InductionVariableOpndPerUnrollMap, allocator); |
| this->memOpInfo->candidates = JitAnew(allocator, Loop::MemOpList, allocator); |
| } |
| } |
| |
| // Walk the basic blocks backwards until we find the loop header. |
| // Mark basic blocks in the loop by looking at the predecessors |
| // of blocks known to be in the loop. |
| // Recurse on inner loops. |
| void |
| FlowGraph::WalkLoopBlocks(BasicBlock *block, Loop *loop, JitArenaAllocator *tempAlloc) |
| { |
| AnalysisAssert(loop); |
| |
| BVSparse<JitArenaAllocator> *loopBlocksBv = JitAnew(tempAlloc, BVSparse<JitArenaAllocator>, tempAlloc); |
| BasicBlock *tailBlock = block; |
| BasicBlock *lastBlock; |
| loopBlocksBv->Set(block->GetBlockNum()); |
| |
| this->AddBlockToLoop(block, loop); |
| |
| if (block == loop->headBlock) |
| { |
| // Single block loop, we're done |
| return; |
| } |
| |
| do |
| { |
| BOOL isInLoop = loopBlocksBv->Test(block->GetBlockNum()); |
| |
| FOREACH_SUCCESSOR_BLOCK(succ, block) |
| { |
| if (succ->isLoopHeader) |
| { |
| // Found a loop back-edge |
| if (loop->headBlock == succ) |
| { |
| isInLoop = true; |
| } |
| else if (succ->loop == nullptr || succ->loop->headBlock != succ) |
| { |
| // Recurse on inner loop |
| BuildLoop(succ, block, isInLoop ? loop : nullptr); |
| } |
| } |
| } NEXT_SUCCESSOR_BLOCK; |
| |
| if (isInLoop) |
| { |
| // This block is in the loop. All of it's predecessors should be contained in the loop as well. |
| FOREACH_PREDECESSOR_BLOCK(pred, block) |
| { |
| // Fix up loop parent if it isn't set already. |
| // If pred->loop != loop, we're looking at an inner loop, which was already visited. |
| // If pred->loop->parent == nullptr, this is the first time we see this loop from an outer |
| // loop, so this must be an immediate child. |
| if (pred->loop && pred->loop != loop && loop->headBlock->number < pred->loop->headBlock->number |
| && (pred->loop->parent == nullptr || pred->loop->parent->headBlock->number < loop->headBlock->number)) |
| { |
| pred->loop->parent = loop; |
| loop->isLeaf = false; |
| if (pred->loop->hasCall) |
| { |
| loop->SetHasCall(); |
| } |
| loop->SetImplicitCallFlags(pred->loop->GetImplicitCallFlags()); |
| } |
| // Add pred to loop bit vector |
| loopBlocksBv->Set(pred->GetBlockNum()); |
| } NEXT_PREDECESSOR_BLOCK; |
| |
| if (block->loop == nullptr || block->loop->IsDescendentOrSelf(loop)) |
| { |
| block->loop = loop; |
| } |
| |
| if (block != tailBlock) |
| { |
| this->AddBlockToLoop(block, loop); |
| } |
| } |
| lastBlock = block; |
| block = block->GetPrev(); |
| } while (lastBlock != loop->headBlock); |
| } |
| |
| // Add block to this loop, and it's parent loops. |
| void |
| FlowGraph::AddBlockToLoop(BasicBlock *block, Loop *loop) |
| { |
| loop->blockList.Prepend(block); |
| if (block->hasCall) |
| { |
| loop->SetHasCall(); |
| } |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// FlowGraph::AddBlock |
| /// |
| /// Finish processing of a new block: hook up successor arcs, note loops, etc. |
| /// |
| ///---------------------------------------------------------------------------- |
| BasicBlock * |
| FlowGraph::AddBlock( |
| IR::Instr * firstInstr, |
| IR::Instr * lastInstr, |
| BasicBlock * nextBlock, |
| BasicBlock *prevBlock) |
| { |
| BasicBlock * block; |
| IR::LabelInstr * labelInstr; |
| |
| if (firstInstr->IsLabelInstr()) |
| { |
| labelInstr = firstInstr->AsLabelInstr(); |
| } |
| else |
| { |
| labelInstr = IR::LabelInstr::New(Js::OpCode::Label, firstInstr->m_func); |
| labelInstr->SetByteCodeOffset(firstInstr); |
| if (firstInstr->IsEntryInstr()) |
| { |
| firstInstr->InsertAfter(labelInstr); |
| } |
| else |
| { |
| firstInstr->InsertBefore(labelInstr); |
| } |
| firstInstr = labelInstr; |
| } |
| |
| block = labelInstr->GetBasicBlock(); |
| if (block == nullptr) |
| { |
| block = BasicBlock::New(this); |
| labelInstr->SetBasicBlock(block); |
| // Remember last block in function to target successor of RETs. |
| if (!this->tailBlock) |
| { |
| this->tailBlock = block; |
| } |
| } |
| |
| // Hook up the successor edges |
| if (lastInstr->EndsBasicBlock()) |
| { |
| BasicBlock * blockTarget = nullptr; |
| |
| if (lastInstr->IsBranchInstr()) |
| { |
| // Hook up a successor edge to the branch target. |
| IR::BranchInstr * branchInstr = lastInstr->AsBranchInstr(); |
| |
| if(branchInstr->IsMultiBranch()) |
| { |
| BasicBlock * blockMultiBrTarget; |
| |
| IR::MultiBranchInstr * multiBranchInstr = branchInstr->AsMultiBrInstr(); |
| |
| multiBranchInstr->MapUniqueMultiBrLabels([&](IR::LabelInstr * labelInstr) -> void |
| { |
| blockMultiBrTarget = SetBlockTargetAndLoopFlag(labelInstr); |
| this->AddEdge(block, blockMultiBrTarget); |
| }); |
| } |
| else |
| { |
| IR::LabelInstr * targetLabelInstr = branchInstr->GetTarget(); |
| blockTarget = SetBlockTargetAndLoopFlag(targetLabelInstr); |
| if (branchInstr->IsConditional()) |
| { |
| IR::Instr *instrNext = branchInstr->GetNextRealInstrOrLabel(); |
| |
| if (instrNext->IsLabelInstr()) |
| { |
| SetBlockTargetAndLoopFlag(instrNext->AsLabelInstr()); |
| } |
| } |
| } |
| } |
| else if (lastInstr->m_opcode == Js::OpCode::Ret && block != this->tailBlock) |
| { |
| blockTarget = this->tailBlock; |
| } |
| |
| if (blockTarget) |
| { |
| this->AddEdge(block, blockTarget); |
| } |
| } |
| |
| if (lastInstr->HasFallThrough()) |
| { |
| // Add a branch to next instruction so that we don't have to update the flow graph |
| // when the glob opt tries to insert instructions. |
| // We don't run the globopt with try/catch, don't need to insert branch to next for fall through blocks. |
| if (!this->func->HasTry() && !lastInstr->IsBranchInstr()) |
| { |
| IR::BranchInstr * instr = IR::BranchInstr::New(Js::OpCode::Br, |
| lastInstr->m_next->AsLabelInstr(), lastInstr->m_func); |
| instr->SetByteCodeOffset(lastInstr->m_next); |
| lastInstr->InsertAfter(instr); |
| lastInstr = instr; |
| } |
| this->AddEdge(block, nextBlock); |
| } |
| |
| block->SetBlockNum(this->blockCount++); |
| block->SetFirstInstr(firstInstr); |
| block->SetLastInstr(lastInstr); |
| |
| if (!prevBlock) |
| { |
| if (this->blockList) |
| { |
| this->blockList->prev = block; |
| } |
| block->next = this->blockList; |
| this->blockList = block; |
| } |
| else |
| { |
| prevBlock->next = block; |
| block->prev = prevBlock; |
| block->next = nextBlock; |
| nextBlock->prev = block; |
| } |
| return block; |
| } |
| |
| BasicBlock * |
| FlowGraph::SetBlockTargetAndLoopFlag(IR::LabelInstr * labelInstr) |
| { |
| BasicBlock * blockTarget = nullptr; |
| blockTarget = labelInstr->GetBasicBlock(); |
| |
| if (blockTarget == nullptr) |
| { |
| blockTarget = BasicBlock::New(this); |
| labelInstr->SetBasicBlock(blockTarget); |
| } |
| if (labelInstr->m_isLoopTop) |
| { |
| blockTarget->isLoopHeader = true; |
| this->hasLoop = true; |
| } |
| |
| return blockTarget; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// FlowGraph::AddEdge |
| /// |
| /// Add an edge connecting the two given blocks. |
| /// |
| ///---------------------------------------------------------------------------- |
| FlowEdge * |
| FlowGraph::AddEdge(BasicBlock * blockPred, BasicBlock * blockSucc) |
| { |
| FlowEdge * edge = FlowEdge::New(this); |
| edge->SetPred(blockPred); |
| edge->SetSucc(blockSucc); |
| blockPred->AddSucc(edge, this); |
| blockSucc->AddPred(edge, this); |
| |
| return edge; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// FlowGraph::Destroy |
| /// |
| /// Remove all references to FG structures from the IR in preparation for freeing |
| /// the FG. |
| /// |
| ///---------------------------------------------------------------------------- |
| void |
| FlowGraph::Destroy(void) |
| { |
| BOOL fHasTry = this->func->HasTry(); |
| if (fHasTry) |
| { |
| // Do unreachable code removal up front to avoid problems |
| // with unreachable back edges, etc. |
| this->RemoveUnreachableBlocks(); |
| } |
| |
| FOREACH_BLOCK_ALL(block, this) |
| { |
| IR::Instr * firstInstr = block->GetFirstInstr(); |
| if (block->isDeleted && !block->isDead) |
| { |
| if (firstInstr->IsLabelInstr()) |
| { |
| IR::LabelInstr * labelInstr = firstInstr->AsLabelInstr(); |
| labelInstr->UnlinkBasicBlock(); |
| // Removing the label for non try blocks as we have a deleted block which has the label instruction |
| // still not removed; this prevents the assert for cases where the deleted blocks fall through to a helper block, |
| // i.e. helper introduced by polymorphic inlining bailout. |
| // Skipping Try blocks as we have dependency on blocks to get the last instr(see below in this function) |
| if (!fHasTry) |
| { |
| if (this->func->GetJITFunctionBody()->IsCoroutine()) |
| { |
| // the label could be a yield resume label, in which case we also need to remove it from the YieldOffsetResumeLabels list |
| this->func->MapUntilYieldOffsetResumeLabels([this, &labelInstr](int i, const YieldOffsetResumeLabel& yorl) |
| { |
| if (labelInstr == yorl.Second()) |
| { |
| labelInstr->m_hasNonBranchRef = false; |
| this->func->RemoveYieldOffsetResumeLabel(yorl); |
| return true; |
| } |
| return false; |
| }); |
| } |
| |
| Assert(labelInstr->IsUnreferenced()); |
| labelInstr->Remove(); |
| } |
| } |
| continue; |
| } |
| |
| if (block->isLoopHeader && !block->isDead) |
| { |
| // Mark the tail block of this loop (the last back-edge). The register allocator |
| // uses this to lexically find loops. |
| BasicBlock *loopTail = nullptr; |
| |
| AssertMsg(firstInstr->IsLabelInstr() && firstInstr->AsLabelInstr()->m_isLoopTop, |
| "Label not marked as loop top..."); |
| FOREACH_BLOCK_IN_LOOP(loopBlock, block->loop) |
| { |
| FOREACH_SUCCESSOR_BLOCK(succ, loopBlock) |
| { |
| if (succ == block) |
| { |
| loopTail = loopBlock; |
| break; |
| } |
| } NEXT_SUCCESSOR_BLOCK; |
| } NEXT_BLOCK_IN_LOOP; |
| |
| if (loopTail) |
| { |
| AssertMsg(loopTail->GetLastInstr()->IsBranchInstr(), "LastInstr of loop should always be a branch no?"); |
| block->loop->SetLoopTopInstr(block->GetFirstInstr()->AsLabelInstr()); |
| } |
| else |
| { |
| // This loop doesn't have a back-edge: that is, it is not a loop |
| // anymore... |
| firstInstr->AsLabelInstr()->m_isLoopTop = FALSE; |
| } |
| } |
| |
| if (fHasTry) |
| { |
| this->UpdateRegionForBlock(block); |
| } |
| |
| if (firstInstr->IsLabelInstr()) |
| { |
| IR::LabelInstr * labelInstr = firstInstr->AsLabelInstr(); |
| labelInstr->UnlinkBasicBlock(); |
| if (labelInstr->IsUnreferenced() && !fHasTry) |
| { |
| // This is an unreferenced label, probably added by FG building. |
| // Delete it now to make extended basic blocks visible. |
| if (firstInstr == block->GetLastInstr()) |
| { |
| labelInstr->Remove(); |
| continue; |
| } |
| else |
| { |
| labelInstr->Remove(); |
| } |
| } |
| } |
| |
| // We don't run the globopt with try/catch, don't need to remove branch to next for fall through blocks |
| IR::Instr * lastInstr = block->GetLastInstr(); |
| if (!fHasTry && lastInstr->IsBranchInstr()) |
| { |
| IR::BranchInstr * branchInstr = lastInstr->AsBranchInstr(); |
| if (!branchInstr->IsConditional() && branchInstr->GetTarget() == branchInstr->m_next) |
| { |
| // Remove branch to next |
| branchInstr->Remove(); |
| } |
| } |
| } |
| NEXT_BLOCK; |
| |
| #if DBG |
| |
| if (fHasTry) |
| { |
| // Now that all blocks have regions, we should see consistently propagated regions at all |
| // block boundaries. |
| FOREACH_BLOCK(block, this) |
| { |
| Region * region = block->GetFirstInstr()->AsLabelInstr()->GetRegion(); |
| Region * predRegion = nullptr; |
| FOREACH_PREDECESSOR_BLOCK(predBlock, block) |
| { |
| predRegion = predBlock->GetFirstInstr()->AsLabelInstr()->GetRegion(); |
| if (predBlock->GetLastInstr() == nullptr) |
| { |
| AssertMsg(region == predRegion, "Bad region propagation through empty block"); |
| } |
| else |
| { |
| switch (predBlock->GetLastInstr()->m_opcode) |
| { |
| case Js::OpCode::TryCatch: |
| case Js::OpCode::TryFinally: |
| AssertMsg(region->GetParent() == predRegion, "Bad region prop on entry to try-catch/finally"); |
| if (block->GetFirstInstr() == predBlock->GetLastInstr()->AsBranchInstr()->GetTarget()) |
| { |
| if (predBlock->GetLastInstr()->m_opcode == Js::OpCode::TryCatch) |
| { |
| AssertMsg(region->GetType() == RegionTypeCatch, "Bad region type on entry to catch"); |
| } |
| else |
| { |
| AssertMsg(region->GetType() == RegionTypeFinally, "Bad region type on entry to finally"); |
| } |
| } |
| else |
| { |
| AssertMsg(region->GetType() == RegionTypeTry, "Bad region type on entry to try"); |
| } |
| break; |
| case Js::OpCode::Leave: |
| AssertMsg(region == predRegion->GetParent() || (predRegion->GetType() == RegionTypeTry && predRegion->GetMatchingFinallyRegion(false) == region) || |
| (region == predRegion && this->func->IsLoopBodyInTry()), "Bad region prop on leaving try-catch/finally"); |
| break; |
| case Js::OpCode::LeaveNull: |
| AssertMsg(region == predRegion->GetParent() || (region == predRegion && this->func->IsLoopBodyInTry()), "Bad region prop on leaving try-catch/finally"); |
| break; |
| |
| // If the try region has a branch out of the loop, |
| // - the branch is moved out of the loop as part of break block removal, and |
| // - BrOnException is inverted to BrOnNoException and a Br is inserted after it. |
| // Otherwise, |
| // - FullJit: BrOnException is removed in the forward pass. |
| case Js::OpCode::BrOnException: |
| Assert(!this->func->DoGlobOpt()); |
| break; |
| case Js::OpCode::BrOnNoException: |
| Assert(region->GetType() == RegionTypeTry || region->GetType() == RegionTypeCatch || region->GetType() == RegionTypeFinally); |
| break; |
| case Js::OpCode::Br: |
| if (region->GetType() == RegionTypeCatch && region != predRegion) |
| { |
| AssertMsg(predRegion->GetType() == RegionTypeTry, "Bad region type for the try"); |
| } |
| else if (region->GetType() == RegionTypeFinally && region != predRegion) |
| { |
| // When we add edge from finally to early exit, and break block removal moves the edge into finally region, we can end up with an edge between finally and non eh region |
| } |
| else |
| { |
| // Leave's within non excepting finallys that are not early exit edges are converted to br |
| AssertMsg((predRegion->IsNonExceptingFinally() && region == predRegion->GetParent()) || region == predRegion, "Bad region propagation through interior block"); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| NEXT_PREDECESSOR_BLOCK; |
| |
| switch (region->GetType()) |
| { |
| case RegionTypeRoot: |
| Assert(!region->GetMatchingTryRegion() && !region->GetMatchingCatchRegion() && !region->GetMatchingFinallyRegion(true) && !region->GetMatchingFinallyRegion(false)); |
| break; |
| |
| case RegionTypeTry: |
| if ((this->func->IsSimpleJit() && this->func->hasBailout) || !this->func->DoOptimizeTry()) |
| { |
| Assert((region->GetMatchingCatchRegion() != nullptr) ^ (region->GetMatchingFinallyRegion(true) && !region->GetMatchingFinallyRegion(false))); |
| } |
| else |
| { |
| Assert((region->GetMatchingCatchRegion() != nullptr) ^ (region->GetMatchingFinallyRegion(true) && region->GetMatchingFinallyRegion(false))); |
| } |
| break; |
| |
| case RegionTypeCatch: |
| case RegionTypeFinally: |
| Assert(region->GetMatchingTryRegion()); |
| break; |
| } |
| } |
| NEXT_BLOCK; |
| FOREACH_BLOCK_DEAD_OR_ALIVE(block, this) |
| { |
| if (block->GetFirstInstr()->IsLabelInstr()) |
| { |
| IR::LabelInstr *labelInstr = block->GetFirstInstr()->AsLabelInstr(); |
| if (labelInstr->IsUnreferenced()) |
| { |
| // This is an unreferenced label, probably added by FG building. |
| // Delete it now to make extended basic blocks visible. |
| labelInstr->Remove(); |
| } |
| } |
| } NEXT_BLOCK_DEAD_OR_ALIVE; |
| } |
| #endif |
| |
| this->func->isFlowGraphValid = false; |
| } |
| |
| bool FlowGraph::IsEHTransitionInstr(IR::Instr *instr) |
| { |
| Js::OpCode op = instr->m_opcode; |
| return (op == Js::OpCode::TryCatch || op == Js::OpCode::TryFinally || op == Js::OpCode::Leave || op == Js::OpCode::LeaveNull); |
| } |
| |
| BasicBlock * FlowGraph::GetPredecessorForRegionPropagation(BasicBlock *block) |
| { |
| BasicBlock *ehPred = nullptr; |
| FOREACH_PREDECESSOR_BLOCK(predBlock, block) |
| { |
| Region * predRegion = predBlock->GetFirstInstr()->AsLabelInstr()->GetRegion(); |
| if (IsEHTransitionInstr(predBlock->GetLastInstr()) && predRegion) |
| { |
| // MGTODO : change this to return, once you know there can exist only one eh transitioning pred |
| Assert(ehPred == nullptr); |
| ehPred = predBlock; |
| } |
| AssertMsg(predBlock->GetBlockNum() < this->blockCount, "Misnumbered block at teardown time?"); |
| } |
| NEXT_PREDECESSOR_BLOCK; |
| return ehPred; |
| } |
| |
| // Propagate the region forward from the block's predecessor(s), tracking the effect |
| // of the flow transition. Record the region in the block-to-region map provided |
| // and on the label at the entry to the block (if any). |
| |
| // We need to know the end of finally for inserting edge at the end of finally to early exit |
| // Store it in regToFinallyEndMap as we visit blocks instead of recomputing later while adding early exit edges |
| void |
| FlowGraph::UpdateRegionForBlock(BasicBlock * block) |
| { |
| Region *region; |
| Region * predRegion = nullptr; |
| IR::Instr * tryInstr = nullptr; |
| IR::Instr * firstInstr = block->GetFirstInstr(); |
| if (firstInstr->IsLabelInstr() && firstInstr->AsLabelInstr()->GetRegion()) |
| { |
| Assert(this->func->HasTry() && (this->func->DoOptimizeTry() || (this->func->IsSimpleJit() && this->func->hasBailout))); |
| return; |
| } |
| |
| if (block == this->blockList) |
| { |
| // Head of the graph: create the root region. |
| region = Region::New(RegionTypeRoot, nullptr, this->func); |
| } |
| else if (block->GetLastInstr()->m_opcode == Js::OpCode::LeaveNull) |
| { |
| region = nullptr; |
| if (block->GetPredList()->Count() == 1) |
| { |
| FOREACH_PREDECESSOR_BLOCK(predBlock, block) |
| { |
| AssertMsg(predBlock->GetBlockNum() < this->blockCount, "Misnumbered block at teardown time?"); |
| predRegion = predBlock->GetFirstInstr()->AsLabelInstr()->GetRegion(); |
| if (predRegion) |
| { |
| region = this->PropagateRegionFromPred(block, predBlock, predRegion, tryInstr); |
| break; |
| } |
| } |
| NEXT_PREDECESSOR_BLOCK; |
| } |
| else |
| { |
| FOREACH_PREDECESSOR_BLOCK(predBlock, block) |
| { |
| AssertMsg(predBlock->GetBlockNum() < this->blockCount, "Misnumbered block at teardown time?"); |
| predRegion = predBlock->GetFirstInstr()->AsLabelInstr()->GetRegion(); |
| if (predRegion && predRegion->GetType() == RegionTypeFinally) |
| { |
| region = this->PropagateRegionFromPred(block, predBlock, predRegion, tryInstr); |
| break; |
| } |
| } |
| NEXT_PREDECESSOR_BLOCK; |
| } |
| if (this->regToFinallyEndMap) |
| { |
| regToFinallyEndMap->Item(region, block); |
| } |
| } |
| else |
| { |
| // Propagate the region forward by finding a predecessor we've already processed. |
| region = nullptr; |
| FOREACH_PREDECESSOR_BLOCK(predBlock, block) |
| { |
| AssertMsg(predBlock->GetBlockNum() < this->blockCount, "Misnumbered block at teardown time?"); |
| predRegion = predBlock->GetFirstInstr()->AsLabelInstr()->GetRegion(); |
| if (predRegion != nullptr) |
| { |
| region = this->PropagateRegionFromPred(block, predBlock, predRegion, tryInstr); |
| break; |
| } |
| } |
| NEXT_PREDECESSOR_BLOCK; |
| } |
| |
| Assert(region || block->GetPredList()->Count() == 0); |
| if (region && !region->ehBailoutData) |
| { |
| region->AllocateEHBailoutData(this->func, tryInstr); |
| } |
| |
| Assert(firstInstr->IsLabelInstr()); |
| if (firstInstr->IsLabelInstr()) |
| { |
| // Record the region on the label and make sure it stays around as a region |
| // marker if we're entering a region at this point. |
| IR::LabelInstr * labelInstr = firstInstr->AsLabelInstr(); |
| labelInstr->SetRegion(region); |
| if (region != predRegion) |
| { |
| labelInstr->m_hasNonBranchRef = true; |
| } |
| |
| if (region && this->func->HasFinally() && region->GetType() == RegionTypeRoot && !labelInstr->m_hasNonBranchRef) |
| { |
| FOREACH_PREDECESSOR_BLOCK(predBlock, block) |
| { |
| AssertMsg(predBlock->GetBlockNum() < this->blockCount, "Misnumbered block at teardown time?"); |
| predRegion = predBlock->GetFirstInstr()->AsLabelInstr()->GetRegion(); |
| if (predRegion != region) |
| { |
| labelInstr->m_hasNonBranchRef = true; |
| break; |
| } |
| } |
| NEXT_PREDECESSOR_BLOCK; |
| } |
| } |
| } |
| |
| void |
| FlowGraph::UpdateRegionForBlockFromEHPred(BasicBlock * block, bool reassign) |
| { |
| Region *region; |
| Region * predRegion = nullptr; |
| IR::Instr * tryInstr = nullptr; |
| IR::Instr * firstInstr = block->GetFirstInstr(); |
| if (!reassign && firstInstr->IsLabelInstr() && firstInstr->AsLabelInstr()->GetRegion()) |
| { |
| Assert(this->func->HasTry() && (this->func->DoOptimizeTry() || (this->func->IsSimpleJit() && this->func->hasBailout))); |
| return; |
| } |
| |
| if (block == this->blockList) |
| { |
| // Head of the graph: create the root region. |
| region = Region::New(RegionTypeRoot, nullptr, this->func); |
| } |
| else |
| { |
| // Propagate the region forward by finding a predecessor we've already processed. |
| // Since we do break block remval after region propagation, we cannot pick the first predecessor which has an assigned region |
| // If there is a eh transitioning pred, we pick that |
| // There cannot be more than one eh transitioning pred (?) |
| region = nullptr; |
| BasicBlock *ehPred = this->GetPredecessorForRegionPropagation(block); |
| if (ehPred) |
| { |
| predRegion = ehPred->GetFirstInstr()->AsLabelInstr()->GetRegion(); |
| Assert(predRegion != nullptr); |
| region = this->PropagateRegionFromPred(block, ehPred, predRegion, tryInstr); |
| } |
| else |
| { |
| FOREACH_PREDECESSOR_BLOCK(predBlock, block) |
| { |
| predRegion = predBlock->GetFirstInstr()->AsLabelInstr()->GetRegion(); |
| if (predRegion != nullptr) |
| { |
| if (predBlock->GetLastInstr()->m_opcode == Js::OpCode::BrOnException && predRegion->IsNonExceptingFinally()) |
| { |
| // early exit |
| continue; |
| } |
| if (predBlock->GetLastInstr()->m_opcode == Js::OpCode::BrOnException && predRegion->IsNonExceptingFinally()) |
| { |
| // early exit |
| continue; |
| } |
| region = this->PropagateRegionFromPred(block, predBlock, predRegion, tryInstr); |
| break; |
| } |
| } |
| NEXT_PREDECESSOR_BLOCK; |
| } |
| if (!region) |
| { |
| Assert(reassign && (block->GetPredList()->Count() == 0 || block->firstInstr->AsLabelInstr()->GetRegion())); |
| } |
| } |
| |
| if (region) |
| { |
| if (!region->ehBailoutData) |
| { |
| region->AllocateEHBailoutData(this->func, tryInstr); |
| } |
| |
| Assert(firstInstr->IsLabelInstr()); |
| if (firstInstr->IsLabelInstr()) |
| { |
| // Record the region on the label and make sure it stays around as a region |
| // marker if we're entering a region at this point. |
| IR::LabelInstr * labelInstr = firstInstr->AsLabelInstr(); |
| labelInstr->SetRegion(region); |
| if (region != predRegion) |
| { |
| labelInstr->m_hasNonBranchRef = true; |
| } |
| } |
| } |
| } |
| |
| Region * |
| FlowGraph::PropagateRegionFromPred(BasicBlock * block, BasicBlock * predBlock, Region * predRegion, IR::Instr * &tryInstr) |
| { |
| // Propagate predRegion to region, looking at the flow transition for an opcode |
| // that affects the region. |
| Region * region = nullptr; |
| IR::Instr * predLastInstr = predBlock->GetLastInstr(); |
| IR::Instr * firstInstr = block->GetFirstInstr(); |
| if (predLastInstr == nullptr) |
| { |
| // Empty block: trivially propagate the region. |
| region = predRegion; |
| } |
| else |
| { |
| Region * tryRegion = nullptr; |
| IR::LabelInstr * tryInstrNext = nullptr; |
| switch (predLastInstr->m_opcode) |
| { |
| case Js::OpCode::TryCatch: |
| // Entry to a try-catch. See whether we're entering the try or the catch |
| // by looking for the handler label. |
| Assert(predLastInstr->m_next->IsLabelInstr()); |
| tryInstrNext = predLastInstr->m_next->AsLabelInstr(); |
| tryRegion = tryInstrNext->GetRegion(); |
| |
| if (firstInstr == predLastInstr->AsBranchInstr()->GetTarget()) |
| { |
| region = Region::New(RegionTypeCatch, predRegion, this->func); |
| Assert(tryRegion); |
| region->SetMatchingTryRegion(tryRegion); |
| tryRegion->SetMatchingCatchRegion(region); |
| } |
| else |
| { |
| region = Region::New(RegionTypeTry, predRegion, this->func); |
| tryInstr = predLastInstr; |
| } |
| break; |
| |
| case Js::OpCode::TryFinally: |
| // Entry to a try-finally. See whether we're entering the try or the finally |
| // by looking for the handler label. |
| Assert(predLastInstr->m_next->IsLabelInstr()); |
| tryInstrNext = predLastInstr->m_next->AsLabelInstr(); |
| tryRegion = tryInstrNext->GetRegion(); |
| |
| if (firstInstr == predLastInstr->AsBranchInstr()->GetTarget()) |
| { |
| Assert(tryRegion && tryRegion->GetType() == RegionType::RegionTypeTry); |
| region = Region::New(RegionTypeFinally, predRegion, this->func); |
| region->SetMatchingTryRegion(tryRegion); |
| tryRegion->SetMatchingFinallyRegion(region, true); |
| tryInstr = predLastInstr; |
| } |
| else |
| { |
| region = Region::New(RegionTypeTry, predRegion, this->func); |
| tryInstr = predLastInstr; |
| } |
| break; |
| |
| case Js::OpCode::Leave: |
| // When there is an unconditional Leave within a finally due to early exit, |
| // we may not be able to find LeaveNull, due to unreachable block elimination |
| // Update the endOfFinally here to handle such cases |
| if (this->regToFinallyEndMap && predRegion->IsNonExceptingFinally()) |
| { |
| BasicBlock *endOfFinally = regToFinallyEndMap->ContainsKey(predRegion) ? regToFinallyEndMap->Item(predRegion) : nullptr; |
| if (!endOfFinally) |
| { |
| regToFinallyEndMap->Add(predRegion, predBlock); |
| } |
| else if (endOfFinally && endOfFinally->GetLastInstr()->m_opcode != Js::OpCode::LeaveNull) |
| { |
| regToFinallyEndMap->Item(predRegion, predBlock); |
| } |
| } |
| if (firstInstr->m_next && firstInstr->m_next->m_opcode == Js::OpCode::Finally) |
| { |
| tryRegion = predRegion; |
| Assert(tryRegion->GetMatchingFinallyRegion(true) != nullptr); |
| region = Region::New(RegionTypeFinally, predRegion->GetParent(), this->func); |
| Assert(tryRegion && tryRegion->GetType() == RegionType::RegionTypeTry); |
| region->SetMatchingTryRegion(tryRegion); |
| tryRegion->SetMatchingFinallyRegion(region, false); |
| break; |
| } |
| |
| // Exiting a try or handler. Retrieve the current region's parent. |
| region = predRegion->GetParent(); |
| if (region == nullptr) |
| { |
| // We found a Leave in the root region- this can only happen when a jitted loop body |
| // in a try block has a return statement. |
| Assert(this->func->IsLoopBodyInTry()); |
| predLastInstr->AsBranchInstr()->m_isOrphanedLeave = true; |
| region = predRegion; |
| } |
| break; |
| case Js::OpCode::LeaveNull: |
| // Exiting a try or handler. Retrieve the current region's parent. |
| region = predRegion->GetParent(); |
| if (region == nullptr) |
| { |
| // We found a Leave in the root region- this can only happen when a jitted loop body |
| // in a try block has a return statement. |
| Assert(this->func->IsLoopBodyInTry()); |
| predLastInstr->AsBranchInstr()->m_isOrphanedLeave = true; |
| region = predRegion; |
| } |
| break; |
| case Js::OpCode::BailOnException: |
| // Infinite loop, no edge to non excepting finally |
| if (firstInstr->m_next && firstInstr->m_next->m_opcode == Js::OpCode::Finally) |
| { |
| tryRegion = predRegion->GetMatchingTryRegion(); |
| region = Region::New(RegionTypeFinally, predRegion->GetParent(), this->func); |
| Assert(tryRegion && tryRegion->GetType() == RegionType::RegionTypeTry); |
| region->SetMatchingTryRegion(tryRegion); |
| tryRegion->SetMatchingFinallyRegion(region, false); |
| } |
| break; |
| case Js::OpCode::BrOnException: |
| // Infinite loop inside another EH region within finally, |
| // We have added edges for all infinite loops inside a finally, identify that and transition to parent |
| if (predRegion->GetType() != RegionTypeFinally && firstInstr->GetNextRealInstr()->m_opcode == Js::OpCode::LeaveNull) |
| { |
| region = predRegion->GetParent(); |
| } |
| else |
| { |
| region = predRegion; |
| } |
| break; |
| default: |
| // Normal (non-EH) transition: just propagate the region. |
| region = predRegion; |
| break; |
| } |
| } |
| return region; |
| } |
| |
| void |
| FlowGraph::InsertCompBlockToLoopList(Loop *loop, BasicBlock* compBlock, BasicBlock* targetBlock, bool postTarget) |
| { |
| if (loop) |
| { |
| bool found = false; |
| FOREACH_BLOCK_IN_LOOP_EDITING(loopBlock, loop, iter) |
| { |
| if (loopBlock == targetBlock) |
| { |
| found = true; |
| break; |
| } |
| } NEXT_BLOCK_IN_LOOP_EDITING; |
| if (found) |
| { |
| if (postTarget) |
| { |
| iter.Next(); |
| } |
| iter.InsertBefore(compBlock); |
| } |
| InsertCompBlockToLoopList(loop->parent, compBlock, targetBlock, postTarget); |
| } |
| } |
| |
| // Insert a block on the given edge |
| BasicBlock * |
| FlowGraph::InsertAirlockBlock(FlowEdge * edge) |
| { |
| BasicBlock * airlockBlock = BasicBlock::New(this); |
| BasicBlock * sourceBlock = edge->GetPred(); |
| BasicBlock * sinkBlock = edge->GetSucc(); |
| |
| BasicBlock * sinkPrevBlock = sinkBlock->prev; |
| IR::Instr * sinkPrevBlockLastInstr = sinkPrevBlock->GetLastInstr(); |
| IR::Instr * sourceLastInstr = sourceBlock->GetLastInstr(); |
| |
| airlockBlock->loop = sinkBlock->loop; |
| airlockBlock->SetBlockNum(this->blockCount++); |
| #ifdef DBG |
| airlockBlock->isAirLockBlock = true; |
| #endif |
| // |
| // Fixup block linkage |
| // |
| |
| // airlock block is inserted right before sourceBlock |
| airlockBlock->prev = sinkBlock->prev; |
| sinkBlock->prev = airlockBlock; |
| |
| airlockBlock->next = sinkBlock; |
| airlockBlock->prev->next = airlockBlock; |
| |
| // |
| // Fixup flow edges |
| // |
| |
| sourceBlock->RemoveSucc(sinkBlock, this, false); |
| |
| // Add sourceBlock -> airlockBlock |
| this->AddEdge(sourceBlock, airlockBlock); |
| |
| // Add airlockBlock -> sinkBlock |
| edge->SetPred(airlockBlock); |
| airlockBlock->AddSucc(edge, this); |
| |
| // Fixup data use count |
| airlockBlock->SetDataUseCount(1); |
| sourceBlock->DecrementDataUseCount(); |
| |
| // |
| // Fixup IR |
| // |
| |
| // Maintain the instruction region for inlining |
| IR::LabelInstr *sinkLabel = sinkBlock->GetFirstInstr()->AsLabelInstr(); |
| Func * sinkLabelFunc = sinkLabel->m_func; |
| |
| IR::LabelInstr *airlockLabel = IR::LabelInstr::New(Js::OpCode::Label, sinkLabelFunc); |
| |
| sinkPrevBlockLastInstr->InsertAfter(airlockLabel); |
| |
| airlockBlock->SetFirstInstr(airlockLabel); |
| airlockLabel->SetBasicBlock(airlockBlock); |
| |
| // Add br to sinkBlock from airlock block |
| IR::BranchInstr *airlockBr = IR::BranchInstr::New(Js::OpCode::Br, sinkLabel, sinkLabelFunc); |
| airlockBr->SetByteCodeOffset(sinkLabel); |
| airlockLabel->InsertAfter(airlockBr); |
| airlockBlock->SetLastInstr(airlockBr); |
| |
| airlockLabel->SetByteCodeOffset(sinkLabel); |
| |
| // Fixup flow out of sourceBlock |
| IR::BranchInstr *sourceBr = sourceLastInstr->AsBranchInstr(); |
| if (sourceBr->IsMultiBranch()) |
| { |
| const bool replaced = sourceBr->AsMultiBrInstr()->ReplaceTarget(sinkLabel, airlockLabel); |
| Assert(replaced); |
| } |
| else if (sourceBr->GetTarget() == sinkLabel) |
| { |
| sourceBr->SetTarget(airlockLabel); |
| } |
| |
| if (!sinkPrevBlockLastInstr->IsBranchInstr() || sinkPrevBlockLastInstr->AsBranchInstr()->HasFallThrough()) |
| { |
| if (!sinkPrevBlock->isDeleted) |
| { |
| FlowEdge *dstEdge = this->FindEdge(sinkPrevBlock, sinkBlock); |
| if (dstEdge) // Possibility that sourceblock may be same as sinkPrevBlock |
| { |
| BasicBlock* compensationBlock = this->InsertCompensationCodeForBlockMove(dstEdge, true /*insert comp block to loop list*/, true); |
| compensationBlock->IncrementDataUseCount(); |
| // We need to skip airlock compensation block in globopt as its inserted while globopt is iteration over the blocks. |
| compensationBlock->isAirLockCompensationBlock = true; |
| } |
| } |
| } |
| |
| #if DBG_DUMP |
| this->Dump(true, _u("\n After insertion of airlock block \n")); |
| #endif |
| |
| return airlockBlock; |
| } |
| |
| // Insert a block on the given edge |
| BasicBlock * |
| FlowGraph::InsertCompensationCodeForBlockMove(FlowEdge * edge, bool insertToLoopList, bool sinkBlockLoop) |
| { |
| BasicBlock * compBlock = BasicBlock::New(this); |
| BasicBlock * sourceBlock = edge->GetPred(); |
| BasicBlock * sinkBlock = edge->GetSucc(); |
| BasicBlock * fallthroughBlock = sourceBlock->next; |
| IR::Instr * sourceLastInstr = sourceBlock->GetLastInstr(); |
| |
| compBlock->SetBlockNum(this->blockCount++); |
| |
| if (insertToLoopList) |
| { |
| // For flow graph edits in |
| if (sinkBlockLoop) |
| { |
| if (sinkBlock->loop && sinkBlock->loop->GetHeadBlock() == sinkBlock) |
| { |
| // BLUE 531255: sinkblock may be the head block of new loop, we shouldn't insert compensation block to that loop |
| // Insert it to all the parent loop lists. |
| compBlock->loop = sinkBlock->loop->parent; |
| InsertCompBlockToLoopList(compBlock->loop, compBlock, sinkBlock, false); |
| } |
| else |
| { |
| compBlock->loop = sinkBlock->loop; |
| InsertCompBlockToLoopList(compBlock->loop, compBlock, sinkBlock, false); // sinkBlock or fallthroughBlock? |
| } |
| #ifdef DBG |
| compBlock->isBreakCompensationBlockAtSink = true; |
| #endif |
| } |
| else |
| { |
| compBlock->loop = sourceBlock->loop; |
| InsertCompBlockToLoopList(compBlock->loop, compBlock, sourceBlock, true); |
| #ifdef DBG |
| compBlock->isBreakCompensationBlockAtSource = true; |
| #endif |
| } |
| } |
| |
| // |
| // Fixup block linkage |
| // |
| |
| // compensation block is inserted right after sourceBlock |
| compBlock->next = fallthroughBlock; |
| fallthroughBlock->prev = compBlock; |
| |
| compBlock->prev = sourceBlock; |
| sourceBlock->next = compBlock; |
| |
| // |
| // Fixup flow edges |
| // |
| sourceBlock->RemoveSucc(sinkBlock, this, false); |
| |
| // Add sourceBlock -> airlockBlock |
| this->AddEdge(sourceBlock, compBlock); |
| |
| // Add airlockBlock -> sinkBlock |
| edge->SetPred(compBlock); |
| compBlock->AddSucc(edge, this); |
| |
| // |
| // Fixup IR |
| // |
| |
| // Maintain the instruction region for inlining |
| IR::LabelInstr *sinkLabel = sinkBlock->GetFirstInstr()->AsLabelInstr(); |
| Func * sinkLabelFunc = sinkLabel->m_func; |
| |
| IR::LabelInstr *compLabel = IR::LabelInstr::New(Js::OpCode::Label, sinkLabelFunc); |
| sourceLastInstr->InsertAfter(compLabel); |
| compBlock->SetFirstInstr(compLabel); |
| compLabel->SetBasicBlock(compBlock); |
| |
| // Add br to sinkBlock from compensation block |
| IR::BranchInstr *compBr = IR::BranchInstr::New(Js::OpCode::Br, sinkLabel, sinkLabelFunc); |
| compBr->SetByteCodeOffset(sinkLabel); |
| compLabel->InsertAfter(compBr); |
| compBlock->SetLastInstr(compBr); |
| |
| compLabel->SetByteCodeOffset(sinkLabel); |
| |
| // Fixup flow out of sourceBlock |
| if (sourceLastInstr->IsBranchInstr()) |
| { |
| IR::BranchInstr *sourceBr = sourceLastInstr->AsBranchInstr(); |
| Assert(sourceBr->IsMultiBranch() || sourceBr->IsConditional()); |
| if (sourceBr->IsMultiBranch()) |
| { |
| const bool replaced = sourceBr->AsMultiBrInstr()->ReplaceTarget(sinkLabel, compLabel); |
| Assert(replaced); |
| } |
| } |
| |
| bool assignRegionsBeforeGlobopt = this->func->HasTry() && (this->func->DoOptimizeTry() || |
| (this->func->IsSimpleJit() && this->func->hasBailout)); |
| // MGTODO : maybe we can just set the pred region instead of calling Propagate function ? |
| if (assignRegionsBeforeGlobopt) |
| { |
| UpdateRegionForBlockFromEHPred(compBlock); |
| } |
| |
| return compBlock; |
| } |
| |
| void |
| FlowGraph::RemoveUnreachableBlocks() |
| { |
| AnalysisAssert(this->blockList); |
| |
| FOREACH_BLOCK(block, this) |
| { |
| block->isVisited = false; |
| } |
| NEXT_BLOCK; |
| |
| this->blockList->isVisited = true; |
| |
| FOREACH_BLOCK_EDITING(block, this) |
| { |
| if (block->isVisited) |
| { |
| FOREACH_SUCCESSOR_BLOCK(succ, block) |
| { |
| succ->isVisited = true; |
| } NEXT_SUCCESSOR_BLOCK; |
| } |
| else |
| { |
| this->RemoveBlock(block); |
| } |
| } |
| NEXT_BLOCK_EDITING; |
| } |
| |
| // If block has no predecessor, remove it. |
| bool |
| FlowGraph::RemoveUnreachableBlock(BasicBlock *block, GlobOpt * globOpt) |
| { |
| bool isDead = false; |
| |
| if ((block->GetPredList() == nullptr || block->GetPredList()->Empty()) && block != this->func->m_fg->blockList) |
| { |
| isDead = true; |
| } |
| else if (block->isLoopHeader) |
| { |
| // A dead loop still has back-edges pointing to it... |
| isDead = true; |
| FOREACH_PREDECESSOR_BLOCK(pred, block) |
| { |
| if (!block->loop->IsDescendentOrSelf(pred->loop)) |
| { |
| isDead = false; |
| } |
| } NEXT_PREDECESSOR_BLOCK; |
| } |
| |
| if (isDead) |
| { |
| this->RemoveBlock(block, globOpt); |
| return true; |
| } |
| return false; |
| } |
| |
| IR::Instr * |
| FlowGraph::PeepTypedCm(IR::Instr *instr) |
| { |
| // Basic pattern, peep: |
| // t1 = CmEq a, b |
| // BrTrue_I4 $L1, t1 |
| // Into: |
| // t1 = 1 |
| // BrEq $L1, a, b |
| // t1 = 0 |
| |
| IR::Instr * instrNext = instr->GetNextRealInstrOrLabel(); |
| |
| // find intermediate Lds |
| IR::Instr * instrLd = nullptr; |
| if (instrNext->m_opcode == Js::OpCode::Ld_I4) |
| { |
| instrLd = instrNext; |
| instrNext = instrNext->GetNextRealInstrOrLabel(); |
| } |
| |
| IR::Instr * instrLd2 = nullptr; |
| if (instrNext->m_opcode == Js::OpCode::Ld_I4) |
| { |
| instrLd2 = instrNext; |
| instrNext = instrNext->GetNextRealInstrOrLabel(); |
| } |
| |
| // Find BrTrue/BrFalse |
| IR::Instr *instrBr; |
| bool brIsTrue; |
| if (instrNext->m_opcode == Js::OpCode::BrTrue_I4) |
| { |
| instrBr = instrNext; |
| brIsTrue = true; |
| } |
| else if (instrNext->m_opcode == Js::OpCode::BrFalse_I4) |
| { |
| instrBr = instrNext; |
| brIsTrue = false; |
| } |
| else |
| { |
| return nullptr; |
| } |
| |
| AssertMsg(instrLd || (!instrLd && !instrLd2), "Either instrLd is non-null or both null"); |
| |
| // if we have intermediate Lds, then make sure pattern is: |
| // t1 = CmEq a, b |
| // t2 = Ld_A t1 |
| // BrTrue $L1, t2 |
| if (instrLd && !instrLd->GetSrc1()->IsEqual(instr->GetDst())) |
| { |
| return nullptr; |
| } |
| |
| if (instrLd2 && !instrLd2->GetSrc1()->IsEqual(instrLd->GetDst())) |
| { |
| return nullptr; |
| } |
| |
| // Make sure we have: |
| // t1 = CmEq a, b |
| // BrTrue/BrFalse t1 |
| if (!(instr->GetDst()->IsEqual(instrBr->GetSrc1()) || (instrLd && instrLd->GetDst()->IsEqual(instrBr->GetSrc1())) || (instrLd2 && instrLd2->GetDst()->IsEqual(instrBr->GetSrc1())))) |
| { |
| return nullptr; |
| } |
| |
| IR::Opnd * src1 = instr->UnlinkSrc1(); |
| IR::Opnd * src2 = instr->UnlinkSrc2(); |
| |
| IR::Instr * instrNew; |
| IR::Opnd * tmpOpnd; |
| if (instr->GetDst()->IsEqual(src1) || (instrLd && instrLd->GetDst()->IsEqual(src1)) || (instrLd2 && instrLd2->GetDst()->IsEqual(src1))) |
| { |
| Assert(src1->IsInt32()); |
| |
| tmpOpnd = IR::RegOpnd::New(TyInt32, instr->m_func); |
| instrNew = IR::Instr::New(Js::OpCode::Ld_I4, tmpOpnd, src1, instr->m_func); |
| instrNew->SetByteCodeOffset(instr); |
| instr->InsertBefore(instrNew); |
| src1 = tmpOpnd; |
| } |
| |
| if (instr->GetDst()->IsEqual(src2) || (instrLd && instrLd->GetDst()->IsEqual(src2)) || (instrLd2 && instrLd2->GetDst()->IsEqual(src2))) |
| { |
| Assert(src2->IsInt32()); |
| |
| tmpOpnd = IR::RegOpnd::New(TyInt32, instr->m_func); |
| instrNew = IR::Instr::New(Js::OpCode::Ld_I4, tmpOpnd, src2, instr->m_func); |
| instrNew->SetByteCodeOffset(instr); |
| instr->InsertBefore(instrNew); |
| src2 = tmpOpnd; |
| } |
| |
| instrBr->ReplaceSrc1(src1); |
| instrBr->SetSrc2(src2); |
| |
| Js::OpCode newOpcode; |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::CmEq_I4: |
| newOpcode = Js::OpCode::BrEq_I4; |
| break; |
| case Js::OpCode::CmGe_I4: |
| newOpcode = Js::OpCode::BrGe_I4; |
| break; |
| case Js::OpCode::CmGt_I4: |
| newOpcode = Js::OpCode::BrGt_I4; |
| break; |
| case Js::OpCode::CmLt_I4: |
| newOpcode = Js::OpCode::BrLt_I4; |
| break; |
| case Js::OpCode::CmLe_I4: |
| newOpcode = Js::OpCode::BrLe_I4; |
| break; |
| case Js::OpCode::CmUnGe_I4: |
| newOpcode = Js::OpCode::BrUnGe_I4; |
| break; |
| case Js::OpCode::CmUnGt_I4: |
| newOpcode = Js::OpCode::BrUnGt_I4; |
| break; |
| case Js::OpCode::CmUnLt_I4: |
| newOpcode = Js::OpCode::BrUnLt_I4; |
| break; |
| case Js::OpCode::CmUnLe_I4: |
| newOpcode = Js::OpCode::BrUnLe_I4; |
| break; |
| case Js::OpCode::CmNeq_I4: |
| newOpcode = Js::OpCode::BrNeq_I4; |
| break; |
| case Js::OpCode::CmEq_A: |
| newOpcode = Js::OpCode::BrEq_A; |
| break; |
| case Js::OpCode::CmGe_A: |
| newOpcode = Js::OpCode::BrGe_A; |
| break; |
| case Js::OpCode::CmGt_A: |
| newOpcode = Js::OpCode::BrGt_A; |
| break; |
| case Js::OpCode::CmLt_A: |
| newOpcode = Js::OpCode::BrLt_A; |
| break; |
| case Js::OpCode::CmLe_A: |
| newOpcode = Js::OpCode::BrLe_A; |
| break; |
| case Js::OpCode::CmUnGe_A: |
| newOpcode = Js::OpCode::BrUnGe_A; |
| break; |
| case Js::OpCode::CmUnGt_A: |
| newOpcode = Js::OpCode::BrUnGt_A; |
| break; |
| case Js::OpCode::CmUnLt_A: |
| newOpcode = Js::OpCode::BrUnLt_A; |
| break; |
| case Js::OpCode::CmUnLe_A: |
| newOpcode = Js::OpCode::BrUnLe_A; |
| break; |
| case Js::OpCode::CmNeq_A: |
| newOpcode = Js::OpCode::BrNeq_A; |
| break; |
| case Js::OpCode::CmSrEq_A: |
| newOpcode = Js::OpCode::BrSrEq_A; |
| break; |
| case Js::OpCode::CmSrNeq_A: |
| newOpcode = Js::OpCode::BrSrNeq_A; |
| break; |
| default: |
| newOpcode = Js::OpCode::InvalidOpCode; |
| Assume(UNREACHED); |
| } |
| |
| instrBr->m_opcode = newOpcode; |
| |
| if (brIsTrue) |
| { |
| instr->SetSrc1(IR::IntConstOpnd::New(1, TyInt8, instr->m_func)); |
| instr->m_opcode = Js::OpCode::Ld_I4; |
| instrNew = IR::Instr::New(Js::OpCode::Ld_I4, instr->GetDst(), IR::IntConstOpnd::New(0, TyInt8, instr->m_func), instr->m_func); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrBr->InsertAfter(instrNew); |
| if (instrLd) |
| { |
| instrLd->ReplaceSrc1(IR::IntConstOpnd::New(1, TyInt8, instr->m_func)); |
| instrNew = IR::Instr::New(Js::OpCode::Ld_I4, instrLd->GetDst(), IR::IntConstOpnd::New(0, TyInt8, instr->m_func), instr->m_func); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrBr->InsertAfter(instrNew); |
| |
| if (instrLd2) |
| { |
| instrLd2->ReplaceSrc1(IR::IntConstOpnd::New(1, TyInt8, instr->m_func)); |
| instrNew = IR::Instr::New(Js::OpCode::Ld_I4, instrLd2->GetDst(), IR::IntConstOpnd::New(0, TyInt8, instr->m_func), instr->m_func); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrBr->InsertAfter(instrNew); |
| } |
| } |
| } |
| else |
| { |
| instrBr->AsBranchInstr()->Invert(); |
| |
| instr->SetSrc1(IR::IntConstOpnd::New(0, TyInt8, instr->m_func)); |
| instr->m_opcode = Js::OpCode::Ld_I4; |
| instrNew = IR::Instr::New(Js::OpCode::Ld_I4, instr->GetDst(), IR::IntConstOpnd::New(1, TyInt8, instr->m_func), instr->m_func); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrBr->InsertAfter(instrNew); |
| if (instrLd) |
| { |
| instrLd->ReplaceSrc1(IR::IntConstOpnd::New(0, TyInt8, instr->m_func)); |
| instrNew = IR::Instr::New(Js::OpCode::Ld_I4, instrLd->GetDst(), IR::IntConstOpnd::New(1, TyInt8, instr->m_func), instr->m_func); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrBr->InsertAfter(instrNew); |
| |
| if (instrLd2) |
| { |
| instrLd2->ReplaceSrc1(IR::IntConstOpnd::New(0, TyInt8, instr->m_func)); |
| instrNew = IR::Instr::New(Js::OpCode::Ld_I4, instrLd2->GetDst(), IR::IntConstOpnd::New(1, TyInt8, instr->m_func), instr->m_func); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrBr->InsertAfter(instrNew); |
| } |
| } |
| } |
| |
| return instrBr; |
| } |
| |
| IR::Instr * |
| FlowGraph::PeepCm(IR::Instr *instr) |
| { |
| // Basic pattern, peep: |
| // t1 = CmEq a, b |
| // t2 = Ld_A t1 |
| // BrTrue $L1, t2 |
| // Into: |
| // t1 = True |
| // t2 = True |
| // BrEq $L1, a, b |
| // t1 = False |
| // t2 = False |
| // |
| // The true/false Ld_A's will most likely end up being dead-stores... |
| |
| // Alternate Pattern |
| // t1= CmEq a, b |
| // BrTrue $L1, t1 |
| // Into: |
| // BrEq $L1, a, b |
| |
| Func *func = instr->m_func; |
| |
| // Find Ld_A |
| IR::Instr *instrNext = instr->GetNextRealInstrOrLabel(); |
| IR::Instr *inlineeEndInstr = nullptr; |
| IR::Instr *instrNew; |
| IR::Instr *instrLd = nullptr, *instrLd2 = nullptr; |
| IR::Instr *instrByteCode = instrNext; |
| bool ldFound = false; |
| IR::Opnd *brSrc = instr->GetDst(); |
| |
| if (instrNext->m_opcode == Js::OpCode::Ld_A && instrNext->GetSrc1()->IsEqual(instr->GetDst())) |
| { |
| ldFound = true; |
| instrLd = instrNext; |
| brSrc = instrNext->GetDst(); |
| |
| if (brSrc->IsEqual(instr->GetSrc1()) || brSrc->IsEqual(instr->GetSrc2())) |
| { |
| return nullptr; |
| } |
| |
| instrNext = instrLd->GetNextRealInstrOrLabel(); |
| |
| // Is there a second Ld_A? |
| if (instrNext->m_opcode == Js::OpCode::Ld_A && instrNext->GetSrc1()->IsEqual(brSrc)) |
| { |
| // We have: |
| // t1 = Cm |
| // t2 = t1 // ldSrc = t1 |
| // t3 = t2 // ldDst = t3 |
| // BrTrue/BrFalse t3 |
| |
| instrLd2 = instrNext; |
| brSrc = instrLd2->GetDst(); |
| instrNext = instrLd2->GetNextRealInstrOrLabel(); |
| if (brSrc->IsEqual(instr->GetSrc1()) || brSrc->IsEqual(instr->GetSrc2())) |
| { |
| return nullptr; |
| } |
| } |
| } |
| |
| // Skip over InlineeEnd |
| if (instrNext->m_opcode == Js::OpCode::InlineeEnd) |
| { |
| inlineeEndInstr = instrNext; |
| instrNext = inlineeEndInstr->GetNextRealInstrOrLabel(); |
| } |
| |
| // Find BrTrue/BrFalse |
| bool brIsTrue; |
| if (instrNext->m_opcode == Js::OpCode::BrTrue_A) |
| { |
| brIsTrue = true; |
| } |
| else if (instrNext->m_opcode == Js::OpCode::BrFalse_A) |
| { |
| brIsTrue = false; |
| } |
| else |
| { |
| return nullptr; |
| } |
| |
| IR::Instr *instrBr = instrNext; |
| |
| // Make sure we have: |
| // t1 = Ld_A |
| // BrTrue/BrFalse t1 |
| if (!instr->GetDst()->IsEqual(instrBr->GetSrc1()) && !brSrc->IsEqual(instrBr->GetSrc1())) |
| { |
| return nullptr; |
| } |
| |
| // |
| // We have a match. Generate the new branch |
| // |
| |
| // BrTrue/BrFalse t1 |
| // Keep a copy of the inliner func and the bytecode offset of the original BrTrue/BrFalse if we end up inserting a new branch out of the inlinee, |
| // and sym id of t1 for proper restoration on a bailout before the branch. |
| Func* origBrFunc = instrBr->m_func; |
| uint32 origBrByteCodeOffset = instrBr->GetByteCodeOffset(); |
| uint32 origBranchSrcSymId = instrBr->GetSrc1()->GetStackSym()->m_id; |
| bool origBranchSrcOpndIsJITOpt = instrBr->GetSrc1()->GetIsJITOptimizedReg(); |
| |
| instrBr->Unlink(); |
| instr->InsertBefore(instrBr); |
| instrBr->ClearByteCodeOffset(); |
| instrBr->SetByteCodeOffset(instr); |
| instrBr->FreeSrc1(); |
| instrBr->SetSrc1(instr->UnlinkSrc1()); |
| instrBr->SetSrc2(instr->UnlinkSrc2()); |
| instrBr->m_func = instr->m_func; |
| |
| Js::OpCode newOpcode; |
| |
| switch(instr->m_opcode) |
| { |
| case Js::OpCode::CmEq_A: |
| newOpcode = Js::OpCode::BrEq_A; |
| break; |
| case Js::OpCode::CmGe_A: |
| newOpcode = Js::OpCode::BrGe_A; |
| break; |
| case Js::OpCode::CmGt_A: |
| newOpcode = Js::OpCode::BrGt_A; |
| break; |
| case Js::OpCode::CmLt_A: |
| newOpcode = Js::OpCode::BrLt_A; |
| break; |
| case Js::OpCode::CmLe_A: |
| newOpcode = Js::OpCode::BrLe_A; |
| break; |
| case Js::OpCode::CmUnGe_A: |
| newOpcode = Js::OpCode::BrUnGe_A; |
| break; |
| case Js::OpCode::CmUnGt_A: |
| newOpcode = Js::OpCode::BrUnGt_A; |
| break; |
| case Js::OpCode::CmUnLt_A: |
| newOpcode = Js::OpCode::BrUnLt_A; |
| break; |
| case Js::OpCode::CmUnLe_A: |
| newOpcode = Js::OpCode::BrUnLe_A; |
| break; |
| case Js::OpCode::CmNeq_A: |
| newOpcode = Js::OpCode::BrNeq_A; |
| break; |
| case Js::OpCode::CmSrEq_A: |
| newOpcode = Js::OpCode::BrSrEq_A; |
| break; |
| case Js::OpCode::CmSrNeq_A: |
| newOpcode = Js::OpCode::BrSrNeq_A; |
| break; |
| default: |
| Assert(UNREACHED); |
| __assume(UNREACHED); |
| } |
| |
| instrBr->m_opcode = newOpcode; |
| |
| IR::AddrOpnd* trueOpnd = IR::AddrOpnd::New(func->GetScriptContextInfo()->GetTrueAddr(), IR::AddrOpndKindDynamicVar, func, true); |
| IR::AddrOpnd* falseOpnd = IR::AddrOpnd::New(func->GetScriptContextInfo()->GetFalseAddr(), IR::AddrOpndKindDynamicVar, func, true); |
| |
| trueOpnd->SetValueType(ValueType::Boolean); |
| falseOpnd->SetValueType(ValueType::Boolean); |
| |
| if (ldFound) |
| { |
| // Split Ld_A into "Ld_A TRUE"/"Ld_A FALSE" |
| if (brIsTrue) |
| { |
| instrNew = IR::Instr::New(Js::OpCode::Ld_A, instrLd->GetSrc1(), trueOpnd, instrBr->m_func); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrNew->GetDst()->AsRegOpnd()->m_fgPeepTmp = true; |
| instrBr->InsertBefore(instrNew); |
| instrNew = IR::Instr::New(Js::OpCode::Ld_A, instrLd->GetDst(), trueOpnd, instrBr->m_func); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrNew->GetDst()->AsRegOpnd()->m_fgPeepTmp = true; |
| instrBr->InsertBefore(instrNew); |
| |
| instrNew = IR::Instr::New(Js::OpCode::Ld_A, instrLd->GetSrc1(), falseOpnd, instrLd->m_func); |
| instrLd->InsertBefore(instrNew); |
| instrNew->SetByteCodeOffset(instrLd); |
| instrNew->GetDst()->AsRegOpnd()->m_fgPeepTmp = true; |
| instrLd->ReplaceSrc1(falseOpnd); |
| |
| if (instrLd2) |
| { |
| instrLd2->ReplaceSrc1(falseOpnd); |
| |
| instrNew = IR::Instr::New(Js::OpCode::Ld_A, instrLd2->GetDst(), trueOpnd, instrBr->m_func); |
| instrBr->InsertBefore(instrNew); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrNew->GetDst()->AsRegOpnd()->m_fgPeepTmp = true; |
| } |
| } |
| else |
| { |
| instrBr->AsBranchInstr()->Invert(); |
| |
| instrNew = IR::Instr::New(Js::OpCode::Ld_A, instrLd->GetSrc1(), falseOpnd, instrBr->m_func); |
| instrBr->InsertBefore(instrNew); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrNew->GetDst()->AsRegOpnd()->m_fgPeepTmp = true; |
| instrNew = IR::Instr::New(Js::OpCode::Ld_A, instrLd->GetDst(), falseOpnd, instrBr->m_func); |
| instrBr->InsertBefore(instrNew); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrNew->GetDst()->AsRegOpnd()->m_fgPeepTmp = true; |
| |
| instrNew = IR::Instr::New(Js::OpCode::Ld_A, instrLd->GetSrc1(), trueOpnd, instrLd->m_func); |
| instrLd->InsertBefore(instrNew); |
| instrNew->SetByteCodeOffset(instrLd); |
| instrLd->ReplaceSrc1(trueOpnd); |
| instrNew->GetDst()->AsRegOpnd()->m_fgPeepTmp = true; |
| |
| if (instrLd2) |
| { |
| instrLd2->ReplaceSrc1(trueOpnd); |
| instrNew = IR::Instr::New(Js::OpCode::Ld_A, instrLd->GetSrc1(), trueOpnd, instrBr->m_func); |
| instrBr->InsertBefore(instrNew); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrNew->GetDst()->AsRegOpnd()->m_fgPeepTmp = true; |
| } |
| } |
| } |
| |
| // Fix InlineeEnd |
| if (inlineeEndInstr) |
| { |
| this->InsertInlineeOnFLowEdge(instrBr->AsBranchInstr(), inlineeEndInstr, instrByteCode , origBrFunc, origBrByteCodeOffset, origBranchSrcOpndIsJITOpt, origBranchSrcSymId); |
| } |
| |
| if (instr->GetDst()->AsRegOpnd()->m_sym->HasByteCodeRegSlot()) |
| { |
| Assert(!instrBr->AsBranchInstr()->HasByteCodeReg()); |
| StackSym *dstSym = instr->GetDst()->AsRegOpnd()->m_sym; |
| instrBr->AsBranchInstr()->SetByteCodeReg(dstSym->GetByteCodeRegSlot()); |
| } |
| instr->Remove(); |
| |
| // |
| // Try optimizing through a second branch. |
| // Peep: |
| // |
| // t2 = True; |
| // BrTrue $L1 |
| // ... |
| // L1: |
| // t1 = Ld_A t2 |
| // BrTrue $L2 |
| // |
| // Into: |
| // t2 = True; |
| // t1 = True; |
| // BrTrue $L2 <--- |
| // ... |
| // L1: |
| // t1 = Ld_A t2 |
| // BrTrue $L2 |
| // |
| // This cleanup helps expose second level Cm peeps. |
| |
| IR::Instr *instrLd3 = instrBr->AsBranchInstr()->GetTarget()->GetNextRealInstrOrLabel(); |
| |
| // Skip over branch to branch |
| while (instrLd3->m_opcode == Js::OpCode::Br) |
| { |
| instrLd3 = instrLd3->AsBranchInstr()->GetTarget()->GetNextRealInstrOrLabel(); |
| } |
| |
| // Find Ld_A |
| if (instrLd3->m_opcode != Js::OpCode::Ld_A) |
| { |
| return instrBr; |
| } |
| |
| IR::Instr *instrBr2 = instrLd3->GetNextRealInstrOrLabel(); |
| IR::Instr *inlineeEndInstr2 = nullptr; |
| |
| // InlineeEnd? |
| // REVIEW: Can we handle 2 inlineeEnds? |
| if (instrBr2->m_opcode == Js::OpCode::InlineeEnd && !inlineeEndInstr) |
| { |
| inlineeEndInstr2 = instrBr2; |
| instrBr2 = instrBr2->GetNextRealInstrOrLabel(); |
| } |
| |
| // Find branch |
| bool brIsTrue2; |
| if (instrBr2->m_opcode == Js::OpCode::BrTrue_A) |
| { |
| brIsTrue2 = true; |
| } |
| else if (instrBr2->m_opcode == Js::OpCode::BrFalse_A) |
| { |
| brIsTrue2 = false; |
| } |
| else |
| { |
| return nullptr; |
| } |
| |
| // Make sure Ld_A operates on the right tmps. |
| if (!instrLd3->GetDst()->IsEqual(instrBr2->GetSrc1()) || !brSrc->IsEqual(instrLd3->GetSrc1())) |
| { |
| return nullptr; |
| } |
| |
| if (instrLd3->GetDst()->IsEqual(instrBr->GetSrc1()) || instrLd3->GetDst()->IsEqual(instrBr->GetSrc2())) |
| { |
| return nullptr; |
| } |
| |
| // Make sure that the reg we're assigning to is not live in the intervening instructions (if this is a forward branch). |
| if (instrLd3->GetByteCodeOffset() > instrBr->GetByteCodeOffset()) |
| { |
| StackSym *symLd3 = instrLd3->GetDst()->AsRegOpnd()->m_sym; |
| if (IR::Instr::FindRegUseInRange(symLd3, instrBr->m_next, instrLd3)) |
| { |
| return nullptr; |
| } |
| } |
| |
| // |
| // We have a match! |
| // |
| |
| if(inlineeEndInstr2) |
| { |
| origBrFunc = instrBr2->m_func; |
| origBrByteCodeOffset = instrBr2->GetByteCodeOffset(); |
| origBranchSrcSymId = instrBr2->GetSrc1()->GetStackSym()->m_id; |
| } |
| |
| // Fix Ld_A |
| if (brIsTrue) |
| { |
| instrNew = IR::Instr::New(Js::OpCode::Ld_A, instrLd3->GetDst(), trueOpnd, instrBr->m_func); |
| instrBr->InsertBefore(instrNew); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrNew->GetDst()->AsRegOpnd()->m_fgPeepTmp = true; |
| } |
| else |
| { |
| instrNew = IR::Instr::New(Js::OpCode::Ld_A, instrLd3->GetDst(), falseOpnd, instrBr->m_func); |
| instrBr->InsertBefore(instrNew); |
| instrNew->SetByteCodeOffset(instrBr); |
| instrNew->GetDst()->AsRegOpnd()->m_fgPeepTmp = true; |
| } |
| |
| IR::LabelInstr *brTarget2; |
| |
| // Retarget branch |
| if (brIsTrue2 == brIsTrue) |
| { |
| brTarget2 = instrBr2->AsBranchInstr()->GetTarget(); |
| } |
| else |
| { |
| brTarget2 = IR::LabelInstr::New(Js::OpCode::Label, instrBr2->m_func); |
| brTarget2->SetByteCodeOffset(instrBr2->m_next); |
| instrBr2->InsertAfter(brTarget2); |
| } |
| |
| instrBr->AsBranchInstr()->SetTarget(brTarget2); |
| |
| // InlineeEnd? |
| if (inlineeEndInstr2) |
| { |
| this->InsertInlineeOnFLowEdge(instrBr->AsBranchInstr(), inlineeEndInstr2, instrByteCode, origBrFunc, origBrByteCodeOffset, origBranchSrcOpndIsJITOpt, origBranchSrcSymId); |
| } |
| |
| return instrBr; |
| } |
| |
| void |
| FlowGraph::InsertInlineeOnFLowEdge(IR::BranchInstr *instrBr, IR::Instr *inlineeEndInstr, IR::Instr *instrBytecode, Func* origBrFunc, uint32 origByteCodeOffset, bool origBranchSrcOpndIsJITOpt, uint32 origBranchSrcSymId) |
| { |
| // Helper for PeepsCm code. |
| // |
| // We've skipped some InlineeEnd. Globopt expects to see these |
| // on all flow paths out of the inlinee. Insert an InlineeEnd |
| // on the new path: |
| // BrEq $L1, a, b |
| // Becomes: |
| // BrNeq $L2, a, b |
| // InlineeEnd |
| // Br $L1 |
| // L2: |
| |
| instrBr->AsBranchInstr()->Invert(); |
| |
| IR::BranchInstr *newBr = IR::BranchInstr::New(Js::OpCode::Br, instrBr->AsBranchInstr()->GetTarget(), origBrFunc); |
| newBr->SetByteCodeOffset(origByteCodeOffset); |
| instrBr->InsertAfter(newBr); |
| |
| IR::LabelInstr *newLabel = IR::LabelInstr::New(Js::OpCode::Label, instrBr->m_func); |
| newLabel->SetByteCodeOffset(instrBytecode); |
| newBr->InsertAfter(newLabel); |
| instrBr->AsBranchInstr()->SetTarget(newLabel); |
| |
| IR::Instr *newInlineeEnd = IR::Instr::New(Js::OpCode::InlineeEnd, inlineeEndInstr->m_func); |
| newInlineeEnd->SetSrc1(inlineeEndInstr->GetSrc1()); |
| newInlineeEnd->SetSrc2(inlineeEndInstr->GetSrc2()); |
| newInlineeEnd->SetByteCodeOffset(instrBytecode); |
| newInlineeEnd->SetIsCloned(true); // Mark it as cloned - this is used later by the inlinee args optimization |
| newBr->InsertBefore(newInlineeEnd); |
| |
| IR::ByteCodeUsesInstr * useOrigBranchSrcInstr = IR::ByteCodeUsesInstr::New(origBrFunc, origByteCodeOffset); |
| useOrigBranchSrcInstr->SetRemovedOpndSymbol(origBranchSrcOpndIsJITOpt, origBranchSrcSymId); |
| newBr->InsertBefore(useOrigBranchSrcInstr); |
| |
| uint newBrFnNumber = newBr->m_func->GetFunctionNumber(); |
| Assert(newBrFnNumber == origBrFunc->GetFunctionNumber()); |
| |
| // The function numbers of the new branch and the inlineeEnd instruction should be different (ensuring that the new branch is not added in the inlinee but in the inliner). |
| // Only case when they can be same is recursive calls - inlinee and inliner are the same function |
| Assert(newBrFnNumber != inlineeEndInstr->m_func->GetFunctionNumber() || |
| newBrFnNumber == inlineeEndInstr->m_func->GetParentFunc()->GetFunctionNumber()); |
| } |
| |
| BasicBlock * |
| BasicBlock::New(FlowGraph * graph) |
| { |
| BasicBlock * block; |
| |
| block = JitAnew(graph->alloc, BasicBlock, graph->alloc, graph->GetFunc()); |
| |
| return block; |
| } |
| |
| void |
| BasicBlock::AddPred(FlowEdge * edge, FlowGraph * graph) |
| { |
| this->predList.Prepend(graph->alloc, edge); |
| } |
| |
| void |
| BasicBlock::AddSucc(FlowEdge * edge, FlowGraph * graph) |
| { |
| this->succList.Prepend(graph->alloc, edge); |
| } |
| |
| void |
| BasicBlock::RemovePred(BasicBlock *block, FlowGraph * graph) |
| { |
| this->RemovePred(block, graph, true, false); |
| } |
| |
| void |
| BasicBlock::RemoveSucc(BasicBlock *block, FlowGraph * graph) |
| { |
| this->RemoveSucc(block, graph, true, false); |
| } |
| |
| void |
| BasicBlock::RemoveDeadPred(BasicBlock *block, FlowGraph * graph) |
| { |
| this->RemovePred(block, graph, true, true); |
| } |
| |
| void |
| BasicBlock::RemoveDeadSucc(BasicBlock *block, FlowGraph * graph) |
| { |
| this->RemoveSucc(block, graph, true, true); |
| } |
| |
| void |
| BasicBlock::RemovePred(BasicBlock *block, FlowGraph * graph, bool doCleanSucc, bool moveToDead) |
| { |
| FOREACH_SLISTBASECOUNTED_ENTRY_EDITING(FlowEdge*, edge, this->GetPredList(), iter) |
| { |
| if (edge->GetPred() == block) |
| { |
| BasicBlock *blockSucc = edge->GetSucc(); |
| if (moveToDead) |
| { |
| iter.MoveCurrentTo(this->GetDeadPredList()); |
| |
| } |
| else |
| { |
| iter.RemoveCurrent(graph->alloc); |
| } |
| if (doCleanSucc) |
| { |
| block->RemoveSucc(this, graph, false, moveToDead); |
| } |
| if (blockSucc->isLoopHeader && blockSucc->loop && blockSucc->GetPredList()->HasOne()) |
| { |
| Loop *loop = blockSucc->loop; |
| loop->isDead = true; |
| } |
| return; |
| } |
| } NEXT_SLISTBASECOUNTED_ENTRY_EDITING; |
| AssertMsg(UNREACHED, "Edge not found."); |
| } |
| |
| void |
| BasicBlock::RemoveSucc(BasicBlock *block, FlowGraph * graph, bool doCleanPred, bool moveToDead) |
| { |
| FOREACH_SLISTBASECOUNTED_ENTRY_EDITING(FlowEdge*, edge, this->GetSuccList(), iter) |
| { |
| if (edge->GetSucc() == block) |
| { |
| if (moveToDead) |
| { |
| iter.MoveCurrentTo(this->GetDeadSuccList()); |
| } |
| else |
| { |
| iter.RemoveCurrent(graph->alloc); |
| } |
| |
| if (doCleanPred) |
| { |
| block->RemovePred(this, graph, false, moveToDead); |
| } |
| |
| if (block->isLoopHeader && block->loop && block->GetPredList()->HasOne()) |
| { |
| Loop *loop = block->loop; |
| loop->isDead = true; |
| } |
| return; |
| } |
| } NEXT_SLISTBASECOUNTED_ENTRY_EDITING; |
| AssertMsg(UNREACHED, "Edge not found."); |
| } |
| |
| void |
| BasicBlock::UnlinkPred(BasicBlock *block) |
| { |
| this->UnlinkPred(block, true); |
| } |
| |
| void |
| BasicBlock::UnlinkSucc(BasicBlock *block) |
| { |
| this->UnlinkSucc(block, true); |
| } |
| |
| void |
| BasicBlock::UnlinkPred(BasicBlock *block, bool doCleanSucc) |
| { |
| FOREACH_SLISTBASECOUNTED_ENTRY_EDITING(FlowEdge*, edge, this->GetPredList(), iter) |
| { |
| if (edge->GetPred() == block) |
| { |
| iter.UnlinkCurrent(); |
| if (doCleanSucc) |
| { |
| block->UnlinkSucc(this, false); |
| } |
| return; |
| } |
| } NEXT_SLISTBASECOUNTED_ENTRY_EDITING; |
| AssertMsg(UNREACHED, "Edge not found."); |
| } |
| |
| void |
| BasicBlock::UnlinkSucc(BasicBlock *block, bool doCleanPred) |
| { |
| FOREACH_SLISTBASECOUNTED_ENTRY_EDITING(FlowEdge*, edge, this->GetSuccList(), iter) |
| { |
| if (edge->GetSucc() == block) |
| { |
| iter.UnlinkCurrent(); |
| if (doCleanPred) |
| { |
| block->UnlinkPred(this, false); |
| } |
| return; |
| } |
| } NEXT_SLISTBASECOUNTED_ENTRY_EDITING; |
| AssertMsg(UNREACHED, "Edge not found."); |
| } |
| |
| bool |
| BasicBlock::IsLandingPad() |
| { |
| BasicBlock * nextBlock = this->GetNext(); |
| return nextBlock && nextBlock->loop && nextBlock->isLoopHeader && nextBlock->loop->landingPad == this; |
| } |
| |
| IR::Instr * |
| FlowGraph::RemoveInstr(IR::Instr *instr, GlobOpt * globOpt) |
| { |
| IR::Instr *instrPrev = instr->m_prev; |
| if (globOpt) |
| { |
| // Removing block during glob opt. Need to maintain the graph so that |
| // bailout will record the byte code use in case the dead code is exposed |
| // by dyno-pogo optimization (where bailout need the byte code uses from |
| // the dead blocks where it may not be dead after bailing out) |
| if (instr->IsLabelInstr()) |
| { |
| instr->AsLabelInstr()->m_isLoopTop = false; |
| return instr; |
| } |
| else if (instr->IsByteCodeUsesInstr()) |
| { |
| return instr; |
| } |
| |
| /* |
| * Scope object has to be implicitly live whenever Heap Arguments object is live. |
| * - When we restore HeapArguments object in the bail out path, it expects the scope object also to be restored - if one was created. |
| */ |
| Js::OpCode opcode = instr->m_opcode; |
| if (opcode == Js::OpCode::LdElemI_A && instr->DoStackArgsOpt(this->func) && |
| globOpt->CurrentBlockData()->IsArgumentsOpnd(instr->GetSrc1()) && instr->m_func->GetScopeObjSym()) |
| { |
| IR::ByteCodeUsesInstr * byteCodeUsesInstr = IR::ByteCodeUsesInstr::New(instr); |
| byteCodeUsesInstr->SetNonOpndSymbol(instr->m_func->GetScopeObjSym()->m_id); |
| instr->InsertAfter(byteCodeUsesInstr); |
| } |
| |
| IR::ByteCodeUsesInstr * newByteCodeUseInstr = globOpt->ConvertToByteCodeUses(instr); |
| if (newByteCodeUseInstr != nullptr) |
| { |
| // We don't care about property used in these instruction |
| // It is only necessary for field copy prop so that we will keep the implicit call |
| // up to the copy prop location. |
| newByteCodeUseInstr->propertySymUse = nullptr; |
| |
| if (opcode == Js::OpCode::Yield) |
| { |
| IR::Instr *instrLabel = newByteCodeUseInstr->m_next; |
| while (instrLabel->m_opcode != Js::OpCode::Label) |
| { |
| instrLabel = instrLabel->m_next; |
| } |
| func->RemoveDeadYieldOffsetResumeLabel(instrLabel->AsLabelInstr()); |
| instrLabel->AsLabelInstr()->m_hasNonBranchRef = false; |
| } |
| |
| // Save the last instruction to update the block with |
| return newByteCodeUseInstr; |
| } |
| else |
| { |
| return instrPrev; |
| } |
| } |
| else |
| { |
| instr->Remove(); |
| return instrPrev; |
| } |
| } |
| |
| void |
| FlowGraph::RemoveBlock(BasicBlock *block, GlobOpt * globOpt, bool tailDuping) |
| { |
| Assert(!block->isDead && !block->isDeleted); |
| IR::Instr * lastInstr = nullptr; |
| FOREACH_INSTR_IN_BLOCK_EDITING(instr, instrNext, block) |
| { |
| if (instr->m_opcode == Js::OpCode::FunctionExit) |
| { |
| // Removing FunctionExit causes problems downstream... |
| // We could change the opcode, or have FunctionEpilog/FunctionExit to get |
| // rid of the epilog. |
| break; |
| } |
| if (instr == block->GetFirstInstr()) |
| { |
| Assert(instr->IsLabelInstr()); |
| instr->AsLabelInstr()->m_isLoopTop = false; |
| } |
| else |
| { |
| lastInstr = this->RemoveInstr(instr, globOpt); |
| } |
| } NEXT_INSTR_IN_BLOCK_EDITING; |
| |
| if (lastInstr) |
| { |
| block->SetLastInstr(lastInstr); |
| } |
| FOREACH_SLISTBASECOUNTED_ENTRY(FlowEdge*, edge, block->GetPredList()) |
| { |
| edge->GetPred()->RemoveSucc(block, this, false, globOpt != nullptr); |
| } NEXT_SLISTBASECOUNTED_ENTRY; |
| |
| FOREACH_SLISTBASECOUNTED_ENTRY(FlowEdge*, edge, block->GetSuccList()) |
| { |
| edge->GetSucc()->RemovePred(block, this, false, globOpt != nullptr); |
| } NEXT_SLISTBASECOUNTED_ENTRY; |
| |
| if (block->isLoopHeader && this->loopList) |
| { |
| // If loop graph is built, remove loop from loopList |
| Loop **pPrevLoop = &this->loopList; |
| |
| while (*pPrevLoop != block->loop) |
| { |
| pPrevLoop = &((*pPrevLoop)->next); |
| } |
| *pPrevLoop = (*pPrevLoop)->next; |
| this->hasLoop = (this->loopList != nullptr); |
| } |
| if (globOpt != nullptr) |
| { |
| block->isDead = true; |
| block->GetPredList()->MoveTo(block->GetDeadPredList()); |
| block->GetSuccList()->MoveTo(block->GetDeadSuccList()); |
| } |
| if (tailDuping) |
| { |
| block->isDead = true; |
| } |
| block->isDeleted = true; |
| block->SetDataUseCount(0); |
| } |
| |
| void |
| BasicBlock::UnlinkInstr(IR::Instr * instr) |
| { |
| Assert(this->Contains(instr)); |
| Assert(this->GetFirstInstr() != this->GetLastInstr()); |
| if (instr == this->GetFirstInstr()) |
| { |
| Assert(!this->GetFirstInstr()->IsLabelInstr()); |
| this->SetFirstInstr(instr->m_next); |
| } |
| else if (instr == this->GetLastInstr()) |
| { |
| this->SetLastInstr(instr->m_prev); |
| } |
| |
| instr->Unlink(); |
| } |
| |
| void |
| BasicBlock::RemoveInstr(IR::Instr * instr) |
| { |
| Assert(this->Contains(instr)); |
| if (instr == this->GetFirstInstr()) |
| { |
| this->SetFirstInstr(instr->m_next); |
| } |
| else if (instr == this->GetLastInstr()) |
| { |
| this->SetLastInstr(instr->m_prev); |
| } |
| |
| instr->Remove(); |
| } |
| |
| void |
| BasicBlock::InsertInstrBefore(IR::Instr *newInstr, IR::Instr *beforeThisInstr) |
| { |
| Assert(this->Contains(beforeThisInstr)); |
| beforeThisInstr->InsertBefore(newInstr); |
| |
| if(this->GetFirstInstr() == beforeThisInstr) |
| { |
| Assert(!beforeThisInstr->IsLabelInstr()); |
| this->SetFirstInstr(newInstr); |
| } |
| } |
| |
| void |
| BasicBlock::InsertInstrAfter(IR::Instr *newInstr, IR::Instr *afterThisInstr) |
| { |
| Assert(this->Contains(afterThisInstr)); |
| afterThisInstr->InsertAfter(newInstr); |
| |
| if (this->GetLastInstr() == afterThisInstr) |
| { |
| Assert(afterThisInstr->HasFallThrough()); |
| this->SetLastInstr(newInstr); |
| } |
| } |
| |
| void |
| BasicBlock::InsertAfter(IR::Instr *newInstr) |
| { |
| Assert(this->GetLastInstr()->HasFallThrough()); |
| this->GetLastInstr()->InsertAfter(newInstr); |
| this->SetLastInstr(newInstr); |
| } |
| |
| void |
| Loop::SetHasCall() |
| { |
| Loop * current = this; |
| do |
| { |
| if (current->hasCall) |
| { |
| #if DBG |
| current = current->parent; |
| while (current) |
| { |
| Assert(current->hasCall); |
| current = current->parent; |
| } |
| #endif |
| break; |
| } |
| current->hasCall = true; |
| current = current->parent; |
| } |
| while (current != nullptr); |
| } |
| |
| void |
| Loop::SetImplicitCallFlags(Js::ImplicitCallFlags newFlags) |
| { |
| Loop * current = this; |
| do |
| { |
| if ((current->implicitCallFlags & newFlags) == newFlags) |
| { |
| #if DBG |
| current = current->parent; |
| while (current) |
| { |
| Assert((current->implicitCallFlags & newFlags) == newFlags); |
| current = current->parent; |
| } |
| #endif |
| break; |
| } |
| newFlags = (Js::ImplicitCallFlags)(implicitCallFlags | newFlags); |
| current->implicitCallFlags = newFlags; |
| current = current->parent; |
| } |
| while (current != nullptr); |
| } |
| |
| Js::ImplicitCallFlags |
| Loop::GetImplicitCallFlags() |
| { |
| if (this->implicitCallFlags == Js::ImplicitCall_HasNoInfo) |
| { |
| if (this->parent == nullptr) |
| { |
| // We don't have any information, and we don't have any parent, so just assume that there aren't any implicit calls |
| this->implicitCallFlags = Js::ImplicitCall_None; |
| } |
| else |
| { |
| // We don't have any information, get it from the parent and hope for the best |
| this->implicitCallFlags = this->parent->GetImplicitCallFlags(); |
| } |
| } |
| return this->implicitCallFlags; |
| } |
| |
| bool |
| Loop::CanDoFieldCopyProp() |
| { |
| #if DBG_DUMP |
| if (((this->implicitCallFlags & ~(Js::ImplicitCall_External)) == 0) && |
| Js::Configuration::Global.flags.Trace.IsEnabled(Js::HostOptPhase)) |
| { |
| Output::Print(_u("fieldcopyprop disabled because external: loop count: %d"), GetLoopNumber()); |
| GetFunc()->DumpFullFunctionName(); |
| Output::Print(_u("\n")); |
| Output::Flush(); |
| } |
| #endif |
| return GlobOpt::ImplicitCallFlagsAllowOpts(this); |
| } |
| |
| bool |
| Loop::CanDoFieldHoist() |
| { |
| // We can do field hoist wherever we can do copy prop |
| return CanDoFieldCopyProp(); |
| } |
| |
| bool |
| Loop::CanHoistInvariants() const |
| { |
| Func * func = this->GetHeadBlock()->firstInstr->m_func->GetTopFunc(); |
| |
| if (PHASE_OFF(Js::InvariantsPhase, func)) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| IR::LabelInstr * |
| Loop::GetLoopTopInstr() const |
| { |
| IR::LabelInstr * instr = nullptr; |
| if (this->topFunc->isFlowGraphValid) |
| { |
| instr = this->GetHeadBlock()->GetFirstInstr()->AsLabelInstr(); |
| } |
| else |
| { |
| // Flowgraph gets torn down after the globopt, so can't get the loopTop from the head block. |
| instr = this->loopTopLabel; |
| } |
| if (instr) |
| { |
| Assert(instr->m_isLoopTop); |
| } |
| return instr; |
| } |
| |
| void |
| Loop::SetLoopTopInstr(IR::LabelInstr * loopTop) |
| { |
| this->loopTopLabel = loopTop; |
| } |
| |
| #if DBG_DUMP |
| uint |
| Loop::GetLoopNumber() const |
| { |
| IR::LabelInstr * loopTopInstr = this->GetLoopTopInstr(); |
| if (loopTopInstr->IsProfiledLabelInstr()) |
| { |
| return loopTopInstr->AsProfiledLabelInstr()->loopNum; |
| } |
| return Js::LoopHeader::NoLoop; |
| } |
| |
| bool |
| BasicBlock::Contains(IR::Instr * instr) |
| { |
| FOREACH_INSTR_IN_BLOCK(blockInstr, this) |
| { |
| if (instr == blockInstr) |
| { |
| return true; |
| } |
| } |
| NEXT_INSTR_IN_BLOCK; |
| return false; |
| } |
| #endif |
| |
| FlowEdge * |
| FlowEdge::New(FlowGraph * graph) |
| { |
| FlowEdge * edge; |
| |
| edge = JitAnew(graph->alloc, FlowEdge); |
| |
| return edge; |
| } |
| |
| bool |
| Loop::IsDescendentOrSelf(Loop const * loop) const |
| { |
| Loop const * currentLoop = loop; |
| while (currentLoop != nullptr) |
| { |
| if (currentLoop == this) |
| { |
| return true; |
| } |
| currentLoop = currentLoop->parent; |
| } |
| return false; |
| } |
| |
| void FlowGraph::SafeRemoveInstr(IR::Instr *instr) |
| { |
| BasicBlock *block; |
| |
| if (instr->m_next->IsLabelInstr()) |
| { |
| block = instr->m_next->AsLabelInstr()->GetBasicBlock()->GetPrev(); |
| block->RemoveInstr(instr); |
| } |
| else if (instr->IsLabelInstr()) |
| { |
| block = instr->AsLabelInstr()->GetBasicBlock(); |
| block->RemoveInstr(instr); |
| } |
| else |
| { |
| Assert(!instr->EndsBasicBlock() && !instr->StartsBasicBlock()); |
| instr->Remove(); |
| } |
| } |
| |
| bool FlowGraph::IsUnsignedOpnd(IR::Opnd *src, IR::Opnd **pShrSrc1) |
| { |
| // Look for an unsigned constant, or the result of an unsigned shift by zero |
| if (!src->IsRegOpnd()) |
| { |
| return false; |
| } |
| if (!src->AsRegOpnd()->m_sym->IsSingleDef()) |
| { |
| return false; |
| } |
| |
| if (src->AsRegOpnd()->m_sym->IsIntConst()) |
| { |
| int32 intConst = src->AsRegOpnd()->m_sym->GetIntConstValue(); |
| |
| if (intConst >= 0) |
| { |
| *pShrSrc1 = src; |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| IR::Instr * shrUInstr = src->AsRegOpnd()->m_sym->GetInstrDef(); |
| |
| if (shrUInstr->m_opcode != Js::OpCode::ShrU_A) |
| { |
| return false; |
| } |
| |
| IR::Opnd *shrCnt = shrUInstr->GetSrc2(); |
| |
| if (!shrCnt->IsRegOpnd() || !shrCnt->AsRegOpnd()->m_sym->IsTaggableIntConst() || shrCnt->AsRegOpnd()->m_sym->GetIntConstValue() != 0) |
| { |
| return false; |
| } |
| |
| IR::Opnd *shrSrc = shrUInstr->GetSrc1(); |
| |
| *pShrSrc1 = shrSrc; |
| return true; |
| } |
| |
| bool FlowGraph::UnsignedCmpPeep(IR::Instr *cmpInstr) |
| { |
| IR::Opnd *cmpSrc1 = cmpInstr->GetSrc1(); |
| IR::Opnd *cmpSrc2 = cmpInstr->GetSrc2(); |
| IR::Opnd *newSrc1; |
| IR::Opnd *newSrc2; |
| |
| // Look for something like: |
| // t1 = ShrU_A x, 0 |
| // t2 = 10; |
| // BrGt t1, t2, L |
| // |
| // Peep to: |
| // |
| // t1 = ShrU_A x, 0 |
| // t2 = 10; |
| // t3 = Or_A x, 0 |
| // BrUnGt x, t3, L |
| // ByteCodeUse t1 |
| // |
| // Hopefully dead-store can get rid of the ShrU |
| |
| if (!this->func->DoGlobOpt() || !GlobOpt::DoAggressiveIntTypeSpec(this->func) || !GlobOpt::DoLossyIntTypeSpec(this->func)) |
| { |
| return false; |
| } |
| |
| if (cmpInstr->IsBranchInstr() && !cmpInstr->AsBranchInstr()->IsConditional()) |
| { |
| return false; |
| } |
| |
| if (!cmpInstr->GetSrc2()) |
| { |
| return false; |
| } |
| |
| if (!this->IsUnsignedOpnd(cmpSrc1, &newSrc1)) |
| { |
| return false; |
| } |
| if (!this->IsUnsignedOpnd(cmpSrc2, &newSrc2)) |
| { |
| return false; |
| } |
| |
| switch(cmpInstr->m_opcode) |
| { |
| case Js::OpCode::BrEq_A: |
| case Js::OpCode::BrNeq_A: |
| case Js::OpCode::BrSrEq_A: |
| case Js::OpCode::BrSrNeq_A: |
| case Js::OpCode::CmEq_A: |
| case Js::OpCode::CmNeq_A: |
| case Js::OpCode::CmSrEq_A: |
| case Js::OpCode::CmSrNeq_A: |
| break; |
| case Js::OpCode::BrLe_A: |
| cmpInstr->m_opcode = Js::OpCode::BrUnLe_A; |
| break; |
| case Js::OpCode::BrLt_A: |
| cmpInstr->m_opcode = Js::OpCode::BrUnLt_A; |
| break; |
| case Js::OpCode::BrGe_A: |
| cmpInstr->m_opcode = Js::OpCode::BrUnGe_A; |
| break; |
| case Js::OpCode::BrGt_A: |
| cmpInstr->m_opcode = Js::OpCode::BrUnGt_A; |
| break; |
| case Js::OpCode::CmLe_A: |
| cmpInstr->m_opcode = Js::OpCode::CmUnLe_A; |
| break; |
| case Js::OpCode::CmLt_A: |
| cmpInstr->m_opcode = Js::OpCode::CmUnLt_A; |
| break; |
| case Js::OpCode::CmGe_A: |
| cmpInstr->m_opcode = Js::OpCode::CmUnGe_A; |
| break; |
| case Js::OpCode::CmGt_A: |
| cmpInstr->m_opcode = Js::OpCode::CmUnGt_A; |
| break; |
| |
| default: |
| return false; |
| } |
| |
| IR::ByteCodeUsesInstr * bytecodeInstr = IR::ByteCodeUsesInstr::New(cmpInstr); |
| cmpInstr->InsertAfter(bytecodeInstr); |
| |
| if (cmpSrc1 != newSrc1) |
| { |
| if (cmpSrc1->IsRegOpnd() && !cmpSrc1->GetIsJITOptimizedReg()) |
| { |
| bytecodeInstr->Set(cmpSrc1); |
| } |
| |
| IR::RegOpnd * unsignedSrc = IR::RegOpnd::New(newSrc1->GetType(), cmpInstr->m_func); |
| IR::Instr * orZero = IR::Instr::New(Js::OpCode::Or_A, unsignedSrc, newSrc1, IR::IntConstOpnd::New(0, TyMachReg, cmpInstr->m_func), cmpInstr->m_func); |
| orZero->SetByteCodeOffset(cmpInstr); |
| cmpInstr->InsertBefore(orZero); |
| cmpInstr->ReplaceSrc1(unsignedSrc); |
| if (newSrc1->IsRegOpnd()) |
| { |
| cmpInstr->GetSrc1()->AsRegOpnd()->SetIsJITOptimizedReg(true); |
| orZero->GetSrc1()->SetIsJITOptimizedReg(true); |
| } |
| } |
| if (cmpSrc2 != newSrc2) |
| { |
| if (cmpSrc2->IsRegOpnd() && !cmpSrc2->GetIsJITOptimizedReg()) |
| { |
| bytecodeInstr->Set(cmpSrc2); |
| } |
| |
| IR::RegOpnd * unsignedSrc = IR::RegOpnd::New(newSrc2->GetType(), cmpInstr->m_func); |
| IR::Instr * orZero = IR::Instr::New(Js::OpCode::Or_A, unsignedSrc, newSrc2, IR::IntConstOpnd::New(0, TyMachReg, cmpInstr->m_func), cmpInstr->m_func); |
| orZero->SetByteCodeOffset(cmpInstr); |
| cmpInstr->InsertBefore(orZero); |
| cmpInstr->ReplaceSrc2(unsignedSrc); |
| if (newSrc2->IsRegOpnd()) |
| { |
| cmpInstr->GetSrc2()->AsRegOpnd()->SetIsJITOptimizedReg(true); |
| orZero->GetSrc1()->SetIsJITOptimizedReg(true); |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| #if DBG |
| |
| void |
| FlowGraph::VerifyLoopGraph() |
| { |
| FOREACH_BLOCK(block, this) |
| { |
| Loop *loop = block->loop; |
| FOREACH_SUCCESSOR_BLOCK(succ, block) |
| { |
| if (loop == succ->loop) |
| { |
| Assert(succ->isLoopHeader == false || loop->GetHeadBlock() == succ); |
| continue; |
| } |
| if (succ->isLoopHeader) |
| { |
| Assert(succ->loop->parent == loop |
| || (!loop->IsDescendentOrSelf(succ->loop))); |
| continue; |
| } |
| Assert(succ->loop == nullptr || succ->loop->IsDescendentOrSelf(loop)); |
| } NEXT_SUCCESSOR_BLOCK; |
| |
| if (!PHASE_OFF(Js::RemoveBreakBlockPhase, this->GetFunc())) |
| { |
| // Make sure all break blocks have been removed. |
| if (loop && !block->isLoopHeader && !(this->func->HasTry() && !this->func->DoOptimizeTry())) |
| { |
| Assert(loop->IsDescendentOrSelf(block->GetPrev()->loop)); |
| } |
| } |
| } NEXT_BLOCK; |
| } |
| |
| #endif |
| |
| #if DBG_DUMP |
| |
| void |
| FlowGraph::Dump(bool onlyOnVerboseMode, const char16 *form) |
| { |
| if(PHASE_DUMP(Js::FGBuildPhase, this->GetFunc())) |
| { |
| if (!onlyOnVerboseMode || Js::Configuration::Global.flags.Verbose) |
| { |
| if (form) |
| { |
| Output::Print(form); |
| } |
| this->Dump(); |
| } |
| } |
| } |
| |
| void |
| FlowGraph::Dump() |
| { |
| Output::Print(_u("\nFlowGraph\n")); |
| FOREACH_BLOCK(block, this) |
| { |
| Loop * loop = block->loop; |
| while (loop) |
| { |
| Output::Print(_u(" ")); |
| loop = loop->parent; |
| } |
| block->DumpHeader(false); |
| } NEXT_BLOCK; |
| |
| Output::Print(_u("\nLoopGraph\n")); |
| |
| for (Loop *loop = this->loopList; loop; loop = loop->next) |
| { |
| Output::Print(_u("\nLoop\n")); |
| FOREACH_BLOCK_IN_LOOP(block, loop) |
| { |
| block->DumpHeader(false); |
| }NEXT_BLOCK_IN_LOOP; |
| Output::Print(_u("Loop Ends\n")); |
| } |
| } |
| |
| void |
| BasicBlock::DumpHeader(bool insertCR) |
| { |
| if (insertCR) |
| { |
| Output::Print(_u("\n")); |
| } |
| Output::Print(_u("BLOCK %d:"), this->number); |
| |
| if (this->isDead) |
| { |
| Output::Print(_u(" **** DEAD ****")); |
| } |
| |
| if (this->isBreakBlock) |
| { |
| Output::Print(_u(" **** Break Block ****")); |
| } |
| else if (this->isAirLockBlock) |
| { |
| Output::Print(_u(" **** Air lock Block ****")); |
| } |
| else if (this->isBreakCompensationBlockAtSource) |
| { |
| Output::Print(_u(" **** Break Source Compensation Code ****")); |
| } |
| else if (this->isBreakCompensationBlockAtSink) |
| { |
| Output::Print(_u(" **** Break Sink Compensation Code ****")); |
| } |
| else if (this->isAirLockCompensationBlock) |
| { |
| Output::Print(_u(" **** Airlock block Compensation Code ****")); |
| } |
| |
| if (!this->predList.Empty()) |
| { |
| BOOL fFirst = TRUE; |
| Output::Print(_u(" In(")); |
| FOREACH_PREDECESSOR_BLOCK(blockPred, this) |
| { |
| if (!fFirst) |
| { |
| Output::Print(_u(", ")); |
| } |
| Output::Print(_u("%d"), blockPred->GetBlockNum()); |
| fFirst = FALSE; |
| } |
| NEXT_PREDECESSOR_BLOCK; |
| Output::Print(_u(")")); |
| } |
| |
| |
| if (!this->succList.Empty()) |
| { |
| BOOL fFirst = TRUE; |
| Output::Print(_u(" Out(")); |
| FOREACH_SUCCESSOR_BLOCK(blockSucc, this) |
| { |
| if (!fFirst) |
| { |
| Output::Print(_u(", ")); |
| } |
| Output::Print(_u("%d"), blockSucc->GetBlockNum()); |
| fFirst = FALSE; |
| } |
| NEXT_SUCCESSOR_BLOCK; |
| Output::Print(_u(")")); |
| } |
| |
| if (!this->deadPredList.Empty()) |
| { |
| BOOL fFirst = TRUE; |
| Output::Print(_u(" DeadIn(")); |
| FOREACH_DEAD_PREDECESSOR_BLOCK(blockPred, this) |
| { |
| if (!fFirst) |
| { |
| Output::Print(_u(", ")); |
| } |
| Output::Print(_u("%d"), blockPred->GetBlockNum()); |
| fFirst = FALSE; |
| } |
| NEXT_DEAD_PREDECESSOR_BLOCK; |
| Output::Print(_u(")")); |
| } |
| |
| if (!this->deadSuccList.Empty()) |
| { |
| BOOL fFirst = TRUE; |
| Output::Print(_u(" DeadOut(")); |
| FOREACH_DEAD_SUCCESSOR_BLOCK(blockSucc, this) |
| { |
| if (!fFirst) |
| { |
| Output::Print(_u(", ")); |
| } |
| Output::Print(_u("%d"), blockSucc->GetBlockNum()); |
| fFirst = FALSE; |
| } |
| NEXT_DEAD_SUCCESSOR_BLOCK; |
| Output::Print(_u(")")); |
| } |
| |
| if (this->loop) |
| { |
| Output::Print(_u(" Loop(%d) header: %d"), this->loop->loopNumber, this->loop->GetHeadBlock()->GetBlockNum()); |
| |
| if (this->loop->parent) |
| { |
| Output::Print(_u(" parent(%d): %d"), this->loop->parent->loopNumber, this->loop->parent->GetHeadBlock()->GetBlockNum()); |
| } |
| |
| if (this->loop->GetHeadBlock() == this) |
| { |
| Output::SkipToColumn(50); |
| Output::Print(_u("Call Exp/Imp: ")); |
| if (this->loop->GetHasCall()) |
| { |
| Output::Print(_u("yes/")); |
| } |
| else |
| { |
| Output::Print(_u(" no/")); |
| } |
| Output::Print(Js::DynamicProfileInfo::GetImplicitCallFlagsString(this->loop->GetImplicitCallFlags())); |
| } |
| } |
| |
| Output::Print(_u("\n")); |
| if (insertCR) |
| { |
| Output::Print(_u("\n")); |
| } |
| } |
| |
| void |
| BasicBlock::Dump() |
| { |
| // Dumping the first instruction (label) will dump the block header as well. |
| FOREACH_INSTR_IN_BLOCK(instr, this) |
| { |
| instr->Dump(); |
| } |
| NEXT_INSTR_IN_BLOCK; |
| } |
| |
| void |
| AddPropertyCacheBucket::Dump() const |
| { |
| Assert(this->initialType != nullptr); |
| Assert(this->finalType != nullptr); |
| Output::Print(_u(" initial type: 0x%x, final type: 0x%x "), this->initialType->GetAddr(), this->finalType->GetAddr()); |
| } |
| |
| void |
| ObjTypeGuardBucket::Dump() const |
| { |
| Assert(this->guardedPropertyOps != nullptr); |
| this->guardedPropertyOps->Dump(); |
| } |
| |
| void |
| ObjWriteGuardBucket::Dump() const |
| { |
| Assert(this->writeGuards != nullptr); |
| this->writeGuards->Dump(); |
| } |
| |
| #endif |
| |
| void |
| BasicBlock::CleanUpValueMaps() |
| { |
| // Don't do cleanup if it's been done recently. |
| // Landing pad could get optimized twice... |
| // We want the same info out the first and second time. So always cleanup. |
| // Increasing the cleanup threshold count for asmjs to 500 |
| uint cleanupCount = (!this->globOptData.globOpt->GetIsAsmJSFunc()) ? CONFIG_FLAG(GoptCleanupThreshold) : CONFIG_FLAG(AsmGoptCleanupThreshold); |
| if (!this->IsLandingPad() && this->globOptData.globOpt->instrCountSinceLastCleanUp < cleanupCount) |
| { |
| return; |
| } |
| this->globOptData.globOpt->instrCountSinceLastCleanUp = 0; |
| |
| JitArenaAllocator* tempAlloc = this->globOptData.globOpt->tempAlloc; |
| |
| GlobHashTable *thisTable = this->globOptData.symToValueMap; |
| BVSparse<JitArenaAllocator> deadSymsBv(tempAlloc); |
| BVSparse<JitArenaAllocator> keepAliveSymsBv(tempAlloc); |
| BVSparse<JitArenaAllocator> availableValueNumbers(tempAlloc); |
| availableValueNumbers.Copy(this->globOptData.globOpt->byteCodeConstantValueNumbersBv); |
| BVSparse<JitArenaAllocator> *upwardExposedUses = this->upwardExposedUses; |
| BVSparse<JitArenaAllocator> *upwardExposedFields = this->upwardExposedFields; |
| bool isInLoop = !!this->loop; |
| |
| BVSparse<JitArenaAllocator> symsInCallSequence(tempAlloc); |
| SListBase<IR::Opnd *> * callSequence = this->globOptData.callSequence; |
| if (callSequence && !callSequence->Empty()) |
| { |
| FOREACH_SLISTBASE_ENTRY(IR::Opnd *, opnd, callSequence) |
| { |
| StackSym * sym = opnd->GetStackSym(); |
| symsInCallSequence.Set(sym->m_id); |
| } |
| } |
| NEXT_SLISTBASE_ENTRY; |
| |
| for (uint i = 0; i < thisTable->tableSize; i++) |
| { |
| FOREACH_SLISTBASE_ENTRY_EDITING(GlobHashBucket, bucket, &thisTable->table[i], iter) |
| { |
| bool isSymUpwardExposed = upwardExposedUses->Test(bucket.value->m_id) || upwardExposedFields->Test(bucket.value->m_id); |
| if (!isSymUpwardExposed && symsInCallSequence.Test(bucket.value->m_id)) |
| { |
| // Don't remove/shrink sym-value pair if the sym is referenced in callSequence even if the sym is dead according to backward data flow. |
| // This is possible in some edge cases that an infinite loop is involved when evaluating parameter for a function (between StartCall and Call), |
| // there is no backward data flow into the infinite loop block, but non empty callSequence still populates to it in this (forward) pass |
| // which causes error when looking up value for the syms in callSequence (cannot find the value). |
| // It would cause error to fill out the bailout information for the loop blocks. |
| // Remove dead syms from callSequence has some risk because there are various associated counters which need to be consistent. |
| continue; |
| } |
| // Make sure symbol was created before backward pass. |
| // If symbols isn't upward exposed, mark it as dead. |
| // If a symbol was copy-prop'd in a loop prepass, the upwardExposedUses info could be wrong. So wait until we are out of the loop before clearing it. |
| if ((SymID)bucket.value->m_id <= this->globOptData.globOpt->maxInitialSymID && !isSymUpwardExposed |
| && (!isInLoop || !this->globOptData.globOpt->prePassCopyPropSym->Test(bucket.value->m_id))) |
| { |
| Value *val = bucket.element; |
| ValueInfo *valueInfo = val->GetValueInfo(); |
| |
| Sym * sym = bucket.value; |
| Sym *symStore = valueInfo->GetSymStore(); |
| |
| if (symStore && symStore == bucket.value) |
| { |
| // Keep constants around, as we don't know if there will be further uses |
| if (!bucket.element->GetValueInfo()->IsVarConstant() && !bucket.element->GetValueInfo()->HasIntConstantValue()) |
| { |
| // Symbol may still be a copy-prop candidate. Wait before deleting it. |
| deadSymsBv.Set(bucket.value->m_id); |
| |
| // Make sure the type sym is added to the dead syms vector as well, because type syms are |
| // created in backward pass and so their symIds > maxInitialSymID. |
| if (sym->IsStackSym() && sym->AsStackSym()->HasObjectTypeSym()) |
| { |
| deadSymsBv.Set(sym->AsStackSym()->GetObjectTypeSym()->m_id); |
| } |
| } |
| availableValueNumbers.Set(val->GetValueNumber()); |
| } |
| else |
| { |
| // Make sure the type sym is added to the dead syms vector as well, because type syms are |
| // created in backward pass and so their symIds > maxInitialSymID. Perhaps we could remove |
| // it explicitly here, but would it work alright with the iterator? |
| if (sym->IsStackSym() && sym->AsStackSym()->HasObjectTypeSym()) |
| { |
| deadSymsBv.Set(sym->AsStackSym()->GetObjectTypeSym()->m_id); |
| } |
| |
| // Not a copy-prop candidate; delete it right away. |
| iter.RemoveCurrent(thisTable->alloc); |
| this->globOptData.liveInt32Syms->Clear(sym->m_id); |
| this->globOptData.liveLossyInt32Syms->Clear(sym->m_id); |
| this->globOptData.liveFloat64Syms->Clear(sym->m_id); |
| } |
| } |
| else |
| { |
| Sym * sym = bucket.value; |
| |
| if (sym->IsPropertySym() && !this->globOptData.liveFields->Test(sym->m_id)) |
| { |
| // Remove propertySyms which are not live anymore. |
| iter.RemoveCurrent(thisTable->alloc); |
| this->globOptData.liveInt32Syms->Clear(sym->m_id); |
| this->globOptData.liveLossyInt32Syms->Clear(sym->m_id); |
| this->globOptData.liveFloat64Syms->Clear(sym->m_id); |
| } |
| else |
| { |
| // Look at the copy-prop candidate. We don't want to get rid of the data for a symbol which is |
| // a copy-prop candidate. |
| Value *val = bucket.element; |
| ValueInfo *valueInfo = val->GetValueInfo(); |
| |
| Sym *symStore = valueInfo->GetSymStore(); |
| |
| if (symStore && symStore != bucket.value) |
| { |
| keepAliveSymsBv.Set(symStore->m_id); |
| if (symStore->IsStackSym() && symStore->AsStackSym()->HasObjectTypeSym()) |
| { |
| keepAliveSymsBv.Set(symStore->AsStackSym()->GetObjectTypeSym()->m_id); |
| } |
| } |
| availableValueNumbers.Set(val->GetValueNumber()); |
| } |
| } |
| } NEXT_SLISTBASE_ENTRY_EDITING; |
| } |
| |
| deadSymsBv.Minus(&keepAliveSymsBv); |
| |
| // Now cleanup exprToValueMap table |
| ExprHashTable *thisExprTable = this->globOptData.exprToValueMap; |
| bool oldHasCSECandidatesValue = this->globOptData.hasCSECandidates; // Could be false if none need bailout. |
| this->globOptData.hasCSECandidates = false; |
| |
| for (uint i = 0; i < thisExprTable->tableSize; i++) |
| { |
| FOREACH_SLISTBASE_ENTRY_EDITING(ExprHashBucket, bucket, &thisExprTable->table[i], iter) |
| { |
| ExprHash hash = bucket.value; |
| ValueNumber src1ValNum = hash.GetSrc1ValueNumber(); |
| ValueNumber src2ValNum = hash.GetSrc2ValueNumber(); |
| |
| // If src1Val or src2Val are not available anymore, no point keeping this CSE candidate |
| bool removeCurrent = false; |
| if ((src1ValNum && !availableValueNumbers.Test(src1ValNum)) |
| || (src2ValNum && !availableValueNumbers.Test(src2ValNum))) |
| { |
| removeCurrent = true; |
| } |
| else |
| { |
| // If we are keeping this value, make sure we also keep the symStore in the value table |
| removeCurrent = true; // Remove by default, unless it's set to false later below. |
| Value *val = bucket.element; |
| if (val) |
| { |
| Sym *symStore = val->GetValueInfo()->GetSymStore(); |
| if (symStore) |
| { |
| Value *symStoreVal = this->globOptData.FindValue(symStore); |
| |
| if (symStoreVal && symStoreVal->GetValueNumber() == val->GetValueNumber()) |
| { |
| removeCurrent = false; |
| deadSymsBv.Clear(symStore->m_id); |
| if (symStore->IsStackSym() && symStore->AsStackSym()->HasObjectTypeSym()) |
| { |
| deadSymsBv.Clear(symStore->AsStackSym()->GetObjectTypeSym()->m_id); |
| } |
| } |
| } |
| } |
| } |
| |
| if(removeCurrent) |
| { |
| iter.RemoveCurrent(thisExprTable->alloc); |
| } |
| else |
| { |
| this->globOptData.hasCSECandidates = oldHasCSECandidatesValue; |
| } |
| } NEXT_SLISTBASE_ENTRY_EDITING; |
| } |
| |
| FOREACH_BITSET_IN_SPARSEBV(dead_id, &deadSymsBv) |
| { |
| thisTable->Clear(dead_id); |
| } |
| NEXT_BITSET_IN_SPARSEBV; |
| |
| if (!deadSymsBv.IsEmpty()) |
| { |
| if (this->func->IsJitInDebugMode()) |
| { |
| // Do not remove non-temp local vars from liveVarSyms (i.e. do not let them become dead). |
| // We will need to restore all initialized/used so far non-temp local during bail out. |
| // (See BackwardPass::ProcessBailOutInfo) |
| Assert(this->func->m_nonTempLocalVars); |
| BVSparse<JitArenaAllocator> tempBv(tempAlloc); |
| tempBv.Minus(&deadSymsBv, this->func->m_nonTempLocalVars); |
| this->globOptData.liveVarSyms->Minus(&tempBv); |
| #if DBG |
| tempBv.And(this->globOptData.liveInt32Syms, this->func->m_nonTempLocalVars); |
| AssertMsg(tempBv.IsEmpty(), "Type spec is disabled under debugger. How come did we get a non-temp local in liveInt32Syms?"); |
| tempBv.And(this->globOptData.liveLossyInt32Syms, this->func->m_nonTempLocalVars); |
| AssertMsg(tempBv.IsEmpty(), "Type spec is disabled under debugger. How come did we get a non-temp local in liveLossyInt32Syms?"); |
| tempBv.And(this->globOptData.liveFloat64Syms, this->func->m_nonTempLocalVars); |
| AssertMsg(tempBv.IsEmpty(), "Type spec is disabled under debugger. How come did we get a non-temp local in liveFloat64Syms?"); |
| #endif |
| } |
| else |
| { |
| this->globOptData.liveVarSyms->Minus(&deadSymsBv); |
| } |
| |
| this->globOptData.liveInt32Syms->Minus(&deadSymsBv); |
| this->globOptData.liveLossyInt32Syms->Minus(&deadSymsBv); |
| this->globOptData.liveFloat64Syms->Minus(&deadSymsBv); |
| } |
| |
| JitAdelete(this->globOptData.globOpt->alloc, upwardExposedUses); |
| this->upwardExposedUses = nullptr; |
| JitAdelete(this->globOptData.globOpt->alloc, upwardExposedFields); |
| this->upwardExposedFields = nullptr; |
| if (this->cloneStrCandidates) |
| { |
| JitAdelete(this->globOptData.globOpt->alloc, this->cloneStrCandidates); |
| this->cloneStrCandidates = nullptr; |
| } |
| } |
| |
| void |
| BasicBlock::MergePredBlocksValueMaps(GlobOpt* globOpt) |
| { |
| Assert(!globOpt->isCallHelper); |
| // We keep a local temporary copy for the merge |
| GlobOptBlockData blockData(this->globOptData.curFunc); |
| |
| if (!globOpt->isRecursiveCallOnLandingPad) |
| { |
| blockData.NullOutBlockData(globOpt, globOpt->func); |
| } |
| else |
| { |
| // If we are going over the landing pad again after field PRE, just start again |
| // with the value table where we left off. |
| blockData.CopyBlockData(&this->globOptData); |
| return; |
| } |
| |
| BVSparse<JitArenaAllocator> symsRequiringCompensation(globOpt->tempAlloc); |
| { |
| BVSparse<JitArenaAllocator> symsCreatedForMerge(globOpt->tempAlloc); |
| bool forceTypeSpecOnLoopHeader = true; |
| FOREACH_PREDECESSOR_BLOCK(pred, this) |
| { |
| if (pred->globOptData.callSequence && pred->globOptData.callSequence->Empty()) |
| { |
| JitAdelete(globOpt->alloc, pred->globOptData.callSequence); |
| pred->globOptData.callSequence = nullptr; |
| } |
| if (this->isLoopHeader && globOpt->IsLoopPrePass() && globOpt->prePassLoop == this->loop && this->loop->IsDescendentOrSelf(pred->loop)) |
| { |
| // Loop back-edge. |
| // First pass on loop runs optimistically, without doing transforms. |
| // Skip this edge for now. |
| continue; |
| } |
| |
| PathDependentInfo *const pathDependentInfo = __edge->GetPathDependentInfo(); |
| PathDependentInfoToRestore pathDependentInfoToRestore; |
| if (pathDependentInfo) |
| { |
| pathDependentInfoToRestore = globOpt->UpdatePathDependentInfo(pathDependentInfo); |
| } |
| |
| Assert(pred->GetDataUseCount()); |
| |
| // First pred? |
| if (blockData.symToValueMap == nullptr) |
| { |
| // Only one edge? |
| if (pred->GetSuccList()->HasOne() && this->GetPredList()->HasOne() && this->loop == nullptr) |
| { |
| blockData.ReuseBlockData(&pred->globOptData); |
| |
| // Don't need to restore the old value info |
| pathDependentInfoToRestore.Clear(); |
| } |
| else |
| { |
| blockData.CloneBlockData(this, pred); |
| } |
| } |
| else |
| { |
| const bool isLoopPrePass = globOpt->IsLoopPrePass(); |
| blockData.MergeBlockData( |
| this, |
| pred, |
| isLoopPrePass ? nullptr : &symsRequiringCompensation, |
| isLoopPrePass ? nullptr : &symsCreatedForMerge, |
| forceTypeSpecOnLoopHeader); |
| forceTypeSpecOnLoopHeader = false; // can force type-spec on the loop header only for the first back edge. |
| } |
| |
| // Restore the value for the next edge |
| if (pathDependentInfo) |
| { |
| globOpt->RestorePathDependentInfo(pathDependentInfo, pathDependentInfoToRestore); |
| __edge->ClearPathDependentInfo(globOpt->alloc); |
| } |
| |
| } NEXT_PREDECESSOR_BLOCK; |
| } |
| |
| // Consider: We can recreate values for hoisted field so it can copy prop out of the loop |
| if (blockData.symToValueMap == nullptr) |
| { |
| Assert(blockData.hoistableFields == nullptr); |
| blockData.InitBlockData(globOpt, globOpt->func); |
| } |
| else if (blockData.hoistableFields) |
| { |
| Assert(globOpt->TrackHoistableFields()); |
| blockData.hoistableFields->And(this->globOptData.liveFields); |
| } |
| |
| if (!globOpt->DoObjTypeSpec()) |
| { |
| // Object type specialization is off, but if copy prop is on (e.g., /force:fieldhoist) we're not clearing liveFields, |
| // so we may be letting type syms slip through this block. |
| globOpt->KillAllObjectTypes(this->globOptData.liveFields); |
| } |
| |
| this->globOptData.CopyBlockData(&blockData); |
| |
| if (globOpt->IsLoopPrePass()) |
| { |
| Assert(this->loop); |
| |
| if(globOpt->DoBoundCheckHoist()) |
| { |
| globOpt->SetInductionVariableValueNumbers(&blockData); |
| } |
| |
| if (this->isLoopHeader && globOpt->rootLoopPrePass == this->loop) |
| { |
| // Capture bail out info in case we have optimization that needs it |
| Assert(this->loop->bailOutInfo == nullptr); |
| IR::Instr * firstInstr = this->GetFirstInstr(); |
| this->loop->bailOutInfo = JitAnew(globOpt->func->m_alloc, BailOutInfo, |
| firstInstr->GetByteCodeOffset(), firstInstr->m_func); |
| globOpt->FillBailOutInfo(this, this->loop->bailOutInfo); |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| this->loop->bailOutInfo->bailOutOpcode = Js::OpCode::LoopBodyStart; |
| #endif |
| } |
| |
| // If loop pre-pass, don't insert convert from type-spec to var |
| return; |
| } |
| |
| this->CleanUpValueMaps(); |
| Sym *symIV = nullptr; |
| |
| // Clean up the syms requiring compensation by checking the final value in the merged block to see if the sym still requires |
| // compensation. All the while, create a mapping from sym to value info in the merged block. This dictionary helps avoid a |
| // value lookup in the merged block per predecessor. |
| SymToValueInfoMap symsRequiringCompensationToMergedValueInfoMap(globOpt->tempAlloc); |
| if(!symsRequiringCompensation.IsEmpty()) |
| { |
| const SymTable *const symTable = func->m_symTable; |
| FOREACH_BITSET_IN_SPARSEBV(id, &symsRequiringCompensation) |
| { |
| Sym *const sym = symTable->Find(id); |
| Assert(sym); |
| |
| Value *const value = blockData.FindValue(sym); |
| if(!value) |
| { |
| continue; |
| } |
| |
| ValueInfo *const valueInfo = value->GetValueInfo(); |
| if(!valueInfo->IsArrayValueInfo()) |
| { |
| continue; |
| } |
| |
| // At least one new sym was created while merging and associated with the merged value info, so those syms will |
| // require compensation in predecessors. For now, the dead store phase is relied upon to remove compensation that is |
| // dead due to no further uses of the new sym. |
| symsRequiringCompensationToMergedValueInfoMap.Add(sym, valueInfo); |
| } NEXT_BITSET_IN_SPARSEBV; |
| symsRequiringCompensation.ClearAll(); |
| } |
| |
| if (this->isLoopHeader) |
| { |
| BVSparse<JitArenaAllocator> * tempBv = globOpt->tempBv; |
| // Values on the back-edge in the prepass may be conservative for syms defined in the loop, and type specialization in |
| // the prepass is not reflective of the value, but rather, is used to determine whether the sym should be specialized |
| // around the loop. Additionally, some syms that are used before defined in the loop may be specialized in the loop |
| // header despite not being specialized in the landing pad. Now that the type specialization bit-vectors are merged, |
| // specialize the corresponding value infos in the loop header too. |
| |
| Assert(tempBv->IsEmpty()); |
| Loop *const loop = this->loop; |
| SymTable *const symTable = func->m_symTable; |
| JitArenaAllocator *const alloc = globOpt->alloc; |
| |
| // Int-specialized syms |
| tempBv->Or(loop->likelyIntSymsUsedBeforeDefined, loop->symsDefInLoop); |
| tempBv->And(blockData.liveInt32Syms); |
| tempBv->Minus(blockData.liveLossyInt32Syms); |
| FOREACH_BITSET_IN_SPARSEBV(id, tempBv) |
| { |
| StackSym *const varSym = symTable->FindStackSym(id); |
| Assert(varSym); |
| Value *const value = blockData.FindValue(varSym); |
| Assert(value); |
| ValueInfo *const valueInfo = value->GetValueInfo(); |
| if(!valueInfo->IsInt()) |
| { |
| globOpt->ChangeValueInfo(nullptr, value, valueInfo->SpecializeToInt32(alloc)); |
| } |
| } NEXT_BITSET_IN_SPARSEBV; |
| |
| // Float-specialized syms |
| tempBv->Or(loop->likelyNumberSymsUsedBeforeDefined, loop->symsDefInLoop); |
| tempBv->Or(loop->forceFloat64SymsOnEntry); |
| tempBv->And(blockData.liveFloat64Syms); |
| GlobOptBlockData &landingPadBlockData = loop->landingPad->globOptData; |
| FOREACH_BITSET_IN_SPARSEBV(id, tempBv) |
| { |
| StackSym *const varSym = symTable->FindStackSym(id); |
| Assert(varSym); |
| |
| // If the type-spec sym is null or if the sym is not float-specialized in the loop landing pad, the sym may have |
| // been merged to float on a loop back-edge when it was live as float on the back-edge, and live as int in the loop |
| // header. In this case, compensation inserted in the loop landing pad will use BailOutNumberOnly, and so it is |
| // guaranteed that the value will be float. Otherwise, if the type-spec sym exists, its field can be checked to see |
| // if it's prevented from being anything but a number. |
| StackSym *const typeSpecSym = varSym->GetFloat64EquivSym(nullptr); |
| if(!typeSpecSym || |
| typeSpecSym->m_requiresBailOnNotNumber || |
| !landingPadBlockData.IsFloat64TypeSpecialized(varSym)) |
| { |
| Value *const value = blockData.FindValue(varSym); |
| if(value) |
| { |
| ValueInfo *const valueInfo = value->GetValueInfo(); |
| if(!valueInfo->IsNumber()) |
| { |
| globOpt->ChangeValueInfo(this, value, valueInfo->SpecializeToFloat64(alloc)); |
| } |
| } |
| else |
| { |
| this->globOptData.SetValue(globOpt->NewGenericValue(ValueType::Float), varSym); |
| } |
| } |
| } NEXT_BITSET_IN_SPARSEBV; |
| |
| // SIMD_JS |
| // Simd128 type-spec syms |
| BVSparse<JitArenaAllocator> tempBv2(globOpt->tempAlloc); |
| |
| // For syms we made alive in loop header because of hoisting, use-before-def, or def in Loop body, set their valueInfo to definite. |
| // Make live on header AND in one of forceSimd128* or likelySimd128* vectors. |
| tempBv->Or(loop->likelySimd128F4SymsUsedBeforeDefined, loop->symsDefInLoop); |
| tempBv->Or(loop->likelySimd128I4SymsUsedBeforeDefined); |
| tempBv->Or(loop->forceSimd128F4SymsOnEntry); |
| tempBv->Or(loop->forceSimd128I4SymsOnEntry); |
| tempBv2.Or(blockData.liveSimd128F4Syms, blockData.liveSimd128I4Syms); |
| tempBv->And(&tempBv2); |
| |
| FOREACH_BITSET_IN_SPARSEBV(id, tempBv) |
| { |
| StackSym * typeSpecSym = nullptr; |
| StackSym *const varSym = symTable->FindStackSym(id); |
| Assert(varSym); |
| |
| if (blockData.liveSimd128F4Syms->Test(id)) |
| { |
| typeSpecSym = varSym->GetSimd128F4EquivSym(nullptr); |
| |
| |
| if (!typeSpecSym || !landingPadBlockData.IsSimd128F4TypeSpecialized(varSym)) |
| { |
| Value *const value = blockData.FindValue(varSym); |
| if (value) |
| { |
| ValueInfo *const valueInfo = value->GetValueInfo(); |
| if (!valueInfo->IsSimd128Float32x4()) |
| { |
| globOpt->ChangeValueInfo(this, value, valueInfo->SpecializeToSimd128F4(alloc)); |
| } |
| } |
| else |
| { |
| this->globOptData.SetValue(globOpt->NewGenericValue(ValueType::GetSimd128(ObjectType::Simd128Float32x4), varSym), varSym); |
| } |
| } |
| } |
| else if (blockData.liveSimd128I4Syms->Test(id)) |
| { |
| |
| typeSpecSym = varSym->GetSimd128I4EquivSym(nullptr); |
| if (!typeSpecSym || !landingPadBlockData.IsSimd128I4TypeSpecialized(varSym)) |
| { |
| Value *const value = blockData.FindValue(varSym); |
| if (value) |
| { |
| ValueInfo *const valueInfo = value->GetValueInfo(); |
| if (!valueInfo->IsSimd128Int32x4()) |
| { |
| globOpt->ChangeValueInfo(this, value, valueInfo->SpecializeToSimd128I4(alloc)); |
| } |
| } |
| else |
| { |
| this->globOptData.SetValue(globOpt->NewGenericValue(ValueType::GetSimd128(ObjectType::Simd128Int32x4), varSym), varSym); |
| } |
| } |
| } |
| else |
| { |
| Assert(UNREACHED); |
| } |
| } NEXT_BITSET_IN_SPARSEBV; |
| tempBv->ClearAll(); |
| } |
| |
| // We need to handle the case where a symbol is type-spec'd coming from some predecessors, |
| // but not from others. |
| // |
| // We can do this by inserting the right conversion in the predecessor block, but we |
| // can only do this if we are the first successor of that block, since the previous successors |
| // would have already been processed. Instead, we'll need to break the edge and insert a block |
| // (airlock block) to put in the conversion code. |
| Assert(globOpt->tempBv->IsEmpty()); |
| |
| BVSparse<JitArenaAllocator> tempBv2(globOpt->tempAlloc); |
| BVSparse<JitArenaAllocator> tempBv3(globOpt->tempAlloc); |
| BVSparse<JitArenaAllocator> tempBv4(globOpt->tempAlloc); |
| |
| // SIMD_JS |
| BVSparse<JitArenaAllocator> simd128F4SymsToUnbox(globOpt->tempAlloc); |
| BVSparse<JitArenaAllocator> simd128I4SymsToUnbox(globOpt->tempAlloc); |
| |
| FOREACH_PREDECESSOR_EDGE_EDITING(edge, this, iter) |
| { |
| BasicBlock *pred = edge->GetPred(); |
| |
| if (pred->loop && pred->loop->GetHeadBlock() == this) |
| { |
| pred->DecrementDataUseCount(); |
| // Skip loop back-edges. We will handle these when we get to the exit blocks. |
| continue; |
| } |
| |
| BasicBlock *orgPred = nullptr; |
| if (pred->isAirLockCompensationBlock) |
| { |
| Assert(pred->GetPredList()->HasOne()); |
| orgPred = pred; |
| pred = (pred->GetPredList()->Head())->GetPred(); |
| } |
| |
| // Lossy int in the merged block, and no int in the predecessor - need a lossy conversion to int |
| tempBv2.Minus(blockData.liveLossyInt32Syms, pred->globOptData.liveInt32Syms); |
| |
| // Lossless int in the merged block, and no lossless int in the predecessor - need a lossless conversion to int |
| tempBv3.Minus(blockData.liveInt32Syms, this->globOptData.liveLossyInt32Syms); |
| globOpt->tempBv->Minus(pred->globOptData.liveInt32Syms, pred->globOptData.liveLossyInt32Syms); |
| tempBv3.Minus(globOpt->tempBv); |
| |
| globOpt->tempBv->Minus(blockData.liveVarSyms, pred->globOptData.liveVarSyms); |
| tempBv4.Minus(blockData.liveFloat64Syms, pred->globOptData.liveFloat64Syms); |
| |
| bool symIVNeedsSpecializing = (symIV && !pred->globOptData.liveInt32Syms->Test(symIV->m_id) && !tempBv3.Test(symIV->m_id)); |
| |
| // SIMD_JS |
| simd128F4SymsToUnbox.Minus(blockData.liveSimd128F4Syms, pred->globOptData.liveSimd128F4Syms); |
| simd128I4SymsToUnbox.Minus(blockData.liveSimd128I4Syms, pred->globOptData.liveSimd128I4Syms); |
| |
| |
| if (!globOpt->tempBv->IsEmpty() || |
| !tempBv2.IsEmpty() || |
| !tempBv3.IsEmpty() || |
| !tempBv4.IsEmpty() || |
| !simd128F4SymsToUnbox.IsEmpty() || |
| !simd128I4SymsToUnbox.IsEmpty() || |
| symIVNeedsSpecializing || |
| symsRequiringCompensationToMergedValueInfoMap.Count() != 0) |
| { |
| // We can't un-specialize a symbol in a predecessor if we've already processed |
| // a successor of that block. Instead, insert a new block on the flow edge |
| // (an airlock block) and do the un-specialization there. |
| // |
| // Alternatively, the current block could be an exit block out of this loop, and so the predecessor may exit the |
| // loop. In that case, if the predecessor may continue into the loop without exiting, then we need an airlock block |
| // to do the appropriate conversions only on the exit path (preferring not to do the conversions inside the loop). |
| // If, on the other hand, the predecessor always flows into the current block, then it always exits, so we don't need |
| // an airlock block and can just do the conversions in the predecessor. |
| if (pred->GetSuccList()->Head()->GetSucc() != this|| |
| (pred->loop && pred->loop->parent == this->loop && pred->GetSuccList()->Count() > 1)) |
| { |
| BasicBlock *airlockBlock = nullptr; |
| if (!orgPred) |
| { |
| if (PHASE_TRACE(Js::GlobOptPhase, this->func) && !globOpt->IsLoopPrePass()) |
| { |
| Output::Print(_u("TRACE: ")); |
| Output::Print(_u("Inserting airlock block to convert syms to var between block %d and %d\n"), |
| pred->GetBlockNum(), this->GetBlockNum()); |
| Output::Flush(); |
| } |
| airlockBlock = globOpt->func->m_fg->InsertAirlockBlock(edge); |
| } |
| else |
| { |
| Assert(orgPred->isAirLockCompensationBlock); |
| airlockBlock = orgPred; |
| pred->DecrementDataUseCount(); |
| airlockBlock->isAirLockCompensationBlock = false; // This is airlock block now. So remove the attribute. |
| } |
| globOpt->CloneBlockData(airlockBlock, pred); |
| |
| pred = airlockBlock; |
| } |
| if (!globOpt->tempBv->IsEmpty()) |
| { |
| globOpt->ToVar(globOpt->tempBv, pred); |
| } |
| if (!tempBv2.IsEmpty()) |
| { |
| globOpt->ToInt32(&tempBv2, pred, true /* lossy */); |
| } |
| if (!tempBv3.IsEmpty()) |
| { |
| globOpt->ToInt32(&tempBv3, pred, false /* lossy */); |
| } |
| if (!tempBv4.IsEmpty()) |
| { |
| globOpt->ToFloat64(&tempBv4, pred); |
| } |
| if (symIVNeedsSpecializing) |
| { |
| globOpt->tempBv->ClearAll(); |
| globOpt->tempBv->Set(symIV->m_id); |
| globOpt->ToInt32(globOpt->tempBv, pred, false /* lossy */); |
| } |
| if(symsRequiringCompensationToMergedValueInfoMap.Count() != 0) |
| { |
| globOpt->InsertValueCompensation(pred, symsRequiringCompensationToMergedValueInfoMap); |
| } |
| |
| // SIMD_JS |
| if (!simd128F4SymsToUnbox.IsEmpty()) |
| { |
| globOpt->ToTypeSpec(&simd128F4SymsToUnbox, pred, TySimd128F4, IR::BailOutSimd128F4Only); |
| } |
| |
| if (!simd128I4SymsToUnbox.IsEmpty()) |
| { |
| globOpt->ToTypeSpec(&simd128I4SymsToUnbox, pred, TySimd128I4, IR::BailOutSimd128I4Only); |
| } |
| } |
| } NEXT_PREDECESSOR_EDGE_EDITING; |
| |
| FOREACH_PREDECESSOR_EDGE(edge, this) |
| { |
| // Peak Memory optimization: |
| // These are in an arena, but putting them on the free list greatly reduces |
| // the peak memory used by the global optimizer for complex flow graphs. |
| BasicBlock *pred = edge->GetPred(); |
| if (!this->isLoopHeader || this->loop != pred->loop) |
| { |
| // Skip airlock compensation block as we are not going to walk this block. |
| if (pred->isAirLockCompensationBlock) |
| { |
| pred->DecrementDataUseCount(); |
| Assert(pred->GetPredList()->HasOne()); |
| pred = (pred->GetPredList()->Head())->GetPred(); |
| } |
| |
| if (pred->DecrementDataUseCount() == 0 && (!this->loop || this->loop->landingPad != pred)) |
| { |
| if (!(pred->GetSuccList()->HasOne() && this->GetPredList()->HasOne() && this->loop == nullptr)) |
| { |
| pred->globOptData.DeleteBlockData(); |
| } |
| else |
| { |
| pred->globOptData.NullOutBlockData(globOpt, globOpt->func); |
| } |
| } |
| } |
| } NEXT_PREDECESSOR_EDGE; |
| |
| globOpt->tempBv->ClearAll(); |
| Assert(!globOpt->IsLoopPrePass()); // We already early return if we are in prepass |
| |
| if (this->isLoopHeader) |
| { |
| Loop *const loop = this->loop; |
| |
| // Save values live on loop entry, such that we can adjust the state of the |
| // values on the back-edge to match. |
| loop->varSymsOnEntry = JitAnew(globOpt->alloc, BVSparse<JitArenaAllocator>, globOpt->alloc); |
| loop->varSymsOnEntry->Copy(this->globOptData.liveVarSyms); |
| |
| loop->int32SymsOnEntry = JitAnew(globOpt->alloc, BVSparse<JitArenaAllocator>, globOpt->alloc); |
| loop->int32SymsOnEntry->Copy(this->globOptData.liveInt32Syms); |
| |
| loop->lossyInt32SymsOnEntry = JitAnew(globOpt->alloc, BVSparse<JitArenaAllocator>, globOpt->alloc); |
| loop->lossyInt32SymsOnEntry->Copy(this->globOptData.liveLossyInt32Syms); |
| |
| loop->float64SymsOnEntry = JitAnew(globOpt->alloc, BVSparse<JitArenaAllocator>, globOpt->alloc); |
| loop->float64SymsOnEntry->Copy(this->globOptData.liveFloat64Syms); |
| |
| // SIMD_JS |
| loop->simd128F4SymsOnEntry = JitAnew(globOpt->alloc, BVSparse<JitArenaAllocator>, globOpt->alloc); |
| loop->simd128F4SymsOnEntry->Copy(this->globOptData.liveSimd128F4Syms); |
| |
| loop->simd128I4SymsOnEntry = JitAnew(globOpt->alloc, BVSparse<JitArenaAllocator>, globOpt->alloc); |
| loop->simd128I4SymsOnEntry->Copy(this->globOptData.liveSimd128I4Syms); |
| |
| |
| loop->liveFieldsOnEntry = JitAnew(globOpt->alloc, BVSparse<JitArenaAllocator>, globOpt->alloc); |
| loop->liveFieldsOnEntry->Copy(this->globOptData.liveFields); |
| |
| if(globOpt->DoBoundCheckHoist() && loop->inductionVariables) |
| { |
| globOpt->FinalizeInductionVariables(loop, &blockData); |
| if(globOpt->DoLoopCountBasedBoundCheckHoist()) |
| { |
| globOpt->DetermineDominatingLoopCountableBlock(loop, this); |
| } |
| } |
| } |
| else if (!this->loop) |
| { |
| this->SetDataUseCount(this->GetSuccList()->Count()); |
| } |
| else if(this == this->loop->dominatingLoopCountableBlock) |
| { |
| globOpt->DetermineLoopCount(this->loop); |
| } |
| } |
| |
| void GlobOpt::CloneBlockData(BasicBlock *const toBlock, BasicBlock *const fromBlock) |
| { |
| toBlock->globOptData.CloneBlockData(toBlock, fromBlock); |
| } |
| |
| void |
| GlobOpt::CloneValues(BasicBlock *const toBlock, GlobOptBlockData *toData, GlobOptBlockData *fromData) |
| { |
| ValueSet *const valuesToKillOnCalls = JitAnew(this->alloc, ValueSet, this->alloc); |
| toData->valuesToKillOnCalls = valuesToKillOnCalls; |
| |
| // Values are shared between symbols with the same ValueNumber. |
| // Use a dictionary to share the clone values. |
| ValueSetByValueNumber *const valuesCreatedForClone = this->valuesCreatedForClone; |
| Assert(valuesCreatedForClone); |
| Assert(valuesCreatedForClone->Count() == 0); |
| DebugOnly(ValueSetByValueNumber originalValues(tempAlloc, 64)); |
| |
| const uint tableSize = toData->symToValueMap->tableSize; |
| SListBase<GlobHashBucket> *const table = toData->symToValueMap->table; |
| for (uint i = 0; i < tableSize; i++) |
| { |
| FOREACH_SLISTBASE_ENTRY(GlobHashBucket, bucket, &table[i]) |
| { |
| Value *value = bucket.element; |
| ValueNumber valueNum = value->GetValueNumber(); |
| #if DBG |
| // Ensure that the set of values in fromData contains only one value per value number. Byte-code constant values |
| // are reused in multiple blocks without cloning, so exclude those value numbers. |
| { |
| Value *const previouslyClonedOriginalValue = originalValues.Lookup(valueNum); |
| if (previouslyClonedOriginalValue) |
| { |
| if (!byteCodeConstantValueNumbersBv->Test(valueNum)) |
| { |
| Assert(value == previouslyClonedOriginalValue); |
| } |
| } |
| else |
| { |
| originalValues.Add(value); |
| } |
| } |
| #endif |
| |
| Value *newValue = valuesCreatedForClone->Lookup(valueNum); |
| if (!newValue) |
| { |
| newValue = CopyValue(value, valueNum); |
| TrackMergedValueForKills(newValue, toData, nullptr); |
| valuesCreatedForClone->Add(newValue); |
| } |
| bucket.element = newValue; |
| } NEXT_SLISTBASE_ENTRY; |
| } |
| |
| valuesCreatedForClone->Clear(); |
| |
| ProcessValueKills(toBlock, toData); |
| } |
| |
| PRECandidatesList * GlobOpt::FindBackEdgePRECandidates(BasicBlock *block, JitArenaAllocator *alloc) |
| { |
| // Iterate over the value table looking for propertySyms which are candidates to |
| // pre-load in the landing pad for field PRE |
| |
| GlobHashTable *valueTable = block->globOptData.symToValueMap; |
| Loop *loop = block->loop; |
| PRECandidatesList *candidates = nullptr; |
| |
| for (uint i = 0; i < valueTable->tableSize; i++) |
| { |
| FOREACH_SLISTBASE_ENTRY(GlobHashBucket, bucket, &valueTable->table[i]) |
| { |
| Sym *sym = bucket.value; |
| |
| if (!sym->IsPropertySym()) |
| { |
| continue; |
| } |
| |
| PropertySym *propertySym = sym->AsPropertySym(); |
| |
| // Field should be live on the back-edge |
| if (!block->globOptData.liveFields->Test(propertySym->m_id)) |
| { |
| continue; |
| } |
| |
| // Field should be live in the landing pad as well |
| if (!loop->landingPad->globOptData.liveFields->Test(propertySym->m_id)) |
| { |
| continue; |
| } |
| |
| Value *value = bucket.element; |
| Sym *symStore = value->GetValueInfo()->GetSymStore(); |
| |
| if (!symStore || !symStore->IsStackSym()) |
| { |
| continue; |
| } |
| |
| // Check upwardExposed in case of: |
| // s1 = 0; |
| // loop: |
| // = o.x; |
| // foo(); |
| // o.x = s1; |
| // Can't thrash s1 in loop top. |
| if (!symStore->AsStackSym()->IsSingleDef() || loop->GetHeadBlock()->upwardExposedUses->Test(symStore->m_id)) |
| { |
| // If symStore isn't singleDef, we need to make sure it still has the same value. |
| // This usually fails if we are not aggressive at transferring values in the prepass. |
| Value **pSymStoreFromValue = valueTable->Get(symStore->m_id); |
| |
| // Consider: We should be fine if symStore isn't live in landing pad... |
| if (!pSymStoreFromValue || (*pSymStoreFromValue)->GetValueNumber() != value->GetValueNumber()) |
| { |
| continue; |
| } |
| } |
| |
| BasicBlock *landingPad = loop->landingPad; |
| Value *landingPadValue = landingPad->globOptData.FindValue(propertySym); |
| |
| if (!landingPadValue) |
| { |
| // Value should be added as initial value or already be there. |
| return nullptr; |
| } |
| |
| IR::Instr * ldInstr = this->prePassInstrMap->Lookup(propertySym->m_id, nullptr); |
| |
| if (!ldInstr) |
| { |
| continue; |
| } |
| |
| if (!candidates) |
| { |
| candidates = Anew(alloc, PRECandidatesList, alloc); |
| } |
| |
| candidates->Prepend(&bucket); |
| |
| } NEXT_SLISTBASE_ENTRY; |
| } |
| |
| return candidates; |
| } |
| |
| void GlobOpt::InsertCloneStrs(BasicBlock *toBlock, GlobOptBlockData *toData, GlobOptBlockData *fromData) |
| { |
| if (toBlock->isLoopHeader // isLoopBackEdge |
| && toBlock->cloneStrCandidates |
| && !IsLoopPrePass()) |
| { |
| Loop *loop = toBlock->loop; |
| BasicBlock *landingPad = loop->landingPad; |
| const SymTable *const symTable = func->m_symTable; |
| Assert(tempBv->IsEmpty()); |
| tempBv->And(toBlock->cloneStrCandidates, fromData->isTempSrc); |
| FOREACH_BITSET_IN_SPARSEBV(id, tempBv) |
| { |
| StackSym *const sym = (StackSym *)symTable->Find(id); |
| Assert(sym); |
| |
| if (!landingPad->globOptData.liveVarSyms->Test(id) |
| || !fromData->liveVarSyms->Test(id)) |
| { |
| continue; |
| } |
| |
| Value * landingPadValue = landingPad->globOptData.FindValue(sym); |
| if (landingPadValue == nullptr) |
| { |
| continue; |
| } |
| |
| Value * loopValue = fromData->FindValue(sym); |
| if (loopValue == nullptr) |
| { |
| continue; |
| } |
| |
| ValueInfo *landingPadValueInfo = landingPadValue->GetValueInfo(); |
| ValueInfo *loopValueInfo = loopValue->GetValueInfo(); |
| |
| if (landingPadValueInfo->IsLikelyString() |
| && loopValueInfo->IsLikelyString()) |
| { |
| IR::Instr *cloneStr = IR::Instr::New(Js::OpCode::CloneStr, this->func); |
| IR::RegOpnd *opnd = IR::RegOpnd::New(sym, IRType::TyVar, this->func); |
| cloneStr->SetDst(opnd); |
| cloneStr->SetSrc1(opnd); |
| if (loop->bailOutInfo->bailOutInstr) |
| { |
| loop->bailOutInfo->bailOutInstr->InsertBefore(cloneStr); |
| } |
| else |
| { |
| landingPad->InsertAfter(cloneStr); |
| } |
| toData->isTempSrc->Set(id); |
| } |
| } |
| NEXT_BITSET_IN_SPARSEBV; |
| tempBv->ClearAll(); |
| } |
| } |