Bug 1382837 - Optimize Array.join in baseline for empty and single-item arrays. r=tcampbell
authorKannan Vijayan <kvijayan@mozilla.com>
Wed, 09 Aug 2017 16:27:58 -0400
changeset 373879 5c9b13e813391a433976999f59a5090e66311697
parent 373878 440697b45d73109f868c88fbf39df32ee29c6d47
child 373880 903db0ac80c6a27b61b68e7df699782acc7f8842
push id32311
push userkwierso@gmail.com
push dateFri, 11 Aug 2017 01:14:57 +0000
treeherdermozilla-central@253a8560dc34 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1382837
milestone57.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 1382837 - Optimize Array.join in baseline for empty and single-item arrays. r=tcampbell
js/src/jit/BaselineIC.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/CacheIRCompiler.cpp
js/src/jit/CacheIRCompiler.h
js/src/jit/MacroAssembler.h
js/src/jit/arm/MacroAssembler-arm-inl.h
js/src/jit/arm64/MacroAssembler-arm64-inl.h
js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h
js/src/jit/x86/MacroAssembler-x86.h
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -2526,17 +2526,17 @@ DoCallFallback(JSContext* cx, BaselineFr
         stub->discardStubs(cx);
 
     bool canAttachStub = stub->state().canAttachStub();
     bool handled = false;
 
     // Only bother to try optimizing JSOP_CALL with CacheIR if the chain is still
     // allowed to attach stubs.
     if (canAttachStub) {
-        CallIRGenerator gen(cx, script, pc, stub, stub->state().mode(), argc,
+        CallIRGenerator gen(cx, script, pc, op, stub, stub->state().mode(), argc,
                             callee, callArgs.thisv(),
                             HandleValueArray::fromMarkedLocation(argc, vp+2));
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         gen.cacheIRStubKind(),
                                                         ICStubEngine::Baseline,
                                                         script, stub, &handled);
 
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -3913,20 +3913,21 @@ GetIteratorIRGenerator::tryAttachNativeI
     ObjOperandId iterId =
         writer.guardAndGetIterator(objId, iterobj, &cx_->compartment()->enumerators);
     writer.loadObjectResult(iterId);
     writer.returnFromIC();
 
     return true;
 }
 
-CallIRGenerator::CallIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
+CallIRGenerator::CallIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, JSOp op,
                                  ICCall_Fallback* stub, ICState::Mode mode, uint32_t argc,
                                  HandleValue callee, HandleValue thisval, HandleValueArray args)
   : IRGenerator(cx, script, pc, CacheKind::Call, mode),
+    op_(op),
     argc_(argc),
     callee_(callee),
     thisval_(thisval),
     args_(args),
     typeCheckInfo_(cx, /* needsTypeBarrier = */ true),
     cacheIRStubKind_(BaselineCacheIRStubKind::Regular)
 { }
 
@@ -4069,40 +4070,124 @@ CallIRGenerator::tryAttachArrayPush()
 
     cacheIRStubKind_ = BaselineCacheIRStubKind::Updated;
 
     trackAttached("ArrayPush");
     return true;
 }
 
 bool
+CallIRGenerator::tryAttachArrayJoin()
+{
+    // Only handle argc <= 1.
+    if (argc_ > 1)
+        return false;
+
+    // Only optimize on obj.join(...);
+    if (!thisval_.isObject())
+        return false;
+
+    // Where |obj| is a native array.
+    RootedObject thisobj(cx_, &thisval_.toObject());
+    if (!thisobj->is<ArrayObject>())
+        return false;
+
+    RootedArrayObject thisarray(cx_, &thisobj->as<ArrayObject>());
+
+    // And the array is of length 0 or 1.
+    if (thisarray->length() > 1)
+        return false;
+
+    // And the array is packed.
+    if (thisarray->getDenseInitializedLength() != thisarray->length())
+        return false;
+
+    // We don't need to worry about indexed properties because we can perform
+    // hole check manually.
+
+    // Generate code.
+    AutoAssertNoPendingException aanpe(cx_);
+    Int32OperandId argcId(writer.setInputOperandId(0));
+
+    // if 0 arguments:
+    //  1: Callee
+    //  0: ThisValue <-- Top of stack.
+    //
+    // if 1 argument:
+    //  2: Callee
+    //  1: ThisValue
+    //  0: Arg0 [optional] <-- Top of stack.
+
+    // Guard callee is the |js::array_join| native function.
+    uint32_t calleeIndex = (argc_ == 0) ? 1 : 2;
+    ValOperandId calleeValId = writer.loadStackValue(calleeIndex);
+    ObjOperandId calleeObjId = writer.guardIsObject(calleeValId);
+    writer.guardIsNativeFunction(calleeObjId, js::array_join);
+
+    if (argc_ == 1) {
+        // If argcount is 1, guard that the argument is a string.
+        ValOperandId argValId = writer.loadStackValue(0);
+        writer.guardIsString(argValId);
+    }
+
+    // Guard this is an array object.
+    uint32_t thisIndex = (argc_ == 0) ? 0 : 1;
+    ValOperandId thisValId = writer.loadStackValue(thisIndex);
+    ObjOperandId thisObjId = writer.guardIsObject(thisValId);
+    writer.guardClass(thisObjId, GuardClassKind::Array);
+
+    // Do the join.
+    writer.arrayJoinResult(thisObjId);
+
+    writer.returnFromIC();
+
+    // The result of this stub does not need to be monitored because it will
+    // always return a string.  We will add String to the stack typeset when
+    // attaching this stub.
+
+    // Set the stub kind to Regular 
+    cacheIRStubKind_ = BaselineCacheIRStubKind::Regular;
+
+    trackAttached("ArrayJoin");
+    return true;
+}
+
+bool
 CallIRGenerator::tryAttachStub()
 {
+    // Only optimize on JSOP_CALL or JSOP_CALL_IGNORES_RV.  No fancy business for now.
+    if ((op_ != JSOP_CALL) && (op_ != JSOP_CALL_IGNORES_RV))
+        return false;
+
     // Only optimize when the mode is Specialized.
     if (mode_ != ICState::Mode::Specialized)
         return false;
 
     // Ensure callee is a function.
     if (!callee_.isObject() || !callee_.toObject().is<JSFunction>())
         return false;
 
     RootedFunction calleeFunc(cx_, &callee_.toObject().as<JSFunction>());
 
     // Check for native-function optimizations.
     if (calleeFunc->isNative()) {
-
         if (calleeFunc->native() == js::intrinsic_StringSplitString) {
             if (tryAttachStringSplit())
                 return true;
         }
 
         if (calleeFunc->native() == js::array_push) {
             if (tryAttachArrayPush())
                 return true;
         }
+
+        if (calleeFunc->native() == js::array_join) {
+            if (tryAttachArrayJoin())
+                return true;
+        }
     }
 
     return false;
 }
 
 void
 CallIRGenerator::trackAttached(const char* name)
 {
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -217,16 +217,17 @@ extern const char* CacheKindNames[];
     _(AddAndStoreDynamicSlot)             \
     _(AllocateAndStoreDynamicSlot)        \
     _(StoreTypedObjectReferenceProperty)  \
     _(StoreTypedObjectScalarProperty)     \
     _(StoreUnboxedProperty)               \
     _(StoreDenseElement)                  \
     _(StoreDenseElementHole)              \
     _(ArrayPush)                          \
+    _(ArrayJoinResult)                    \
     _(StoreTypedElement)                  \
     _(StoreUnboxedArrayElement)           \
     _(StoreUnboxedArrayElementHole)       \
     _(CallNativeSetter)                   \
     _(CallScriptedSetter)                 \
     _(CallSetArrayLength)                 \
     _(CallProxySet)                       \
     _(CallProxySetByValue)                \
@@ -575,20 +576,23 @@ class MOZ_RAII CacheIRWriter : public JS
     void guardSpecificAtom(StringOperandId str, JSAtom* expected) {
         writeOpWithOperandId(CacheOp::GuardSpecificAtom, str);
         addStubField(uintptr_t(expected), StubField::Type::String);
     }
     void guardSpecificSymbol(SymbolOperandId sym, JS::Symbol* expected) {
         writeOpWithOperandId(CacheOp::GuardSpecificSymbol, sym);
         addStubField(uintptr_t(expected), StubField::Type::Symbol);
     }
-    void guardSpecificInt32Immediate(Int32OperandId operand, int32_t expected) {
+    void guardSpecificInt32Immediate(Int32OperandId operand, int32_t expected,
+                                     Assembler::Condition cond = Assembler::Equal)
+    {
         writeOp(CacheOp::GuardSpecificInt32Immediate);
         writeOperandId(operand);
         writeInt32Immediate(expected);
+        buffer_.writeByte(uint32_t(cond));
     }
     void guardMagicValue(ValOperandId val, JSWhyMagic magic) {
         writeOpWithOperandId(CacheOp::GuardMagicValue, val);
         buffer_.writeByte(uint32_t(magic));
     }
     void guardCompartment(ObjOperandId obj, JSObject* global, JSCompartment* compartment) {
         writeOpWithOperandId(CacheOp::GuardCompartment, obj);
         // Add a reference to the compartment's global to keep it alive.
@@ -823,16 +827,19 @@ class MOZ_RAII CacheIRWriter : public JS
         writeOperandId(index);
         writeOperandId(rhs);
         buffer_.writeByte(handleAdd);
     }
     void arrayPush(ObjOperandId obj, ValOperandId rhs) {
         writeOpWithOperandId(CacheOp::ArrayPush, obj);
         writeOperandId(rhs);
     }
+    void arrayJoinResult(ObjOperandId obj) {
+        writeOpWithOperandId(CacheOp::ArrayJoinResult, obj);
+    }
     void callScriptedSetter(ObjOperandId obj, JSFunction* setter, ValOperandId rhs) {
         writeOpWithOperandId(CacheOp::CallScriptedSetter, obj);
         addStubField(uintptr_t(setter), StubField::Type::JSObject);
         writeOperandId(rhs);
     }
     void callNativeSetter(ObjOperandId obj, JSFunction* setter, ValOperandId rhs) {
         writeOpWithOperandId(CacheOp::CallNativeSetter, obj);
         addStubField(uintptr_t(setter), StubField::Type::JSObject);
@@ -1081,16 +1088,19 @@ class MOZ_RAII CacheIRReader
     int32_t int32Immediate() { return buffer_.readSigned(); }
     uint32_t uint32Immediate() { return buffer_.readUnsigned(); }
     void* pointer() { return buffer_.readRawPointer(); }
 
     ReferenceTypeDescr::Type referenceTypeDescrType() {
         return ReferenceTypeDescr::Type(buffer_.readByte());
     }
 
+    uint8_t readByte() {
+        return buffer_.readByte();
+    }
     bool readBool() {
         uint8_t b = buffer_.readByte();
         MOZ_ASSERT(b <= 1);
         return bool(b);
     }
 
     bool matchOp(CacheOp op) {
         const uint8_t* pos = buffer_.currentPosition();
@@ -1496,32 +1506,34 @@ class MOZ_RAII GetIteratorIRGenerator : 
                            HandleValue value);
 
     bool tryAttachStub();
 };
 
 class MOZ_RAII CallIRGenerator : public IRGenerator
 {
   private:
+    JSOp op_;
     uint32_t argc_;
     HandleValue callee_;
     HandleValue thisval_;
     HandleValueArray args_;
     PropertyTypeCheckInfo typeCheckInfo_;
     BaselineCacheIRStubKind cacheIRStubKind_;
 
     bool tryAttachStringSplit();
     bool tryAttachArrayPush();
+    bool tryAttachArrayJoin();
 
     void trackAttached(const char* name);
     void trackNotAttached();
 
   public:
     CallIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
-                    ICCall_Fallback* stub, ICState::Mode mode,
+                    JSOp op, ICCall_Fallback* stub, ICState::Mode mode,
                     uint32_t argc, HandleValue callee, HandleValue thisval,
                     HandleValueArray args);
 
     bool tryAttachStub();
 
     BaselineCacheIRStubKind cacheIRStubKind() const {
         return cacheIRStubKind_;
     }
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -1450,22 +1450,23 @@ CacheIRCompiler::emitGuardNotDOMProxy()
     return true;
 }
 
 bool
 CacheIRCompiler::emitGuardSpecificInt32Immediate()
 {
     Register reg = allocator.useRegister(masm, reader.int32OperandId());
     int32_t ival = reader.int32Immediate();
+    Assembler::Condition cond = (Assembler::Condition) reader.readByte();
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
-    masm.branch32(Assembler::NotEqual, reg, Imm32(ival), failure->label());
+    masm.branch32(Assembler::InvertCondition(cond), reg, Imm32(ival), failure->label());
     return true;
 }
 
 bool
 CacheIRCompiler::emitGuardMagicValue()
 {
     ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
     JSWhyMagic magic = reader.whyMagic();
@@ -2063,16 +2064,64 @@ CacheIRCompiler::emitLoadUnboxedArrayEle
     // Load value.
     size_t width = UnboxedTypeSize(elementType);
     BaseIndex addr(scratch, index, ScaleFromElemWidth(width));
     masm.loadUnboxedProperty(addr, elementType, output);
     return true;
 }
 
 bool
+CacheIRCompiler::emitArrayJoinResult()
+{
+    ObjOperandId objId = reader.objOperandId();
+
+    AutoOutputRegister output(*this);
+    Register obj = allocator.useRegister(masm, objId);
+    AutoScratchRegister scratch(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    // Load obj->elements in scratch.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+    Address lengthAddr(scratch, ObjectElements::offsetOfLength());
+
+    // If array length is 0, return empty string.
+    Label finished;
+
+    {
+        Label arrayNotEmpty;
+        masm.branch32(Assembler::NotEqual, lengthAddr, Imm32(0), &arrayNotEmpty);
+        masm.movePtr(ImmGCPtr(cx_->names().empty), scratch);
+        masm.tagValue(JSVAL_TYPE_STRING, scratch, output.valueReg());
+        masm.jump(&finished);
+        masm.bind(&arrayNotEmpty);
+    }
+
+    // Otherwise, handle array length 1 case.
+    masm.branch32(Assembler::NotEqual, lengthAddr, Imm32(1), failure->label());
+
+    // But only if initializedLength is also 1.
+    Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
+    masm.branch32(Assembler::NotEqual, initLength, Imm32(1), failure->label());
+
+    // And only if elem0 is a string.
+    Address elementAddr(scratch, 0);
+    masm.branchTestString(Assembler::NonZero, elementAddr, failure->label());
+
+    // Store the value.
+    masm.loadValue(elementAddr, output.valueReg());
+
+    masm.bind(&finished);
+
+    return true;
+}
+
+bool
 CacheIRCompiler::emitLoadTypedElementResult()
 {
     AutoOutputRegister output(*this);
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     Register index = allocator.useRegister(masm, reader.int32OperandId());
     TypedThingLayout layout = reader.typedThingLayout();
     Scalar::Type type = reader.scalarType();
 
--- a/js/src/jit/CacheIRCompiler.h
+++ b/js/src/jit/CacheIRCompiler.h
@@ -53,16 +53,17 @@ namespace jit {
     _(LoadDenseElementHoleExistsResult)   \
     _(LoadUnboxedArrayElementResult)      \
     _(LoadTypedElementResult)             \
     _(LoadObjectResult)                   \
     _(LoadTypeOfObjectResult)             \
     _(CompareStringResult)                \
     _(CompareObjectResult)                \
     _(CompareSymbolResult)                \
+    _(ArrayJoinResult)                    \
     _(CallPrintString)                    \
     _(Breakpoint)                         \
     _(MegamorphicLoadSlotByValueResult)   \
     _(MegamorphicHasOwnResult)            \
     _(WrapResult)
 
 // Represents a Value on the Baseline frame's expression stack. Slot 0 is the
 // value on top of the stack (the most recently pushed value), slot 1 is the
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1210,16 +1210,17 @@ class MacroAssembler : public MacroAssem
     inline void branchTestNumber(Condition cond, const ValueOperand& value, Label* label)
         DEFINED_ON(arm, arm64, mips32, mips64, x86_shared);
 
     inline void branchTestBoolean(Condition cond, const Address& address, Label* label) PER_SHARED_ARCH;
     inline void branchTestBoolean(Condition cond, const BaseIndex& address, Label* label) PER_SHARED_ARCH;
     inline void branchTestBoolean(Condition cond, const ValueOperand& value, Label* label)
         DEFINED_ON(arm, arm64, mips32, mips64, x86_shared);
 
+    inline void branchTestString(Condition cond, const Address& address, Label* label) PER_SHARED_ARCH;
     inline void branchTestString(Condition cond, const BaseIndex& address, Label* label) PER_SHARED_ARCH;
     inline void branchTestString(Condition cond, const ValueOperand& value, Label* label)
         DEFINED_ON(arm, arm64, mips32, mips64, x86_shared);
 
     inline void branchTestSymbol(Condition cond, const BaseIndex& address, Label* label) PER_SHARED_ARCH;
     inline void branchTestSymbol(Condition cond, const ValueOperand& value, Label* label)
         DEFINED_ON(arm, arm64, mips32, mips64, x86_shared);
 
--- a/js/src/jit/arm/MacroAssembler-arm-inl.h
+++ b/js/src/jit/arm/MacroAssembler-arm-inl.h
@@ -1856,16 +1856,22 @@ MacroAssembler::branchTestBooleanTruthy(
 
 void
 MacroAssembler::branchTestString(Condition cond, Register tag, Label* label)
 {
     branchTestStringImpl(cond, tag, label);
 }
 
 void
+MacroAssembler::branchTestString(Condition cond, const Address& address, Label* label)
+{
+    branchTestStringImpl(cond, address, label);
+}
+
+void
 MacroAssembler::branchTestString(Condition cond, const BaseIndex& address, Label* label)
 {
     branchTestStringImpl(cond, address, label);
 }
 
 void
 MacroAssembler::branchTestString(Condition cond, const ValueOperand& value, Label* label)
 {
--- a/js/src/jit/arm64/MacroAssembler-arm64-inl.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64-inl.h
@@ -1454,16 +1454,22 @@ MacroAssembler::branchTestBooleanTruthy(
 
 void
 MacroAssembler::branchTestString(Condition cond, Register tag, Label* label)
 {
     branchTestStringImpl(cond, tag, label);
 }
 
 void
+MacroAssembler::branchTestString(Condition cond, const Address& address, Label* label)
+{
+    branchTestStringImpl(cond, address, label);
+}
+
+void
 MacroAssembler::branchTestString(Condition cond, const BaseIndex& address, Label* label)
 {
     branchTestStringImpl(cond, address, label);
 }
 
 void
 MacroAssembler::branchTestString(Condition cond, const ValueOperand& value, Label* label)
 {
--- a/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
+++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
@@ -863,16 +863,24 @@ MacroAssembler::branchTestBoolean(Condit
 void
 MacroAssembler::branchTestString(Condition cond, Register tag, Label* label)
 {
     MOZ_ASSERT(cond == Equal || cond == NotEqual);
     ma_b(tag, ImmTag(JSVAL_TAG_STRING), label, cond);
 }
 
 void
+MacroAssembler::branchTestString(Condition cond, const Address& address, Label* label)
+{
+    SecondScratchRegisterScope scratch2(*this);
+    extractTag(address, scratch2);
+    branchTestString(cond, scratch2, label);
+}
+
+void
 MacroAssembler::branchTestString(Condition cond, const BaseIndex& address, Label* label)
 {
     SecondScratchRegisterScope scratch2(*this);
     extractTag(address, scratch2);
     branchTestString(cond, scratch2, label);
 }
 
 void
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h
+++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h
@@ -894,16 +894,22 @@ MacroAssembler::branchTestBooleanImpl(Co
 
 void
 MacroAssembler::branchTestString(Condition cond, Register tag, Label* label)
 {
     branchTestStringImpl(cond, tag, label);
 }
 
 void
+MacroAssembler::branchTestString(Condition cond, const Address& address, Label* label)
+{
+    branchTestStringImpl(cond, address, label);
+}
+
+void
 MacroAssembler::branchTestString(Condition cond, const BaseIndex& address, Label* label)
 {
     branchTestStringImpl(cond, address, label);
 }
 
 void
 MacroAssembler::branchTestString(Condition cond, const ValueOperand& value, Label* label)
 {
--- a/js/src/jit/x86/MacroAssembler-x86.h
+++ b/js/src/jit/x86/MacroAssembler-x86.h
@@ -409,16 +409,21 @@ class MacroAssemblerX86 : public MacroAs
         cmp32(tagOf(address), ImmTag(JSVAL_TAG_NULL));
         return cond;
     }
     Condition testBoolean(Condition cond, const BaseIndex& address) {
         MOZ_ASSERT(cond == Equal || cond == NotEqual);
         cmp32(tagOf(address), ImmTag(JSVAL_TAG_BOOLEAN));
         return cond;
     }
+    Condition testString(Condition cond, const Address& address) {
+        MOZ_ASSERT(cond == Equal || cond == NotEqual);
+        cmp32(tagOf(address), ImmTag(JSVAL_TAG_STRING));
+        return cond;
+    }
     Condition testString(Condition cond, const BaseIndex& address) {
         MOZ_ASSERT(cond == Equal || cond == NotEqual);
         cmp32(tagOf(address), ImmTag(JSVAL_TAG_STRING));
         return cond;
     }
     Condition testSymbol(Condition cond, const BaseIndex& address) {
         MOZ_ASSERT(cond == Equal || cond == NotEqual);
         cmp32(tagOf(address), ImmTag(JSVAL_TAG_SYMBOL));