Bug 1195030 - Backout of bug 890329 for breaking email reading in gmail. r=bustage
authorTill Schneidereit <till@tillschneidereit.net>
Sat, 15 Aug 2015 23:58:24 +0200
changeset 257957 9b34691fd5cda4be9ad9674a0883f51de2198d69
parent 257956 ca20318c36272e2060bd94894a82807cc6db216b
child 257958 f9a0ce79b27e3b0bc7a2cdf62f40e77456455754
push id29238
push userryanvm@gmail.com
push dateMon, 17 Aug 2015 13:06:57 +0000
treeherdermozilla-central@a6eeb28458fd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbustage
bugs1195030, 890329
milestone43.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
Bug 1195030 - Backout of bug 890329 for breaking email reading in gmail. r=bustage
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)