Backed out changeset b535cc24f7d0 (bug 1233642)
authorTooru Fujisawa <arai_a@mac.com>
Mon, 28 Mar 2016 06:49:57 +0900
changeset 290674 bc9e586a8f1944d87e53f601d41ea860b272a3bc
parent 290673 705cf9ac47e9ccb246d18b5c8a0b6ce6419fa556
child 290675 203c7e4b8b20e9912f330ac630520508ec1e2f98
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1233642
milestone48.0a1
backs outb535cc24f7d0b2703a43cf43fa371c6087dbb5e4
Backed out changeset b535cc24f7d0 (bug 1233642)
js/src/builtin/Array.js
js/src/jit/BaselineIC.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/InlinableNatives.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/jsarray.h
js/src/tests/ecma_3/Array/regress-322135-02.js
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -864,94 +864,8 @@ function ArrayToString() {
     // Steps 3-4.
     var func = array.join;
 
     // Steps 5-6.
     if (!IsCallable(func))
         return callFunction(std_Object_toString, array);
     return callContentFunction(func, array);
 }
-
-// ES 2016 draft Mar 25, 2016 22.1.3.1.
-// Note: Array.prototype.concat.length is 1.
-function ArrayConcat(arg1) {
-    // Step 1.
-    var O = ToObject(this);
-
-    // Step 2.
-    var A = std_Array(0);
-
-    // Step 3.
-    var n = 0;
-
-    // Step 4 (implicit in |arguments|).
-
-    // Step 5.
-    var i = 0, argsLen = arguments.length;
-
-    // Step 5.a (first element).
-    var E = O;
-
-    var k, len;
-    while (true) {
-        // Steps 5.b-c.
-        // IsArray should be replaced with IsConcatSpreadable (bug 1041586).
-        if (IsArray(E)) {
-            // Step 5.c.ii.
-            len = ToLength(E.length);
-
-            // Step 5.c.iii.
-            if (n + len > MAX_NUMERIC_INDEX)
-                ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
-
-            if (IsPackedArray(E)) {
-                // Step 5.c.i, 5.c.iv, and 5.c.iv.5.
-                for (k = 0; k < len; k++) {
-                    // Steps 5.c.iv.1-3.
-                    // IsPackedArray(E) ensures that |k in E| is always true.
-                    _DefineDataProperty(A, n, E[k]);
-
-                    // Step 5.c.iv.4.
-                    n++;
-                }
-            } else {
-                // Step 5.c.i, 5.c.iv, and 5.c.iv.5.
-                for (k = 0; k < len; k++) {
-                    // Steps 5.c.iv.1-3.
-                    if (k in E)
-                        _DefineDataProperty(A, n, E[k]);
-
-                    // Step 5.c.iv.4.
-                    n++;
-                }
-            }
-        } else {
-            // Step 5.d.i.
-            if (n >= MAX_NUMERIC_INDEX)
-                ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
-
-            // Step 5.d.ii.
-            _DefineDataProperty(A, n, E);
-
-            // Step 5.d.iii.
-            n++;
-        }
-
-        if (i >= argsLen)
-            break;
-        // Step 5.a (subsequent elements).
-        E = arguments[i];
-        i++;
-    }
-
-    // Step 6.
-    A.length = n;
-
-    // Step 7.
-    return A;
-}
-
-function ArrayStaticConcat(arr, arg1) {
-    if (arguments.length < 1)
-        ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.concat');
-    var args = callFunction(std_Array_slice, arguments, 1);
-    return callFunction(std_Function_apply, ArrayConcat, arr, args);
-}
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -5641,17 +5641,17 @@ GetTemplateObjectForNative(JSContext* cx
             // don't end up with a template whose structure might change later.
             res.set(NewFullyAllocatedArrayForCallingAllocationSite(cx, count, TenuredObject));
             if (!res)
                 return false;
             return true;
         }
     }
 
-    if (native == js::array_slice) {
+    if (native == js::array_concat || native == js::array_slice) {
         if (args.thisv().isObject()) {
             JSObject* obj = &args.thisv().toObject();
             if (!obj->isSingleton()) {
                 if (obj->group()->maybePreliminaryObjects()) {
                     *skipAttach = true;
                     return true;
                 }
                 res.set(NewFullyAllocatedArrayTryReuseGroup(cx, &args.thisv().toObject(), 0,
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -7971,16 +7971,65 @@ CodeGenerator::visitArrayPushT(LArrayPus
     ConstantOrRegister value;
     if (lir->value()->isConstant())
         value = ConstantOrRegister(lir->value()->toConstant()->toJSValue());
     else
         value = TypedOrValueRegister(lir->mir()->value()->type(), ToAnyRegister(lir->value()));
     emitArrayPush(lir, lir->mir(), obj, value, elementsTemp, length);
 }
 
+typedef JSObject* (*ArrayConcatDenseFn)(JSContext*, HandleObject, HandleObject, HandleObject);
+static const VMFunction ArrayConcatDenseInfo = FunctionInfo<ArrayConcatDenseFn>(ArrayConcatDense);
+
+void
+CodeGenerator::visitArrayConcat(LArrayConcat* lir)
+{
+    Register lhs = ToRegister(lir->lhs());
+    Register rhs = ToRegister(lir->rhs());
+    Register temp1 = ToRegister(lir->temp1());
+    Register temp2 = ToRegister(lir->temp2());
+
+    // If 'length == initializedLength' for both arrays we try to allocate an object
+    // inline and pass it to the stub. Else, we just pass nullptr and the stub falls
+    // back to a slow path.
+    Label fail, call;
+    if (lir->mir()->unboxedThis()) {
+        masm.load32(Address(lhs, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), temp1);
+        masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), temp1);
+        masm.branch32(Assembler::NotEqual, Address(lhs, UnboxedArrayObject::offsetOfLength()), temp1, &fail);
+    } else {
+        masm.loadPtr(Address(lhs, NativeObject::offsetOfElements()), temp1);
+        masm.load32(Address(temp1, ObjectElements::offsetOfInitializedLength()), temp2);
+        masm.branch32(Assembler::NotEqual, Address(temp1, ObjectElements::offsetOfLength()), temp2, &fail);
+    }
+    if (lir->mir()->unboxedArg()) {
+        masm.load32(Address(rhs, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), temp1);
+        masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), temp1);
+        masm.branch32(Assembler::NotEqual, Address(rhs, UnboxedArrayObject::offsetOfLength()), temp1, &fail);
+    } else {
+        masm.loadPtr(Address(rhs, NativeObject::offsetOfElements()), temp1);
+        masm.load32(Address(temp1, ObjectElements::offsetOfInitializedLength()), temp2);
+        masm.branch32(Assembler::NotEqual, Address(temp1, ObjectElements::offsetOfLength()), temp2, &fail);
+    }
+
+    // Try to allocate an object.
+    masm.createGCObject(temp1, temp2, lir->mir()->templateObj(), lir->mir()->initialHeap(), &fail);
+    masm.jump(&call);
+    {
+        masm.bind(&fail);
+        masm.movePtr(ImmPtr(nullptr), temp1);
+    }
+    masm.bind(&call);
+
+    pushArg(temp1);
+    pushArg(ToRegister(lir->rhs()));
+    pushArg(ToRegister(lir->lhs()));
+    callVM(ArrayConcatDenseInfo, lir);
+}
+
 typedef JSObject* (*ArraySliceDenseFn)(JSContext*, HandleObject, int32_t, int32_t, HandleObject);
 static const VMFunction ArraySliceDenseInfo = FunctionInfo<ArraySliceDenseFn>(array_slice_dense);
 
 void
 CodeGenerator::visitArraySlice(LArraySlice* lir)
 {
     Register object = ToRegister(lir->object());
     Register begin = ToRegister(lir->begin());
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -292,16 +292,17 @@ class CodeGenerator : public CodeGenerat
     void emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, Register obj,
                            Register elementsTemp, Register lengthTemp, TypedOrValueRegister out);
     void visitArrayPopShiftV(LArrayPopShiftV* lir);
     void visitArrayPopShiftT(LArrayPopShiftT* lir);
     void emitArrayPush(LInstruction* lir, const MArrayPush* mir, Register obj,
                        ConstantOrRegister value, Register elementsTemp, Register length);
     void visitArrayPushV(LArrayPushV* lir);
     void visitArrayPushT(LArrayPushT* lir);
+    void visitArrayConcat(LArrayConcat* lir);
     void visitArraySlice(LArraySlice* lir);
     void visitArrayJoin(LArrayJoin* lir);
     void visitLoadUnboxedScalar(LLoadUnboxedScalar* lir);
     void visitLoadTypedArrayElementHole(LLoadTypedArrayElementHole* lir);
     void visitStoreUnboxedScalar(LStoreUnboxedScalar* lir);
     void visitStoreTypedArrayElementHole(LStoreTypedArrayElementHole* lir);
     void visitAtomicIsLockFree(LAtomicIsLockFree* lir);
     void visitGuardSharedTypedArray(LGuardSharedTypedArray* lir);
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -9,16 +9,17 @@
 
 #define INLINABLE_NATIVE_LIST(_)    \
     _(Array)                        \
     _(ArrayIsArray)                 \
     _(ArrayJoin)                    \
     _(ArrayPop)                     \
     _(ArrayShift)                   \
     _(ArrayPush)                    \
+    _(ArrayConcat)                  \
     _(ArraySlice)                   \
     _(ArraySplice)                  \
                                     \
     _(AtomicsCompareExchange)       \
     _(AtomicsExchange)              \
     _(AtomicsLoad)                  \
     _(AtomicsStore)                 \
     _(AtomicsFence)                 \
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -783,16 +783,17 @@ class IonBuilder
     // The known MIR type of getInlineReturnTypeSet.
     MIRType getInlineReturnType();
 
     // Array natives.
     InliningStatus inlineArray(CallInfo& callInfo);
     InliningStatus inlineArrayIsArray(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);
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -3171,16 +3171,31 @@ LIRGenerator::visitArrayPush(MArrayPush*
         define(lir, ins);
         assignSafepoint(lir, ins);
         break;
       }
     }
 }
 
 void
+LIRGenerator::visitArrayConcat(MArrayConcat* ins)
+{
+    MOZ_ASSERT(ins->type() == MIRType_Object);
+    MOZ_ASSERT(ins->lhs()->type() == MIRType_Object);
+    MOZ_ASSERT(ins->rhs()->type() == MIRType_Object);
+
+    LArrayConcat* lir = new(alloc()) LArrayConcat(useFixed(ins->lhs(), CallTempReg1),
+                                                  useFixed(ins->rhs(), CallTempReg2),
+                                                  tempFixed(CallTempReg3),
+                                                  tempFixed(CallTempReg4));
+    defineReturn(lir, ins);
+    assignSafepoint(lir, ins);
+}
+
+void
 LIRGenerator::visitArraySlice(MArraySlice* ins)
 {
     MOZ_ASSERT(ins->type() == MIRType_Object);
     MOZ_ASSERT(ins->object()->type() == MIRType_Object);
     MOZ_ASSERT(ins->begin()->type() == MIRType_Int32);
     MOZ_ASSERT(ins->end()->type() == MIRType_Int32);
 
     LArraySlice* lir = new(alloc()) LArraySlice(useFixed(ins->object(), CallTempReg0),
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -215,16 +215,17 @@ class LIRGenerator : public LIRGenerator
     void visitStoreElement(MStoreElement* ins);
     void visitStoreElementHole(MStoreElementHole* ins);
     void visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull* ins);
     void visitStoreUnboxedString(MStoreUnboxedString* ins);
     void visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative* ins);
     void visitEffectiveAddress(MEffectiveAddress* ins);
     void visitArrayPopShift(MArrayPopShift* ins);
     void visitArrayPush(MArrayPush* ins);
+    void visitArrayConcat(MArrayConcat* ins);
     void visitArraySlice(MArraySlice* ins);
     void visitArrayJoin(MArrayJoin* ins);
     void visitLoadUnboxedScalar(MLoadUnboxedScalar* ins);
     void visitLoadTypedArrayElementHole(MLoadTypedArrayElementHole* ins);
     void visitLoadTypedArrayElementStatic(MLoadTypedArrayElementStatic* ins);
     void visitStoreUnboxedScalar(MStoreUnboxedScalar* ins);
     void visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole* ins);
     void visitClampToUint8(MClampToUint8* ins);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -72,16 +72,18 @@ IonBuilder::inlineNativeCall(CallInfo& c
       case InlinableNative::ArrayJoin:
         return inlineArrayJoin(callInfo);
       case InlinableNative::ArrayPop:
         return inlineArrayPopShift(callInfo, MArrayPopShift::Pop);
       case InlinableNative::ArrayShift:
         return inlineArrayPopShift(callInfo, MArrayPopShift::Shift);
       case InlinableNative::ArrayPush:
         return inlineArrayPush(callInfo);
+      case InlinableNative::ArrayConcat:
+        return inlineArrayConcat(callInfo);
       case InlinableNative::ArraySlice:
         return inlineArraySlice(callInfo);
       case InlinableNative::ArraySplice:
         return inlineArraySplice(callInfo);
 
       // Atomic natives.
       case InlinableNative::AtomicsCompareExchange:
         return inlineAtomicsCompareExchange(callInfo);
@@ -761,16 +763,138 @@ IonBuilder::inlineArrayPush(CallInfo& ca
     current->push(ins);
 
     if (!resumeAfter(ins))
         return InliningStatus_Error;
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
+IonBuilder::inlineArrayConcat(CallInfo& callInfo)
+{
+    if (callInfo.argc() != 1 || callInfo.constructing()) {
+        trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
+        return InliningStatus_NotInlined;
+    }
+
+    MDefinition* thisArg = convertUnboxedObjects(callInfo.thisArg());
+    MDefinition* objArg = convertUnboxedObjects(callInfo.getArg(0));
+
+    // Ensure |this|, argument and result are objects.
+    if (getInlineReturnType() != MIRType_Object)
+        return InliningStatus_NotInlined;
+    if (thisArg->type() != MIRType_Object)
+        return InliningStatus_NotInlined;
+    if (objArg->type() != MIRType_Object)
+        return InliningStatus_NotInlined;
+
+    // |this| and the argument must be dense arrays.
+    TemporaryTypeSet* thisTypes = thisArg->resultTypeSet();
+    TemporaryTypeSet* argTypes = objArg->resultTypeSet();
+    if (!thisTypes || !argTypes)
+        return InliningStatus_NotInlined;
+
+    const Class* thisClasp = thisTypes->getKnownClass(constraints());
+    if (thisClasp != &ArrayObject::class_ && thisClasp != &UnboxedArrayObject::class_)
+        return InliningStatus_NotInlined;
+    bool unboxedThis = (thisClasp == &UnboxedArrayObject::class_);
+    if (thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_SPARSE_INDEXES |
+                                  OBJECT_FLAG_LENGTH_OVERFLOW))
+    {
+        trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags);
+        return InliningStatus_NotInlined;
+    }
+
+    const Class* argClasp = argTypes->getKnownClass(constraints());
+    if (argClasp != &ArrayObject::class_ && argClasp != &UnboxedArrayObject::class_)
+        return InliningStatus_NotInlined;
+    bool unboxedArg = (argClasp == &UnboxedArrayObject::class_);
+    if (argTypes->hasObjectFlags(constraints(), OBJECT_FLAG_SPARSE_INDEXES |
+                                 OBJECT_FLAG_LENGTH_OVERFLOW))
+    {
+        trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags);
+        return InliningStatus_NotInlined;
+    }
+
+    // Watch out for indexed properties on the prototype.
+    if (ArrayPrototypeHasIndexedProperty(this, script())) {
+        trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
+        return InliningStatus_NotInlined;
+    }
+
+    // Require the 'this' types to have a specific type matching the current
+    // global, so we can create the result object inline.
+    if (thisTypes->getObjectCount() != 1)
+        return InliningStatus_NotInlined;
+
+    ObjectGroup* thisGroup = thisTypes->getGroup(0);
+    if (!thisGroup)
+        return InliningStatus_NotInlined;
+    TypeSet::ObjectKey* thisKey = TypeSet::ObjectKey::get(thisGroup);
+    if (thisKey->unknownProperties())
+        return InliningStatus_NotInlined;
+
+    // Don't inline if 'this' is packed and the argument may not be packed
+    // (the result array will reuse the 'this' type).
+    if (!thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_NON_PACKED) &&
+        argTypes->hasObjectFlags(constraints(), OBJECT_FLAG_NON_PACKED))
+    {
+        trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags);
+        return InliningStatus_NotInlined;
+    }
+
+    // Constraints modeling this concat have not been generated by inference,
+    // so check that type information already reflects possible side effects of
+    // this call.
+    HeapTypeSetKey thisElemTypes = thisKey->property(JSID_VOID);
+
+    TemporaryTypeSet* resTypes = getInlineReturnTypeSet();
+    if (!resTypes->hasType(TypeSet::ObjectType(thisKey)))
+        return InliningStatus_NotInlined;
+
+    for (unsigned i = 0; i < argTypes->getObjectCount(); i++) {
+        TypeSet::ObjectKey* key = argTypes->getObject(i);
+        if (!key)
+            continue;
+
+        if (key->unknownProperties())
+            return InliningStatus_NotInlined;
+
+        HeapTypeSetKey elemTypes = key->property(JSID_VOID);
+        if (!elemTypes.knownSubset(constraints(), thisElemTypes))
+            return InliningStatus_NotInlined;
+
+        if (thisGroup->clasp() == &UnboxedArrayObject::class_ &&
+            !CanStoreUnboxedType(alloc(), thisGroup->unboxedLayout().elementType(),
+                                 MIRType_Value, elemTypes.maybeTypes()))
+        {
+            return InliningStatus_NotInlined;
+        }
+    }
+
+    // Inline the call.
+    JSObject* templateObj = inspector->getTemplateObjectForNative(pc, js::array_concat);
+    if (!templateObj || templateObj->group() != thisGroup)
+        return InliningStatus_NotInlined;
+
+    callInfo.setImplicitlyUsedUnchecked();
+
+    MArrayConcat* ins = MArrayConcat::New(alloc(), constraints(), thisArg, objArg,
+                                          templateObj,
+                                          templateObj->group()->initialHeap(constraints()),
+                                          unboxedThis, unboxedArg);
+    current->add(ins);
+    current->push(ins);
+
+    if (!resumeAfter(ins))
+        return InliningStatus_Error;
+    return InliningStatus_Inlined;
+}
+
+IonBuilder::InliningStatus
 IonBuilder::inlineArraySlice(CallInfo& callInfo)
 {
     if (callInfo.constructing()) {
         trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
         return InliningStatus_NotInlined;
     }
 
     MDefinition* obj = convertUnboxedObjects(callInfo.thisArg());
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -9834,16 +9834,76 @@ class MArrayPush
         return AliasSet::Store(AliasSet::ObjectFields |
                                AliasSet::BoxedOrUnboxedElements(unboxedType()));
     }
     void computeRange(TempAllocator& alloc) override;
 
     ALLOW_CLONE(MArrayPush)
 };
 
+// Array.prototype.concat on two dense arrays.
+class MArrayConcat
+  : public MBinaryInstruction,
+    public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1> >::Data
+{
+    CompilerObject templateObj_;
+    gc::InitialHeap initialHeap_;
+    bool unboxedThis_, unboxedArg_;
+
+    MArrayConcat(CompilerConstraintList* constraints, MDefinition* lhs, MDefinition* rhs,
+                 JSObject* templateObj, gc::InitialHeap initialHeap,
+                 bool unboxedThis, bool unboxedArg)
+      : MBinaryInstruction(lhs, rhs),
+        templateObj_(templateObj),
+        initialHeap_(initialHeap),
+        unboxedThis_(unboxedThis),
+        unboxedArg_(unboxedArg)
+    {
+        setResultType(MIRType_Object);
+        setResultTypeSet(MakeSingletonTypeSet(constraints, templateObj));
+    }
+
+  public:
+    INSTRUCTION_HEADER(ArrayConcat)
+
+    static MArrayConcat* New(TempAllocator& alloc, CompilerConstraintList* constraints,
+                             MDefinition* lhs, MDefinition* rhs,
+                             JSObject* templateObj, gc::InitialHeap initialHeap,
+                             bool unboxedThis, bool unboxedArg)
+    {
+        return new(alloc) MArrayConcat(constraints, lhs, rhs, templateObj,
+                                       initialHeap, unboxedThis, unboxedArg);
+    }
+
+    JSObject* templateObj() const {
+        return templateObj_;
+    }
+
+    gc::InitialHeap initialHeap() const {
+        return initialHeap_;
+    }
+
+    bool unboxedThis() const {
+        return unboxedThis_;
+    }
+
+    bool unboxedArg() const {
+        return unboxedArg_;
+    }
+
+    AliasSet getAliasSet() const override {
+        return AliasSet::Store(AliasSet::BoxedOrUnboxedElements(unboxedThis() ? JSVAL_TYPE_INT32 : JSVAL_TYPE_MAGIC) |
+                               AliasSet::BoxedOrUnboxedElements(unboxedArg() ? JSVAL_TYPE_INT32 : JSVAL_TYPE_MAGIC) |
+                               AliasSet::ObjectFields);
+    }
+    bool possiblyCalls() const override {
+        return true;
+    }
+};
+
 // Array.prototype.slice on a dense array.
 class MArraySlice
   : public MTernaryInstruction,
     public Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2>>::Data
 {
     CompilerObject templateObj_;
     gc::InitialHeap initialHeap_;
     JSValueType unboxedType_;
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -205,16 +205,17 @@ namespace jit {
     _(StoreElement)                                                         \
     _(StoreElementHole)                                                     \
     _(StoreUnboxedScalar)                                                   \
     _(StoreUnboxedObjectOrNull)                                             \
     _(StoreUnboxedString)                                                   \
     _(ConvertUnboxedObjectToNative)                                         \
     _(ArrayPopShift)                                                        \
     _(ArrayPush)                                                            \
+    _(ArrayConcat)                                                          \
     _(ArraySlice)                                                           \
     _(ArrayJoin)                                                            \
     _(LoadTypedArrayElementHole)                                            \
     _(LoadTypedArrayElementStatic)                                          \
     _(StoreTypedArrayElementHole)                                           \
     _(StoreTypedArrayElementStatic)                                         \
     _(AtomicIsLockFree)                                                     \
     _(GuardSharedTypedArray)                                                \
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -366,16 +366,35 @@ ArrayShiftDense(JSContext* cx, HandleObj
     // If the result is |undefined|, the array was probably empty and we
     // have to monitor the return value.
     rval.set(argv[0]);
     if (rval.isUndefined())
         TypeScript::Monitor(cx, rval);
     return true;
 }
 
+JSObject*
+ArrayConcatDense(JSContext* cx, HandleObject obj1, HandleObject obj2, HandleObject objRes)
+{
+    if (objRes) {
+        // Fast path if we managed to allocate an object inline.
+        if (!js::array_concat_dense(cx, obj1, obj2, objRes))
+            return nullptr;
+        return objRes;
+    }
+
+    JS::AutoValueArray<3> argv(cx);
+    argv[0].setUndefined();
+    argv[1].setObject(*obj1);
+    argv[2].setObject(*obj2);
+    if (!js::array_concat(cx, 1, argv.begin()))
+        return nullptr;
+    return &argv[0].toObject();
+}
+
 JSString*
 ArrayJoin(JSContext* cx, HandleObject array, HandleString sep)
 {
     JS::AutoValueArray<3> argv(cx);
     argv[0].setUndefined();
     argv[1].setObject(*array);
     argv[2].setString(sep);
     if (!js::array_join(cx, 1, argv.begin()))
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -608,16 +608,17 @@ bool GreaterThan(JSContext* cx, MutableH
 bool GreaterThanOrEqual(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res);
 
 template<bool Equal>
 bool StringsEqual(JSContext* cx, HandleString left, HandleString right, bool* res);
 
 bool ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval);
 bool ArrayPushDense(JSContext* cx, HandleObject obj, HandleValue v, uint32_t* length);
 bool ArrayShiftDense(JSContext* cx, HandleObject obj, MutableHandleValue rval);
+JSObject* ArrayConcatDense(JSContext* cx, HandleObject obj1, HandleObject obj2, HandleObject res);
 JSString* ArrayJoin(JSContext* cx, HandleObject array, HandleString sep);
 
 bool CharCodeAt(JSContext* cx, HandleString str, int32_t index, uint32_t* code);
 JSFlatString* StringFromCharCode(JSContext* cx, int32_t code);
 
 bool SetProperty(JSContext* cx, HandleObject obj, HandlePropertyName name, HandleValue value,
                  bool strict, jsbytecode* pc);
 
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -5405,16 +5405,45 @@ class LArrayPushT : public LInstructionH
     const LAllocation* value() {
         return getOperand(1);
     }
     const LDefinition* temp() {
         return getTemp(0);
     }
 };
 
+class LArrayConcat : public LCallInstructionHelper<1, 2, 2>
+{
+  public:
+    LIR_HEADER(ArrayConcat)
+
+    LArrayConcat(const LAllocation& lhs, const LAllocation& rhs,
+                 const LDefinition& temp1, const LDefinition& temp2) {
+        setOperand(0, lhs);
+        setOperand(1, rhs);
+        setTemp(0, temp1);
+        setTemp(1, temp2);
+    }
+    const MArrayConcat* mir() const {
+        return mir_->toArrayConcat();
+    }
+    const LAllocation* lhs() {
+        return getOperand(0);
+    }
+    const LAllocation* rhs() {
+        return getOperand(1);
+    }
+    const LDefinition* temp1() {
+        return getTemp(0);
+    }
+    const LDefinition* temp2() {
+        return getTemp(1);
+    }
+};
+
 class LArraySlice : public LCallInstructionHelper<1, 3, 2>
 {
   public:
     LIR_HEADER(ArraySlice)
 
     LArraySlice(const LAllocation& obj, const LAllocation& begin, const LAllocation& end,
                 const LDefinition& temp1, const LDefinition& temp2) {
         setOperand(0, obj);
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -260,16 +260,17 @@
     _(StoreElementT)                \
     _(StoreUnboxedScalar)           \
     _(StoreUnboxedPointer)          \
     _(ConvertUnboxedObjectToNative) \
     _(ArrayPopShiftV)               \
     _(ArrayPopShiftT)               \
     _(ArrayPushV)                   \
     _(ArrayPushT)                   \
+    _(ArrayConcat)                  \
     _(ArraySlice)                   \
     _(ArrayJoin)                    \
     _(StoreElementHoleV)            \
     _(StoreElementHoleT)            \
     _(LoadTypedArrayElementHole)    \
     _(LoadTypedArrayElementStatic)  \
     _(StoreTypedArrayElementHole)   \
     _(StoreTypedArrayElementStatic) \
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -469,19 +469,16 @@ MSG_DEF(JSMSG_INVALID_PROTOTYPE,       0
 MSG_DEF(JSMSG_TYPEDOBJECT_BAD_ARGS,    0, JSEXN_TYPEERR, "invalid arguments")
 MSG_DEF(JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
 MSG_DEF(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED, 0, JSEXN_TYPEERR, "handle unattached")
 MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS, 0, JSEXN_RANGEERR, "invalid field descriptor")
 MSG_DEF(JSMSG_TYPEDOBJECT_TOO_BIG,     0, JSEXN_ERR, "Type is too large to allocate")
 MSG_DEF(JSMSG_SIMD_FAILED_CONVERSION,  0, JSEXN_RANGEERR, "SIMD conversion loses precision")
 MSG_DEF(JSMSG_SIMD_TO_NUMBER,          0, JSEXN_TYPEERR, "can't convert SIMD value to number")
 
-// Array
-MSG_DEF(JSMSG_TOO_LONG_ARRAY,         0, JSEXN_TYPEERR, "Too long array")
-
 // Typed array
 MSG_DEF(JSMSG_BAD_INDEX,               0, JSEXN_RANGEERR, "invalid or out-of-range index")
 MSG_DEF(JSMSG_NON_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected ArrayBuffer, but species constructor returned non-ArrayBuffer")
 MSG_DEF(JSMSG_SAME_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different ArrayBuffer, but species constructor returned same ArrayBuffer")
 MSG_DEF(JSMSG_SHORT_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected ArrayBuffer with at least {0} bytes, but species constructor returns ArrayBuffer with {1} bytes")
 MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS,    0, JSEXN_TYPEERR, "invalid arguments")
 MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG,1, JSEXN_RANGEERR, "argument {0} must be >= 0")
 MSG_DEF(JSMSG_TYPED_ARRAY_DETACHED,    0, JSEXN_TYPEERR, "attempting to access detached ArrayBuffer")
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2583,16 +2583,194 @@ js::array_splice_impl(JSContext* cx, uns
 
     /* Step 17. */
     if (returnValueIsUsed)
         args.rval().setObject(*arr);
 
     return true;
 }
 
+template <JSValueType TypeOne, JSValueType TypeTwo>
+DenseElementResult
+ArrayConcatDenseKernel(JSContext* cx, JSObject* obj1, JSObject* obj2, JSObject* result)
+{
+    uint32_t initlen1 = GetBoxedOrUnboxedInitializedLength<TypeOne>(obj1);
+    MOZ_ASSERT(initlen1 == GetAnyBoxedOrUnboxedArrayLength(obj1));
+
+    uint32_t initlen2 = GetBoxedOrUnboxedInitializedLength<TypeTwo>(obj2);
+    MOZ_ASSERT(initlen2 == GetAnyBoxedOrUnboxedArrayLength(obj2));
+
+    /* No overflow here due to nelements limit. */
+    uint32_t len = initlen1 + initlen2;
+
+    MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength<TypeOne>(result) == 0);
+
+    DenseElementResult rv = EnsureBoxedOrUnboxedDenseElements<TypeOne>(cx, result, len);
+    if (rv != DenseElementResult::Success)
+        return rv;
+
+    CopyBoxedOrUnboxedDenseElements<TypeOne, TypeOne>(cx, result, obj1, 0, 0, initlen1);
+    CopyBoxedOrUnboxedDenseElements<TypeOne, TypeTwo>(cx, result, obj2, initlen1, 0, initlen2);
+
+    SetAnyBoxedOrUnboxedArrayLength(cx, result, len);
+    return DenseElementResult::Success;
+}
+
+DefineBoxedOrUnboxedFunctorPair4(ArrayConcatDenseKernel,
+                                 JSContext*, JSObject*, JSObject*, JSObject*);
+
+bool
+js::array_concat_dense(JSContext* cx, HandleObject obj1, HandleObject obj2,
+                       HandleObject result)
+{
+    ArrayConcatDenseKernelFunctor functor(cx, obj1, obj2, result);
+    DenseElementResult rv = CallBoxedOrUnboxedSpecialization(functor, obj1, obj2);
+    MOZ_ASSERT(rv != DenseElementResult::Incomplete);
+    return rv == DenseElementResult::Success;
+}
+
+/*
+ * Python-esque sequence operations.
+ */
+bool
+js::array_concat(JSContext* cx, unsigned argc, Value* vp)
+{
+    AutoSPSEntry pseudoFrame(cx->runtime(), "Array.prototype.concat");
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    /* Treat our |this| object as the first argument; see ECMA 15.4.4.4. */
+    Value* p = args.array() - 1;
+
+    /* Create a new Array object and root it using *vp. */
+    RootedObject aobj(cx, ToObject(cx, args.thisv()));
+    if (!aobj)
+        return false;
+
+    RootedObject narr(cx);
+    uint32_t length;
+    bool isArray;
+    if (!IsArray(cx, aobj, &isArray))
+        return false;
+    if (isArray && !ObjectMayHaveExtraIndexedProperties(aobj)) {
+        if (!GetLengthProperty(cx, aobj, &length))
+            return false;
+
+        size_t initlen = GetAnyBoxedOrUnboxedInitializedLength(aobj);
+        narr = NewFullyAllocatedArrayTryReuseGroup(cx, aobj, initlen);
+        if (!narr)
+            return false;
+        CopyAnyBoxedOrUnboxedDenseElements(cx, narr, aobj, 0, 0, initlen);
+        SetAnyBoxedOrUnboxedArrayLength(cx, narr, length);
+
+        args.rval().setObject(*narr);
+        if (argc == 0)
+            return true;
+        argc--;
+        p++;
+
+        if (length == initlen) {
+            while (argc) {
+                HandleValue v = HandleValue::fromMarkedLocation(p);
+                if (!v.isObject())
+                    break;
+                RootedObject obj(cx, &v.toObject());
+
+                // This should be IsConcatSpreadable
+                bool isArray;
+                if (!IsArray(cx, obj, &isArray))
+                    return false;
+                if (!isArray || ObjectMayHaveExtraIndexedProperties(obj))
+                    break;
+
+                uint32_t argLength;
+                if (!GetLengthProperty(cx, obj, &argLength))
+                    return false;
+
+                initlen = GetAnyBoxedOrUnboxedInitializedLength(obj);
+                if (argLength != initlen)
+                    break;
+
+                DenseElementResult result =
+                    EnsureAnyBoxedOrUnboxedDenseElements(cx, narr, length + argLength);
+                if (result == DenseElementResult::Failure)
+                    return false;
+                if (result == DenseElementResult::Incomplete)
+                    break;
+
+                SetAnyBoxedOrUnboxedInitializedLength(cx, narr, length + argLength);
+
+                bool success = true;
+                for (size_t i = 0; i < initlen; i++) {
+                    Value v = GetAnyBoxedOrUnboxedDenseElement(obj, i);
+                    if (!InitAnyBoxedOrUnboxedDenseElement(cx, narr, length + i, v)) {
+                        success = false;
+                        break;
+                    }
+                }
+                if (!success) {
+                    SetAnyBoxedOrUnboxedInitializedLength(cx, narr, length);
+                    break;
+                }
+
+                length += argLength;
+                SetAnyBoxedOrUnboxedArrayLength(cx, narr, length);
+
+                argc--;
+                p++;
+            }
+        }
+    } else {
+        narr = NewFullyAllocatedArrayTryReuseGroup(cx, aobj, 0);
+        if (!narr)
+            return false;
+        args.rval().setObject(*narr);
+        length = 0;
+    }
+
+    /* Loop over [0, argc] to concat args into narr, expanding all Arrays. */
+    for (unsigned i = 0; i <= argc; i++) {
+        if (!CheckForInterrupt(cx))
+            return false;
+        HandleValue v = HandleValue::fromMarkedLocation(&p[i]);
+        if (v.isObject()) {
+            RootedObject obj(cx, &v.toObject());
+            // This should be IsConcatSpreadable
+            bool isArray;
+            if (!IsArray(cx, obj, &isArray))
+                return false;
+            if (isArray) {
+                uint32_t alength;
+                if (!GetLengthProperty(cx, obj, &alength))
+                    return false;
+                RootedValue tmp(cx);
+                for (uint32_t slot = 0; slot < alength; slot++) {
+                    bool hole;
+                    if (!CheckForInterrupt(cx) || !GetElement(cx, obj, slot, &hole, &tmp))
+                        return false;
+
+                    /*
+                     * Per ECMA 262, 15.4.4.4, step 9, ignore nonexistent
+                     * properties.
+                     */
+                    if (!hole && !SetArrayElement(cx, narr, length + slot, tmp))
+                        return false;
+                }
+                length += alength;
+                continue;
+            }
+        }
+
+        if (!SetArrayElement(cx, narr, length, v))
+            return false;
+        length++;
+    }
+
+    return SetLengthProperty(cx, narr, length);
+}
+
 struct SortComparatorIndexes
 {
     bool operator()(uint32_t a, uint32_t b, bool* lessOrEqualp) {
         *lessOrEqualp = (a <= b);
         return true;
     }
 };
 
@@ -2964,17 +3142,17 @@ static const JSFunctionSpec array_method
     JS_FN("sort",               array_sort,         1,JSFUN_GENERIC_NATIVE),
     JS_INLINABLE_FN("push",     array_push,         1,JSFUN_GENERIC_NATIVE, ArrayPush),
     JS_INLINABLE_FN("pop",      array_pop,          0,JSFUN_GENERIC_NATIVE, ArrayPop),
     JS_INLINABLE_FN("shift",    array_shift,        0,JSFUN_GENERIC_NATIVE, ArrayShift),
     JS_FN("unshift",            array_unshift,      1,JSFUN_GENERIC_NATIVE),
     JS_INLINABLE_FN("splice",   array_splice,       2,JSFUN_GENERIC_NATIVE, ArraySplice),
 
     /* Pythonic sequence methods. */
-    JS_SELF_HOSTED_FN("concat",      "ArrayConcat",      1,0),
+    JS_INLINABLE_FN("concat",   array_concat,       1,JSFUN_GENERIC_NATIVE, ArrayConcat),
     JS_INLINABLE_FN("slice",    array_slice,        2,JSFUN_GENERIC_NATIVE, ArraySlice),
 
     JS_SELF_HOSTED_FN("lastIndexOf", "ArrayLastIndexOf", 1,0),
     JS_SELF_HOSTED_FN("indexOf",     "ArrayIndexOf",     1,0),
     JS_SELF_HOSTED_FN("forEach",     "ArrayForEach",     1,0),
     JS_SELF_HOSTED_FN("map",         "ArrayMap",         1,0),
     JS_SELF_HOSTED_FN("filter",      "ArrayFilter",      1,0),
     JS_SELF_HOSTED_FN("reduce",      "ArrayReduce",      1,0),
@@ -2996,17 +3174,16 @@ static const JSFunctionSpec array_method
 
     /* ES7 additions */
     JS_SELF_HOSTED_FN("includes",    "ArrayIncludes",    2,0),
     JS_FS_END
 };
 
 static const JSFunctionSpec array_static_methods[] = {
     JS_INLINABLE_FN("isArray",       array_isArray,        1,0, ArrayIsArray),
-    JS_SELF_HOSTED_FN("concat",      "ArrayStaticConcat", 2,0),
     JS_SELF_HOSTED_FN("lastIndexOf", "ArrayStaticLastIndexOf", 2,0),
     JS_SELF_HOSTED_FN("indexOf",     "ArrayStaticIndexOf", 2,0),
     JS_SELF_HOSTED_FN("forEach",     "ArrayStaticForEach", 2,0),
     JS_SELF_HOSTED_FN("map",         "ArrayStaticMap",   2,0),
     JS_SELF_HOSTED_FN("filter",      "ArrayStaticFilter", 2,0),
     JS_SELF_HOSTED_FN("every",       "ArrayStaticEvery", 2,0),
     JS_SELF_HOSTED_FN("some",        "ArrayStaticSome",  2,0),
     JS_SELF_HOSTED_FN("reduce",      "ArrayStaticReduce", 2,0),
--- a/js/src/jsarray.h
+++ b/js/src/jsarray.h
@@ -162,16 +162,23 @@ array_push(JSContext* cx, unsigned argc,
 
 extern bool
 array_pop(JSContext* cx, unsigned argc, js::Value* vp);
 
 extern bool
 array_splice_impl(JSContext* cx, unsigned argc, js::Value* vp, bool pop);
 
 extern bool
+array_concat(JSContext* cx, unsigned argc, js::Value* vp);
+
+extern bool
+array_concat_dense(JSContext* cx, HandleObject arr1, HandleObject arr2,
+                   HandleObject result);
+
+extern bool
 array_join(JSContext* cx, unsigned argc, js::Value* vp);
 
 extern void
 ArrayShiftMoveElements(JSObject* obj);
 
 extern bool
 array_shift(JSContext* cx, unsigned argc, js::Value* vp);
 
--- a/js/src/tests/ecma_3/Array/regress-322135-02.js
+++ b/js/src/tests/ecma_3/Array/regress-322135-02.js
@@ -1,9 +1,8 @@
-// |reftest| skip -- slow (bug 1234947)
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 322135;
 var summary = 'Array.prototype.concat on Array with length 2^32-1';