author | Till Schneidereit <till@tillschneidereit.net> |
Sat, 15 Aug 2015 23:58:24 +0200 | |
changeset 257957 | 9b34691fd5cda4be9ad9674a0883f51de2198d69 |
parent 257956 | ca20318c36272e2060bd94894a82807cc6db216b |
child 257958 | f9a0ce79b27e3b0bc7a2cdf62f40e77456455754 |
push id | 29238 |
push user | ryanvm@gmail.com |
push date | Mon, 17 Aug 2015 13:06:57 +0000 |
treeherder | mozilla-central@a6eeb28458fd [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bustage |
bugs | 1195030, 890329 |
milestone | 43.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -836,116 +836,8 @@ function ArrayToString() { // Steps 3-4. var func = array.join; // Steps 5-6. if (!IsCallable(func)) return callFunction(std_Object_toString, array); return callFunction(func, array); } - -//ES 2015, 22.1.3.25 Array.prototype.splice. -function ArraySplice(start, deleteCount /*, ...items */) { - // Steps 1-2. - var O = ToObject(this); - - // Steps 3-4. - // FIXME: Array operations should use ToLength (bug 924058). - var len = ToInteger(O.length); - - // Steps 5-6. - var relativeStart = ToInteger(start); - - // Step 7. - var actualStart = relativeStart < 0 - ? std_Math_max(len + relativeStart, 0) - : std_Math_min(relativeStart, len); - - // Steps 8-10. - var insertCount = 0; - var actualDeleteCount = 0; - var numArgs = arguments.length; - if (numArgs === 1) { - actualDeleteCount = len - actualStart; - } else if (numArgs > 1) { - // Step 10.a. - insertCount = numArgs - 2; - // Steps 10.b-c. - var dc = ToInteger(deleteCount); - // Step 10.d. - actualDeleteCount = std_Math_min(std_Math_max(dc, 0), len - actualStart); - } - - // Step 11. - if (len + insertCount - actualDeleteCount > 2 ** 53 - 1) - ThrowTypeError(JSMSG_BAD_ARRAY_LENGTH_SPLICE); - - // Steps 12-13. - // FIXME: Use ArraySpeciesCreate here. - var A = NewDenseArray(actualDeleteCount); - - // Steps 14-15. - for (var k = 0; k < actualDeleteCount; k++) { - // Step 15.a. - var from = actualStart + k; - // Steps 15.b-d. - if (from in O) - _DefineDataProperty(A, k, O[from]); - } - - // Steps 16-17. - A.length = actualDeleteCount; - - // Step 18 (implicit). - // Step 19. - var itemCount = insertCount; - - // Step 20. - if (itemCount < actualDeleteCount) { - // Steps 20.a-b. - for (var k = actualStart, kMax = len - actualDeleteCount; k < kMax; k++) { - // Step 20.b.i. - var from = k + actualDeleteCount; - // Step 20.b.ii. - var to = k + itemCount; - // Steps 20.b.iii-v. - if (from in O) { - O[to] = O[from]; - } else { - // Step 20.b.vi. - delete O[to]; - } - } - // Steps 20.c-d. - // For packed arrays we can skip these steps: the fact that we don't - // delete the elements one by one isn't visible to content code. - if (!IsPackedArray(O)) { - for (var k = len, kMin = len - actualDeleteCount + itemCount; k > kMin; k--) - delete O[k - 1]; - } - } else if (itemCount > actualDeleteCount) { - // Step 21. - // Steps 21 a-b. - for (var k = len - actualDeleteCount, kMin = actualStart; k > kMin; k--) { - // Step 21.b.i. - var from = k + actualDeleteCount - 1; - // Step 21.b.ii. - var to = k + itemCount - 1; - // Steps 21.b.iii-v. - if (from in O) { - O[to] = O[from]; - } else { - // Step 21.b.vi. - delete O[to]; - } - } - } - - // Steps 22-23. - for (var k = actualStart, itemIndex = 2; itemIndex < numArgs; k++, itemIndex++) - O[k] = arguments[itemIndex]; - - // Steps 24-25. - O.length = len - actualDeleteCount + itemCount; - - // Step 26. - return A; -}
--- a/js/src/builtin/Number.js +++ b/js/src/builtin/Number.js @@ -65,18 +65,18 @@ function Number_isSafeInteger(number) { // Step 3. var integer = ToInteger(number); // Step 4. if (integer !== number) return false; - // Step 5. - if (std_Math_abs(integer) <= 2 ** 53 - 1) + // Step 5. If abs(integer) <= 2**53 - 1, return true. + if (std_Math_abs(integer) <= 9007199254740991) return true; // Step 6. return false; } function Global_isNaN(number) { return Number_isNaN(ToNumber(number));
--- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -105,17 +105,18 @@ function RequireObjectCoercible(v) { /* Spec: ECMAScript Draft, 6 edition May 22, 2014, 7.1.15 */ function ToLength(v) { v = ToInteger(v); if (v <= 0) return 0; - return std_Math_min(v, 2 ** 53 - 1); + // Math.pow(2, 53) - 1 = 0x1fffffffffffff + return std_Math_min(v, 0x1fffffffffffff); } /* Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4 */ function SameValueZero(x, y) { return x === y || (x !== x && y !== y); } /* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.3.8 */
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -3433,16 +3433,28 @@ CodeGenerator::visitApplyArgsGeneric(LAp emitCallInvokeFunction(apply, extraStackSpace); } // Pop arguments and continue. masm.bind(&end); emitPopArguments(apply, extraStackSpace); } +typedef bool (*ArraySpliceDenseFn)(JSContext*, HandleObject, uint32_t, uint32_t); +static const VMFunction ArraySpliceDenseInfo = FunctionInfo<ArraySpliceDenseFn>(ArraySpliceDense); + +void +CodeGenerator::visitArraySplice(LArraySplice* lir) +{ + pushArg(ToRegister(lir->getDeleteCount())); + pushArg(ToRegister(lir->getStart())); + pushArg(ToRegister(lir->getObject())); + callVM(ArraySpliceDenseInfo, lir); +} + void CodeGenerator::visitBail(LBail* lir) { bailout(lir->snapshot()); } void CodeGenerator::visitUnreachable(LUnreachable* lir)
--- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -211,16 +211,17 @@ class CodeGenerator : public CodeGenerat void visitStoreFixedSlotT(LStoreFixedSlotT* ins); void emitGetPropertyPolymorphic(LInstruction* lir, Register obj, Register scratch, const TypedOrValueRegister& output); void visitGetPropertyPolymorphicV(LGetPropertyPolymorphicV* ins); void visitGetPropertyPolymorphicT(LGetPropertyPolymorphicT* ins); void emitSetPropertyPolymorphic(LInstruction* lir, Register obj, Register scratch, const ConstantOrRegister& value); void visitSetPropertyPolymorphicV(LSetPropertyPolymorphicV* ins); + void visitArraySplice(LArraySplice* splice); void visitSetPropertyPolymorphicT(LSetPropertyPolymorphicT* ins); void visitAbsI(LAbsI* lir); void visitAtan2D(LAtan2D* lir); void visitHypot(LHypot* lir); void visitPowI(LPowI* lir); void visitPowD(LPowD* lir); void visitMathFunctionD(LMathFunctionD* ins); void visitMathFunctionF(LMathFunctionF* ins);
--- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -748,16 +748,17 @@ class IonBuilder // Array natives. InliningStatus inlineArray(CallInfo& callInfo); InliningStatus inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode); InliningStatus inlineArrayPush(CallInfo& callInfo); InliningStatus inlineArrayConcat(CallInfo& callInfo); InliningStatus inlineArraySlice(CallInfo& callInfo); InliningStatus inlineArrayJoin(CallInfo& callInfo); + InliningStatus inlineArraySplice(CallInfo& callInfo); // Math natives. InliningStatus inlineMathAbs(CallInfo& callInfo); InliningStatus inlineMathFloor(CallInfo& callInfo); InliningStatus inlineMathCeil(CallInfo& callInfo); InliningStatus inlineMathClz32(CallInfo& callInfo); InliningStatus inlineMathRound(CallInfo& callInfo); InliningStatus inlineMathSqrt(CallInfo& callInfo);
--- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -552,16 +552,26 @@ LIRGenerator::visitAssertFloat32(MAssert void LIRGenerator::visitAssertRecoveredOnBailout(MAssertRecoveredOnBailout* assertion) { MOZ_CRASH("AssertRecoveredOnBailout nodes are always recovered on bailouts."); } void +LIRGenerator::visitArraySplice(MArraySplice* ins) +{ + LArraySplice* lir = new(alloc()) LArraySplice(useRegisterAtStart(ins->object()), + useRegisterAtStart(ins->start()), + useRegisterAtStart(ins->deleteCount())); + add(lir, ins); + assignSafepoint(lir, ins); +} + +void LIRGenerator::visitGetDynamicName(MGetDynamicName* ins) { MDefinition* scopeChain = ins->getScopeChain(); MOZ_ASSERT(scopeChain->type() == MIRType_Object); MDefinition* name = ins->getName(); MOZ_ASSERT(name->type() == MIRType_String);
--- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -94,16 +94,17 @@ class LIRGenerator : public LIRGenerator void visitCreateArgumentsObject(MCreateArgumentsObject* ins); void visitGetArgumentsObjectArg(MGetArgumentsObjectArg* ins); void visitSetArgumentsObjectArg(MSetArgumentsObjectArg* ins); void visitReturnFromCtor(MReturnFromCtor* ins); void visitComputeThis(MComputeThis* ins); void visitLoadArrowThis(MLoadArrowThis* ins); void visitCall(MCall* call); void visitApplyArgs(MApplyArgs* apply); + void visitArraySplice(MArraySplice* splice); void visitBail(MBail* bail); void visitUnreachable(MUnreachable* unreachable); void visitEncodeSnapshot(MEncodeSnapshot* ins); void visitAssertFloat32(MAssertFloat32* ins); void visitAssertRecoveredOnBailout(MAssertRecoveredOnBailout* ins); void visitGetDynamicName(MGetDynamicName* ins); void visitFilterArgumentsOrEval(MFilterArgumentsOrEval* ins); void visitCallDirectEval(MCallDirectEval* ins);
--- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -85,16 +85,18 @@ IonBuilder::inlineNativeCall(CallInfo& c if (native == js::array_shift) return inlineArrayPopShift(callInfo, MArrayPopShift::Shift); if (native == js::array_push) return inlineArrayPush(callInfo); if (native == js::array_concat) return inlineArrayConcat(callInfo); if (native == js::array_slice) return inlineArraySlice(callInfo); + if (native == js::array_splice) + return inlineArraySplice(callInfo); // Math natives. if (native == js::math_abs) return inlineMathAbs(callInfo); if (native == js::math_floor) return inlineMathFloor(callInfo); if (native == js::math_ceil) return inlineMathCeil(callInfo); @@ -687,16 +689,56 @@ IonBuilder::inlineArrayPopShift(CallInfo if (!pushTypeBarrier(ins, returnTypes, barrier)) return InliningStatus_Error; return InliningStatus_Inlined; } IonBuilder::InliningStatus +IonBuilder::inlineArraySplice(CallInfo& callInfo) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + // Ensure |this|, argument and result are objects. + if (getInlineReturnType() != MIRType_Object) + return InliningStatus_NotInlined; + if (callInfo.thisArg()->type() != MIRType_Object) + return InliningStatus_NotInlined; + if (callInfo.getArg(0)->type() != MIRType_Int32) + return InliningStatus_NotInlined; + if (callInfo.getArg(1)->type() != MIRType_Int32) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + // Specialize arr.splice(start, deleteCount) with unused return value and + // avoid creating the result array in this case. + if (!BytecodeIsPopped(pc)) { + trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric); + return InliningStatus_NotInlined; + } + + MArraySplice* ins = MArraySplice::New(alloc(), + callInfo.thisArg(), + callInfo.getArg(0), + callInfo.getArg(1)); + + current->add(ins); + pushConstant(UndefinedValue()); + + if (!resumeAfter(ins)) + return InliningStatus_Error; + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus IonBuilder::inlineArrayJoin(CallInfo& callInfo) { if (callInfo.argc() != 1 || callInfo.constructing()) { trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); return InliningStatus_NotInlined; } if (getInlineReturnType() != MIRType_String)
--- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -3808,16 +3808,52 @@ class MCallDOMNative : public MCall virtual bool isCallDOMNative() const override { return true; } virtual void computeMovable() override; }; +// arr.splice(start, deleteCount) with unused return value. +class MArraySplice + : public MTernaryInstruction, + public Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2> >::Data +{ + private: + + MArraySplice(MDefinition* object, MDefinition* start, MDefinition* deleteCount) + : MTernaryInstruction(object, start, deleteCount) + { } + + public: + INSTRUCTION_HEADER(ArraySplice) + static MArraySplice* New(TempAllocator& alloc, MDefinition* object, + MDefinition* start, MDefinition* deleteCount) + { + return new(alloc) MArraySplice(object, start, deleteCount); + } + + MDefinition* object() const { + return getOperand(0); + } + + MDefinition* start() const { + return getOperand(1); + } + + MDefinition* deleteCount() const { + return getOperand(2); + } + + bool possiblyCalls() const override { + return true; + } +}; + // fun.apply(self, arguments) class MApplyArgs : public MAryInstruction<3>, public Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, BoxPolicy<2> >::Data { protected: // Monomorphic cache of single target from TI, or nullptr. CompilerFunction target_;
--- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -57,16 +57,17 @@ namespace jit { _(CreateThisWithTemplate) \ _(CreateArgumentsObject) \ _(GetArgumentsObjectArg) \ _(SetArgumentsObjectArg) \ _(ComputeThis) \ _(LoadArrowThis) \ _(Call) \ _(ApplyArgs) \ + _(ArraySplice) \ _(Bail) \ _(Unreachable) \ _(EncodeSnapshot) \ _(AssertFloat32) \ _(AssertRecoveredOnBailout) \ _(GetDynamicName) \ _(FilterArgumentsOrEval) \ _(CallDirectEval) \
--- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -247,16 +247,28 @@ StringsEqual(JSContext* cx, HandleString *res = !*res; return true; } template bool StringsEqual<true>(JSContext* cx, HandleString lhs, HandleString rhs, bool* res); template bool StringsEqual<false>(JSContext* cx, HandleString lhs, HandleString rhs, bool* res); bool +ArraySpliceDense(JSContext* cx, HandleObject obj, uint32_t start, uint32_t deleteCount) +{ + JS::AutoValueArray<4> argv(cx); + argv[0].setUndefined(); + argv[1].setObject(*obj); + argv[2].set(Int32Value(start)); + argv[3].set(Int32Value(deleteCount)); + + return js::array_splice_impl(cx, 2, argv.begin(), false); +} + +bool ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval) { MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()); AutoDetectInvalidation adi(cx, rval); JS::AutoValueArray<2> argv(cx); argv[0].setUndefined();
--- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -683,16 +683,18 @@ bool DebugLeaveThenFreshenBlockScope(JSC bool DebugLeaveBlock(JSContext* cx, BaselineFrame* frame, jsbytecode* pc); bool InitBaselineFrameForOsr(BaselineFrame* frame, InterpreterFrame* interpFrame, uint32_t numStackValues); JSObject* CreateDerivedTypedObj(JSContext* cx, HandleObject descr, HandleObject owner, int32_t offset); +bool ArraySpliceDense(JSContext* cx, HandleObject obj, uint32_t start, uint32_t deleteCount); + bool Recompile(JSContext* cx); bool ForcedRecompile(JSContext* cx); JSString* RegExpReplace(JSContext* cx, HandleString string, HandleObject regexp, HandleString repl); JSString* StringReplace(JSContext* cx, HandleString string, HandleString pattern, HandleString repl); bool SetDenseOrUnboxedArrayElement(JSContext* cx, HandleObject obj, int32_t index,
--- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -1804,16 +1804,44 @@ class LApplyArgsGeneric : public LCallIn const LDefinition* getTempObject() { return getTemp(0); } const LDefinition* getTempStackCounter() { return getTemp(1); } }; +class LArraySplice : public LCallInstructionHelper<0, 3, 0> +{ + public: + LIR_HEADER(ArraySplice) + + LArraySplice(const LAllocation& object, const LAllocation& start, + const LAllocation& deleteCount) + { + setOperand(0, object); + setOperand(1, start); + setOperand(2, deleteCount); + } + + MArraySplice* mir() const { + return mir_->toArraySplice(); + } + + const LAllocation* getObject() { + return getOperand(0); + } + const LAllocation* getStart() { + return getOperand(1); + } + const LAllocation* getDeleteCount() { + return getOperand(2); + } +}; + class LGetDynamicName : public LCallInstructionHelper<BOX_PIECES, 2, 3> { public: LIR_HEADER(GetDynamicName) LGetDynamicName(const LAllocation& scopeChain, const LAllocation& name, const LDefinition& temp1, const LDefinition& temp2, const LDefinition& temp3) {
--- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -48,16 +48,17 @@ _(Callee) \ _(IsConstructing) \ _(TableSwitch) \ _(TableSwitchV) \ _(Goto) \ _(NewArray) \ _(NewArrayCopyOnWrite) \ _(NewArrayDynamicLength) \ + _(ArraySplice) \ _(NewObject) \ _(NewTypedObject) \ _(NewDeclEnvObject) \ _(NewCallObject) \ _(NewSingletonCallObject) \ _(NewStringObject) \ _(NewDerivedTypedObject) \ _(InitElem) \
--- a/js/src/js.msg +++ b/js/src/js.msg @@ -56,17 +56,16 @@ MSG_DEF(JSMSG_NOT_CONSTRUCTOR, 1 MSG_DEF(JSMSG_CANT_CONVERT_TO, 2, JSEXN_TYPEERR, "can't convert {0} to {1}") MSG_DEF(JSMSG_NO_PROPERTIES, 1, JSEXN_TYPEERR, "{0} has no properties") MSG_DEF(JSMSG_BAD_REGEXP_FLAG, 1, JSEXN_SYNTAXERR, "invalid regular expression flag {0}") MSG_DEF(JSMSG_ARG_INDEX_OUT_OF_RANGE, 1, JSEXN_RANGEERR, "argument {0} accesses an index that is out of range") MSG_DEF(JSMSG_SPREAD_TOO_LARGE, 0, JSEXN_RANGEERR, "array too large due to spread operand(s)") MSG_DEF(JSMSG_BAD_WEAKMAP_KEY, 0, JSEXN_TYPEERR, "cannot use the given object as a weak map key") MSG_DEF(JSMSG_BAD_GETTER_OR_SETTER, 1, JSEXN_TYPEERR, "invalid {0} usage") MSG_DEF(JSMSG_BAD_ARRAY_LENGTH, 0, JSEXN_RANGEERR, "invalid array length") -MSG_DEF(JSMSG_BAD_ARRAY_LENGTH_SPLICE, 0, JSEXN_TYPEERR, "resulting array length too large") MSG_DEF(JSMSG_REDECLARED_VAR, 2, JSEXN_TYPEERR, "redeclaration of {0} {1}") MSG_DEF(JSMSG_UNDECLARED_VAR, 1, JSEXN_REFERENCEERR, "assignment to undeclared variable {0}") MSG_DEF(JSMSG_GETTER_ONLY, 0, JSEXN_TYPEERR, "setting a property that has only a getter") MSG_DEF(JSMSG_OVERWRITING_ACCESSOR, 1, JSEXN_TYPEERR, "can't overwrite accessor property {0}") MSG_DEF(JSMSG_UNDEFINED_PROP, 1, JSEXN_REFERENCEERR, "reference to undefined property {0}") MSG_DEF(JSMSG_INVALID_MAP_ITERABLE, 1, JSEXN_TYPEERR, "iterable for {0} should have array-like objects") MSG_DEF(JSMSG_NESTING_GENERATOR, 0, JSEXN_TYPEERR, "already executing generator") MSG_DEF(JSMSG_INCOMPATIBLE_METHOD, 3, JSEXN_TYPEERR, "{0} {1} called on incompatible {2}")
--- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -2256,16 +2256,287 @@ js::array_unshift(JSContext* cx, unsigne if (!SetLengthProperty(cx, obj, newlen)) return false; /* Follow Perl by returning the new array length. */ args.rval().setNumber(newlen); return true; } +/* + * Returns true if this is a dense or unboxed array whose |count| properties + * starting from |startingIndex| may be accessed (get, set, delete) directly + * through its contiguous vector of elements without fear of getters, setters, + * etc. along the prototype chain, or of enumerators requiring notification of + * modifications. + */ +static inline bool +CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t count, JSContext* cx) +{ + /* If the desired properties overflow dense storage, we can't optimize. */ + if (UINT32_MAX - startingIndex < count) + return false; + + /* There's no optimizing possible if it's not an array. */ + if (!arr->is<ArrayObject>() && !arr->is<UnboxedArrayObject>()) + return false; + + /* + * Don't optimize if the array might be in the midst of iteration. We + * rely on this to be able to safely move dense array elements around with + * just a memmove (see NativeObject::moveDenseArrayElements), without worrying + * about updating any in-progress enumerators for properties implicitly + * deleted if a hole is moved from one location to another location not yet + * visited. See bug 690622. + */ + ObjectGroup* arrGroup = arr->getGroup(cx); + if (MOZ_UNLIKELY(!arrGroup || arrGroup->hasAllFlags(OBJECT_FLAG_ITERATED))) + return false; + + /* + * Another potential wrinkle: what if the enumeration is happening on an + * object which merely has |arr| on its prototype chain? + */ + if (arr->isDelegate()) + return false; + + /* + * Now watch out for getters and setters along the prototype chain or in + * other indexed properties on the object. (Note that non-writable length + * is subsumed by the initializedLength comparison.) + */ + return !ObjectMayHaveExtraIndexedProperties(arr) && + startingIndex + count <= GetAnyBoxedOrUnboxedInitializedLength(arr); +} + +/* ES5 15.4.4.12. */ +bool +js::array_splice(JSContext* cx, unsigned argc, Value* vp) +{ + return array_splice_impl(cx, argc, vp, true); +} + +bool +js::array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUsed) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + /* Step 1. */ + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) + return false; + + /* Steps 3-4. */ + uint32_t len; + if (!GetLengthProperty(cx, obj, &len)) + return false; + + /* Step 5. */ + double relativeStart; + if (!ToInteger(cx, args.get(0), &relativeStart)) + return false; + + /* Step 6. */ + uint32_t actualStart; + if (relativeStart < 0) + actualStart = Max(len + relativeStart, 0.0); + else + actualStart = Min(relativeStart, double(len)); + + /* Step 7. */ + uint32_t actualDeleteCount; + if (args.length() != 1) { + double deleteCountDouble; + RootedValue cnt(cx, args.length() >= 2 ? args[1] : Int32Value(0)); + if (!ToInteger(cx, cnt, &deleteCountDouble)) + return false; + actualDeleteCount = Min(Max(deleteCountDouble, 0.0), double(len - actualStart)); + } else { + /* + * Non-standard: if start was specified but deleteCount was omitted, + * delete to the end of the array. See bug 668024 for discussion. + */ + actualDeleteCount = len - actualStart; + } + + MOZ_ASSERT(len - actualStart >= actualDeleteCount); + + /* Steps 2, 8-9. */ + RootedObject arr(cx); + if (CanOptimizeForDenseStorage(obj, actualStart, actualDeleteCount, cx)) { + if (returnValueIsUsed) { + arr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, actualDeleteCount); + if (!arr) + return false; + DebugOnly<DenseElementResult> result = + CopyAnyBoxedOrUnboxedDenseElements(cx, arr, obj, 0, actualStart, actualDeleteCount); + MOZ_ASSERT(result.value == DenseElementResult::Success); + } + } else { + arr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, actualDeleteCount); + if (!arr) + return false; + + RootedValue fromValue(cx); + for (uint32_t k = 0; k < actualDeleteCount; k++) { + bool hole; + if (!CheckForInterrupt(cx) || + !GetElement(cx, obj, actualStart + k, &hole, &fromValue) || + (!hole && !DefineElement(cx, arr, k, fromValue))) + { + return false; + } + } + } + + /* Step 11. */ + uint32_t itemCount = (args.length() >= 2) ? (args.length() - 2) : 0; + + if (itemCount < actualDeleteCount) { + /* Step 12: the array is being shrunk. */ + uint32_t sourceIndex = actualStart + actualDeleteCount; + uint32_t targetIndex = actualStart + itemCount; + uint32_t finalLength = len - actualDeleteCount + itemCount; + + if (CanOptimizeForDenseStorage(obj, 0, len, cx)) { + /* Steps 12(a)-(b). */ + DenseElementResult result = + MoveAnyBoxedOrUnboxedDenseElements(cx, obj, targetIndex, sourceIndex, + len - sourceIndex); + MOZ_ASSERT(result != DenseElementResult::Incomplete); + if (result == DenseElementResult::Failure) + return false; + + /* Steps 12(c)-(d). */ + SetAnyBoxedOrUnboxedInitializedLength(cx, obj, finalLength); + } else { + /* + * This is all very slow if the length is very large. We don't yet + * have the ability to iterate in sorted order, so we just do the + * pessimistic thing and let CheckForInterrupt handle the + * fallout. + */ + + /* Steps 12(a)-(b). */ + RootedValue fromValue(cx); + for (uint32_t from = sourceIndex, to = targetIndex; from < len; from++, to++) { + if (!CheckForInterrupt(cx)) + return false; + + bool hole; + if (!GetElement(cx, obj, from, &hole, &fromValue)) + return false; + if (hole) { + if (!DeletePropertyOrThrow(cx, obj, to)) + return false; + } else { + if (!SetArrayElement(cx, obj, to, fromValue)) + return false; + } + } + + /* Steps 12(c)-(d). */ + for (uint32_t k = len; k > finalLength; k--) { + if (!DeletePropertyOrThrow(cx, obj, k - 1)) + return false; + } + } + } else if (itemCount > actualDeleteCount) { + /* Step 13. */ + + /* + * Optimize only if the array is already dense and we can extend it to + * its new length. It would be wrong to extend the elements here for a + * number of reasons. + * + * First, this could cause us to fall into the fast-path below. This + * would cause elements to be moved into places past the non-writable + * length. And when the dense initialized length is updated, that'll + * cause the |in| operator to think that those elements actually exist, + * even though, properly, setting them must fail. + * + * Second, extending the elements here will trigger assertions inside + * ensureDenseElements that the elements aren't being extended past the + * length of a non-writable array. This is because extending elements + * will extend capacity -- which might extend them past a non-writable + * length, violating the |capacity <= length| invariant for such + * arrays. And that would make the various JITted fast-path method + * implementations of [].push, [].unshift, and so on wrong. + * + * If the array length is non-writable, this method *will* throw. For + * simplicity, have the slow-path code do it. (Also note that the slow + * path may validly *not* throw -- if all the elements being moved are + * holes.) + */ + if (obj->is<ArrayObject>()) { + Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>()); + if (arr->lengthIsWritable()) { + DenseElementResult result = + arr->ensureDenseElements(cx, arr->length(), itemCount - actualDeleteCount); + if (result == DenseElementResult::Failure) + return false; + } + } + + if (CanOptimizeForDenseStorage(obj, len, itemCount - actualDeleteCount, cx)) { + DenseElementResult result = + MoveAnyBoxedOrUnboxedDenseElements(cx, obj, actualStart + itemCount, + actualStart + actualDeleteCount, + len - (actualStart + actualDeleteCount)); + MOZ_ASSERT(result != DenseElementResult::Incomplete); + if (result == DenseElementResult::Failure) + return false; + + /* Steps 12(c)-(d). */ + SetAnyBoxedOrUnboxedInitializedLength(cx, obj, len + itemCount - actualDeleteCount); + } else { + RootedValue fromValue(cx); + for (double k = len - actualDeleteCount; k > actualStart; k--) { + if (!CheckForInterrupt(cx)) + return false; + + double from = k + actualDeleteCount - 1; + double to = k + itemCount - 1; + + bool hole; + if (!GetElement(cx, obj, from, &hole, &fromValue)) + return false; + + if (hole) { + if (!DeletePropertyOrThrow(cx, obj, to)) + return false; + } else { + if (!SetArrayElement(cx, obj, to, fromValue)) + return false; + } + } + } + } + + /* Step 10. */ + Value* items = args.array() + 2; + + /* Steps 14-15. */ + for (uint32_t k = actualStart, i = 0; i < itemCount; i++, k++) { + if (!SetArrayElement(cx, obj, k, HandleValue::fromMarkedLocation(&items[i]))) + return false; + } + + /* Step 16. */ + double finalLength = double(len) - actualDeleteCount + itemCount; + if (!SetLengthProperty(cx, obj, finalLength)) + return false; + + /* Step 17. */ + if (returnValueIsUsed) + args.rval().setObject(*arr); + + return true; +} + template <JSValueType Type> DenseElementResult ArrayConcatDenseKernel(JSContext* cx, JSObject* obj1, JSObject* obj2, JSObject* result) { uint32_t initlen1 = GetBoxedOrUnboxedInitializedLength<Type>(obj1); MOZ_ASSERT(initlen1 == GetAnyBoxedOrUnboxedArrayLength(obj1)); uint32_t initlen2 = GetBoxedOrUnboxedInitializedLength<Type>(obj2); @@ -2753,17 +3024,17 @@ static const JSFunctionSpec array_method /* Perl-ish methods. */ JS_FN("join", array_join, 1,JSFUN_GENERIC_NATIVE), JS_FN("reverse", array_reverse, 0,JSFUN_GENERIC_NATIVE), JS_FN("sort", array_sort, 1,JSFUN_GENERIC_NATIVE), JS_FN("push", array_push, 1,JSFUN_GENERIC_NATIVE), JS_FN("pop", array_pop, 0,JSFUN_GENERIC_NATIVE), JS_FN("shift", array_shift, 0,JSFUN_GENERIC_NATIVE), JS_FN("unshift", array_unshift, 1,JSFUN_GENERIC_NATIVE), - JS_SELF_HOSTED_FN("splice", "ArraySplice", 2,0), + JS_FN("splice", array_splice, 2,JSFUN_GENERIC_NATIVE), /* Pythonic sequence methods. */ JS_FN("concat", array_concat, 1,JSFUN_GENERIC_NATIVE), JS_FN("slice", array_slice, 2,JSFUN_GENERIC_NATIVE), JS_SELF_HOSTED_FN("lastIndexOf", "ArrayLastIndexOf", 1,0), JS_SELF_HOSTED_FN("indexOf", "ArrayIndexOf", 1,0), JS_SELF_HOSTED_FN("forEach", "ArrayForEach", 1,0),
deleted file mode 100644 --- a/js/src/tests/ecma_6/Array/splice_length_overflow_throws.js +++ /dev/null @@ -1,14 +0,0 @@ -var a = {}; -a[2 ** 53 - 2] = 1; -a.length = 2 ** 53 - 1; - -var exception; -try { - [].splice.call(a, 2 ** 53 - 2, 0, 2, 3, 4, 5); -} catch (e) { - exception = e; -} -reportCompare(a[2 ** 53 - 2], 1); -reportCompare(a.length, 2 ** 53 - 1); -reportCompare(exception instanceof TypeError, true, "Array#splice throws TypeError for length overflows"); -reportCompare(exception.message.indexOf('array length') > -1, true, "Array#splice throws correct error for length overflows");
--- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -24,21 +24,21 @@ namespace js { * versions. If deserialization fails, the data should be invalidated if * possible. * * When you change this, run make_opcode_doc.py and copy the new output into * this wiki page: * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 300; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 301; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 408, +static_assert(JSErr_Limit == 407, "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or " "removed MSG_DEFs from js.msg, you should increment " "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's " "expected JSErr_Limit value."); class XDRBuffer { public: explicit XDRBuffer(JSContext* cx)