Bug 863018 part 2 - Add JSShortString path back to ConcatStrings and LConcat. r=luke
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 26 Apr 2013 14:08:54 +0200
changeset 141089 3b7a2cbccaaa848eb10b2f9b50f89b5a322ba379
parent 141088 f60596a19c985fe05b398c4aa027ed3f8468ceee
child 141090 dbe8ad85f3596ceeb9b52fc44ed8085eb70e47d6
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs863018
milestone23.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 863018 part 2 - Add JSShortString path back to ConcatStrings and LConcat. r=luke
js/src/ion/CodeGenerator.cpp
js/src/ion/Ion.cpp
js/src/ion/IonCompartment.h
js/src/ion/IonMacroAssembler.cpp
js/src/ion/IonMacroAssembler.h
js/src/ion/LIR-Common.h
js/src/ion/Lowering.cpp
js/src/ion/arm/Assembler-arm.h
js/src/ion/arm/MacroAssembler-arm.h
js/src/ion/x64/Assembler-x64.h
js/src/ion/x64/MacroAssembler-x64.h
js/src/ion/x86/Assembler-x86.h
js/src/ion/x86/MacroAssembler-x86.h
js/src/jit-test/tests/ion/string-concat-short.js
js/src/vm/String.cpp
js/src/vm/String.h
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -3585,62 +3585,162 @@ CodeGenerator::visitEmulatesUndefinedAnd
 
 bool
 CodeGenerator::visitConcat(LConcat *lir)
 {
     Register lhs = ToRegister(lir->lhs());
     Register rhs = ToRegister(lir->rhs());
 
     Register output = ToRegister(lir->output());
-    Register temp = ToRegister(lir->temp());
+
+    JS_ASSERT(lhs == CallTempReg0);
+    JS_ASSERT(rhs == CallTempReg1);
+    JS_ASSERT(ToRegister(lir->temp1()) == CallTempReg2);
+    JS_ASSERT(ToRegister(lir->temp2()) == CallTempReg3);
+    JS_ASSERT(ToRegister(lir->temp3()) == CallTempReg4);
+    JS_ASSERT(ToRegister(lir->temp4()) == CallTempReg5);
+    JS_ASSERT(output == CallTempReg6);
 
     OutOfLineCode *ool = oolCallVM(ConcatStringsInfo, lir, (ArgList(), lhs, rhs),
                                    StoreRegisterTo(output));
     if (!ool)
         return false;
 
-    Label done;
+    IonCode *stringConcatStub = gen->ionCompartment()->stringConcatStub();
+    masm.call(stringConcatStub);
+    masm.branchTestPtr(Assembler::Zero, output, output, ool->entry());
+
+    masm.bind(ool->rejoin());
+    return true;
+}
+
+static void
+CopyStringChars(MacroAssembler &masm, Register to, Register from, Register len, Register scratch)
+{
+    // Copy |len| jschars from |from| to |to|. Assumes len > 0 (checked below in
+    // debug builds), and when done |to| must point to the next available char.
+
+#ifdef DEBUG
+    Label ok;
+    masm.branch32(Assembler::GreaterThan, len, Imm32(0), &ok);
+    masm.breakpoint();
+    masm.bind(&ok);
+#endif
+
+    JS_STATIC_ASSERT(sizeof(jschar) == 2);
+
+    Label start;
+    masm.bind(&start);
+    masm.load16ZeroExtend(Address(from, 0), scratch);
+    masm.store16(scratch, Address(to, 0));
+    masm.addPtr(Imm32(2), from);
+    masm.addPtr(Imm32(2), to);
+    masm.sub32(Imm32(1), len);
+    masm.j(Assembler::NonZero, &start);
+}
+
+IonCode *
+IonCompartment::generateStringConcatStub(JSContext *cx)
+{
+    MacroAssembler masm(cx);
+
+    Register lhs = CallTempReg0;
+    Register rhs = CallTempReg1;
+    Register temp1 = CallTempReg2;
+    Register temp2 = CallTempReg3;
+    Register temp3 = CallTempReg4;
+    Register temp4 = CallTempReg5;
+    Register output = CallTempReg6;
+
+    Label failure;
 
     // If lhs is empty, return rhs.
     Label leftEmpty;
-    masm.loadStringLength(lhs, temp);
-    masm.branchTest32(Assembler::Zero, temp, temp, &leftEmpty);
+    masm.loadStringLength(lhs, temp1);
+    masm.branchTest32(Assembler::Zero, temp1, temp1, &leftEmpty);
 
     // If rhs is empty, return lhs.
     Label rightEmpty;
-    masm.loadStringLength(rhs, output);
-    masm.branchTest32(Assembler::Zero, output, output, &rightEmpty);
-
-    // Ensure total length <= JSString::MAX_LENGTH.
-    masm.add32(output, temp);
-    masm.branch32(Assembler::Above, temp, Imm32(JSString::MAX_LENGTH), ool->entry());
+    masm.loadStringLength(rhs, temp2);
+    masm.branchTest32(Assembler::Zero, temp2, temp2, &rightEmpty);
+
+    masm.add32(temp1, temp2);
+
+    // Check if we can use a JSShortString.
+    Label isShort;
+    masm.branch32(Assembler::BelowOrEqual, temp2, Imm32(JSShortString::MAX_SHORT_LENGTH),
+                  &isShort);
+
+    // Ensure result length <= JSString::MAX_LENGTH.
+    masm.branch32(Assembler::Above, temp1, Imm32(JSString::MAX_LENGTH), &failure);
 
     // Allocate a new rope.
-    masm.newGCString(output, ool->entry());
+    masm.newGCString(output, &failure);
 
     // Store lengthAndFlags.
     JS_STATIC_ASSERT(JSString::ROPE_FLAGS == 0);
-    masm.lshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp);
-    masm.storePtr(temp, Address(output, JSString::offsetOfLengthAndFlags()));
+    masm.lshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp2);
+    masm.storePtr(temp2, Address(output, JSString::offsetOfLengthAndFlags()));
 
     // Store left and right nodes.
     masm.storePtr(lhs, Address(output, JSRope::offsetOfLeft()));
     masm.storePtr(rhs, Address(output, JSRope::offsetOfRight()));
-    masm.jump(&done);
+    masm.ret();
 
     masm.bind(&leftEmpty);
     masm.mov(rhs, output);
-    masm.jump(&done);
+    masm.ret();
 
     masm.bind(&rightEmpty);
     masm.mov(lhs, output);
-
-    masm.bind(&done);
-    masm.bind(ool->rejoin());
-    return true;
+    masm.ret();
+
+    masm.bind(&isShort);
+
+    // State: lhs length in temp1, result length in temp2.
+
+    // Ensure both strings are linear (flags != 0).
+    JS_STATIC_ASSERT(JSString::ROPE_FLAGS == 0);
+    masm.branchTestPtr(Assembler::Zero, Address(lhs, JSString::offsetOfLengthAndFlags()),
+                       Imm32(JSString::FLAGS_MASK), &failure);
+    masm.branchTestPtr(Assembler::Zero, Address(rhs, JSString::offsetOfLengthAndFlags()),
+                       Imm32(JSString::FLAGS_MASK), &failure);
+
+    // Allocate a JSShortString.
+    masm.newGCShortString(output, &failure);
+
+    // Set lengthAndFlags.
+    masm.lshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp2);
+    masm.orPtr(Imm32(JSString::FIXED_FLAGS), temp2);
+    masm.storePtr(temp2, Address(output, JSString::offsetOfLengthAndFlags()));
+
+    // Set chars pointer, keep in temp2 for copy loop below.
+    masm.computeEffectiveAddress(Address(output, JSShortString::offsetOfInlineStorage()), temp2);
+    masm.storePtr(temp2, Address(output, JSShortString::offsetOfChars()));
+
+    // Copy lhs chars. Temp1 still holds the lhs length. Note that this
+    // advances temp2 to point to the next char.
+    masm.loadPtr(Address(lhs, JSString::offsetOfChars()), temp3);
+    CopyStringChars(masm, temp2, temp3, temp1, temp4);
+
+    // Copy rhs chars.
+    masm.loadPtr(Address(rhs, JSString::offsetOfChars()), temp3);
+    masm.loadStringLength(rhs, temp1);
+    CopyStringChars(masm, temp2, temp3, temp1, temp4);
+
+    // Null-terminate.
+    masm.store16(Imm32(0), Address(temp2, 0));
+    masm.ret();
+
+    masm.bind(&failure);
+    masm.movePtr(ImmWord((void *)NULL), output);
+    masm.ret();
+
+    Linker linker(masm);
+    return linker.newCode(cx, JSC::OTHER_CODE);
 }
 
 typedef bool (*CharCodeAtFn)(JSContext *, HandleString, int32_t, uint32_t *);
 static const VMFunction CharCodeAtInfo = FunctionInfo<CharCodeAtFn>(ion::CharCodeAt);
 
 bool
 CodeGenerator::visitCharCodeAt(LCharCodeAt *lir)
 {
--- a/js/src/ion/Ion.cpp
+++ b/js/src/ion/Ion.cpp
@@ -178,26 +178,26 @@ IonRuntime::~IonRuntime()
     freeOsrTempData();
 }
 
 bool
 IonRuntime::initialize(JSContext *cx)
 {
     AutoEnterAtomsCompartment ac(cx);
 
-    if (!cx->compartment->ensureIonCompartmentExists(cx))
-        return false;
-
     IonContext ictx(cx, NULL);
     AutoFlushCache afc("IonRuntime::initialize");
 
     execAlloc_ = cx->runtime->getExecAlloc(cx);
     if (!execAlloc_)
         return false;
 
+    if (!cx->compartment->ensureIonCompartmentExists(cx))
+        return false;
+
     functionWrappers_ = cx->new_<VMWrapperMap>(cx);
     if (!functionWrappers_ || !functionWrappers_->init())
         return false;
 
     if (cx->runtime->jitSupportsFloatingPoint) {
         // Initialize some Ion-only stubs that require floating-point support.
         if (!bailoutTables_.reserve(FrameSizeClass::ClassLimit().classId()))
             return false;
@@ -279,32 +279,46 @@ IonRuntime::freeOsrTempData()
 {
     js_free(osrTempData_);
     osrTempData_ = NULL;
 }
 
 IonCompartment::IonCompartment(IonRuntime *rt)
   : rt(rt),
     stubCodes_(NULL),
-    baselineCallReturnAddr_(NULL)
+    baselineCallReturnAddr_(NULL),
+    stringConcatStub_(NULL)
 {
 }
 
 IonCompartment::~IonCompartment()
 {
     if (stubCodes_)
         js_delete(stubCodes_);
 }
 
 bool
 IonCompartment::initialize(JSContext *cx)
 {
     stubCodes_ = cx->new_<ICStubCodeMap>(cx);
     if (!stubCodes_ || !stubCodes_->init())
         return false;
+
+    return true;
+}
+
+bool
+IonCompartment::ensureIonStubsExist(JSContext *cx)
+{
+    if (!stringConcatStub_) {
+        stringConcatStub_ = generateStringConcatStub(cx);
+        if (!stringConcatStub_)
+            return false;
+    }
+
     return true;
 }
 
 void
 ion::FinishOffThreadBuilder(IonBuilder *builder)
 {
     JS_ASSERT(builder->info().executionMode() == SequentialExecution);
 
@@ -359,16 +373,19 @@ IonCompartment::mark(JSTracer *trc, JSCo
 void
 IonCompartment::sweep(FreeOp *fop)
 {
     stubCodes_->sweep(fop);
 
     // If the sweep removed the ICCall_Fallback stub, NULL the baselineCallReturnAddr_ field.
     if (!stubCodes_->lookup(static_cast<uint32_t>(ICStub::Call_Fallback)))
         baselineCallReturnAddr_ = NULL;
+
+    if (stringConcatStub_ && !IsIonCodeMarked(stringConcatStub_.unsafeGet()))
+        stringConcatStub_ = NULL;
 }
 
 IonCode *
 IonCompartment::getBailoutTable(const FrameSizeClass &frameClass)
 {
     JS_ASSERT(frameClass != FrameSizeClass::None());
     return rt->bailoutTables_[frameClass.classId()];
 }
@@ -1318,16 +1335,19 @@ IonCompile(JSContext *cx, JSScript *scri
 
     IonContext ictx(cx, temp);
 
     types::AutoEnterAnalysis enter(cx);
 
     if (!cx->compartment->ensureIonCompartmentExists(cx))
         return AbortReason_Alloc;
 
+    if (!cx->compartment->ionCompartment()->ensureIonStubsExist(cx))
+        return AbortReason_Alloc;
+
     MIRGraph *graph = alloc->new_<MIRGraph>(temp);
     ExecutionMode executionMode = compileContext.executionMode();
     CompileInfo *info = alloc->new_<CompileInfo>(script, script->function(), osrPc, constructing,
                                                  executionMode);
     if (!info)
         return AbortReason_Alloc;
 
     BaselineInspector inspector(cx, script);
--- a/js/src/ion/IonCompartment.h
+++ b/js/src/ion/IonCompartment.h
@@ -192,16 +192,24 @@ class IonCompartment
 
     // Keep track of offset into baseline ICCall_Scripted stub's code at return
     // point from called script.
     void *baselineCallReturnAddr_;
 
     // Allocated space for optimized baseline stubs.
     OptimizedICStubSpace optimizedStubSpace_;
 
+    // Stub to concatenate two strings inline. Note that it can't be
+    // stored in IonRuntime because masm.newGCString bakes in zone-specific
+    // pointers. This has to be a weak pointer to avoid keeping the whole
+    // compartment alive.
+    ReadBarriered<IonCode> stringConcatStub_;
+
+    IonCode *generateStringConcatStub(JSContext *cx);
+
   public:
     IonCode *getVMWrapper(const VMFunction &f);
 
     OffThreadCompilationVector &finishedOffThreadCompilations() {
         return finishedOffThreadCompilations_;
     }
 
     IonCode *getStubCode(uint32_t key) {
@@ -230,16 +238,19 @@ class IonCompartment
     void toggleBaselineStubBarriers(bool enabled);
 
   public:
     IonCompartment(IonRuntime *rt);
     ~IonCompartment();
 
     bool initialize(JSContext *cx);
 
+    // Initialize code stubs only used by Ion, not Baseline.
+    bool ensureIonStubsExist(JSContext *cx);
+
     void mark(JSTracer *trc, JSCompartment *compartment);
     void sweep(FreeOp *fop);
 
     JSC::ExecutableAllocator *execAlloc() {
         return rt->execAlloc_;
     }
 
     IonCode *getGenericBailoutHandler() {
@@ -279,16 +290,20 @@ class IonCompartment
     IonCode *shapePreBarrier() {
         return rt->shapePreBarrier_;
     }
 
     IonCode *debugTrapHandler(JSContext *cx) {
         return rt->debugTrapHandler(cx);
     }
 
+    IonCode *stringConcatStub() {
+        return stringConcatStub_;
+    }
+
     AutoFlushCache *flusher() {
         return rt->flusher();
     }
     void setFlusher(AutoFlushCache *fl) {
         rt->setFlusher(fl);
     }
     OptimizedICStubSpace *optimizedStubSpace() {
         return &optimizedStubSpace_;
--- a/js/src/ion/IonMacroAssembler.cpp
+++ b/js/src/ion/IonMacroAssembler.cpp
@@ -459,16 +459,22 @@ MacroAssembler::newGCThing(const Registe
 
 void
 MacroAssembler::newGCString(const Register &result, Label *fail)
 {
     newGCThing(result, js::gc::FINALIZE_STRING, fail);
 }
 
 void
+MacroAssembler::newGCShortString(const Register &result, Label *fail)
+{
+    newGCThing(result, js::gc::FINALIZE_SHORT_STRING, fail);
+}
+
+void
 MacroAssembler::parNewGCThing(const Register &result,
                               const Register &threadContextReg,
                               const Register &tempReg1,
                               const Register &tempReg2,
                               JSObject *templateObject,
                               Label *fail)
 {
     // Similar to ::newGCThing(), except that it allocates from a
--- a/js/src/ion/IonMacroAssembler.h
+++ b/js/src/ion/IonMacroAssembler.h
@@ -575,16 +575,17 @@ class MacroAssembler : public MacroAssem
 
         bind(&done);
     }
 
     // Inline allocation.
     void newGCThing(const Register &result, gc::AllocKind allocKind, Label *fail);
     void newGCThing(const Register &result, JSObject *templateObject, Label *fail);
     void newGCString(const Register &result, Label *fail);
+    void newGCShortString(const Register &result, Label *fail);
 
     void parNewGCThing(const Register &result,
                        const Register &threadContextReg,
                        const Register &tempReg1,
                        const Register &tempReg2,
                        JSObject *templateObject,
                        Label *fail);
     void initGCThing(const Register &obj, JSObject *templateObject);
--- a/js/src/ion/LIR-Common.h
+++ b/js/src/ion/LIR-Common.h
@@ -2243,36 +2243,49 @@ class LBinaryV : public LCallInstruction
         return jsop_;
     }
 
     static const size_t LhsInput = 0;
     static const size_t RhsInput = BOX_PIECES;
 };
 
 // Adds two string, returning a string.
-class LConcat : public LInstructionHelper<1, 2, 1>
+class LConcat : public LInstructionHelper<1, 2, 4>
 {
   public:
     LIR_HEADER(Concat)
 
-    LConcat(const LAllocation &lhs, const LAllocation &rhs, const LDefinition &temp) {
+    LConcat(const LAllocation &lhs, const LAllocation &rhs, const LDefinition &temp1,
+            const LDefinition &temp2, const LDefinition &temp3, const LDefinition &temp4) {
         setOperand(0, lhs);
         setOperand(1, rhs);
-        setTemp(0, temp);
+        setTemp(0, temp1);
+        setTemp(1, temp2);
+        setTemp(2, temp3);
+        setTemp(3, temp4);
     }
 
     const LAllocation *lhs() {
         return this->getOperand(0);
     }
     const LAllocation *rhs() {
         return this->getOperand(1);
     }
-    const LDefinition *temp() {
+    const LDefinition *temp1() {
         return this->getTemp(0);
     }
+    const LDefinition *temp2() {
+        return this->getTemp(1);
+    }
+    const LDefinition *temp3() {
+        return this->getTemp(2);
+    }
+    const LDefinition *temp4() {
+        return this->getTemp(3);
+    }
 };
 
 // Get uint16 character code from a string.
 class LCharCodeAt : public LInstructionHelper<1, 2, 0>
 {
   public:
     LIR_HEADER(CharCodeAt)
 
--- a/js/src/ion/Lowering.cpp
+++ b/js/src/ion/Lowering.cpp
@@ -1288,18 +1288,23 @@ LIRGenerator::visitConcat(MConcat *ins)
 {
     MDefinition *lhs = ins->getOperand(0);
     MDefinition *rhs = ins->getOperand(1);
 
     JS_ASSERT(lhs->type() == MIRType_String);
     JS_ASSERT(rhs->type() == MIRType_String);
     JS_ASSERT(ins->type() == MIRType_String);
 
-    LConcat *lir = new LConcat(useRegister(lhs), useRegister(rhs), temp());
-    if (!define(lir, ins))
+    LConcat *lir = new LConcat(useFixed(lhs, CallTempReg0),
+                               useFixed(rhs, CallTempReg1),
+                               tempFixed(CallTempReg2),
+                               tempFixed(CallTempReg3),
+                               tempFixed(CallTempReg4),
+                               tempFixed(CallTempReg5));
+    if (!defineFixed(lir, ins, LAllocation(AnyRegister(CallTempReg6))))
         return false;
     return assignSafepoint(lir, ins);
 }
 
 bool
 LIRGenerator::visitCharCodeAt(MCharCodeAt *ins)
 {
     MDefinition *str = ins->getOperand(0);
--- a/js/src/ion/arm/Assembler-arm.h
+++ b/js/src/ion/arm/Assembler-arm.h
@@ -50,17 +50,17 @@ static const Register ScratchRegister = 
 static const Register OsrFrameReg = r3;
 static const Register ArgumentsRectifierReg = r8;
 static const Register CallTempReg0 = r5;
 static const Register CallTempReg1 = r6;
 static const Register CallTempReg2 = r7;
 static const Register CallTempReg3 = r8;
 static const Register CallTempReg4 = r0;
 static const Register CallTempReg5 = r1;
-
+static const Register CallTempReg6 = r2;
 
 static const Register IntArgReg0 = r0;
 static const Register IntArgReg1 = r1;
 static const Register IntArgReg2 = r2;
 static const Register IntArgReg3 = r3;
 static const Register GlobalReg = r10;
 static const Register HeapReg = r11;
 static const Register CallTempNonArgRegs[] = { r5, r6, r7, r8 };
--- a/js/src/ion/arm/MacroAssembler-arm.h
+++ b/js/src/ion/arm/MacroAssembler-arm.h
@@ -848,16 +848,19 @@ class MacroAssemblerARMCompat : public M
         branchTest32(cond, ScratchRegister, imm, label);
     }
     void branchTestPtr(Condition cond, const Register &lhs, const Register &rhs, Label *label) {
         branchTest32(cond, lhs, rhs, label);
     }
     void branchTestPtr(Condition cond, const Register &lhs, const Imm32 rhs, Label *label) {
         branchTest32(cond, lhs, rhs, label);
     }
+    void branchTestPtr(Condition cond, const Address &lhs, Imm32 imm, Label *label) {
+        branchTest32(cond, lhs, imm, label);
+    }
     void branchPtr(Condition cond, Register lhs, Register rhs, Label *label) {
         branch32(cond, lhs, rhs, label);
     }
     void branchPtr(Condition cond, Register lhs, ImmGCPtr ptr, Label *label) {
         movePtr(ptr, ScratchRegister);
         branchPtr(cond, lhs, ScratchRegister, label);
     }
     void branchPtr(Condition cond, Register lhs, ImmWord imm, Label *label) {
--- a/js/src/ion/x64/Assembler-x64.h
+++ b/js/src/ion/x64/Assembler-x64.h
@@ -77,16 +77,17 @@ static const FloatRegister ScratchFloatR
 
 static const Register ArgumentsRectifierReg = r8;
 static const Register CallTempReg0 = rax;
 static const Register CallTempReg1 = rdi;
 static const Register CallTempReg2 = rbx;
 static const Register CallTempReg3 = rcx;
 static const Register CallTempReg4 = rsi;
 static const Register CallTempReg5 = rdx;
+static const Register CallTempReg6 = rbp;
 
 // Different argument registers for WIN64
 #if defined(_WIN64)
 static const Register IntArgReg0 = rcx;
 static const Register IntArgReg1 = rdx;
 static const Register IntArgReg2 = r8;
 static const Register IntArgReg3 = r9;
 static const uint32_t NumIntArgRegs = 4;
@@ -621,16 +622,29 @@ class Assembler : public AssemblerX86Sha
     }
 
     void testq(const Register &lhs, Imm32 rhs) {
         masm.testq_i32r(rhs.value, lhs.code());
     }
     void testq(const Register &lhs, const Register &rhs) {
         masm.testq_rr(rhs.code(), lhs.code());
     }
+    void testq(const Operand &lhs, Imm32 rhs) {
+        switch (lhs.kind()) {
+          case Operand::REG:
+            masm.testq_i32r(rhs.value, lhs.reg());
+            break;
+          case Operand::REG_DISP:
+            masm.testq_i32m(rhs.value, lhs.disp(), lhs.base());
+            break;
+          default:
+            JS_NOT_REACHED("unexpected operand kind");
+            break;
+        }
+    }
 
     void jmp(void *target, Relocation::Kind reloc = Relocation::HARDCODED) {
         JmpSrc src = masm.jmp();
         addPendingJump(src, target, reloc);
     }
     void j(Condition cond, void *target,
            Relocation::Kind reloc = Relocation::HARDCODED) {
         JmpSrc src = masm.jCC(static_cast<JSC::X86Assembler::Condition>(cond));
--- a/js/src/ion/x64/MacroAssembler-x64.h
+++ b/js/src/ion/x64/MacroAssembler-x64.h
@@ -505,16 +505,20 @@ class MacroAssemblerX64 : public MacroAs
     void branchTestPtr(Condition cond, Register lhs, Register rhs, Label *label) {
         testq(lhs, rhs);
         j(cond, label);
     }
     void branchTestPtr(Condition cond, Register lhs, Imm32 imm, Label *label) {
         testq(lhs, imm);
         j(cond, label);
     }
+    void branchTestPtr(Condition cond, const Address &lhs, Imm32 imm, Label *label) {
+        testq(Operand(lhs), imm);
+        j(cond, label);
+    }
     void decBranchPtr(Condition cond, const Register &lhs, Imm32 imm, Label *label) {
         subPtr(imm, lhs);
         j(cond, label);
     }
 
     void movePtr(const Register &src, const Register &dest) {
         movq(src, dest);
     }
--- a/js/src/ion/x86/Assembler-x86.h
+++ b/js/src/ion/x86/Assembler-x86.h
@@ -49,16 +49,17 @@ static const FloatRegister ScratchFloatR
 
 static const Register ArgumentsRectifierReg = esi;
 static const Register CallTempReg0 = edi;
 static const Register CallTempReg1 = eax;
 static const Register CallTempReg2 = ebx;
 static const Register CallTempReg3 = ecx;
 static const Register CallTempReg4 = esi;
 static const Register CallTempReg5 = edx;
+static const Register CallTempReg6 = ebp;
 
 // We have no arg regs, so our NonArgRegs are just our CallTempReg*
 static const Register CallTempNonArgRegs[] = { edi, eax, ebx, ecx, esi, edx };
 static const uint32_t NumCallTempNonArgRegs =
     mozilla::ArrayLength(CallTempNonArgRegs);
 
 class ABIArgGenerator
 {
--- a/js/src/ion/x86/MacroAssembler-x86.h
+++ b/js/src/ion/x86/MacroAssembler-x86.h
@@ -539,16 +539,20 @@ class MacroAssemblerX86 : public MacroAs
     void branchTestPtr(Condition cond, Register lhs, Register rhs, Label *label) {
         testl(lhs, rhs);
         j(cond, label);
     }
     void branchTestPtr(Condition cond, Register lhs, Imm32 imm, Label *label) {
         testl(lhs, imm);
         j(cond, label);
     }
+    void branchTestPtr(Condition cond, const Address &lhs, Imm32 imm, Label *label) {
+        testl(Operand(lhs), imm);
+        j(cond, label);
+    }
     void decBranchPtr(Condition cond, const Register &lhs, Imm32 imm, Label *label) {
         subPtr(imm, lhs);
         j(cond, label);
     }
 
     void movePtr(ImmWord imm, Register dest) {
         movl(Imm32(imm.value), dest);
     }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/string-concat-short.js
@@ -0,0 +1,13 @@
+function f() {
+    var res = 0;
+    for (var i=0; i<100; i++) {
+	var s = "test" + i;
+	res += s.length;
+	assertEq(s[0], "t");
+	assertEq(s[3], "t");
+	if (i > 90)
+	    assertEq(s[4], "9");
+    }
+    return res;
+}
+assertEq(f(), 590);
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -314,16 +314,34 @@ js::ConcatStrings(JSContext *cx,
     if (rightLen == 0)
         return left;
 
     size_t wholeLength = leftLen + rightLen;
     JSContext *cxIfCanGC = allowGC ? cx : NULL;
     if (!JSString::validateLength(cxIfCanGC, wholeLength))
         return NULL;
 
+    if (JSShortString::lengthFits(wholeLength)) {
+        JSShortString *str = js_NewGCShortString<allowGC>(cx);
+        if (!str)
+            return NULL;
+        const jschar *leftChars = left->getChars(cx);
+        if (!leftChars)
+            return NULL;
+        const jschar *rightChars = right->getChars(cx);
+        if (!rightChars)
+            return NULL;
+
+        jschar *buf = str->init(wholeLength);
+        PodCopy(buf, leftChars, leftLen);
+        PodCopy(buf + leftLen, rightChars, rightLen);
+        buf[wholeLength] = 0;
+        return str;
+    }
+
     return JSRope::new_<allowGC>(cx, left, right, wholeLength);
 }
 
 template JSString *
 js::ConcatStrings<CanGC>(JSContext *cx, HandleString left, HandleString right);
 
 template JSString *
 js::ConcatStrings<NoGC>(JSContext *cx, JSString *left, JSString *right);
--- a/js/src/vm/String.h
+++ b/js/src/vm/String.h
@@ -672,16 +672,20 @@ class JSInlineString : public JSFlatStri
 
     JSStableString *uninline(JSContext *cx);
 
     inline void resetLength(size_t length);
 
     static bool lengthFits(size_t length) {
         return length <= MAX_INLINE_LENGTH;
     }
+
+    static size_t offsetOfInlineStorage() {
+        return offsetof(JSInlineString, d.inlineStorage);
+    }
 };
 
 JS_STATIC_ASSERT(sizeof(JSInlineString) == sizeof(JSString));
 
 class JSShortString : public JSInlineString
 {
     /* This can be any value that is a multiple of CellSize. */
     static const size_t INLINE_EXTENSION_CHARS = sizeof(JSString::Data) / sizeof(jschar);