Bug 1195030 - Backout of bug 890329 for breaking email reading in gmail. r=bustage a=Ms2ger
authorTill Schneidereit <till@tillschneidereit.net>
Sat, 15 Aug 2015 23:58:24 +0200
changeset 257904 0876695d1abdeb363a780bda8b6cc84f20ba51c9
parent 257903 2ddfc9180971a212127a370911ba2e8ef36a6605
child 257916 028fa40d0ab80b2224b0f9d3eb8daab052f700bd
push id29235
push userMs2ger@gmail.com
push dateSun, 16 Aug 2015 14:23:02 +0000
treeherdermozilla-central@0876695d1abd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbustage, Ms2ger
bugs1195030, 890329
milestone43.0a1
first release with
nightly linux32
0876695d1abd / 43.0a1 / 20150816072437 / files
nightly linux64
0876695d1abd / 43.0a1 / 20150816072437 / files
nightly mac
0876695d1abd / 43.0a1 / 20150816072437 / files
nightly win32
0876695d1abd / 43.0a1 / 20150816072437 / files
nightly win64
0876695d1abd / 43.0a1 / 20150816091433 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1195030 - Backout of bug 890329 for breaking email reading in gmail. r=bustage a=Ms2ger
js/src/builtin/Array.js
js/src/builtin/Number.js
js/src/builtin/Utilities.js
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/IonBuilder.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jit/shared/LIR-shared.h
js/src/jit/shared/LOpcodes-shared.h
js/src/js.msg
js/src/jsarray.cpp
js/src/tests/ecma_6/Array/splice_length_overflow_throws.js
js/src/vm/Xdr.h
--- 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)