/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "jit/InstructionReordering.h" #include "jit/MIRGraph.h" using namespace js; using namespace js::jit; static void MoveBefore(MBasicBlock* block, MInstruction* at, MInstruction* ins) { if (at == ins) { return; } // Update instruction numbers. for (MInstructionIterator iter(block->begin(at)); *iter != ins; iter++) { MOZ_ASSERT(iter->id() < ins->id()); iter->setId(iter->id() + 1); } ins->setId(at->id() - 1); block->moveBefore(at, ins); } static bool IsLastUse(MDefinition* ins, MDefinition* input, MBasicBlock* loopHeader) { // If we are in a loop, this cannot be the last use of any definitions from // outside the loop, as those definitions can be used in future iterations. if (loopHeader && input->block()->id() < loopHeader->id()) { return false; } for (MUseDefIterator iter(input); iter; iter++) { // Watch for uses defined in blocks which ReorderInstructions hasn't // processed yet. These nodes have not had their ids set yet. if (iter.def()->block()->id() > ins->block()->id()) { return false; } if (iter.def()->id() > ins->id()) { return false; } } return true; } static void MoveConstantsToStart(MBasicBlock* block, MInstruction* insertionPoint) { // Move constants with a single use in the current block to the start of the // block. Constants won't be reordered by ReorderInstructions, as they have no // inputs. Moving them up as high as possible can allow their use to be moved // up further, though, and has no cost if the constant is emitted at its use. MInstructionIterator iter(block->begin(insertionPoint)); while (iter != block->end()) { MInstruction* ins = *iter; iter++; if (!ins->isConstant() || !ins->hasOneUse() || ins->usesBegin()->consumer()->block() != block || IsFloatingPointType(ins->type())) { continue; } MOZ_ASSERT(ins->isMovable()); MOZ_ASSERT(insertionPoint != ins); // Note: we don't need to use MoveBefore here because MoveConstantsToStart // is called right before we renumber all instructions in this block. block->moveBefore(insertionPoint, ins); } } bool jit::ReorderInstructions(MIRGraph& graph) { // Renumber all instructions in the graph as we go. size_t nextId = 0; // List of the headers of any loops we are in. Vector loopHeaders; for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) { // Don't reorder instructions within entry blocks, which have special // requirements. bool isEntryBlock = *block == graph.entryBlock() || *block == graph.osrBlock(); MInstruction* insertionPoint = nullptr; if (!isEntryBlock) { // Move constants to the start of the block before renumbering all // instructions. insertionPoint = block->safeInsertTop(); MoveConstantsToStart(*block, insertionPoint); } // Renumber all definitions inside the basic blocks. for (MPhiIterator iter(block->phisBegin()); iter != block->phisEnd(); iter++) { iter->setId(nextId++); } for (MInstructionIterator iter(block->begin()); iter != block->end(); iter++) { iter->setId(nextId++); } if (isEntryBlock) { continue; } if (block->isLoopHeader()) { if (!loopHeaders.append(*block)) { return false; } } MBasicBlock* innerLoop = loopHeaders.empty() ? nullptr : loopHeaders.back(); MInstructionReverseIterator rtop = ++block->rbegin(insertionPoint); for (MInstructionIterator iter(block->begin(insertionPoint)); iter != block->end();) { MInstruction* ins = *iter; // Filter out some instructions which are never reordered. if (ins->isEffectful() || !ins->isMovable() || ins->resumePoint() || ins == block->lastIns()) { iter++; continue; } // Look for inputs where this instruction is the last use of that // input. If we move this instruction up, the input's lifetime will // be shortened, modulo resume point uses (which don't need to be // stored in a register, and can be handled by the register // allocator by just spilling at some point with no reload). Vector lastUsedInputs; for (size_t i = 0; i < ins->numOperands(); i++) { MDefinition* input = ins->getOperand(i); if (!input->isConstant() && IsLastUse(ins, input, innerLoop)) { if (!lastUsedInputs.append(input)) { return false; } } } // Don't try to move instructions which aren't the last use of any // of their inputs (we really ought to move these down instead). if (lastUsedInputs.length() < 2) { iter++; continue; } MInstruction* target = ins; MInstruction* postCallTarget = nullptr; for (MInstructionReverseIterator riter = ++block->rbegin(ins); riter != rtop; riter++) { MInstruction* prev = *riter; if (prev->isInterruptCheck()) { break; } if (prev->isSetInitializedLength()) { break; } // The instruction can't be moved before any of its uses. bool isUse = false; for (size_t i = 0; i < ins->numOperands(); i++) { if (ins->getOperand(i) == prev) { isUse = true; break; } } if (isUse) { break; } // The instruction can't be moved before an instruction that // stores to a location read by the instruction. if (prev->isEffectful() && (ins->getAliasSet().flags() & prev->getAliasSet().flags()) && ins->mightAlias(prev) != MDefinition::AliasType::NoAlias) { break; } // Make sure the instruction will still be the last use of one // of its inputs when moved up this far. for (size_t i = 0; i < lastUsedInputs.length();) { bool found = false; for (size_t j = 0; j < prev->numOperands(); j++) { if (prev->getOperand(j) == lastUsedInputs[i]) { found = true; break; } } if (found) { lastUsedInputs[i] = lastUsedInputs.back(); lastUsedInputs.popBack(); } else { i++; } } if (lastUsedInputs.length() < 2) { break; } // If we see a captured call result, either move the instruction before // the corresponding call or don't move it at all. if (prev->isCallResultCapture()) { if (!postCallTarget) { postCallTarget = target; } } else if (postCallTarget) { MOZ_ASSERT(MWasmCallBase::IsWasmCall(prev) || prev->isIonToWasmCall()); postCallTarget = nullptr; } // We can move the instruction before this one. target = prev; } if (postCallTarget) { // We would have plonked this instruction between a call and its // captured return value. Instead put it after the last corresponding // return value. target = postCallTarget; } iter++; MoveBefore(*block, target, ins); // Instruction reordering can move a bailing instruction up past a call // that throws an exception, causing spurious bailouts. This should rarely // be an issue in practice, so we only update the bailout kind if we don't // have anything more specific. if (ins->bailoutKind() == BailoutKind::TranspiledCacheIR) { ins->setBailoutKind(BailoutKind::InstructionReordering); } } if (block->isLoopBackedge()) { loopHeaders.popBack(); } } return true; }