Bug 992845 - Add Scalar replacement of objects with simplest escape analysis. r=jandem

This commit is contained in:
Nicolas B. Pierron 2014-07-17 03:04:30 -07:00
parent e7d6b75c35
commit 3496d46720
18 changed files with 646 additions and 40 deletions

View File

@ -0,0 +1,106 @@
setJitCompilerOption("baseline.usecount.trigger", 10);
setJitCompilerOption("ion.usecount.trigger", 20);
var uceFault = function (i) {
if (i > 98)
uceFault = function (i) { return true; };
return false;
};
// Without "use script" in the inner function, the arguments might be
// obersvable.
function inline_notSoEmpty1(a, b, c, d) {
// This function is not strict, so we might be able to observe its
// arguments, if somebody ever called fun.arguments inside it.
return { v: (a.v + b.v + c.v + d.v - 10) / 4 };
}
var uceFault_notSoEmpty1 = eval(uneval(uceFault).replace('uceFault', 'uceFault_notSoEmpty1'));
function notSoEmpty1() {
var a = { v: i };
var b = { v: 1 + a.v };
var c = { v: 2 + b.v };
var d = { v: 3 + c.v };
var unused = { v: 4 + d.v };
var res = inline_notSoEmpty1(a, b, c, d);
if (uceFault_notSoEmpty1(i) || uceFault_notSoEmpty1(i))
assertEq(i, res.v);
}
// Check that we can recover objects with their content.
function inline_notSoEmpty2(a, b, c, d) {
"use strict";
return { v: (a.v + b.v + c.v + d.v - 10) / 4 };
}
var uceFault_notSoEmpty2 = eval(uneval(uceFault).replace('uceFault', 'uceFault_notSoEmpty2'));
function notSoEmpty2(i) {
var a = { v: i };
var b = { v: 1 + a.v };
var c = { v: 2 + b.v };
var d = { v: 3 + c.v };
var unused = { v: 4 + d.v };
var res = inline_notSoEmpty2(a, b, c, d);
if (uceFault_notSoEmpty2(i) || uceFault_notSoEmpty2(i))
assertEq(i, res.v);
}
// Check that we can recover objects with their content.
var argFault_observeArg = function (i) {
if (i > 98)
return inline_observeArg.arguments[0];
return { test : i };
};
function inline_observeArg(obj, i) {
return argFault_observeArg(i);
}
function observeArg(i) {
var obj = { test: i };
var res = inline_observeArg(obj, i);
assertEq(res.test, i);
}
// Check case where one successor can have multiple times the same predecessor.
function complexPhi(i) {
var obj = { test: i };
switch (i) { // TableSwitch
case 0: obj.test = 0; break;
case 1: obj.test = 1; break;
case 2: obj.test = 2; break;
case 3: case 4: case 5: case 6:
default: obj.test = i; break;
case 7: obj.test = 7; break;
case 8: obj.test = 8; break;
case 9: obj.test = 9; break;
}
assertEq(obj.test, i);
}
// Check case where one successor can have multiple times the same predecessor.
function withinIf(i) {
var x = undefined;
if (i > 5) {
let obj = { foo: i };
x = obj.foo;
obj = undefined;
} else {
let obj = { bar: i };
x = obj.bar;
obj = undefined;
}
assertEq(x, i);
}
// Check case where one successor can have multiple times the same predecessor.
function unknownLoad(i) {
var obj = { foo: i };
assertEq(obj.bar, undefined);
}
for (var i = 0; i < 100; i++) {
notSoEmpty1(i);
notSoEmpty2(i);
observeArg(i);
complexPhi(i);
withinIf(i);
unknownLoad(i);
}

View File

@ -36,6 +36,7 @@
#include "jit/ParallelSafetyAnalysis.h"
#include "jit/PerfSpewer.h"
#include "jit/RangeAnalysis.h"
#include "jit/ScalarReplacement.h"
#include "jit/StupidAllocator.h"
#include "jit/UnreachableCodeElimination.h"
#include "jit/ValueNumbering.h"
@ -1331,6 +1332,17 @@ OptimizeMIR(MIRGenerator *mir)
return false;
}
if (mir->optimizationInfo().scalarReplacementEnabled()) {
AutoTraceLog log(logger, TraceLogger::ScalarReplacement);
if (!ScalarReplacement(mir, graph))
return false;
IonSpewPass("Scalar Replacement");
AssertGraphCoherency(graph);
if (mir->shouldCancel("Scalar Replacement"))
return false;
}
{
AutoTraceLog log(logger, TraceLogger::PhiAnalysis);
// Aggressive phi elimination must occur before any code elimination. If the

View File

@ -187,9 +187,12 @@ jit::EliminateDeadCode(MIRGenerator *mir, MIRGraph &graph)
for (MInstructionReverseIterator inst = block->rbegin(); inst != block->rend(); ) {
if (!inst->isEffectful() && !inst->resumePoint() &&
!inst->hasUses() && !inst->isGuard() &&
!inst->isControlInstruction()) {
!inst->isControlInstruction())
{
inst = block->discardAt(inst);
} else if (!inst->hasLiveDefUses() && inst->canRecoverOnBailout()) {
} else if (!inst->isRecoveredOnBailout() && !inst->hasLiveDefUses() &&
inst->canRecoverOnBailout())
{
inst->setRecoveredOnBailout();
inst++;
} else {

View File

@ -38,6 +38,7 @@ OptimizationInfo::initNormalOptimizationInfo()
inlineMaxTotalBytecodeLength_ = 1000;
inliningMaxCallerBytecodeLength_ = 10000;
maxInlineDepth_ = 3;
scalarReplacement_ = true;
smallFunctionMaxInlineDepth_ = 10;
usesBeforeCompile_ = 1000;
usesBeforeInliningFactor_ = 0.125;
@ -57,6 +58,7 @@ OptimizationInfo::initAsmjsOptimizationInfo()
eliminateRedundantChecks_ = false;
autoTruncate_ = false;
registerAllocator_ = RegisterAllocator_Backtracking;
scalarReplacement_ = false; // AsmJS has no objects.
}
uint32_t

View File

@ -91,6 +91,9 @@ class OptimizationInfo
// The maximum inlining depth.
uint32_t maxInlineDepth_;
// Toggles whether scalar replacement is used.
bool scalarReplacement_;
// The maximum inlining depth for functions.
//
// Inlining small functions has almost no compiling overhead
@ -165,6 +168,10 @@ class OptimizationInfo
return js_JitOptions.forcedRegisterAllocator;
}
bool scalarReplacementEnabled() const {
return scalarReplacement_ && !js_JitOptions.disableScalarReplacement;
}
uint32_t smallFunctionMaxInlineDepth() const {
return smallFunctionMaxInlineDepth_;
}

View File

@ -36,6 +36,9 @@ JitOptions::JitOptions()
// Whether Ion should compile try-catch statements.
compileTryCatch = true;
// Toggle whether eager scalar replacement is globally disabled.
disableScalarReplacement = true; // experimental
// Toggle whether global value numbering is globally disabled.
disableGvn = false;

View File

@ -36,6 +36,7 @@ struct JitOptions
#endif
bool checkRangeAnalysis;
bool compileTryCatch;
bool disableScalarReplacement;
bool disableGvn;
bool disableLicm;
bool disableInlining;

View File

@ -2814,6 +2814,47 @@ MNewObject::shouldUseVM() const
return obj->hasSingletonType() || obj->hasDynamicSlots();
}
MObjectState::MObjectState(MDefinition *obj)
{
// This instruction is only used as a summary for bailout paths.
setRecoveredOnBailout();
JSObject *templateObject = obj->toNewObject()->templateObject();
numSlots_ = templateObject->slotSpan();
numFixedSlots_ = templateObject->numFixedSlots();
}
bool
MObjectState::init(TempAllocator &alloc, MDefinition *obj)
{
if (!MVariadicInstruction::init(alloc, numSlots() + 1))
return false;
initOperand(0, obj);
return true;
}
MObjectState *
MObjectState::New(TempAllocator &alloc, MDefinition *obj, MDefinition *undefinedVal)
{
MObjectState *res = new(alloc) MObjectState(obj);
if (!res || !res->init(alloc, obj))
return nullptr;
for (size_t i = 0; i < res->numSlots(); i++)
res->initSlot(i, undefinedVal);
return res;
}
MObjectState *
MObjectState::Copy(TempAllocator &alloc, MObjectState *state)
{
MDefinition *obj = state->object();
MObjectState *res = new(alloc) MObjectState(obj);
if (!res || !res->init(alloc, obj))
return nullptr;
for (size_t i = 0; i < res->numSlots(); i++)
res->initSlot(i, state->getSlot(i));
return res;
}
bool
MNewArray::shouldUseVM() const
{

View File

@ -953,6 +953,43 @@ class MQuaternaryInstruction : public MAryInstruction<4>
}
};
class MVariadicInstruction : public MInstruction
{
FixedList<MUse> operands_;
protected:
bool init(TempAllocator &alloc, size_t length) {
return operands_.init(alloc, length);
}
void initOperand(size_t index, MDefinition *operand) {
// FixedList doesn't initialize its elements, so do an unchecked init.
operands_[index].initUnchecked(operand, this);
}
MUse *getUseFor(size_t index) MOZ_FINAL MOZ_OVERRIDE {
return &operands_[index];
}
const MUse *getUseFor(size_t index) const MOZ_FINAL MOZ_OVERRIDE {
return &operands_[index];
}
public:
// Will assert if called before initialization.
MDefinition *getOperand(size_t index) const MOZ_FINAL MOZ_OVERRIDE {
return operands_[index].producer();
}
size_t numOperands() const MOZ_FINAL MOZ_OVERRIDE {
return operands_.length();
}
size_t indexOf(const MUse *u) const MOZ_FINAL MOZ_OVERRIDE {
MOZ_ASSERT(u >= &operands_[0]);
MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
return u - &operands_[0];
}
void replaceOperand(size_t index, MDefinition *operand) MOZ_FINAL MOZ_OVERRIDE {
operands_[index].replaceProducer(operand);
}
};
// Generates an LSnapshot without further effect.
class MStart : public MNullaryInstruction
{
@ -1811,6 +1848,68 @@ class MNewDerivedTypedObject
}
};
// Represent the content of all slots of an object. This instruction is not
// lowered and is not used to generate code.
class MObjectState : public MVariadicInstruction
{
private:
uint32_t numSlots_;
uint32_t numFixedSlots_;
MObjectState(MDefinition *obj);
bool init(TempAllocator &alloc, MDefinition *obj);
void initSlot(uint32_t slot, MDefinition *def) {
initOperand(slot + 1, def);
}
public:
INSTRUCTION_HEADER(ObjectState)
static MObjectState *New(TempAllocator &alloc, MDefinition *obj, MDefinition *undefinedVal);
static MObjectState *Copy(TempAllocator &alloc, MObjectState *state);
MDefinition *object() const {
return getOperand(0);
}
size_t numFixedSlots() const {
return numFixedSlots_;
}
size_t numSlots() const {
return numSlots_;
}
MDefinition *getSlot(uint32_t slot) const {
return getOperand(slot + 1);
}
void setSlot(uint32_t slot, MDefinition *def) {
replaceOperand(slot + 1, def);
}
MDefinition *getFixedSlot(uint32_t slot) const {
MOZ_ASSERT(slot < numFixedSlots());
return getSlot(slot);
}
void setFixedSlot(uint32_t slot, MDefinition *def) {
MOZ_ASSERT(slot < numFixedSlots());
setSlot(slot, def);
}
MDefinition *getDynamicSlot(uint32_t slot) const {
return getSlot(slot + numFixedSlots());
}
void setDynamicSlot(uint32_t slot, MDefinition *def) {
setSlot(slot + numFixedSlots(), def);
}
bool writeRecoverData(CompactBufferWriter &writer) const;
bool canRecoverOnBailout() const {
return true;
}
};
// Setting __proto__ in an object literal.
class MMutateProto
: public MAryInstruction<2>,
@ -1994,43 +2093,6 @@ class MInitElemGetterSetter
}
};
class MVariadicInstruction : public MInstruction
{
FixedList<MUse> operands_;
protected:
bool init(TempAllocator &alloc, size_t length) {
return operands_.init(alloc, length);
}
void initOperand(size_t index, MDefinition *operand) {
// FixedList doesn't initialize its elements, so do an unchecked init.
operands_[index].initUnchecked(operand, this);
}
MUse *getUseFor(size_t index) MOZ_FINAL MOZ_OVERRIDE {
return &operands_[index];
}
const MUse *getUseFor(size_t index) const MOZ_FINAL MOZ_OVERRIDE {
return &operands_[index];
}
public:
// Will assert if called before initialization.
MDefinition *getOperand(size_t index) const MOZ_FINAL MOZ_OVERRIDE {
return operands_[index].producer();
}
size_t numOperands() const MOZ_FINAL MOZ_OVERRIDE {
return operands_.length();
}
size_t indexOf(const MUse *u) const MOZ_FINAL MOZ_OVERRIDE {
MOZ_ASSERT(u >= &operands_[0]);
MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
return u - &operands_[0];
}
void replaceOperand(size_t index, MDefinition *operand) MOZ_FINAL MOZ_OVERRIDE {
operands_[index].replaceProducer(operand);
}
};
class MCall
: public MVariadicInstruction,
public CallPolicy

View File

@ -94,6 +94,7 @@ namespace jit {
_(NewCallObject) \
_(NewRunOnceCallObject) \
_(NewStringObject) \
_(ObjectState) \
_(InitElem) \
_(InitElemGetterSetter) \
_(MutateProto) \

View File

@ -187,6 +187,7 @@ class ParallelSafetyVisitor : public MInstructionVisitor
CUSTOM_OP(NewCallObject)
CUSTOM_OP(NewRunOnceCallObject)
CUSTOM_OP(NewDerivedTypedObject)
SAFE_OP(ObjectState)
UNSAFE_OP(InitElem)
UNSAFE_OP(InitElemGetterSetter)
UNSAFE_OP(MutateProto)

View File

@ -945,3 +945,34 @@ RNewDerivedTypedObject::recover(JSContext *cx, SnapshotIterator &iter) const
iter.storeInstructionResult(result);
return true;
}
bool
MObjectState::writeRecoverData(CompactBufferWriter &writer) const
{
MOZ_ASSERT(canRecoverOnBailout());
writer.writeUnsigned(uint32_t(RInstruction::Recover_ObjectState));
writer.writeUnsigned(numSlots());
return true;
}
RObjectState::RObjectState(CompactBufferReader &reader)
{
numSlots_ = reader.readUnsigned();
}
bool
RObjectState::recover(JSContext *cx, SnapshotIterator &iter) const
{
RootedObject object(cx, &iter.read().toObject());
MOZ_ASSERT(object->slotSpan() == numSlots());
RootedValue val(cx);
for (size_t i = 0; i < numSlots(); i++) {
val = iter.read();
object->nativeSetSlot(i, val);
}
val.setObject(*object);
iter.storeInstructionResult(val);
return true;
}

View File

@ -46,7 +46,8 @@ namespace jit {
_(Atan2) \
_(StringSplit) \
_(NewObject) \
_(NewDerivedTypedObject)
_(NewDerivedTypedObject) \
_(ObjectState)
class RResumePoint;
class SnapshotIterator;
@ -484,6 +485,25 @@ class RNewDerivedTypedObject MOZ_FINAL : public RInstruction
bool recover(JSContext *cx, SnapshotIterator &iter) const;
};
class RObjectState MOZ_FINAL : public RInstruction
{
private:
uint32_t numSlots_; // Number of slots.
public:
RINSTRUCTION_HEADER_(ObjectState)
uint32_t numSlots() const {
return numSlots_;
}
virtual uint32_t numOperands() const {
// +1 for the object.
return numSlots() + 1;
}
bool recover(JSContext *cx, SnapshotIterator &iter) const;
};
#undef RINSTRUCTION_HEADER_
const RResumePoint *

View File

@ -0,0 +1,276 @@
/* -*- 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/ScalarReplacement.h"
#include "mozilla/Vector.h"
#include "jit/MIR.h"
#include "jit/MIRGenerator.h"
#include "jit/MIRGraph.h"
namespace js {
namespace jit {
// Scan resume point operands in search of a local variable which captures the
// current object, and replace it by the current object with its state.
static void
ReplaceResumePointOperands(MResumePoint *resumePoint, MDefinition *object, MDefinition *state)
{
// Note: This function iterates over the caller as well, this is wrong
// because if the object appears in one of the caller, we want to correctly
// recover the object value from any block having the same caller. In
// practice, this is correct for 2 reasons:
//
// 1. We replace resume point operands in RPO, this implies that the caller
// would first be updated when we update the resume point of entry block of
// the inner function. This implies that the object state would only hold
// valid data for the caller resume point.
//
// 2. The caller resume point will have no reference of the new object
// allocation if the object allocation is done within the callee.
//
// A side-effect of this implementation is that we would be restoring and
// keeping tracks of the content of the object at the entry of the function,
// in addition to the content of the object within the function.
for (MResumePoint *rp = resumePoint; rp; rp = rp->caller()) {
for (size_t op = 0; op < rp->numOperands(); op++) {
if (rp->getOperand(op) == object) {
rp->replaceOperand(op, state);
// This assertion verifies the comment which is above still
// holds. Note, this is not true if rp == resumePoint, as the
// object state can be a new one created at the beginning of the
// block to keep track of the merge state.
MOZ_ASSERT_IF(rp != resumePoint, state->block()->dominates(rp->block()));
}
}
}
}
// Returns False if the object is not escaped and if it is optimizable by
// ScalarReplacementOfObject.
//
// For the moment, this code is dumb as it only supports objects which are not
// changing shape, and which are known by TI at the object creation.
static bool
IsObjectEscaped(MInstruction *ins)
{
MOZ_ASSERT(ins->type() == MIRType_Object);
// Check if the object is escaped. If the object is not the first argument
// of either a known Store / Load, then we consider it as escaped. This is a
// cheap an conservative escape analysis.
for (MUseIterator i(ins->usesBegin()); i != ins->usesEnd(); i++) {
MNode *consumer = (*i)->consumer();
if (!consumer->isDefinition()) {
// Cannot optimize if it is observable from fun.arguments or others.
if (consumer->toResumePoint()->isObservableOperand(*i))
return true;
continue;
}
MDefinition *def = consumer->toDefinition();
switch (def->op()) {
case MDefinition::Op_StoreFixedSlot:
case MDefinition::Op_LoadFixedSlot:
// Not escaped if it is the first argument.
if (def->indexOf(*i) == 0)
break;
return true;
default:
return true;
}
}
return false;
}
typedef MObjectState BlockState;
typedef Vector<BlockState *, 8, SystemAllocPolicy> GraphState;
// This function replaces every MStoreFixedSlot / MStoreSlot by an MObjectState
// which emulates the content of the object. Every MLoadFixedSlot and MLoadSlot
// is replaced by the corresponding value.
//
// In order to restore the value of the object correctly in case of bailouts, we
// replace all references of the allocation by the MObjectState definitions.
static bool
ScalarReplacementOfObject(MIRGenerator *mir, MIRGraph &graph, GraphState &states,
MInstruction *obj)
{
// For each basic block, we record the last/first state of the object in
// each of the basic blocks.
if (!states.appendN(nullptr, graph.numBlocks()))
return false;
// Uninitialized slots have an "undefined" value.
MBasicBlock *objBlock = obj->block();
MConstant *undefinedVal = MConstant::New(graph.alloc(), UndefinedValue());
objBlock->insertBefore(obj, undefinedVal);
states[objBlock->id()] = BlockState::New(graph.alloc(), obj, undefinedVal);
// Iterate over each basic block and save the object's layout.
for (ReversePostorderIterator block = graph.rpoBegin(obj->block()); block != graph.rpoEnd(); block++) {
if (mir->shouldCancel("Scalar Replacement of Object"))
return false;
BlockState *state = states[block->id()];
if (!state) {
MOZ_ASSERT(!objBlock->dominates(*block));
continue;
}
// Insert the state either at the location of the new object, or after
// all the phi nodes if the block has multiple predecessors.
if (*block == objBlock)
objBlock->insertAfter(obj, state);
else if (block->numPredecessors() > 1)
block->insertBefore(*block->begin(), state);
else
MOZ_ASSERT(state->block()->dominates(*block));
// Replace the local variable references by references to the object state.
ReplaceResumePointOperands(block->entryResumePoint(), obj, state);
for (MDefinitionIterator ins(*block); ins; ) {
switch (ins->op()) {
case MDefinition::Op_ObjectState: {
ins++;
continue;
}
case MDefinition::Op_LoadFixedSlot: {
MLoadFixedSlot *def = ins->toLoadFixedSlot();
// Skip loads made on other objects.
if (def->object() != obj)
break;
// Replace load by the slot value.
ins->replaceAllUsesWith(state->getFixedSlot(def->slot()));
// Remove original instruction.
ins = block->discardDefAt(ins);
continue;
}
case MDefinition::Op_StoreFixedSlot: {
MStoreFixedSlot *def = ins->toStoreFixedSlot();
// Skip stores made on other objects.
if (def->object() != obj)
break;
// Clone the state and update the slot value.
state = BlockState::Copy(graph.alloc(), state);
state->setFixedSlot(def->slot(), def->value());
block->insertBefore(ins->toInstruction(), state);
// Remove original instruction.
ins = block->discardDefAt(ins);
continue;
}
default:
break;
}
// Replace the local variable references by references to the object state.
if (ins->isInstruction())
ReplaceResumePointOperands(ins->toInstruction()->resumePoint(), obj, state);
ins++;
}
// For each successor, copy/merge the current state as being the initial
// state of the successor block.
for (size_t s = 0; s < block->numSuccessors(); s++) {
MBasicBlock *succ = block->getSuccessor(s);
BlockState *succState = states[succ->id()];
// When a block has no state yet, create an empty one for the
// successor.
if (!succState) {
// If the successor is not dominated then the object cannot flow
// in this basic block without a Phi. We know that no Phi exist
// in non-dominated successors as the conservative escaped
// analysis fails otherwise. Such condition can succeed if the
// successor is a join at the end of a if-block and the object
// only exists within the branch.
if (!objBlock->dominates(succ))
continue;
if (succ->numPredecessors() > 1) {
succState = states[succ->id()] = BlockState::Copy(graph.alloc(), state);
size_t numPreds = succ->numPredecessors();
for (size_t slot = 0; slot < state->numSlots(); slot++) {
MPhi *phi = MPhi::New(graph.alloc());
if (!phi->reserveLength(numPreds))
return false;
// Fill the input of the successors Phi with undefined
// values, and each block later fills the Phi inputs.
for (size_t p = 0; p < numPreds; p++)
phi->addInput(undefinedVal);
// Add Phi in the list of Phis of the basic block.
succ->addPhi(phi);
succState->setSlot(slot, phi);
}
} else {
succState = states[succ->id()] = state;
}
}
if (succ->numPredecessors() > 1) {
// The current block might appear multiple times among the
// predecessors. As we need to replace all the inputs, we need
// to check all predecessors against the current block to
// replace the Phi node operands.
size_t numPreds = succ->numPredecessors();
for (size_t p = 0; p < numPreds; p++) {
if (succ->getPredecessor(p) != *block)
continue;
// Copy the current slot state to the predecessor index of
// each Phi of the same slot.
for (size_t slot = 0; slot < state->numSlots(); slot++) {
MPhi *phi = succState->getSlot(slot)->toPhi();
phi->replaceOperand(p, state->getSlot(slot));
}
}
}
}
}
MOZ_ASSERT(!obj->hasLiveDefUses());
obj->setRecoveredOnBailout();
states.clear();
return true;
}
bool
ScalarReplacement(MIRGenerator *mir, MIRGraph &graph)
{
GraphState objectStates;
for (ReversePostorderIterator block = graph.rpoBegin(); block != graph.rpoEnd(); block++) {
if (mir->shouldCancel("Scalar Replacement (main loop)"))
return false;
for (MInstructionIterator ins = block->begin(); ins != block->end(); ins++) {
if (ins->isNewObject() && !IsObjectEscaped(*ins)) {
if (!ScalarReplacementOfObject(mir, graph, objectStates, *ins))
return false;
}
}
}
return true;
}
} /* namespace jit */
} /* namespace js */

View File

@ -0,0 +1,27 @@
/* -*- 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/. */
// This file declares scalar replacement of objects transformation.
#ifndef jit_ScalarReplacement_h
#define jit_ScalarReplacement_h
#ifdef JS_ION
namespace js {
namespace jit {
class MIRGenerator;
class MIRGraph;
bool
ScalarReplacement(MIRGenerator *mir, MIRGraph &graph);
} // namespace jit
} // namespace js
#endif // JS_ION
#endif /* jit_ScalarReplacement_h */

View File

@ -296,6 +296,7 @@ if CONFIG['ENABLE_ION']:
'jit/RegisterAllocator.cpp',
'jit/RematerializedFrame.cpp',
'jit/Safepoints.cpp',
'jit/ScalarReplacement.cpp',
'jit/shared/BaselineCompiler-shared.cpp',
'jit/shared/CodeGenerator-shared.cpp',
'jit/shared/Lowering-shared.cpp',

View File

@ -5980,6 +5980,15 @@ SetRuntimeOptions(JSRuntime *rt, const OptionParser &op)
.setAsmJS(enableAsmJS)
.setNativeRegExp(enableNativeRegExp);
if (const char *str = op.getStringOption("ion-scalar-replacement")) {
if (strcmp(str, "on") == 0)
jit::js_JitOptions.disableScalarReplacement = false;
else if (strcmp(str, "off") == 0)
jit::js_JitOptions.disableScalarReplacement = true;
else
return OptionFailure("ion-scalar-replacement", str);
}
if (const char *str = op.getStringOption("ion-gvn")) {
if (strcmp(str, "off") == 0) {
jit::js_JitOptions.disableGvn = true;
@ -6276,6 +6285,8 @@ main(int argc, char **argv, char **envp)
|| !op.addBoolOption('\0', "no-ion", "Disable IonMonkey")
|| !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation")
|| !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation")
|| !op.addStringOption('\0', "ion-scalar-replacement", "on/off",
"Scalar Replacement (default: off, on to enable)")
|| !op.addStringOption('\0', "ion-gvn", "[mode]",
"Specify Ion global value numbering:\n"
" off: disable GVN\n"

View File

@ -133,6 +133,7 @@ namespace jit {
/* Specific passes during ion compilation */ \
_(SplitCriticalEdges) \
_(RenumberBlocks) \
_(ScalarReplacement) \
_(DominatorTree) \
_(PhiAnalysis) \
_(MakeLoopsContiguous) \