From b001b60a026ea575bb5095d369063aa4c6f19df6 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Tue, 18 Oct 2011 11:24:28 -0700 Subject: [PATCH] Add Array.concat stub for concatenating known dense arrays, bug 692960. r=dvander --- js/src/jit-test/tests/basic/arrayConcat.js | 18 ++++ js/src/jsarray.cpp | 34 +++++++- js/src/jsarray.h | 3 + js/src/jsinfer.cpp | 26 +++++- js/src/jsinfer.h | 3 + js/src/jsinferinlines.h | 3 +- js/src/jsobjinlines.h | 3 +- js/src/methodjit/Compiler.h | 2 + js/src/methodjit/FastBuiltins.cpp | 97 ++++++++++++++++++++++ js/src/methodjit/StubCalls-inl.h | 4 +- js/src/methodjit/StubCalls.h | 4 +- 11 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 js/src/jit-test/tests/basic/arrayConcat.js diff --git a/js/src/jit-test/tests/basic/arrayConcat.js b/js/src/jit-test/tests/basic/arrayConcat.js new file mode 100644 index 00000000000..17ad8771139 --- /dev/null +++ b/js/src/jit-test/tests/basic/arrayConcat.js @@ -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(); diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 4413c991a69..7c8acc4ee34 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -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; diff --git a/js/src/jsarray.h b/js/src/jsarray.h index 6643d7efa38..fe176137e6b 100644 --- a/js/src/jsarray.h +++ b/js/src/jsarray.h @@ -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); diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 5d04d7e4be4..3bbdc1294bd 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -1771,12 +1771,34 @@ TypeSet::knownNonEmpty(JSContext *cx) if (baseFlags() != 0 || baseObjectCount() != 0) return true; - add(cx, cx->typeLifoAlloc().new_( - 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) { diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index dcc5ccd007a..1acb405e53a 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -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. diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index 40a65ce107a..ea93a3ece8f 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -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) diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index b2b40d96a7e..e290ddaf2a0 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -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 diff --git a/js/src/methodjit/Compiler.h b/js/src/methodjit/Compiler.h index db8f6f1cae1..67664ce11c2 100644 --- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -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); diff --git a/js/src/methodjit/FastBuiltins.cpp b/js/src/methodjit/FastBuiltins.cpp index 279032b8c5b..34bf6685628 100644 --- a/js/src/methodjit/FastBuiltins.cpp +++ b/js/src/methodjit/FastBuiltins.cpp @@ -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); diff --git a/js/src/methodjit/StubCalls-inl.h b/js/src/methodjit/StubCalls-inl.h index 9cacad72abc..967296743d3 100644 --- a/js/src/methodjit/StubCalls-inl.h +++ b/js/src/methodjit/StubCalls-inl.h @@ -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) diff --git a/js/src/methodjit/StubCalls.h b/js/src/methodjit/StubCalls.h index 09b640c12d4..5ce1762d9fb 100644 --- a/js/src/methodjit/StubCalls.h +++ b/js/src/methodjit/StubCalls.h @@ -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 */