Bug 1382837 - Optimize Array.join in ion for empty and single-item arrays. r=tcampbell
authorKannan Vijayan <kvijayan@mozilla.com>
Wed, 09 Aug 2017 17:06:40 -0400
changeset 373882 5e8015a81320d3801fea41417434e38a7d97f6ea
parent 373881 6f7aa77d3ac9b57d1718a7d855d39c491a2ce35b
child 373883 eefbc4d0360e2e439d9c5c93cff981bd04171642
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 ion for empty and single-item arrays. r=tcampbell
js/src/jit/CodeGenerator.cpp
js/src/jit/Lowering.cpp
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.h
js/src/jit/shared/LIR-shared.h
js/src/jit/x64/MacroAssembler-x64.h
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -9290,20 +9290,55 @@ CodeGenerator::visitArraySlice(LArraySli
 }
 
 typedef JSString* (*ArrayJoinFn)(JSContext*, HandleObject, HandleString);
 static const VMFunction ArrayJoinInfo = FunctionInfo<ArrayJoinFn>(jit::ArrayJoin, "ArrayJoin");
 
 void
 CodeGenerator::visitArrayJoin(LArrayJoin* lir)
 {
-    pushArg(ToRegister(lir->separator()));
-    pushArg(ToRegister(lir->array()));
-
+    Label skipCall;
+
+    Register output = ToRegister(lir->output());
+    Register sep = ToRegister(lir->separator());
+    Register array = ToRegister(lir->array());
+    if (lir->mir()->optimizeForArray()) {
+        Register temp = ToRegister(lir->temp());
+
+        masm.loadPtr(Address(array, NativeObject::offsetOfElements()), temp);
+        Address length(temp, ObjectElements::offsetOfLength());
+        Address initLength(temp, ObjectElements::offsetOfInitializedLength());
+
+        // Check for length == 0
+        Label notEmpty;
+        masm.branch32(Assembler::NotEqual, length, Imm32(0), &notEmpty);
+        const JSAtomState& names = GetJitContext()->runtime->names();
+        masm.movePtr(ImmGCPtr(names.empty), output);
+        masm.jump(&skipCall);
+
+        masm.bind(&notEmpty);
+        Label notSingleString;
+        // Check for length == 1, initializedLength >= 1, arr[0].isString()
+        masm.branch32(Assembler::NotEqual, length, Imm32(1), &notSingleString);
+        masm.branch32(Assembler::LessThan, initLength, Imm32(1), &notSingleString);
+
+        Address elem0(temp, 0);
+        masm.branchTestString(Assembler::NotEqual, elem0, &notSingleString);
+
+        // At this point, 'output' can be used as a scratch register, since we're
+        // guaranteed to succeed.
+        masm.unboxString(elem0, output);
+        masm.jump(&skipCall);
+        masm.bind(&notSingleString);
+    }
+
+    pushArg(sep);
+    pushArg(array);
     callVM(ArrayJoinInfo, lir);
+    masm.bind(&skipCall);
 }
 
 void
 CodeGenerator::visitGetIteratorCache(LGetIteratorCache* lir)
 {
     LiveRegisterSet liveRegs = lir->safepoint()->liveRegs();
     TypedOrValueRegister val =
         toConstantOrRegister(lir, LGetIteratorCache::Value, lir->mir()->value()->type()).reg();
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -3537,18 +3537,23 @@ LIRGenerator::visitArraySlice(MArraySlic
 
 void
 LIRGenerator::visitArrayJoin(MArrayJoin* ins)
 {
     MOZ_ASSERT(ins->type() == MIRType::String);
     MOZ_ASSERT(ins->array()->type() == MIRType::Object);
     MOZ_ASSERT(ins->sep()->type() == MIRType::String);
 
+    LDefinition tempDef = LDefinition::BogusTemp();
+    if (ins->optimizeForArray())
+        tempDef = temp();
+
     LArrayJoin* lir = new(alloc()) LArrayJoin(useRegisterAtStart(ins->array()),
-                                              useRegisterAtStart(ins->sep()));
+                                              useRegisterAtStart(ins->sep()),
+                                              tempDef);
     defineReturn(lir, ins);
     assignSafepoint(lir, ins);
 }
 
 void
 LIRGenerator::visitSinCos(MSinCos *ins)
 {
     MOZ_ASSERT(ins->type() == MIRType::SinCosDouble);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -753,19 +753,35 @@ IonBuilder::inlineArrayJoin(CallInfo& ca
 
     if (getInlineReturnType() != MIRType::String)
         return InliningStatus_NotInlined;
     if (callInfo.thisArg()->type() != MIRType::Object)
         return InliningStatus_NotInlined;
     if (callInfo.getArg(0)->type() != MIRType::String)
         return InliningStatus_NotInlined;
 
+    // If we can confirm that the class is an array, the codegen
+    // for MArrayJoin can be notified to check for common empty and one-item arrays.
+    bool optimizeForArray = ([&](){
+        TemporaryTypeSet* thisTypes = callInfo.thisArg()->resultTypeSet();
+        if (!thisTypes)
+            return false;
+
+        const Class* clasp = thisTypes->getKnownClass(constraints());
+        // TODO: Handle unboxed arrays?
+        if (clasp != &ArrayObject::class_)
+            return false;
+
+        return true;
+    })();
+
     callInfo.setImplicitlyUsedUnchecked();
 
-    MArrayJoin* ins = MArrayJoin::New(alloc(), callInfo.thisArg(), callInfo.getArg(0));
+    MArrayJoin* ins = MArrayJoin::New(alloc(), callInfo.thisArg(), callInfo.getArg(0),
+                                      optimizeForArray);
 
     current->add(ins);
     current->push(ins);
 
     MOZ_TRY(resumeAfter(ins));
     return InliningStatus_Inlined;
 }
 
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -10162,26 +10162,32 @@ class MArraySlice
         return roots.append(templateObj_);
     }
 };
 
 class MArrayJoin
     : public MBinaryInstruction,
       public MixPolicy<ObjectPolicy<0>, StringPolicy<1> >::Data
 {
-    MArrayJoin(MDefinition* array, MDefinition* sep)
-        : MBinaryInstruction(array, sep)
+    bool optimizeForArray_;
+
+    MArrayJoin(MDefinition* array, MDefinition* sep, bool optimizeForArray)
+        : MBinaryInstruction(array, sep),
+          optimizeForArray_(optimizeForArray)
     {
         setResultType(MIRType::String);
     }
   public:
     INSTRUCTION_HEADER(ArrayJoin)
     TRIVIAL_NEW_WRAPPERS
     NAMED_OPERANDS((0, array), (1, sep))
 
+    bool optimizeForArray() const {
+        return optimizeForArray_;
+    }
     bool possiblyCalls() const override {
         return true;
     }
     virtual AliasSet getAliasSet() const override {
         // Array.join might coerce the elements of the Array to strings.  This
         // coercion might cause the evaluation of the some JavaScript code.
         return AliasSet::Store(AliasSet::Any);
     }
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -6174,35 +6174,44 @@ class LArraySlice : public LCallInstruct
     const LDefinition* temp1() {
         return getTemp(0);
     }
     const LDefinition* temp2() {
         return getTemp(1);
     }
 };
 
-class LArrayJoin : public LCallInstructionHelper<1, 2, 0>
+class LArrayJoin : public LCallInstructionHelper<1, 2, 1>
 {
   public:
     LIR_HEADER(ArrayJoin)
 
-    LArrayJoin(const LAllocation& array, const LAllocation& sep) {
+    LArrayJoin(const LAllocation& array, const LAllocation& sep,
+               const LDefinition& temp)
+    {
         setOperand(0, array);
         setOperand(1, sep);
+        setTemp(0, temp);
     }
 
     const MArrayJoin* mir() const {
         return mir_->toArrayJoin();
     }
+    const LDefinition* output() {
+        return getDef(0);
+    }
     const LAllocation* array() {
         return getOperand(0);
     }
     const LAllocation* separator() {
         return getOperand(1);
     }
+    const LDefinition* temp() {
+        return getTemp(0);
+    }
 };
 
 class LLoadUnboxedScalar : public LInstructionHelper<1, 2, 1>
 {
   public:
     LIR_HEADER(LoadUnboxedScalar)
 
     LLoadUnboxedScalar(const LAllocation& elements, const LAllocation& index,
--- a/js/src/jit/x64/MacroAssembler-x64.h
+++ b/js/src/jit/x64/MacroAssembler-x64.h
@@ -784,16 +784,17 @@ class MacroAssemblerX64 : public MacroAs
         }
     }
     void unboxNonDouble(const Address& src, Register dest) {
         unboxNonDouble(Operand(src), dest);
     }
 
     void unboxString(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); }
     void unboxString(const Operand& src, Register dest) { unboxNonDouble(src, dest); }
+    void unboxString(const Address& src, Register dest) { unboxNonDouble(src, dest); }
 
     void unboxSymbol(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); }
     void unboxSymbol(const Operand& src, Register dest) { unboxNonDouble(src, dest); }
 
     void unboxObject(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); }
     void unboxObject(const Operand& src, Register dest) { unboxNonDouble(src, dest); }
     void unboxObject(const Address& src, Register dest) { unboxNonDouble(Operand(src), dest); }
     void unboxObject(const BaseIndex& src, Register dest) { unboxNonDouble(Operand(src), dest); }