Bug 1108290 - optimize apply with Array. r=nbp

--HG--
extra : rebase_source : b64fc074fcdafbfa68e1e0c1a1eb7ddce57bb171
This commit is contained in:
Lars T Hansen 2015-11-24 14:56:03 +01:00
Родитель 4a082e1689
Коммит 086b3ba3e9
14 изменённых файлов: 427 добавлений и 39 удалений

Просмотреть файл

@ -0,0 +1,98 @@
// Test inlining in Ion of fun.apply(..., array).
if (!this.getJitCompilerOptions() || !getJitCompilerOptions()['ion.enable'])
quit(0);
var itercount = 1000;
var warmup = 100;
// Force Ion to do something predictable without having to wait
// forever for it.
if (getJitCompilerOptions()["ion.warmup.trigger"] > warmup)
setJitCompilerOption("ion.warmup.trigger", warmup);
setJitCompilerOption("offthread-compilation.enable", 0);
function g(a, b, c, d) {
return a + b + c + (d === undefined);
}
var g_inIonInLoop = false;
var g_inIonAtEnd = false;
function f(xs) {
var sum = 0;
var inIonInLoop = 0;
for ( var i=0 ; i < itercount ; i++ ) {
inIonInLoop |= inIon();
sum += g.apply(null, xs);
}
g_ionAtEnd = inIon();
g_inIonInLoop = !!inIonInLoop;
return sum;
}
// Basic test
assertEq(f([1,2,3,4]), 6*itercount);
// Attempt to detect a botched optimization: either we ion-compiled
// the loop, or we did not ion-compile the function (ion not actually
// effective at all, this can happen).
assertEq(g_inIonInLoop || !g_inIonAtEnd, true);
// If Ion is inert just leave.
if (!g_inIonInLoop) {
print("Leaving early - ion not kicking in at all");
quit(0);
}
// Test that we get the correct argument value even if the array has
// fewer initialized members than its length.
var headroom = [1,2,3];
headroom.length = 13;
assertEq(f(headroom), 7*itercount);
// Test that we throw when the array is too long.
var thrown = false;
try {
var long = [];
long.length = getMaxArgs() + 1;
f(long);
}
catch (e) {
thrown = true;
assertEq(e instanceof RangeError, true);
}
assertEq(thrown, true);
// Test that the optimization is effective. There's the possibility
// of some false results here, and in release builds the margins are
// actually small.
itercount *= 2;
var A = Date.now();
assertEq(f([1,2,3,4]), 6*itercount) // Fast path because a sane array
var AinLoop = g_inIonInLoop;
var B = Date.now();
assertEq(f([1,2,3]), 7*itercount); // Fast path because a sane array, even if short
var BinLoop = g_inIonInLoop;
var C = Date.now();
assertEq(f(headroom), 7*itercount); // Slow path because length > initializedLength
var CinLoop = g_inIonInLoop;
var D = Date.now();
if (AinLoop && BinLoop && CinLoop) {
print("No bailout: " + (B - A));
print("Short: " + (C - B));
print("Bailout: " + (D - C));
assertEq((D - C) >= (B - A), true);
assertEq((D - C) >= (C - B), true);
} else {
print("Not running perf test");
}

Просмотреть файл

@ -3270,8 +3270,9 @@ CodeGenerator::visitCallKnown(LCallKnown* call)
}
}
template<typename T>
void
CodeGenerator::emitCallInvokeFunction(LApplyArgsGeneric* apply, Register extraStackSize)
CodeGenerator::emitCallInvokeFunction(T* apply, Register extraStackSize)
{
Register objreg = ToRegister(apply->getTempObject());
MOZ_ASSERT(objreg != extraStackSize);
@ -3294,12 +3295,8 @@ CodeGenerator::emitCallInvokeFunction(LApplyArgsGeneric* apply, Register extraSt
// Do not bailout after the execution of this function since the stack no longer
// correspond to what is expected by the snapshots.
void
CodeGenerator::emitPushArguments(LApplyArgsGeneric* apply, Register extraStackSpace)
CodeGenerator::emitAllocateSpaceForApply(Register argcreg, Register extraStackSpace, Label* end)
{
// Holds the function nargs. Initially undefined.
Register argcreg = ToRegister(apply->getArgc());
Register copyreg = ToRegister(apply->getTempObject());
// Initialize the loop counter AND Compute the stack usage (if == 0)
masm.movePtr(argcreg, extraStackSpace);
@ -3335,9 +3332,53 @@ CodeGenerator::emitPushArguments(LApplyArgsGeneric* apply, Register extraStackSp
}
#endif
// Skip the copy of arguments.
// Skip the copy of arguments if there are none.
masm.branchTestPtr(Assembler::Zero, argcreg, argcreg, end);
}
// Destroys argvIndex and copyreg.
void
CodeGenerator::emitCopyValuesForApply(Register argvSrcBase, Register argvIndex, Register copyreg,
size_t argvSrcOffset, size_t argvDstOffset)
{
Label loop;
masm.bind(&loop);
// As argvIndex is off by 1, and we use the decBranchPtr instruction
// to loop back, we have to substract the size of the word which are
// copied.
BaseValueIndex srcPtr(argvSrcBase, argvIndex, argvSrcOffset - sizeof(void*));
BaseValueIndex dstPtr(masm.getStackPointer(), argvIndex, argvDstOffset - sizeof(void*));
masm.loadPtr(srcPtr, copyreg);
masm.storePtr(copyreg, dstPtr);
// Handle 32 bits architectures.
if (sizeof(Value) == 2 * sizeof(void*)) {
BaseValueIndex srcPtrLow(argvSrcBase, argvIndex, argvSrcOffset - 2 * sizeof(void*));
BaseValueIndex dstPtrLow(masm.getStackPointer(), argvIndex, argvDstOffset - 2 * sizeof(void*));
masm.loadPtr(srcPtrLow, copyreg);
masm.storePtr(copyreg, dstPtrLow);
}
masm.decBranchPtr(Assembler::NonZero, argvIndex, Imm32(1), &loop);
}
void
CodeGenerator::emitPopArguments(Register extraStackSpace)
{
// Pop |this| and Arguments.
masm.freeStack(extraStackSpace);
}
void
CodeGenerator::emitPushArguments(LApplyArgsGeneric* apply, Register extraStackSpace)
{
// Holds the function nargs. Initially the number of args to the caller.
Register argcreg = ToRegister(apply->getArgc());
Register copyreg = ToRegister(apply->getTempObject());
Label end;
masm.branchTestPtr(Assembler::Zero, argcreg, argcreg, &end);
emitAllocateSpaceForApply(argcreg, extraStackSpace, &end);
// We are making a copy of the arguments which are above the JitFrameLayout
// of the current Ion frame.
@ -3365,28 +3406,7 @@ CodeGenerator::emitPushArguments(LApplyArgsGeneric* apply, Register extraStackSp
masm.addStackPtrTo(argvSrcBase);
// Copy arguments.
{
Label loop;
masm.bind(&loop);
// As argvIndex is off by 1, and we use the decBranchPtr instruction
// to loop back, we have to substract the size of the word which are
// copied.
BaseValueIndex srcPtr(argvSrcBase, argvIndex, argvSrcOffset - sizeof(void*));
BaseValueIndex dstPtr(masm.getStackPointer(), argvIndex, argvDstOffset - sizeof(void*));
masm.loadPtr(srcPtr, copyreg);
masm.storePtr(copyreg, dstPtr);
// Handle 32 bits architectures.
if (sizeof(Value) == 2 * sizeof(void*)) {
BaseValueIndex srcPtrLow(argvSrcBase, argvIndex, argvSrcOffset - 2 * sizeof(void*));
BaseValueIndex dstPtrLow(masm.getStackPointer(), argvIndex, argvDstOffset - 2 * sizeof(void*));
masm.loadPtr(srcPtrLow, copyreg);
masm.storePtr(copyreg, dstPtrLow);
}
masm.decBranchPtr(Assembler::NonZero, argvIndex, Imm32(1), &loop);
}
emitCopyValuesForApply(argvSrcBase, argvIndex, copyreg, argvSrcOffset, argvDstOffset);
// Restore argcreg and the extra stack space counter.
masm.pop(argcreg);
@ -3401,14 +3421,61 @@ CodeGenerator::emitPushArguments(LApplyArgsGeneric* apply, Register extraStackSp
}
void
CodeGenerator::emitPopArguments(LApplyArgsGeneric* apply, Register extraStackSpace)
CodeGenerator::emitPushArguments(LApplyArrayGeneric* apply, Register extraStackSpace)
{
// Pop |this| and Arguments.
masm.freeStack(extraStackSpace);
Label noCopy, epilogue;
Register tmpArgc = ToRegister(apply->getTempObject());
Register elementsAndArgc = ToRegister(apply->getElements());
// Invariants guarded in the caller:
// - the array is not too long
// - the array length equals its initialized length
// The array length is our argc for the purposes of allocating space.
Address length(ToRegister(apply->getElements()), ObjectElements::offsetOfLength());
masm.load32(length, tmpArgc);
// Allocate space for the values.
emitAllocateSpaceForApply(tmpArgc, extraStackSpace, &noCopy);
// Copy the values. This code is skipped entirely if there are
// no values.
size_t argvDstOffset = 0;
Register argvSrcBase = elementsAndArgc; // Elements value
masm.push(extraStackSpace);
Register copyreg = extraStackSpace;
argvDstOffset += sizeof(void*);
masm.push(tmpArgc);
Register argvIndex = tmpArgc;
argvDstOffset += sizeof(void*);
// Copy
emitCopyValuesForApply(argvSrcBase, argvIndex, copyreg, 0, argvDstOffset);
// Restore.
masm.pop(elementsAndArgc);
masm.pop(extraStackSpace);
masm.jump(&epilogue);
// Clear argc if we skipped the copy step.
masm.bind(&noCopy);
masm.movePtr(ImmPtr(0), elementsAndArgc);
// Join with all arguments copied and the extra stack usage computed.
// Note, "elements" has become "argc".
masm.bind(&epilogue);
// Push |this|.
masm.addPtr(Imm32(sizeof(Value)), extraStackSpace);
masm.pushValue(ToValue(apply, LApplyArgsGeneric::ThisIndex));
}
template<typename T>
void
CodeGenerator::visitApplyArgsGeneric(LApplyArgsGeneric* apply)
CodeGenerator::emitApplyGeneric(T* apply)
{
// Holds the function object.
Register calleereg = ToRegister(apply->getFunction());
@ -3417,7 +3484,8 @@ CodeGenerator::visitApplyArgsGeneric(LApplyArgsGeneric* apply)
Register objreg = ToRegister(apply->getTempObject());
Register extraStackSpace = ToRegister(apply->getTempStackCounter());
// Holds the function nargs. Initially undefined.
// Holds the function nargs, computed in the invoker or (for
// ApplyArray) in the argument pusher.
Register argcreg = ToRegister(apply->getArgc());
// Unless already known, guard that calleereg is actually a function object.
@ -3429,6 +3497,15 @@ CodeGenerator::visitApplyArgsGeneric(LApplyArgsGeneric* apply)
}
// Copy the arguments of the current function.
//
// In the case of ApplyArray, also compute argc: the argc register
// and the elements register are the same; argc must not be
// referenced before the call to emitPushArguments() and elements
// must not be referenced after it returns.
//
// objreg is dead across this call.
//
// extraStackSpace is garbage on entry and defined on exit.
emitPushArguments(apply, extraStackSpace);
masm.checkStackAlignment();
@ -3436,7 +3513,7 @@ CodeGenerator::visitApplyArgsGeneric(LApplyArgsGeneric* apply)
// If the function is native, only emit the call to InvokeFunction.
if (apply->hasSingleTarget() && apply->getSingleTarget()->isNative()) {
emitCallInvokeFunction(apply, extraStackSpace);
emitPopArguments(apply, extraStackSpace);
emitPopArguments(extraStackSpace);
return;
}
@ -3518,7 +3595,31 @@ CodeGenerator::visitApplyArgsGeneric(LApplyArgsGeneric* apply)
// Pop arguments and continue.
masm.bind(&end);
emitPopArguments(apply, extraStackSpace);
emitPopArguments(extraStackSpace);
}
void
CodeGenerator::visitApplyArgsGeneric(LApplyArgsGeneric* apply)
{
emitApplyGeneric(apply);
}
void
CodeGenerator::visitApplyArrayGeneric(LApplyArrayGeneric* apply)
{
LSnapshot* snapshot = apply->snapshot();
Register tmp = ToRegister(apply->getTempObject());
Address length(ToRegister(apply->getElements()), ObjectElements::offsetOfLength());
masm.load32(length, tmp);
bailoutCmp32(Assembler::Above, tmp, Imm32(js_JitOptions.maxStackArgs), snapshot);
Address initializedLength(ToRegister(apply->getElements()),
ObjectElements::offsetOfInitializedLength());
masm.sub32(initializedLength, tmp);
bailoutCmp32(Assembler::NotEqual, tmp, Imm32(0), snapshot);
emitApplyGeneric(apply);
}
typedef bool (*ArraySpliceDenseFn)(JSContext*, HandleObject, uint32_t, uint32_t);

Просмотреть файл

@ -151,10 +151,16 @@ class CodeGenerator : public CodeGeneratorSpecific
uint32_t numFormals,
uint32_t unusedStack);
void visitCallKnown(LCallKnown* call);
void emitCallInvokeFunction(LApplyArgsGeneric* apply, Register extraStackSize);
template<typename T> void emitApplyGeneric(T* apply);
template<typename T> void emitCallInvokeFunction(T* apply, Register extraStackSize);
void emitAllocateSpaceForApply(Register argcreg, Register extraStackSpace, Label* end);
void emitCopyValuesForApply(Register argvSrcBase, Register argvIndex, Register copyreg,
size_t argvSrcOffset, size_t argvDstOffset);
void emitPopArguments(Register extraStackSize);
void emitPushArguments(LApplyArgsGeneric* apply, Register extraStackSpace);
void emitPopArguments(LApplyArgsGeneric* apply, Register extraStackSize);
void visitApplyArgsGeneric(LApplyArgsGeneric* apply);
void emitPushArguments(LApplyArrayGeneric* apply, Register extraStackSpace);
void visitApplyArrayGeneric(LApplyArrayGeneric* apply);
void visitBail(LBail* lir);
void visitUnreachable(LUnreachable* unreachable);
void visitEncodeSnapshot(LEncodeSnapshot* lir);

Просмотреть файл

@ -6335,6 +6335,17 @@ IonBuilder::jsop_funapply(uint32_t argc)
// Fallback to regular call if arg 2 is not definitely |arguments|.
if (argument->type() != MIRType_MagicOptimizedArguments) {
// Optimize fun.apply(self, array) if the length is sane and there are no holes.
TemporaryTypeSet* objTypes = argument->resultTypeSet();
if (native && native->isNative() && native->native() == fun_apply &&
objTypes &&
objTypes->getKnownClass(constraints()) == &ArrayObject::class_ &&
!objTypes->hasObjectFlags(constraints(), OBJECT_FLAG_LENGTH_OVERFLOW) &&
ElementAccessIsPacked(constraints(), argument))
{
return jsop_funapplyarray(argc);
}
CallInfo callInfo(alloc(), false);
if (!callInfo.init(current, argc))
return false;
@ -6352,6 +6363,43 @@ IonBuilder::jsop_funapply(uint32_t argc)
return jsop_funapplyarguments(argc);
}
bool
IonBuilder::jsop_funapplyarray(uint32_t argc)
{
MOZ_ASSERT(argc == 2);
int funcDepth = -((int)argc + 1);
// Extract call target.
TemporaryTypeSet* funTypes = current->peek(funcDepth)->resultTypeSet();
JSFunction* target = getSingleCallTarget(funTypes);
// Pop the array agument
MDefinition* argObj = current->pop();
MElements* elements = MElements::New(alloc(), argObj);
current->add(elements);
// Pop the |this| argument.
MDefinition* argThis = current->pop();
// Unwrap the (JSFunction *) parameter.
MDefinition* argFunc = current->pop();
// Pop apply function.
MDefinition* nativeFunc = current->pop();
nativeFunc->setImplicitlyUsedUnchecked();
MApplyArray* apply = MApplyArray::New(alloc(), target, argFunc, elements, argThis);
current->add(apply);
current->push(apply);
if (!resumeAfter(apply))
return false;
TemporaryTypeSet* types = bytecodeTypes(pc);
return pushTypeBarrier(apply, types, BarrierKind::TypeSet);
}
bool
IonBuilder::jsop_funapplyarguments(uint32_t argc)
{

Просмотреть файл

@ -666,6 +666,7 @@ class IonBuilder
bool jsop_funcall(uint32_t argc);
bool jsop_funapply(uint32_t argc);
bool jsop_funapplyarguments(uint32_t argc);
bool jsop_funapplyarray(uint32_t argc);
bool jsop_call(uint32_t argc, bool constructing);
bool jsop_eval(uint32_t argc);
bool jsop_ifeq(JSOp op);

Просмотреть файл

@ -525,6 +525,38 @@ LIRGenerator::visitApplyArgs(MApplyArgs* apply)
assignSafepoint(lir, apply);
}
void
LIRGenerator::visitApplyArray(MApplyArray* apply)
{
MOZ_ASSERT(apply->getFunction()->type() == MIRType_Object);
// Assert if we cannot build a rectifier frame.
MOZ_ASSERT(CallTempReg0 != ArgumentsRectifierReg);
MOZ_ASSERT(CallTempReg1 != ArgumentsRectifierReg);
// Assert if the return value is already erased.
MOZ_ASSERT(CallTempReg2 != JSReturnReg_Type);
MOZ_ASSERT(CallTempReg2 != JSReturnReg_Data);
LApplyArrayGeneric* lir = new(alloc()) LApplyArrayGeneric(
useFixed(apply->getFunction(), CallTempReg3),
useFixed(apply->getElements(), CallTempReg0),
tempFixed(CallTempReg1), // object register
tempFixed(CallTempReg2)); // stack counter register
MDefinition* self = apply->getThis();
useBoxFixed(lir, LApplyArrayGeneric::ThisIndex, self, CallTempReg4, CallTempReg5);
// Bailout is needed in the case of possible non-JSFunction callee
// too many values in the array, or empty space at the end of the
// array. I'm going to use NonJSFunctionCallee for the code even
// if that is not an adequate description.
assignSnapshot(lir, Bailout_NonJSFunctionCallee);
defineReturn(lir, apply);
assignSafepoint(lir, apply);
}
void
LIRGenerator::visitBail(MBail* bail)
{

Просмотреть файл

@ -102,6 +102,7 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitComputeThis(MComputeThis* ins);
void visitCall(MCall* call);
void visitApplyArgs(MApplyArgs* apply);
void visitApplyArray(MApplyArray* apply);
void visitArraySplice(MArraySplice* splice);
void visitBail(MBail* bail);
void visitUnreachable(MUnreachable* unreachable);

Просмотреть файл

@ -1436,6 +1436,13 @@ MApplyArgs::New(TempAllocator& alloc, JSFunction* target, MDefinition* fun, MDef
return new(alloc) MApplyArgs(target, fun, argc, self);
}
MApplyArray*
MApplyArray::New(TempAllocator& alloc, JSFunction* target, MDefinition* fun, MDefinition* elements,
MDefinition* self)
{
return new(alloc) MApplyArray(target, fun, elements, self);
}
MDefinition*
MStringLength::foldsTo(TempAllocator& alloc)
{

Просмотреть файл

@ -3906,6 +3906,49 @@ class MApplyArgs
}
};
// fun.apply(fn, array)
class MApplyArray
: public MAryInstruction<3>,
public Mix3Policy<ObjectPolicy<0>, ObjectPolicy<1>, BoxPolicy<2> >::Data
{
protected:
// Monomorphic cache of single target from TI, or nullptr.
CompilerFunction target_;
MApplyArray(JSFunction* target, MDefinition* fun, MDefinition* elements, MDefinition* self)
: target_(target)
{
initOperand(0, fun);
initOperand(1, elements);
initOperand(2, self);
setResultType(MIRType_Value);
}
public:
INSTRUCTION_HEADER(ApplyArray)
static MApplyArray* New(TempAllocator& alloc, JSFunction* target, MDefinition* fun,
MDefinition* elements, MDefinition* self);
MDefinition* getFunction() const {
return getOperand(0);
}
// For TI-informed monomorphic callsites.
JSFunction* getSingleTarget() const {
return target_;
}
MDefinition* getElements() const {
return getOperand(1);
}
MDefinition* getThis() const {
return getOperand(2);
}
bool possiblyCalls() const override {
return true;
}
};
class MBail : public MNullaryInstruction
{
protected:

Просмотреть файл

@ -64,6 +64,7 @@ namespace jit {
_(ComputeThis) \
_(Call) \
_(ApplyArgs) \
_(ApplyArray) \
_(ArraySplice) \
_(Bail) \
_(Unreachable) \

Просмотреть файл

@ -1212,6 +1212,7 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins)
_(Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, BoxPolicy<2> >) \
_(Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2> >) \
_(Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, TruncateToInt32Policy<2> >) \
_(Mix3Policy<ObjectPolicy<0>, ObjectPolicy<1>, BoxPolicy<2> >) \
_(Mix3Policy<ObjectPolicy<0>, ObjectPolicy<1>, IntPolicy<2> >) \
_(Mix3Policy<ObjectPolicy<0>, ObjectPolicy<1>, ObjectPolicy<2> >) \
_(Mix3Policy<StringPolicy<0>, IntPolicy<1>, IntPolicy<2>>) \

Просмотреть файл

@ -1814,6 +1814,52 @@ class LApplyArgsGeneric : public LCallInstructionHelper<BOX_PIECES, BOX_PIECES +
}
};
class LApplyArrayGeneric : public LCallInstructionHelper<BOX_PIECES, BOX_PIECES + 2, 2>
{
public:
LIR_HEADER(ApplyArrayGeneric)
LApplyArrayGeneric(const LAllocation& func, const LAllocation& elements,
const LDefinition& tmpobjreg, const LDefinition& tmpcopy)
{
setOperand(0, func);
setOperand(1, elements);
setTemp(0, tmpobjreg);
setTemp(1, tmpcopy);
}
MApplyArray* mir() const {
return mir_->toApplyArray();
}
bool hasSingleTarget() const {
return getSingleTarget() != nullptr;
}
JSFunction* getSingleTarget() const {
return mir()->getSingleTarget();
}
const LAllocation* getFunction() {
return getOperand(0);
}
const LAllocation* getElements() {
return getOperand(1);
}
// argc is mapped to the same register as elements: argc becomes
// live as elements is dying, all registers are calltemps.
const LAllocation* getArgc() {
return getOperand(1);
}
static const size_t ThisIndex = 2;
const LDefinition* getTempObject() {
return getTemp(0);
}
const LDefinition* getTempStackCounter() {
return getTemp(1);
}
};
class LArraySplice : public LCallInstructionHelper<0, 3, 0>
{
public:

Просмотреть файл

@ -74,6 +74,7 @@
_(CallGeneric) \
_(CallNative) \
_(ApplyArgsGeneric) \
_(ApplyArrayGeneric) \
_(Bail) \
_(Unreachable) \
_(EncodeSnapshot) \

Просмотреть файл

@ -9,6 +9,7 @@
#include "mozilla/ArrayUtils.h"
#include "mozilla/Casting.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Maybe.h"
#include "jscntxt.h"
#include "jscompartment.h"
@ -53,6 +54,7 @@ using mozilla::IsInRange;
using mozilla::Maybe;
using mozilla::PodMove;
using mozilla::UniquePtr;
using mozilla::Maybe;
static void
selfHosting_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)