Add Array.concat stub for concatenating known dense arrays, bug 692960. r=dvander

This commit is contained in:
Brian Hackett 2011-10-18 11:24:28 -07:00
parent 9393f3f433
commit 2762d10152
11 changed files with 187 additions and 10 deletions

View File

@ -0,0 +1,18 @@
/* Test concat compiler paths. */
for (var i = 9; i < 10; i++)
assertEq([2].concat([3])[0], 2);
function f(a, b) {
return a.concat(b)[0];
}
function g() {
var x = [];
var y = [1];
for (var i = 0; i < 50; i++)
assertEq(f(x, y), 1);
eval('y[0] = "three"');
assertEq(f(x, y), "three");
}
g();

View File

@ -2940,11 +2940,41 @@ array_splice(JSContext *cx, uintN argc, Value *vp)
return true;
}
#ifdef JS_METHODJIT
void JS_FASTCALL
mjit::stubs::ArrayConcatTwoArrays(VMFrame &f)
{
JSObject *result = &f.regs.sp[-3].toObject();
JSObject *obj1 = &f.regs.sp[-2].toObject();
JSObject *obj2 = &f.regs.sp[-1].toObject();
JS_ASSERT(result->isDenseArray() && obj1->isDenseArray() && obj2->isDenseArray());
uint32 initlen1 = obj1->getDenseArrayInitializedLength();
JS_ASSERT(initlen1 == obj1->getArrayLength());
uint32 initlen2 = obj2->getDenseArrayInitializedLength();
JS_ASSERT(initlen2 == obj2->getArrayLength());
/* No overflow here due to nslots limit. */
uint32 len = initlen1 + initlen2;
if (!result->ensureSlots(f.cx, len))
THROW();
result->copyDenseArrayElements(0, obj1->getDenseArrayElements(), initlen1);
result->copyDenseArrayElements(initlen1, obj2->getDenseArrayElements(), initlen2);
result->setDenseArrayInitializedLength(len);
result->setDenseArrayLength(len);
}
#endif /* JS_METHODJIT */
/*
* Python-esque sequence operations.
*/
static JSBool
array_concat(JSContext *cx, uintN argc, Value *vp)
JSBool
js::array_concat(JSContext *cx, uintN argc, Value *vp)
{
/* Treat our |this| object as the first argument; see ECMA 15.4.4.4. */
Value *p = JS_ARGV(cx, vp) - 1;

View File

@ -227,6 +227,9 @@ array_push(JSContext *cx, uintN argc, js::Value *vp);
extern JSBool
array_pop(JSContext *cx, uintN argc, js::Value *vp);
extern JSBool
array_concat(JSContext *cx, uintN argc, js::Value *vp);
extern JSBool
array_shift(JSContext *cx, uintN argc, js::Value *vp);

View File

@ -1771,12 +1771,34 @@ TypeSet::knownNonEmpty(JSContext *cx)
if (baseFlags() != 0 || baseObjectCount() != 0)
return true;
add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreeze>(
cx->compartment->types.compiledScript), false);
addFreeze(cx);
return false;
}
bool
TypeSet::knownSubset(JSContext *cx, TypeSet *other)
{
if ((baseFlags() & other->baseFlags()) != baseFlags())
return false;
if (unknownObject()) {
JS_ASSERT(other->unknownObject());
} else {
for (unsigned i = 0; i < getObjectCount(); i++) {
TypeObjectKey *obj = getObject(i);
if (!obj)
continue;
if (!other->hasType(Type::ObjectType(obj)))
return false;
}
}
addFreeze(cx);
return true;
}
int
TypeSet::getTypedArrayType(JSContext *cx)
{

View File

@ -491,6 +491,9 @@ class TypeSet
/* Get whether this type set is non-empty. */
bool knownNonEmpty(JSContext *cx);
/* Get whether this type set is known to be a subset of other. */
bool knownSubset(JSContext *cx, TypeSet *other);
/*
* Get the typed array type of all objects in this set. Returns
* TypedArray::TYPE_MAX if the set contains different array types.

View File

@ -962,8 +962,9 @@ TypeSet::addType(JSContext *cx, Type type)
return;
if (type.isUnknown()) {
flags = TYPE_FLAG_UNKNOWN | (flags & ~baseFlags());
flags |= TYPE_FLAG_BASE_MASK;
clearObjects();
JS_ASSERT(unknown());
} else if (type.isPrimitive()) {
TypeFlags flag = PrimitiveTypeFlag(type.primitive());
if (flags & flag)

View File

@ -524,7 +524,8 @@ inline void
JSObject::copyDenseArrayElements(uintN dstStart, const js::Value *src, uintN count)
{
JS_ASSERT(isDenseArray());
copySlotRange(dstStart, src, count);
JS_ASSERT(dstStart + count <= capacity);
memcpy(slots + dstStart, src, count * sizeof(js::Value));
}
inline void

View File

@ -780,6 +780,8 @@ private:
Assembler::Condition cond);
CompileStatus compileMathPowSimple(FrameEntry *arg1, FrameEntry *arg2);
CompileStatus compileArrayPush(FrameEntry *thisv, FrameEntry *arg);
CompileStatus compileArrayConcat(types::TypeSet *thisTypes, types::TypeSet *argTypes,
FrameEntry *thisValue, FrameEntry *argValue);
CompileStatus compileArrayPopShift(FrameEntry *thisv, bool isPacked, bool isArrayPop);
CompileStatus compileArrayWithLength(uint32 argc);
CompileStatus compileArrayWithArgs(uint32 argc);

View File

@ -589,6 +589,94 @@ mjit::Compiler::compileArrayPopShift(FrameEntry *thisValue, bool isPacked, bool
return Compile_Okay;
}
CompileStatus
mjit::Compiler::compileArrayConcat(types::TypeSet *thisTypes, types::TypeSet *argTypes,
FrameEntry *thisValue, FrameEntry *argValue)
{
/*
* Require the 'this' types to have a specific type matching the current
* global, so we can create the result object inline.
*/
if (thisTypes->getObjectCount() != 1)
return Compile_InlineAbort;
types::TypeObject *thisType = thisTypes->getTypeObject(0);
if (!thisType || thisType->proto->getGlobal() != globalObj)
return Compile_InlineAbort;
/*
* Constraints modeling this concat have not been generated by inference,
* so check that type information already reflects possible side effects of
* this call.
*/
thisTypes->addFreeze(cx);
argTypes->addFreeze(cx);
types::TypeSet *thisElemTypes = thisType->getProperty(cx, JSID_VOID, false);
if (!thisElemTypes)
return Compile_Error;
if (!pushedTypeSet(0)->hasType(types::Type::ObjectType(thisType)))
return Compile_InlineAbort;
for (unsigned i = 0; i < argTypes->getObjectCount(); i++) {
if (argTypes->getSingleObject(i))
return Compile_InlineAbort;
types::TypeObject *argType = argTypes->getTypeObject(i);
if (!argType)
continue;
types::TypeSet *elemTypes = argType->getProperty(cx, JSID_VOID, false);
if (!elemTypes)
return Compile_Error;
if (!elemTypes->knownSubset(cx, thisElemTypes))
return Compile_InlineAbort;
}
/* Test for 'length == initializedLength' on both arrays. */
RegisterID reg = frame.allocReg();
Int32Key key = Int32Key::FromRegister(reg);
RegisterID objReg = frame.tempRegForData(thisValue);
masm.load32(Address(objReg, offsetof(JSObject, privateData)), reg);
Jump initlenOneGuard = masm.guardArrayExtent(offsetof(JSObject, initializedLength),
objReg, key, Assembler::NotEqual);
stubcc.linkExit(initlenOneGuard, Uses(3));
objReg = frame.tempRegForData(argValue);
masm.load32(Address(objReg, offsetof(JSObject, privateData)), reg);
Jump initlenTwoGuard = masm.guardArrayExtent(offsetof(JSObject, initializedLength),
objReg, key, Assembler::NotEqual);
stubcc.linkExit(initlenTwoGuard, Uses(3));
frame.freeReg(reg);
frame.syncAndForgetEverything();
/*
* The current stack layout is 'CALLEE THIS ARG'. Allocate the result and
* scribble it over the callee, which will be its final position after the
* call.
*/
JSObject *templateObject = NewDenseEmptyArray(cx, thisType->proto);
if (!templateObject)
return Compile_Error;
templateObject->setType(thisType);
RegisterID result = Registers::ReturnReg;
Jump emptyFreeList = masm.getNewObject(cx, result, templateObject);
stubcc.linkExit(emptyFreeList, Uses(3));
masm.storeValueFromComponents(ImmType(JSVAL_TYPE_OBJECT), result, frame.addressOf(frame.peek(-3)));
INLINE_STUBCALL(stubs::ArrayConcatTwoArrays, REJOIN_FALLTHROUGH);
stubcc.leave();
stubcc.masm.move(Imm32(1), Registers::ArgReg1);
OOL_STUBCALL(stubs::SlowCall, REJOIN_FALLTHROUGH);
frame.popn(3);
frame.pushSynced(JSVAL_TYPE_OBJECT);
stubcc.rejoin(Changes(1));
return Compile_Okay;
}
CompileStatus
mjit::Compiler::compileArrayWithLength(uint32 argc)
{
@ -752,6 +840,9 @@ mjit::Compiler::inlineNativeFunction(uint32 argc, bool callingNew)
}
} else if (argc == 1) {
FrameEntry *arg = frame.peek(-1);
types::TypeSet *argTypes = frame.extra(arg).types;
if (!argTypes)
return Compile_InlineAbort;
JSValueType argType = arg->isTypeKnown() ? arg->getKnownType() : JSVAL_TYPE_UNKNOWN;
if (native == js_math_abs) {
@ -792,6 +883,12 @@ mjit::Compiler::inlineNativeFunction(uint32 argc, bool callingNew)
return compileArrayPush(thisValue, arg);
}
}
if (native == js::array_concat && argType == JSVAL_TYPE_OBJECT &&
thisType == JSVAL_TYPE_OBJECT && type == JSVAL_TYPE_OBJECT &&
!thisTypes->hasObjectFlags(cx, types::OBJECT_FLAG_NON_DENSE_ARRAY) &&
!argTypes->hasObjectFlags(cx, types::OBJECT_FLAG_NON_DENSE_ARRAY)) {
return compileArrayConcat(thisTypes, argTypes, thisValue, arg);
}
} else if (argc == 2) {
FrameEntry *arg1 = frame.peek(-2);
FrameEntry *arg2 = frame.peek(-1);

View File

@ -51,8 +51,8 @@ ThrowException(VMFrame &f)
*f.returnAddressLocation() = ptr;
}
#define THROW() do { ThrowException(f); return; } while (0)
#define THROWV(v) do { ThrowException(f); return v; } while (0)
#define THROW() do { mjit::ThrowException(f); return; } while (0)
#define THROWV(v) do { mjit::ThrowException(f); return v; } while (0)
static inline JSObject *
ValueToObject(JSContext *cx, Value *vp)

View File

@ -235,8 +235,8 @@ void JS_FASTCALL AnyFrameEpilogue(VMFrame &f);
JSObject * JS_FASTCALL
NewDenseUnallocatedArray(VMFrame &f, uint32 length);
void JS_FASTCALL
ArrayShift(VMFrame &f);
void JS_FASTCALL ArrayConcatTwoArrays(VMFrame &f);
void JS_FASTCALL ArrayShift(VMFrame &f);
} /* namespace stubs */