diff --git a/js/src/ion/CodeGenerator.cpp b/js/src/ion/CodeGenerator.cpp index ba449fb9afe0..f8bba3085ed8 100644 --- a/js/src/ion/CodeGenerator.cpp +++ b/js/src/ion/CodeGenerator.cpp @@ -3822,7 +3822,7 @@ CodeGenerator::visitIsNullOrLikeUndefinedAndBranch(LIsNullOrLikeUndefinedAndBran return true; } -typedef JSString *(*ConcatStringsFn)(JSContext *, HandleString, HandleString); +typedef JSString *(*ConcatStringsFn)(ThreadSafeContext *, HandleString, HandleString); static const VMFunction ConcatStringsInfo = FunctionInfo(ConcatStrings); bool @@ -3923,7 +3923,41 @@ CodeGenerator::visitConcat(LConcat *lir) if (!ool) return false; - IonCode *stringConcatStub = gen->ionCompartment()->stringConcatStub(); + IonCode *stringConcatStub = gen->ionCompartment()->stringConcatStub(SequentialExecution); + masm.call(stringConcatStub); + masm.branchTestPtr(Assembler::Zero, output, output, ool->entry()); + + masm.bind(ool->rejoin()); + return true; +} + +typedef ParallelResult (*ParallelConcatStringsFn)(ForkJoinSlice *, HandleString, HandleString, + MutableHandleString); +static const VMFunction ParallelConcatStringsInfo = + FunctionInfo(ParConcatStrings); + +bool +CodeGenerator::visitParConcat(LParConcat *lir) +{ + Register slice = ToRegister(lir->parSlice()); + Register lhs = ToRegister(lir->lhs()); + Register rhs = ToRegister(lir->rhs()); + Register output = ToRegister(lir->output()); + + JS_ASSERT(lhs == CallTempReg0); + JS_ASSERT(rhs == CallTempReg1); + JS_ASSERT(slice == CallTempReg5); + JS_ASSERT(ToRegister(lir->temp1()) == CallTempReg2); + JS_ASSERT(ToRegister(lir->temp2()) == CallTempReg3); + JS_ASSERT(ToRegister(lir->temp3()) == CallTempReg4); + JS_ASSERT(output == CallTempReg6); + + OutOfLineCode *ool = oolCallVM(ParallelConcatStringsInfo, lir, (ArgList(), lhs, rhs), + StoreRegisterTo(output)); + if (!ool) + return false; + + IonCode *stringConcatStub = gen->ionCompartment()->stringConcatStub(ParallelExecution); masm.call(stringConcatStub); masm.branchTestPtr(Assembler::Zero, output, output, ool->entry()); @@ -3957,7 +3991,7 @@ CopyStringChars(MacroAssembler &masm, Register to, Register from, Register len, } IonCode * -IonCompartment::generateStringConcatStub(JSContext *cx) +IonCompartment::generateStringConcatStub(JSContext *cx, ExecutionMode mode) { MacroAssembler masm(cx); @@ -3969,7 +4003,12 @@ IonCompartment::generateStringConcatStub(JSContext *cx) Register temp4 = CallTempReg5; Register output = CallTempReg6; - Label failure; + // In parallel execution, we pass in the ForkJoinSlice in CallTempReg5, as + // by the time we need to use the temp4 we no longer have need of the + // slice. + Register forkJoinSlice = CallTempReg5; + + Label failure, failurePopTemps; // If lhs is empty, return rhs. Label leftEmpty; @@ -3992,7 +4031,20 @@ IonCompartment::generateStringConcatStub(JSContext *cx) masm.branch32(Assembler::Above, temp2, Imm32(JSString::MAX_LENGTH), &failure); // Allocate a new rope. - masm.newGCString(output, &failure); + switch (mode) { + case SequentialExecution: + masm.newGCString(output, &failure); + break; + case ParallelExecution: + masm.push(temp1); + masm.push(temp2); + masm.parNewGCString(output, forkJoinSlice, temp1, temp2, &failurePopTemps); + masm.pop(temp2); + masm.pop(temp1); + break; + default: + JS_NOT_REACHED("No such execution mode"); + } // Store lengthAndFlags. JS_STATIC_ASSERT(JSString::ROPE_FLAGS == 0); @@ -4024,7 +4076,20 @@ IonCompartment::generateStringConcatStub(JSContext *cx) Imm32(JSString::FLAGS_MASK), &failure); // Allocate a JSShortString. - masm.newGCShortString(output, &failure); + switch (mode) { + case SequentialExecution: + masm.newGCShortString(output, &failure); + break; + case ParallelExecution: + masm.push(temp1); + masm.push(temp2); + masm.parNewGCShortString(output, forkJoinSlice, temp1, temp2, &failurePopTemps); + masm.pop(temp2); + masm.pop(temp1); + break; + default: + JS_NOT_REACHED("No such execution mode"); + } // Set lengthAndFlags. masm.lshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp2); @@ -4049,6 +4114,10 @@ IonCompartment::generateStringConcatStub(JSContext *cx) masm.store16(Imm32(0), Address(temp2, 0)); masm.ret(); + masm.bind(&failurePopTemps); + masm.pop(temp2); + masm.pop(temp1); + masm.bind(&failure); masm.movePtr(ImmWord((void *)NULL), output); masm.ret(); diff --git a/js/src/ion/CodeGenerator.h b/js/src/ion/CodeGenerator.h index e732f104bfb1..5831db277ab9 100644 --- a/js/src/ion/CodeGenerator.h +++ b/js/src/ion/CodeGenerator.h @@ -172,6 +172,7 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitEmulatesUndefined(LEmulatesUndefined *lir); bool visitEmulatesUndefinedAndBranch(LEmulatesUndefinedAndBranch *lir); bool visitConcat(LConcat *lir); + bool visitParConcat(LParConcat *lir); bool visitCharCodeAt(LCharCodeAt *lir); bool visitFromCharCode(LFromCharCode *lir); bool visitFunctionEnvironment(LFunctionEnvironment *lir); diff --git a/js/src/ion/Ion.cpp b/js/src/ion/Ion.cpp index c9b315800ce7..9d71babaf6ca 100644 --- a/js/src/ion/Ion.cpp +++ b/js/src/ion/Ion.cpp @@ -298,7 +298,8 @@ IonCompartment::IonCompartment(IonRuntime *rt) : rt(rt), stubCodes_(NULL), baselineCallReturnAddr_(NULL), - stringConcatStub_(NULL) + stringConcatStub_(NULL), + parallelStringConcatStub_(NULL) { } @@ -322,11 +323,19 @@ bool IonCompartment::ensureIonStubsExist(JSContext *cx) { if (!stringConcatStub_) { - stringConcatStub_ = generateStringConcatStub(cx); + stringConcatStub_ = generateStringConcatStub(cx, SequentialExecution); if (!stringConcatStub_) return false; } +#ifdef JS_THREADSAFE + if (!parallelStringConcatStub_) { + parallelStringConcatStub_ = generateStringConcatStub(cx, ParallelExecution); + if (!parallelStringConcatStub_) + return false; + } +#endif + return true; } diff --git a/js/src/ion/IonCompartment.h b/js/src/ion/IonCompartment.h index 7eca88d55fe6..5c847515ba72 100644 --- a/js/src/ion/IonCompartment.h +++ b/js/src/ion/IonCompartment.h @@ -228,8 +228,9 @@ class IonCompartment // pointers. This has to be a weak pointer to avoid keeping the whole // compartment alive. ReadBarriered stringConcatStub_; + ReadBarriered parallelStringConcatStub_; - IonCode *generateStringConcatStub(JSContext *cx); + IonCode *generateStringConcatStub(JSContext *cx, ExecutionMode mode); public: IonCode *getVMWrapper(const VMFunction &f); @@ -321,8 +322,12 @@ class IonCompartment return rt->debugTrapHandler(cx); } - IonCode *stringConcatStub() { - return stringConcatStub_; + IonCode *stringConcatStub(ExecutionMode mode) { + switch (mode) { + case SequentialExecution: return stringConcatStub_; + case ParallelExecution: return parallelStringConcatStub_; + default: JS_NOT_REACHED("No such execution mode"); + } } AutoFlushCache *flusher() { diff --git a/js/src/ion/IonMacroAssembler.cpp b/js/src/ion/IonMacroAssembler.cpp index c3983a4e8db6..caf5a6859619 100644 --- a/js/src/ion/IonMacroAssembler.cpp +++ b/js/src/ion/IonMacroAssembler.cpp @@ -523,7 +523,7 @@ MacroAssembler::parNewGCThing(const Register &result, const Register &threadContextReg, const Register &tempReg1, const Register &tempReg2, - JSObject *templateObject, + gc::AllocKind allocKind, Label *fail) { // Similar to ::newGCThing(), except that it allocates from a @@ -536,7 +536,6 @@ MacroAssembler::parNewGCThing(const Register &result, // register as `threadContextReg`. Then we overwrite that // register which messed up the OOL code. - gc::AllocKind allocKind = templateObject->tenuredGetAllocKind(); uint32_t thingSize = (uint32_t)gc::Arena::thingSize(allocKind); // Load the allocator: @@ -572,6 +571,41 @@ MacroAssembler::parNewGCThing(const Register &result, storePtr(tempReg2, Address(tempReg1, offsetof(gc::FreeSpan, first))); } +void +MacroAssembler::parNewGCThing(const Register &result, + const Register &threadContextReg, + const Register &tempReg1, + const Register &tempReg2, + JSObject *templateObject, + Label *fail) +{ + gc::AllocKind allocKind = templateObject->tenuredGetAllocKind(); + JS_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST); + JS_ASSERT(!templateObject->hasDynamicElements()); + + parNewGCThing(result, threadContextReg, tempReg1, tempReg2, allocKind, fail); +} + +void +MacroAssembler::parNewGCString(const Register &result, + const Register &threadContextReg, + const Register &tempReg1, + const Register &tempReg2, + Label *fail) +{ + parNewGCThing(result, threadContextReg, tempReg1, tempReg2, js::gc::FINALIZE_STRING, fail); +} + +void +MacroAssembler::parNewGCShortString(const Register &result, + const Register &threadContextReg, + const Register &tempReg1, + const Register &tempReg2, + Label *fail) +{ + parNewGCThing(result, threadContextReg, tempReg1, tempReg2, js::gc::FINALIZE_SHORT_STRING, fail); +} + void MacroAssembler::initGCThing(const Register &obj, JSObject *templateObject) { diff --git a/js/src/ion/IonMacroAssembler.h b/js/src/ion/IonMacroAssembler.h index b49b662285b4..110de6d058fa 100644 --- a/js/src/ion/IonMacroAssembler.h +++ b/js/src/ion/IonMacroAssembler.h @@ -600,12 +600,28 @@ class MacroAssembler : public MacroAssemblerSpecific 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, + gc::AllocKind allocKind, + Label *fail); void parNewGCThing(const Register &result, const Register &threadContextReg, const Register &tempReg1, const Register &tempReg2, JSObject *templateObject, Label *fail); + void parNewGCString(const Register &result, + const Register &threadContextReg, + const Register &tempReg1, + const Register &tempReg2, + Label *fail); + void parNewGCShortString(const Register &result, + const Register &threadContextReg, + const Register &tempReg1, + const Register &tempReg2, + Label *fail); void initGCThing(const Register &obj, JSObject *templateObject); // Compares two strings for equality based on the JSOP. diff --git a/js/src/ion/LIR-Common.h b/js/src/ion/LIR-Common.h index c746335e71b6..b7776541e190 100644 --- a/js/src/ion/LIR-Common.h +++ b/js/src/ion/LIR-Common.h @@ -2304,6 +2304,41 @@ class LConcat : public LInstructionHelper<1, 2, 4> } }; +class LParConcat : public LInstructionHelper<1, 3, 3> +{ + public: + LIR_HEADER(ParConcat) + + LParConcat(const LAllocation &parSlice, const LAllocation &lhs, const LAllocation &rhs, + const LDefinition &temp1, const LDefinition &temp2, const LDefinition &temp3) { + setOperand(0, parSlice); + setOperand(1, lhs); + setOperand(2, rhs); + setTemp(0, temp1); + setTemp(1, temp2); + setTemp(2, temp3); + } + + const LAllocation *parSlice() { + return this->getOperand(0); + } + const LAllocation *lhs() { + return this->getOperand(1); + } + const LAllocation *rhs() { + return this->getOperand(2); + } + const LDefinition *temp1() { + return this->getTemp(0); + } + const LDefinition *temp2() { + return this->getTemp(1); + } + const LDefinition *temp3() { + return this->getTemp(2); + } +}; + // Get uint16 character code from a string. class LCharCodeAt : public LInstructionHelper<1, 2, 0> { diff --git a/js/src/ion/LOpcodes.h b/js/src/ion/LOpcodes.h index faadd57f9e2a..35a5eb969077 100644 --- a/js/src/ion/LOpcodes.h +++ b/js/src/ion/LOpcodes.h @@ -108,6 +108,7 @@ _(ModD) \ _(BinaryV) \ _(Concat) \ + _(ParConcat) \ _(CharCodeAt) \ _(FromCharCode) \ _(Int32ToDouble) \ diff --git a/js/src/ion/Lowering.cpp b/js/src/ion/Lowering.cpp index 4d471dcc7f01..23ad4f3a33bf 100644 --- a/js/src/ion/Lowering.cpp +++ b/js/src/ion/Lowering.cpp @@ -1323,6 +1323,28 @@ LIRGenerator::visitConcat(MConcat *ins) return assignSafepoint(lir, ins); } +bool +LIRGenerator::visitParConcat(MParConcat *ins) +{ + MDefinition *parSlice = ins->parSlice(); + MDefinition *lhs = ins->lhs(); + MDefinition *rhs = ins->rhs(); + + JS_ASSERT(lhs->type() == MIRType_String); + JS_ASSERT(rhs->type() == MIRType_String); + JS_ASSERT(ins->type() == MIRType_String); + + LParConcat *lir = new LParConcat(useFixed(parSlice, CallTempReg5), + useFixed(lhs, CallTempReg0), + useFixed(rhs, CallTempReg1), + tempFixed(CallTempReg2), + tempFixed(CallTempReg3), + tempFixed(CallTempReg4)); + if (!defineFixed(lir, ins, LAllocation(AnyRegister(CallTempReg6)))) + return false; + return assignSafepoint(lir, ins); +} + bool LIRGenerator::visitCharCodeAt(MCharCodeAt *ins) { diff --git a/js/src/ion/Lowering.h b/js/src/ion/Lowering.h index 7fa7d402743a..302d109953f1 100644 --- a/js/src/ion/Lowering.h +++ b/js/src/ion/Lowering.h @@ -143,6 +143,7 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitDiv(MDiv *ins); bool visitMod(MMod *ins); bool visitConcat(MConcat *ins); + bool visitParConcat(MParConcat *ins); bool visitCharCodeAt(MCharCodeAt *ins); bool visitFromCharCode(MFromCharCode *ins); bool visitStart(MStart *start); diff --git a/js/src/ion/MIR.h b/js/src/ion/MIR.h index 50481a7cc855..3a4896f31e38 100644 --- a/js/src/ion/MIR.h +++ b/js/src/ion/MIR.h @@ -3545,6 +3545,49 @@ class MConcat } }; +class MParConcat + : public MTernaryInstruction, + public MixPolicy, StringPolicy<2> > +{ + MParConcat(MDefinition *parSlice, MDefinition *left, MDefinition *right) + : MTernaryInstruction(parSlice, left, right) + { + setMovable(); + setResultType(MIRType_String); + } + + public: + INSTRUCTION_HEADER(ParConcat) + + static MParConcat *New(MDefinition *parSlice, MDefinition *left, MDefinition *right) { + return new MParConcat(parSlice, left, right); + } + + static MParConcat *New(MDefinition *parSlice, MConcat *concat) { + return New(parSlice, concat->lhs(), concat->rhs()); + } + + MDefinition *parSlice() const { + return getOperand(0); + } + MDefinition *lhs() const { + return getOperand(1); + } + MDefinition *rhs() const { + return getOperand(2); + } + + TypePolicy *typePolicy() { + return this; + } + bool congruentTo(MDefinition *const &ins) const { + return congruentIfOperandsEqual(ins); + } + AliasSet getAliasSet() const { + return AliasSet::None(); + } +}; + class MCharCodeAt : public MBinaryInstruction, public MixPolicy, IntPolicy<1> > diff --git a/js/src/ion/MOpcodes.h b/js/src/ion/MOpcodes.h index e9f24bd04185..eea1a3a42569 100644 --- a/js/src/ion/MOpcodes.h +++ b/js/src/ion/MOpcodes.h @@ -65,6 +65,7 @@ namespace ion { _(Div) \ _(Mod) \ _(Concat) \ + _(ParConcat) \ _(CharCodeAt) \ _(FromCharCode) \ _(Return) \ diff --git a/js/src/ion/ParallelFunctions.cpp b/js/src/ion/ParallelFunctions.cpp index fef3e8526b9b..7663c9c6bb86 100644 --- a/js/src/ion/ParallelFunctions.cpp +++ b/js/src/ion/ParallelFunctions.cpp @@ -194,6 +194,17 @@ ion::ParExtendArray(ForkJoinSlice *slice, JSObject *array, uint32_t length) return array; } +ParallelResult +ion::ParConcatStrings(ForkJoinSlice *slice, HandleString left, HandleString right, + MutableHandleString out) +{ + JSString *str = ConcatStrings(slice, left, right); + if (!str) + return TP_RETRY_SEQUENTIALLY; + out.set(str); + return TP_SUCCESS; +} + #define PAR_RELATIONAL_OP(OP, EXPECTED) \ do { \ /* Optimize for two int-tagged operands (typical loop control). */ \ diff --git a/js/src/ion/ParallelFunctions.h b/js/src/ion/ParallelFunctions.h index dfb46ea19c8d..bfed8d708890 100644 --- a/js/src/ion/ParallelFunctions.h +++ b/js/src/ion/ParallelFunctions.h @@ -41,6 +41,10 @@ JSObject* ParPush(ParPushArgs *args); // generation. JSObject *ParExtendArray(ForkJoinSlice *slice, JSObject *array, uint32_t length); +// Concatenate two strings. +ParallelResult ParConcatStrings(ForkJoinSlice *slice, HandleString left, HandleString right, + MutableHandleString out); + // These parallel operations fail if they would be required to convert // to a string etc etc. ParallelResult ParStrictlyEqual(ForkJoinSlice *slice, MutableHandleValue v1, MutableHandleValue v2, JSBool *); diff --git a/js/src/ion/ParallelSafetyAnalysis.cpp b/js/src/ion/ParallelSafetyAnalysis.cpp index 9a5b863ceb2b..64d30094ca85 100644 --- a/js/src/ion/ParallelSafetyAnalysis.cpp +++ b/js/src/ion/ParallelSafetyAnalysis.cpp @@ -156,7 +156,8 @@ class ParallelSafetyVisitor : public MInstructionVisitor SPECIALIZED_OP(Mul, PERMIT_NUMERIC) SPECIALIZED_OP(Div, PERMIT_NUMERIC) SPECIALIZED_OP(Mod, PERMIT_NUMERIC) - UNSAFE_OP(Concat) + CUSTOM_OP(Concat) + SAFE_OP(ParConcat) UNSAFE_OP(CharCodeAt) UNSAFE_OP(FromCharCode) SAFE_OP(Return) @@ -555,6 +556,12 @@ ParallelSafetyVisitor::visitRest(MRest *ins) return replace(ins, MParRest::New(parSlice(), ins)); } +bool +ParallelSafetyVisitor::visitConcat(MConcat *ins) +{ + return replace(ins, MParConcat::New(parSlice(), ins)); +} + bool ParallelSafetyVisitor::replaceWithParNew(MInstruction *newInstruction, JSObject *templateObject) diff --git a/js/src/ion/TypePolicy.cpp b/js/src/ion/TypePolicy.cpp index 04ff47af5887..c37f2ccb040d 100644 --- a/js/src/ion/TypePolicy.cpp +++ b/js/src/ion/TypePolicy.cpp @@ -315,6 +315,7 @@ StringPolicy::staticAdjustInputs(MInstruction *def) template bool StringPolicy<0>::staticAdjustInputs(MInstruction *ins); template bool StringPolicy<1>::staticAdjustInputs(MInstruction *ins); +template bool StringPolicy<2>::staticAdjustInputs(MInstruction *ins); template bool diff --git a/js/src/ion/VMFunctions.h b/js/src/ion/VMFunctions.h index 15759c959afc..16819757032e 100644 --- a/js/src/ion/VMFunctions.h +++ b/js/src/ion/VMFunctions.h @@ -332,6 +332,7 @@ template <> struct OutParamToDataType { static const DataType result template <> struct OutParamToDataType { static const DataType result = Type_Pointer; }; template <> struct OutParamToDataType { static const DataType result = Type_Handle; }; template <> struct OutParamToDataType { static const DataType result = Type_Handle; }; +template <> struct OutParamToDataType { static const DataType result = Type_Handle; }; template struct OutParamToRootType { static const VMFunction::RootType result = VMFunction::RootNone; @@ -353,6 +354,12 @@ template <> struct MatchContext { template <> struct MatchContext { static const ExecutionMode execMode = ParallelExecution; }; +template <> struct MatchContext { + // ThreadSafeContext functions can be called from either mode, but for + // calling from parallel they need to be wrapped first to return a + // ParallelResult, so we default to SequentialExecution here. + static const ExecutionMode execMode = SequentialExecution; +}; #define FOR_EACH_ARGS_1(Macro, Sep, Last) Macro(1) Last(1) #define FOR_EACH_ARGS_2(Macro, Sep, Last) FOR_EACH_ARGS_1(Macro, Sep, Sep) Macro(2) Last(2)