//------------------------------------------------------------------------------------------------------- // 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->GetNextBranchOrLabel()->IsBranchInstr()); IR::BranchInstr *leaveChain = leaveTarget->GetNextBranchOrLabel()->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::Instr * nextLabel = leaveChain->GetNextRealInstrOrLabel(); if (!nextLabel->GetNextRealInstrOrLabel()->IsBranchInstr()) { // For jit loop bodies - we can encounter ProfiledLoopEnd before every early return Assert(nextLabel->GetNextRealInstrOrLabel()->m_opcode == Js::OpCode::ProfiledLoopEnd); IR::Instr * loopEnd = nextLabel->GetNextRealInstrOrLabel(); while (!loopEnd->GetNextRealInstrOrLabel()->IsBranchInstr()) { Assert(loopEnd->m_opcode == Js::OpCode::ProfiledLoopEnd); loopEnd = loopEnd->GetNextRealInstrOrLabel(); } leaveChain = loopEnd->GetNextRealInstrOrLabel()->AsBranchInstr(); } else { leaveChain = nextLabel->GetNextRealInstrOrLabel()->AsBranchInstr(); } BasicBlock *curBlock = curLabel->GetBasicBlock(); this->RemoveBlock(curBlock); curLabel = nextLabel->AsLabelInstr(); } instrPrev = leaveChain->m_next; IR::LabelInstr * exitLabel = leaveChain->GetTarget(); BasicBlock * curBlock = curLabel->GetBasicBlock(); 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() || leaveTarget->GetNextRealInstr()->m_opcode == Js::OpCode::ProfiledLoopEnd); IR::BranchInstr *leaveChain = leaveTarget->GetNextBranchOrLabel()->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(); if (!nextLabel->m_next->IsBranchInstr()) { // For jit loop bodies - we can encounter ProfiledLoopEnd before every early return Assert(nextLabel->m_next->m_opcode == Js::OpCode::ProfiledLoopEnd); break; } leaveChain = nextLabel->m_next->AsBranchInstr(); } IR::LabelInstr * exitLabel = leaveChain->GetTarget(); return Dominates(exitLabel->GetRegion(), finallyLabelStack->Top()->GetRegion()); } bool FlowGraph::CheckIfEarlyExitAndAddEdgeToFinally(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); bool assignRegionsBeforeGlobopt = this->func->HasTry() && (this->func->DoOptimizeTry() || (this->func->IsSimpleJit() && this->func->hasBailout) || this->func->IsLoopBodyInTryFinally()); bool createNonExceptionFinally = this->func->HasFinally() && (this->func->DoOptimizeTry() || (this->func->IsSimpleJit() && this->func->hasBailout)); // 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. // We also need accurate flow when we are jitting a loop body and have a tryfinally, because we could be inserting BailOutOnEarlyExit if (assignRegionsBeforeGlobopt) { this->catchLabelStack = JitAnew(this->alloc, SList, this->alloc); } if (this->func->HasFinally() && assignRegionsBeforeGlobopt) { this->finallyLabelStack = JitAnew(this->alloc, SList, 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); } 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 // // Leave L2 // L2 : Br L3 // L1 : Finally // // LeaveNull // L3 : // //to: // // TryFinally L1 // // BrOnException L1 // Leave L2 // L1 : BailOnException // L2 : Finally // // LeaveNull // L3: //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(); this->finallyLabelStack->Push(finallyLabel); if (!createNonExceptionFinally) { break; } // 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(); Assert(leaveTarget->labelRefs.HasOne()); IR::BranchInstr * brOnException = IR::BranchInstr::New(Js::OpCode::BrOnException, finallyLabel, instr->m_func); IR::BranchInstr * leaveInstr = leaveTarget->labelRefs.Head(); brOnException->SetByteCodeOffset(leaveInstr); leaveInstr->InsertBefore(brOnException); instrPrev = instr->m_prev; } break; case Js::OpCode::TryCatch: if (this->catchLabelStack) { AssertOrFailFast(!this->catchLabelStack->Empty()); this->catchLabelStack->Pop(); } break; case Js::OpCode::TryFinally: if (this->finallyLabelStack) { AssertOrFailFast(!this->finallyLabelStack->Empty()); this->finallyLabelStack->Pop(); } 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, ARM64 & X64 (non x86) because of their register calling convention // the ArgOuts need to be moved next to the call. #if !defined(_M_IX86) 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()); // 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. 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 DBG_DUMP if (PHASE_DUMP(Js::FGBuildPhase, this->GetFunc())) { if (assignRegionsBeforeGlobopt) { Output::Print(_u("Before adding early exit edges\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; this->func->Dump(); } } #endif if (this->finallyLabelStack) { Assert(this->finallyLabelStack->Empty()); if (createNonExceptionFinally) { // 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) { Assert(currentLabel != nullptr); __analysis_assume(currentLabel != nullptr); if (createNonExceptionFinally) { 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 if (CheckIfEarlyExitAndAddEdgeToFinally(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); bailOnEarlyExit->SetByteCodeOffset(instr); instr->InsertBefore(bailOnEarlyExit); IR::LabelInstr *exceptFinallyLabel = this->finallyLabelStack->Top(); IR::LabelInstr *nonExceptFinallyLabel = exceptFinallyLabel->m_next->m_next->AsLabelInstr(); // It is possible for the finally region to have a non terminating loop, in which case the end of finally is eliminated // We can skip adding edge from finally to early exit in this case IR::Instr * leaveToFinally = IR::BranchInstr::New(Js::OpCode::Leave, exceptFinallyLabel, this->func); leaveToFinally->SetByteCodeOffset(instr); instr->InsertBefore(leaveToFinally); instr->Remove(); this->AddEdge(currentLabel->GetBasicBlock(), exceptFinallyLabel->GetBasicBlock()); if (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->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; #if DBG instr->AsBranchInstr()->m_leaveConvToBr = true; #endif } } else { if (currentLabel->GetRegion()->GetType() == RegionTypeRoot) { // Found an orphaned leave, should be an early return from the loop body, insert bailout Assert(instr->AsBranchInstr()->m_isOrphanedLeave); IR::Instr * bailOnEarlyExit = IR::BailOutInstr::New(Js::OpCode::BailOnEarlyExit, IR::BailOutOnEarlyExit, instr, instr->m_func); instr->InsertBefore(bailOnEarlyExit); } else if (currentLabel->GetRegion()->GetType() == RegionTypeCatch) { if (!this->finallyLabelStack->Empty()) { BasicBlock *currentBlock = currentLabel->GetBasicBlock(); Assert(currentBlock->GetSuccList()->Count() == 1); BasicBlock *succ = currentBlock->GetSuccList()->Head()->GetSucc(); currentBlock->RemoveSucc(succ, this, true); // Add an edge to the finally block IR::BranchInstr * brOnException = IR::BranchInstr::New(Js::OpCode::BrOnException, this->finallyLabelStack->Top(), instr->m_func); instr->InsertBefore(brOnException); IR::LabelInstr * label = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func); instr->InsertBefore(label); BasicBlock *leaveBlock = this->AddBlock(label, instr, currentBlock->GetNext(), currentBlock); // Add edge to finally block, leave block this->AddEdge(currentBlock, this->finallyLabelStack->Top()->GetBasicBlock()); this->AddEdge(currentBlock, leaveBlock); } } } } else if (instr->m_opcode == Js::OpCode::Finally) { AssertOrFailFast(!this->finallyLabelStack->Empty()); this->finallyLabelStack->Pop(); } } NEXT_INSTR_IN_FUNC_EDITING; this->RemoveUnreachableBlocks(); 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("After adding early exit edges/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; this->func->Dump(); } } #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_DUMP if (PHASE_DUMP(Js::FGBuildPhase, this->GetFunc())) { if (assignRegionsBeforeGlobopt) { Output::Print(_u("After 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; this->func->Dump(); } } #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); brToExit->m_brFinallyToEarlyExit = true; brToExit->SetByteCodeOffset(lastInstr); 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); brToExit->SetByteCodeOffset(lastInstr); brToExit->m_brFinallyToEarlyExit = true; 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()); landingPadLabel->m_hasNonBranchRef = headBlock->GetFirstInstr()->AsLabelInstr()->m_hasNonBranchRef; 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) || this->func->IsLoopBodyInTryFinally()); 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_NO_DISPOSE(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 *loopBlocksBv = JitAnew(tempAlloc, BVSparse, 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(); } } } IR::Instr * lastInstr = block->GetLastInstr(); if (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(); FOREACH_PREDECESSOR_BLOCK(predBlock, block) { BasicBlock* intermediateBlock = block; Region * 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 (intermediateBlock->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() || // edge from early exit to finally in simplejit - in fulljit this Leave would have been deadcoded due to preceeding BailOutOnEarlyExit (predBlock->GetLastInstr()->GetPrevRealInstr()->m_opcode == Js::OpCode::BailOnEarlyExit && region->GetType() == RegionTypeFinally && this->func->IsSimpleJit())), "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 || // A BrOnException from finally to early exit can be converted to BrOnNoException and Br // The Br block maybe a common successor block for early exit along with the BrOnNoException block // Region from Br block will be picked up from a predecessor which is not BrOnNoException due to early exit // See test0() in test/EH/tryfinallytests.js (predRegion->GetType() == RegionTypeFinally && predBlock->GetLastInstr()->AsBranchInstr()->m_brFinallyToEarlyExit)); break; case Js::OpCode::Br: if (predBlock->GetLastInstr()->AsBranchInstr()->m_leaveConvToBr) { // Leave converted to Br in finally region AssertMsg(region == predRegion->GetParent(), "Bad region prop in finally"); } else if (region->GetType() == RegionTypeCatch && region != predRegion) { AssertMsg(predRegion->GetType() == RegionTypeTry, "Bad region type for the try"); } else if (region->GetType() == RegionTypeFinally && region != predRegion) { // We may be left with edges from finally region to early exit AssertMsg(predRegion->IsNonExceptingFinally() || predRegion->GetType() == RegionTypeTry, "Bad region type for the try"); } else { // We may be left with edges from finally region to early exit AssertMsg(predRegion->IsNonExceptingFinally() || 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->DoOptimizeTry() || (this->func->IsSimpleJit() && this->func->hasBailout)) { 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; } #endif if (fHasTry) { FOREACH_BLOCK_ALL(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; } 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()) { #if DBG bool assignRegionsBeforeGlobopt = this->func->HasTry() && (this->func->DoOptimizeTry() || (this->func->IsSimpleJit() && this->func->hasBailout) || this->func->IsLoopBodyInTryFinally()); Assert(assignRegionsBeforeGlobopt); #endif 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. 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; if (block->GetLastInstr()->m_opcode == Js::OpCode::LeaveNull || block->GetLastInstr()->m_opcode == Js::OpCode::Leave) { if (this->regToFinallyEndMap && region->IsNonExceptingFinally()) { BasicBlock *endOfFinally = regToFinallyEndMap->ContainsKey(region) ? regToFinallyEndMap->Item(region) : nullptr; if (!endOfFinally) { regToFinallyEndMap->Add(region, block); } else { Assert(endOfFinally->GetLastInstr()->m_opcode != Js::OpCode::LeaveNull || block == endOfFinally); regToFinallyEndMap->Item(region, 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; } // One of the pred blocks maybe an eh region, in that case it is important to mark this label's m_hasNonBranchRef // If not later in codegen, this label can get deleted. And during SccLiveness, region is propagated to newly created labels in lowerer from the previous label's region // We can end up assigning an eh region to a label in a non eh region. And if there is a bailout in such a region, bad things will happen in the interpreter :) // See test2()/test3() in tryfinallytests.js if (!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 = nullptr; 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->isDead || block->isDeleted) { // We can end up calling this function with such blocks, return doing nothing // See test5() in tryfinallytests.js return; } if (block == this->blockList) { // Head of the graph: create the root region. region = Region::New(RegionTypeRoot, nullptr, this->func); } else if (block->GetPredList()->Count() == 1) { BasicBlock *predBlock = block->GetPredList()->Head()->GetPred(); AssertMsg(predBlock->GetBlockNum() < this->blockCount, "Misnumbered block at teardown time?"); predRegion = predBlock->GetFirstInstr()->AsLabelInstr()->GetRegion(); Assert(predRegion); region = this->PropagateRegionFromPred(block, predBlock, predRegion, tryInstr); } 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 (?) 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 || predBlock->GetLastInstr()->m_opcode == Js::OpCode::BrOnNoException) && predBlock->GetLastInstr()->AsBranchInstr()->m_brFinallyToEarlyExit) { Assert(predRegion->IsNonExceptingFinally()); // BrOnException from finally region to early exit // Skip this edge continue; } if (predBlock->GetLastInstr()->m_opcode == Js::OpCode::Br && predBlock->GetLastInstr()->GetPrevRealInstr()->m_opcode == Js::OpCode::BrOnNoException) { Assert(predBlock->GetLastInstr()->GetPrevRealInstr()->AsBranchInstr()->m_brFinallyToEarlyExit); Assert(predRegion->IsNonExceptingFinally()); // BrOnException from finally region to early exit changed to BrOnNoException and Br during break block removal continue; } region = this->PropagateRegionFromPred(block, predBlock, predRegion, tryInstr); break; } } NEXT_PREDECESSOR_BLOCK; } } Assert(region || 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: 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, bool afterForward /*= false*/) { BasicBlock * airlockBlock = BasicBlock::New(this); BasicBlock * sourceBlock = edge->GetPred(); BasicBlock * sinkBlock = edge->GetSucc(); IR::Instr * sourceLastInstr = sourceBlock->GetLastInstr(); // // Normalize block // if(!sourceLastInstr->IsBranchInstr()) { // There are some cases where the last instruction of a block can be not a branch; // for example, if it was previously a conditional branch that was impossible to take. // In these situations, we can insert an unconditional branch to fallthrough for that // block, to renormalize it. SListBaseCounted* successors = sourceBlock->GetSuccList(); // Only handling the case for one arc left at the moment; other cases are likely bugs. AssertOrFailFastMsg(successors->HasOne(), "Failed to normalize weird block before airlock"); FlowEdge* onlyLink = successors->Head(); AssertOrFailFastMsg(onlyLink == edge, "Found duplicate of edge?"); AssertOrFailFastMsg(onlyLink->GetSucc() == sinkBlock, "State inconsistent"); sourceLastInstr->InsertAfter(IR::BranchInstr::New(Js::OpCode::Br, onlyLink->GetSucc()->GetFirstInstr()->AsLabelInstr(), sourceLastInstr->m_func)); sourceLastInstr = sourceLastInstr->m_next; } BasicBlock * sinkPrevBlock = sinkBlock->prev; IR::Instr * sinkPrevBlockLastInstr = sinkPrevBlock->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); // If we have regions in play, we should update them on the airlock block appropriately if (afterForward) { airlockLabel->SetRegion(sinkLabel->GetRegion()); } // 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, afterForward); 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 /*=false*/, bool sinkBlockLoop /*=false*/, bool afterForward /*=false*/) { 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); } } if (!afterForward) { bool assignRegionsBeforeGlobopt = this->func->HasTry() && (this->func->DoOptimizeTry() || (this->func->IsSimpleJit() && this->func->hasBailout) || this->func->IsLoopBodyInTryFinally()); if (assignRegionsBeforeGlobopt) { UpdateRegionForBlockFromEHPred(compBlock); } } else { compLabel->SetRegion(sinkLabel->GetRegion()); } 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 = Js::OpCode::InvalidOpCode; 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 (!brIsTrue) { instrBr->AsBranchInstr()->Invert(); } 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 { 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, instrLd2->GetDst(), falseOpnd, 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()); } brSrc = brSrc->Copy(this->func); // We need brSrc later, but instr->Remove() might delete it... IR::AutoReuseOpnd brSrcAutoCopy(brSrc, this->func, true); 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::HasSymUseInRange(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; } BailOutInfo * BasicBlock::CreateLoopTopBailOutInfo(GlobOpt * globOpt) { IR::Instr * firstInstr = this->GetFirstInstr(); BailOutInfo* bailOutInfo = JitAnew(globOpt->func->m_alloc, BailOutInfo, firstInstr->GetByteCodeOffset(), firstInstr->m_func); bailOutInfo->isLoopTopBailOutInfo = true; globOpt->FillBailOutInfo(this, bailOutInfo); #if ENABLE_DEBUG_CONFIG_OPTIONS bailOutInfo->bailOutOpcode = Js::OpCode::LoopBodyStart; #endif return bailOutInfo; } 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; instr->AsLabelInstr()->m_hasNonBranchRef = 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::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; } bool Loop::IsSymAssignedToInSelfOrParents(StackSym * const sym) const { for (const Loop* curLoop = this; curLoop != nullptr; curLoop = curLoop->parent) { if (curLoop->symsAssignedToInLoop->Test(sym->m_id)) { return true; } } return false; } BasicBlock * Loop::GetAnyTailBlock() const { BasicBlock * tail = nullptr; BasicBlock * loopHeader = this->GetHeadBlock(); FOREACH_PREDECESSOR_BLOCK(pred, loopHeader) { if (this->IsDescendentOrSelf(pred->loop)) { tail = pred; } } NEXT_PREDECESSOR_BLOCK; Assert(tail); return tail; } #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); 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); } } cmpInstr->InsertBefore(bytecodeInstr); 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 - 1, 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 deadSymsBv(tempAlloc); BVSparse keepAliveSymsBv(tempAlloc); BVSparse availableValueNumbers(tempAlloc); availableValueNumbers.Copy(this->globOptData.globOpt->byteCodeConstantValueNumbersBv); BVSparse *upwardExposedUses = this->upwardExposedUses; BVSparse *upwardExposedFields = this->upwardExposedFields; bool isInLoop = !!this->loop; BVSparse symsInCallSequence(tempAlloc); SListBase * 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) { Sym * sym = bucket.value; bool isSymUpwardExposed = upwardExposedUses->Test(sym->m_id) || upwardExposedFields->Test(sym->m_id); if (!isSymUpwardExposed && symsInCallSequence.Test(sym->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. bool isSymFieldPRESymStore = isInLoop && this->loop->fieldPRESymStores->Test(sym->m_id); if ((SymID)sym->m_id <= this->globOptData.globOpt->maxInitialSymID && !isSymUpwardExposed && !isSymFieldPRESymStore && (!isInLoop || !this->globOptData.globOpt->prePassCopyPropSym->Test(sym->m_id))) { Value *val = bucket.element; ValueInfo *valueInfo = val->GetValueInfo(); Sym *symStore = valueInfo->GetSymStore(); if (symStore && symStore == sym) { // 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(sym->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); this->globOptData.SetChangedSym(sym); } } else { 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 != sym) { 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); Sym* sym = this->func->m_symTable->Find(dead_id); this->globOptData.SetChangedSym(sym); } 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 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; } } static bool IsLegalOpcodeForPathDepBrFold(IR::Instr *instr) { if (!instr->IsRealInstr()) { return true; } switch (instr->m_opcode) { case Js::OpCode::Ld_A: case Js::OpCode::Ld_I4: case Js::OpCode::LdFld: case Js::OpCode::ByteCodeUses: case Js::OpCode::InlineeEnd: return true; } #if DBG if (PHASE_TRACE(Js::PathDepBranchFoldingPhase, instr->m_func) && Js::Configuration::Global.flags.Verbose) { Output::Print(_u("Skipping PathDependentBranchFolding due to: ")); instr->Dump(); } #endif return false; } static bool IsCopyTypeInstr(IR::Instr *instr) { switch (instr->m_opcode) { case Js::OpCode::LdC_A_I4: case Js::OpCode::Ld_I4: case Js::OpCode::Ld_A: case Js::OpCode::LdFld: return true; default: return false; } }; Value * BasicBlock::FindValueInLocalThenGlobalValueTableAndUpdate(GlobOpt *globOpt, GlobHashTable * localSymToValueMap, IR::Instr *instr, Sym *dstSym, Sym *srcSym) { Value ** localDstValue = nullptr; Value * srcVal = nullptr; if (dstSym) { localDstValue = localSymToValueMap->FindOrInsertNew(dstSym); } Value ** localSrcValue = localSymToValueMap->Get(srcSym); if (!localSrcValue) { Value *globalValue = this->globOptData.FindValue(srcSym); if (globOpt->IsLoopPrePass() && globalValue && (!srcSym->IsStackSym() || !globOpt->IsSafeToTransferInPrepass(srcSym->AsStackSym(), globalValue->GetValueInfo()))) { srcVal = nullptr; } else { srcVal = globalValue; } } else { srcVal = *localSrcValue; } if (dstSym) { Assert(IsCopyTypeInstr(instr)); *localDstValue = srcVal; } return srcVal; } IR::LabelInstr* BasicBlock::CanProveConditionalBranch(IR::BranchInstr *branch, GlobOpt* globOpt, GlobHashTable * localSymToValueMap) { if (!branch->GetSrc1() || !branch->GetSrc1()->GetStackSym()) { return nullptr; } Value *src1Val = nullptr, *src2Val = nullptr; Js::Var src1Var = nullptr, src2Var = nullptr; src1Val = FindValueInLocalThenGlobalValueTableAndUpdate(globOpt, localSymToValueMap, branch, nullptr, branch->GetSrc1()->GetStackSym()); if (!src1Val) { return nullptr; } src1Var = globOpt->GetConstantVar(branch->GetSrc1(), src1Val); if (branch->GetSrc2() != nullptr) { if (branch->GetSrc2()->GetStackSym()) { src2Val = FindValueInLocalThenGlobalValueTableAndUpdate(globOpt, localSymToValueMap, branch, nullptr, branch->GetSrc2()->GetStackSym()); } if (!src2Val) { return nullptr; } src2Var = globOpt->GetConstantVar(branch->GetSrc2(), src2Val); } bool provenTrue; if (!globOpt->CanProveConditionalBranch(branch, src1Val, src2Val, src1Var, src2Var, &provenTrue)) { return nullptr; } IR::LabelInstr * newTarget = provenTrue ? branch->GetTarget() : branch->GetNextRealInstrOrLabel()->AsLabelInstr(); return newTarget; } void BasicBlock::CheckLegalityAndFoldPathDepBranches(GlobOpt* globOpt) { IR::LabelInstr * lastBranchTarget = nullptr; IR::Instr *currentInlineeEnd = nullptr, *unskippedInlineeEnd = nullptr; GlobHashTable * localSymToValueMap = nullptr; BVSparse * currentPathDefines = nullptr; auto UpdateValueForCopyTypeInstr = [&](IR::Instr *instr) -> Value* { Value * dstValue = nullptr; if (instr->m_opcode == Js::OpCode::LdFld) { // Special handling for LdFld Assert(instr->GetSrc1()->IsSymOpnd()); IR::SymOpnd *symOpnd = instr->GetSrc1()->AsSymOpnd(); if (symOpnd->m_sym->IsPropertySym()) { PropertySym * originalPropertySym = symOpnd->m_sym->AsPropertySym(); Value *const objectValue = FindValueInLocalThenGlobalValueTableAndUpdate(globOpt, localSymToValueMap, instr, nullptr, originalPropertySym->m_stackSym); Sym* objSym = objectValue ? objectValue->GetValueInfo()->GetSymStore() : nullptr; PropertySym *prop = PropertySym::Find(objSym ? objSym->m_id : originalPropertySym->m_stackSym->m_id, originalPropertySym->m_propertyId, globOpt->func); if (prop) { dstValue = FindValueInLocalThenGlobalValueTableAndUpdate(globOpt, localSymToValueMap, instr, instr->GetDst()->GetStackSym(), prop); } else { Value ** localDstValue = localSymToValueMap->FindOrInsertNew(instr->GetDst()->GetStackSym()); dstValue = *localDstValue = nullptr; } } } else if (instr->GetSrc1()->GetStackSym()) { StackSym* src1Sym = instr->GetSrc1()->GetStackSym(); dstValue = FindValueInLocalThenGlobalValueTableAndUpdate(globOpt, localSymToValueMap, instr, instr->GetDst()->GetSym(), src1Sym); } else if (instr->GetSrc1()->IsIntConstOpnd()) { Value **localValue = localSymToValueMap->FindOrInsertNew(instr->GetDst()->GetSym()); dstValue = *localValue = globOpt->GetIntConstantValue(instr->GetSrc1()->AsIntConstOpnd()->AsInt32(), instr); } else if (instr->GetSrc1()->IsInt64ConstOpnd()) { Value **localValue = localSymToValueMap->FindOrInsertNew(instr->GetDst()->GetSym()); dstValue = *localValue = globOpt->GetIntConstantValue(instr->GetSrc1()->AsInt64ConstOpnd()->GetValue(), instr); } else { ValueType src1Value = instr->GetSrc1()->GetValueType(); Value **localValue = localSymToValueMap->FindOrInsertNew(instr->GetDst()->GetSym()); if (src1Value.IsUndefined() || src1Value.IsBoolean()) { dstValue = *localValue = globOpt->GetVarConstantValue(instr->GetSrc1()->AsAddrOpnd()); } else { dstValue = *localValue = nullptr; } } return dstValue; }; FOREACH_INSTR_IN_BLOCK(instr, this) { if (OpCodeAttr::HasDeadFallThrough(instr->m_opcode)) { return; } if (instr->m_opcode == Js::OpCode::InlineeEnd) { unskippedInlineeEnd = currentInlineeEnd = instr; } } NEXT_INSTR_IN_BLOCK; IR::Instr * instr = this->GetLastInstr(); // We have to first check the legality and only then allocate expensive data structures on the tempArena, because most block will have instructions we cant skip while (instr) { if (!instr->IsBranchInstr() && !instr->IsLabelInstr() && !IsLegalOpcodeForPathDepBrFold(instr)) { return; } if (instr->IsLabelInstr()) { if (instr->AsLabelInstr()->m_isLoopTop) { // don't cross over to loops return; } } if (instr->IsBranchInstr()) { IR::BranchInstr *branch = instr->AsBranchInstr(); if (branch->IsUnconditional()) { if (!branch->GetTarget()) { return; } instr = branch->GetTarget(); } else { // Found only legal instructions until a conditional branch, build expensive data structures and check provability break; } } else { instr = instr->m_next; } } instr = this->GetLastInstr(); // Allocate hefty structures, we will not free them because OptBlock does a Reset on the tempAlloc localSymToValueMap = GlobHashTable::New(globOpt->tempAlloc, 8); currentPathDefines = JitAnew(globOpt->tempAlloc, BVSparse, globOpt->tempAlloc); /* We start from the current instruction and go on scanning for legality, as long as it is legal to skip an instruction, skip. * When we see an unconditional branch, start scanning from the branchTarget * When we see a conditional branch, check if we can prove the branch target, if we can, adjust the flowgraph, and continue in the direction of the proven target * We stop, when we no longer can skip instructions, either due to legality check or a non provable conditional branch */ while (instr) { if (!instr->IsBranchInstr() && !instr->IsLabelInstr() && !IsLegalOpcodeForPathDepBrFold(instr)) { return; } if (OpCodeAttr::HasDeadFallThrough(instr->m_opcode)) // BailOnNoProfile etc { return; } if (instr->IsLabelInstr()) { if (instr->AsLabelInstr()->m_isLoopTop) { // don't cross over to loops return; } } if (instr->m_opcode == Js::OpCode::InlineeEnd) { if (currentInlineeEnd != nullptr) { return; } currentInlineeEnd = instr; } else if (instr->GetDst()) { if (!instr->GetDst()->IsRegOpnd()) // complex dstOpnd, stop. { return; } IR::RegOpnd *dst = instr->GetDst()->AsRegOpnd(); currentPathDefines->Set(dst->GetStackSym()->m_id); if (IsCopyTypeInstr(instr)) { Value *dstValue = UpdateValueForCopyTypeInstr(instr); if (instr->m_opcode == Js::OpCode::LdFld && !dstValue) { // We cannot skip a LdFld if we didnt find its valueInfo in the localValueTable return; } } else { Value ** localDstValue = localSymToValueMap->FindOrInsertNew(instr->GetDst()->GetStackSym()); *localDstValue = nullptr; } } if (instr->IsBranchInstr()) { IR::BranchInstr* branch = instr->AsBranchInstr(); IR::LabelInstr* branchTarget = nullptr; if (branch->IsUnconditional()) { branchTarget = branch->GetTarget(); if (!branchTarget) { return; } if (branchTarget->m_isLoopTop) { return; } } else { if (branch->GetTarget()->m_isLoopTop) { return; } branchTarget = CanProveConditionalBranch(branch, globOpt, localSymToValueMap); if (!branchTarget) { return; } } FOREACH_BITSET_IN_SPARSEBV(id, currentPathDefines) { if (branchTarget->GetBasicBlock()->upwardExposedUses->Test(id)) { // it is used in the direction of the branch, we can't skip it return; } } NEXT_BITSET_IN_SPARSEBV; if (PHASE_TRACE(Js::PathDepBranchFoldingPhase, this->func)) { if (!branch->IsUnconditional()) { Output::Print(_u("TRACE PathDependentBranchFolding: ")); Output::Print(_u("Can prove retarget of branch in Block %d from Block %d to Block %d in func %s\n"), this->GetBlockNum(), this->GetLastInstr()->IsBranchInstr() ? this->GetLastInstr()->AsBranchInstr()->GetTarget()->GetBasicBlock()->GetBlockNum() : this->GetNext()->GetBlockNum(), branchTarget->GetBasicBlock()->GetBlockNum(), this->func->GetJITFunctionBody()->GetDisplayName()); if (globOpt->IsLoopPrePass()) { Output::Print(_u("In LoopPrePass\n")); } Output::Flush(); } } if (this->GetLastInstr()->IsBranchInstr() && (this->GetLastInstr()->AsBranchInstr()->GetTarget() == branchTarget)) { // happens on the first block we start from only lastBranchTarget = branchTarget; instr = lastBranchTarget; continue; } if (branchTarget != this->GetLastInstr()->GetNextRealInstrOrLabel()) { IR::Instr* lastInstr = this->GetLastInstr(); // We add an empty ByteCodeUses with correct bytecodeoffset, for correct info on a post-op bailout of the previous instr IR::Instr* emptyByteCodeUse = IR::ByteCodeUsesInstr::New(lastInstr->m_func, lastInstr->GetByteCodeOffset()); lastInstr->InsertAfter(emptyByteCodeUse); IR::BranchInstr * newBranch = IR::BranchInstr::New(Js::OpCode::Br, branchTarget, branchTarget->m_func); if (lastInstr->IsBranchInstr()) { globOpt->ConvertToByteCodeUses(lastInstr); } emptyByteCodeUse->InsertAfter(newBranch); globOpt->func->m_fg->AddEdge(this, branchTarget->GetBasicBlock()); this->IncrementDataUseCount(); } else { // If the new target is a fall through label, delete the branch globOpt->ConvertToByteCodeUses(this->GetLastInstr()); } if (currentInlineeEnd != nullptr && currentInlineeEnd != unskippedInlineeEnd) { this->GetLastInstr()->InsertBefore(currentInlineeEnd->Copy()); globOpt->ProcessInlineeEnd(currentInlineeEnd); currentInlineeEnd = nullptr; } // We are adding an unconditional branch, go over all the current successors and remove the ones that are dead now FOREACH_SUCCESSOR_BLOCK_EDITING(blockSucc, this, iter) { if (branchTarget != blockSucc->GetFirstInstr()->AsLabelInstr()) { // Change the old succ edge to dead this->RemoveDeadSucc(blockSucc, globOpt->func->m_fg); if (this->GetDataUseCount() > 0) { this->DecrementDataUseCount(); } if (blockSucc->GetPredList()->Count() == 0) { this->func->m_fg->RemoveBlock(blockSucc, globOpt); } } }NEXT_SUCCESSOR_BLOCK_EDITING; lastBranchTarget = branchTarget; instr = lastBranchTarget; #if DBG if (PHASE_TRACE(Js::PathDepBranchFoldingPhase, instr->m_func) && Js::Configuration::Global.flags.Verbose) { Output::Print(_u("After PathDependentBranchFolding: ")); this->func->Dump(); } #endif } else { instr = instr->m_next; } } return; } bool BasicBlock::PathDepBranchFolding(GlobOpt* globOpt) { if (PHASE_OFF(Js::PathDepBranchFoldingPhase, this->func)) { return false; } CheckLegalityAndFoldPathDepBranches(globOpt); return true; } 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 symsRequiringCompensation(globOpt->tempAlloc); { BVSparse 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) { blockData.InitBlockData(globOpt, globOpt->func); } 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); this->loop->bailOutInfo = this->CreateLoopTopBailOutInfo(globOpt); } // 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 * 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; 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 symsNeedingLossyIntConversion(globOpt->tempAlloc); BVSparse symsNeedingLosslessIntConversion(globOpt->tempAlloc); BVSparse symsNeedingFloatConversion(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 symsNeedingLossyIntConversion.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 symsNeedingLosslessIntConversion.Minus(blockData.liveInt32Syms, blockData.liveLossyInt32Syms); globOpt->tempBv->Minus(pred->globOptData.liveInt32Syms, pred->globOptData.liveLossyInt32Syms); symsNeedingLosslessIntConversion.Minus(globOpt->tempBv); globOpt->tempBv->Minus(blockData.liveVarSyms, pred->globOptData.liveVarSyms); symsNeedingFloatConversion.Minus(blockData.liveFloat64Syms, pred->globOptData.liveFloat64Syms); bool symIVNeedsSpecializing = (symIV && !pred->globOptData.liveInt32Syms->Test(symIV->m_id) && !symsNeedingLosslessIntConversion.Test(symIV->m_id)); if (!globOpt->tempBv->IsEmpty() || !symsNeedingLossyIntConversion.IsEmpty() || !symsNeedingLosslessIntConversion.IsEmpty() || !symsNeedingFloatConversion.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 (!symsNeedingLossyIntConversion.IsEmpty()) { globOpt->ToInt32(&symsNeedingLossyIntConversion, pred, true /* lossy */); } if (!symsNeedingLosslessIntConversion.IsEmpty()) { globOpt->ToInt32(&symsNeedingLosslessIntConversion, pred, false /* lossy */); } if (!symsNeedingFloatConversion.IsEmpty()) { globOpt->ToFloat64(&symsNeedingFloatConversion, 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); } } } 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, globOpt->alloc); loop->varSymsOnEntry->Copy(this->globOptData.liveVarSyms); loop->int32SymsOnEntry = JitAnew(globOpt->alloc, BVSparse, globOpt->alloc); loop->int32SymsOnEntry->Copy(this->globOptData.liveInt32Syms); loop->lossyInt32SymsOnEntry = JitAnew(globOpt->alloc, BVSparse, globOpt->alloc); loop->lossyInt32SymsOnEntry->Copy(this->globOptData.liveLossyInt32Syms); loop->float64SymsOnEntry = JitAnew(globOpt->alloc, BVSparse, globOpt->alloc); loop->float64SymsOnEntry->Copy(this->globOptData.liveFloat64Syms); loop->liveFieldsOnEntry = JitAnew(globOpt->alloc, BVSparse, 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 *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); } PRECandidates * 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; PRECandidates *candidates = JitAnew(this->tempAlloc, PRECandidates); 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. continue; } IR::Instr * ldInstr = this->prePassInstrMap->Lookup(propertySym->m_id, nullptr); if (!ldInstr) { continue; } if (!candidates->candidatesList) { candidates->candidatesList = JitAnew(alloc, PRECandidatesList, alloc); candidates->candidatesToProcess = JitAnew(alloc, BVSparse, alloc); candidates->candidatesBv = JitAnew(alloc, BVSparse, alloc); } candidates->candidatesList->Prepend(&bucket); candidates->candidatesToProcess->Set(propertySym->m_id); candidates->candidatesBv->Set(propertySym->m_id); } 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(); } }