diff --git a/js/src/jit/InstructionReordering.cpp b/js/src/jit/InstructionReordering.cpp new file mode 100644 index 000000000000..bc1eb993ca31 --- /dev/null +++ b/js/src/jit/InstructionReordering.cpp @@ -0,0 +1,183 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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" + +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; +} + +bool +jit::ReorderInstructions(MIRGenerator* mir, 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. + if (*block == graph.entryBlock() || *block == graph.osrBlock()) + continue; + + if (block->isLoopHeader()) { + if (!loopHeaders.append(*block)) + return false; + } + + MBasicBlock* innerLoop = loopHeaders.empty() ? nullptr : loopHeaders.back(); + + for (MPhiIterator iter(block->phisBegin()); iter != block->phisEnd(); iter++) + iter->setId(nextId++); + + for (MInstructionIterator iter(block->begin()); iter != block->end(); iter++) + iter->setId(nextId++); + + for (MInstructionIterator iter(block->begin()); 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; + } + + // Move constants with a single use in the current block to the + // start of the block. Constants won't be reordered by the logic + // below, 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. + if (ins->isConstant() && + ins->hasOneUse() && + ins->usesBegin()->consumer()->block() == *block && + !IsFloatingPointType(ins->type())) + { + iter++; + MInstructionIterator targetIter = block->begin(); + if (targetIter->isInterruptCheck()) + targetIter++; + MoveBefore(*block, *targetIter, ins); + 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; + for (MInstructionReverseIterator riter = ++block->rbegin(ins); riter != block->rend(); riter++) { + MInstruction* prev = *riter; + if (prev->isInterruptCheck()) + 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)) + { + 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; + + // We can move the instruction before this one. + target = prev; + } + + iter++; + MoveBefore(*block, target, ins); + } + + if (block->isLoopBackedge()) + loopHeaders.popBack(); + } + + return true; +} diff --git a/js/src/jit/InstructionReordering.h b/js/src/jit/InstructionReordering.h new file mode 100644 index 000000000000..113e4906508f --- /dev/null +++ b/js/src/jit/InstructionReordering.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/. */ + +#ifndef jit_InstructionReordering_h +#define jit_InstructionReordering_h + +#include "jit/IonAnalysis.h" + +namespace js { +namespace jit { + +bool +ReorderInstructions(MIRGenerator* mir, MIRGraph& graph); + +} // namespace jit +} // namespace js + +#endif // jit_InstructionReordering_h diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 48ec876ccb75..1b271e6c2e16 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -1674,6 +1674,18 @@ OptimizeMIR(MIRGenerator* mir) return false; } + if (mir->optimizationInfo().instructionReorderingEnabled()) { + AutoTraceLog log(logger, TraceLogger_ReorderInstructions); + if (!ReorderInstructions(mir, graph)) + return false; + gs.spewPass("Reordering"); + + AssertExtendedGraphCoherency(graph); + + if (mir->shouldCancel("Reordering")) + return false; + } + // Make loops contiguous. We do this after GVN/UCE and range analysis, // which can remove CFG edges, exposing more blocks that can be moved. { diff --git a/js/src/jit/IonOptimizationLevels.cpp b/js/src/jit/IonOptimizationLevels.cpp index 8827dc24b98c..34d1aff459e4 100644 --- a/js/src/jit/IonOptimizationLevels.cpp +++ b/js/src/jit/IonOptimizationLevels.cpp @@ -33,6 +33,7 @@ OptimizationInfo::initNormalOptimizationInfo() licm_ = true; rangeAnalysis_ = true; loopUnrolling_ = true; + reordering_ = true; autoTruncate_ = true; sink_ = true; registerAllocator_ = RegisterAllocator_Backtracking; diff --git a/js/src/jit/IonOptimizationLevels.h b/js/src/jit/IonOptimizationLevels.h index 02f945bbad18..7c55231606bb 100644 --- a/js/src/jit/IonOptimizationLevels.h +++ b/js/src/jit/IonOptimizationLevels.h @@ -79,6 +79,9 @@ class OptimizationInfo // Toggles whether loop unrolling is performed. bool loopUnrolling_; + // Toggles whether instruction reordering is performed. + bool reordering_; + // Toggles whether Truncation based on Range Analysis is used. bool autoTruncate_; @@ -175,6 +178,10 @@ class OptimizationInfo return loopUnrolling_ && !js_JitOptions.disableLoopUnrolling; } + bool instructionReorderingEnabled() const { + return reordering_ && !js_JitOptions.disableInstructionReordering; + } + bool autoTruncateEnabled() const { return autoTruncate_ && rangeAnalysisEnabled(); } diff --git a/js/src/jit/JitOptions.cpp b/js/src/jit/JitOptions.cpp index f7c201f5fa3e..ce78988c1b67 100644 --- a/js/src/jit/JitOptions.cpp +++ b/js/src/jit/JitOptions.cpp @@ -98,6 +98,9 @@ JitOptions::JitOptions() // Toggles whether Loop Unrolling is globally disabled. SET_DEFAULT(disableLoopUnrolling, true); + // Toggles whether instruction reordering is globally disabled. + SET_DEFAULT(disableInstructionReordering, false); + // Toggles whether Range Analysis is globally disabled. SET_DEFAULT(disableRangeAnalysis, false); diff --git a/js/src/jit/JitOptions.h b/js/src/jit/JitOptions.h index 2de9b7924c22..990ff6997dc8 100644 --- a/js/src/jit/JitOptions.h +++ b/js/src/jit/JitOptions.h @@ -55,6 +55,7 @@ struct JitOptions bool disableInlining; bool disableLicm; bool disableLoopUnrolling; + bool disableInstructionReordering; bool disableRangeAnalysis; bool disableScalarReplacement; bool disableSharedStubs; diff --git a/js/src/moz.build b/js/src/moz.build index f6660f5dda46..4fe14f12a742 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -207,6 +207,7 @@ UNIFIED_SOURCES += [ 'jit/EdgeCaseAnalysis.cpp', 'jit/EffectiveAddressAnalysis.cpp', 'jit/ExecutableAllocator.cpp', + 'jit/InstructionReordering.cpp', 'jit/Ion.cpp', 'jit/IonAnalysis.cpp', 'jit/IonBuilder.cpp', diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index efaddc69f1ad..dc5ff619fc0f 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -5933,6 +5933,15 @@ SetRuntimeOptions(JSRuntime* rt, const OptionParser& op) return OptionFailure("ion-loop-unrolling", str); } + if (const char* str = op.getStringOption("ion-instruction-reordering")) { + if (strcmp(str, "on") == 0) + jit::js_JitOptions.disableInstructionReordering = false; + else if (strcmp(str, "off") == 0) + jit::js_JitOptions.disableInstructionReordering = true; + else + return OptionFailure("ion-instruction-reordering", str); + } + if (op.getBoolOption("ion-check-range-analysis")) jit::js_JitOptions.checkRangeAnalysis = true; @@ -6245,6 +6254,8 @@ main(int argc, char** argv, char** envp) "Sink code motion (default: off, on to enable)") || !op.addStringOption('\0', "ion-loop-unrolling", "on/off", "Loop unrolling (default: off, on to enable)") + || !op.addStringOption('\0', "ion-instruction-reordering", "on/off", + "Instruction reordering (default: off, on to enable)") || !op.addBoolOption('\0', "ion-check-range-analysis", "Range analysis checking") || !op.addBoolOption('\0', "ion-extra-checks", diff --git a/js/src/vm/TraceLoggingTypes.h b/js/src/vm/TraceLoggingTypes.h index fb7b50a9af58..c84de1e2240f 100644 --- a/js/src/vm/TraceLoggingTypes.h +++ b/js/src/vm/TraceLoggingTypes.h @@ -54,6 +54,7 @@ _(EffectiveAddressAnalysis) \ _(AlignmentMaskAnalysis) \ _(EliminateDeadCode) \ + _(ReorderInstructions) \ _(EdgeCaseAnalysis) \ _(EliminateRedundantChecks) \ _(AddKeepAliveInstructions) \