summaryrefslogtreecommitdiffstats
path: root/js/src/jit/InstructionReordering.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/InstructionReordering.cpp')
-rw-r--r--js/src/jit/InstructionReordering.cpp248
1 files changed, 248 insertions, 0 deletions
diff --git a/js/src/jit/InstructionReordering.cpp b/js/src/jit/InstructionReordering.cpp
new file mode 100644
index 0000000000..b399e0bb8c
--- /dev/null
+++ b/js/src/jit/InstructionReordering.cpp
@@ -0,0 +1,248 @@
+/* -*- 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<MBasicBlock*, 4, SystemAllocPolicy> 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<MDefinition*, 4, SystemAllocPolicy> 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;
+}