mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 992845 - Add Scalar replacement of objects with simplest escape analysis. r=jandem
This commit is contained in:
parent
e7d6b75c35
commit
3496d46720
106
js/src/jit-test/tests/ion/recover-objects.js
Normal file
106
js/src/jit-test/tests/ion/recover-objects.js
Normal 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);
|
||||
}
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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_;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -36,6 +36,7 @@ struct JitOptions
|
||||
#endif
|
||||
bool checkRangeAnalysis;
|
||||
bool compileTryCatch;
|
||||
bool disableScalarReplacement;
|
||||
bool disableGvn;
|
||||
bool disableLicm;
|
||||
bool disableInlining;
|
||||
|
@ -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
|
||||
{
|
||||
|
136
js/src/jit/MIR.h
136
js/src/jit/MIR.h
@ -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
|
||||
|
@ -94,6 +94,7 @@ namespace jit {
|
||||
_(NewCallObject) \
|
||||
_(NewRunOnceCallObject) \
|
||||
_(NewStringObject) \
|
||||
_(ObjectState) \
|
||||
_(InitElem) \
|
||||
_(InitElemGetterSetter) \
|
||||
_(MutateProto) \
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 *
|
||||
|
276
js/src/jit/ScalarReplacement.cpp
Normal file
276
js/src/jit/ScalarReplacement.cpp
Normal 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 */
|
27
js/src/jit/ScalarReplacement.h
Normal file
27
js/src/jit/ScalarReplacement.h
Normal 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 */
|
@ -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',
|
||||
|
@ -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"
|
||||
|
@ -133,6 +133,7 @@ namespace jit {
|
||||
/* Specific passes during ion compilation */ \
|
||||
_(SplitCriticalEdges) \
|
||||
_(RenumberBlocks) \
|
||||
_(ScalarReplacement) \
|
||||
_(DominatorTree) \
|
||||
_(PhiAnalysis) \
|
||||
_(MakeLoopsContiguous) \
|
||||
|
Loading…
Reference in New Issue
Block a user