diff --git a/js/src/builtin/String.js b/js/src/builtin/String.js index 577be1132b78..c8fa7bbc796e 100644 --- a/js/src/builtin/String.js +++ b/js/src/builtin/String.js @@ -4,6 +4,126 @@ /*global intl_Collator: false, */ +/* ES6 Draft Oct 14, 2014 21.1.3.19 */ +function String_substring(start, end) { + // Steps 1-3. + CheckObjectCoercible(this); + var str = ToString(this); + + // Step 4. + var len = str.length; + + // Step 5. + var intStart = ToInteger(start); + + // Step 6. + var intEnd = (end === undefined) ? len : ToInteger(end); + + // Step 7. + var finalStart = std_Math_min(std_Math_max(intStart, 0), len); + + // Step 8. + var finalEnd = std_Math_min(std_Math_max(intEnd, 0), len); + + // Steps 9-10. + var from, to; + if (finalStart < finalEnd) { + from = finalStart; + to = finalEnd; + } else { + from = finalEnd; + to = finalStart; + } + + // Step 11. + // While |from| and |to - from| are bounded to the length of |str| and this + // and thus definitely in the int32 range, they can still be typed as + // double. Eagerly truncate since SubstringKernel only accepts int32. + return SubstringKernel(str, from | 0, (to - from) | 0); +} + +function String_static_substring(string, start, end) { + if (arguments.length < 1) + ThrowError(JSMSG_MISSING_FUN_ARG, 0, 'String.substring'); + return callFunction(String_substring, string, start, end); +} + +/* ES6 Draft Oct 14, 2014 B.2.3.1 */ +function String_substr(start, length) { + // Steps 1-2. + CheckObjectCoercible(this); + var str = ToString(this); + + // Steps 3-4. + var intStart = ToInteger(start); + + // Steps 5-7. + var size = str.length; + // Use |size| instead of +Infinity to avoid performing calculations with + // doubles. (The result is the same either way.) + var end = (length === undefined) ? size : ToInteger(length); + + // Step 8. + if (intStart < 0) + intStart = std_Math_max(intStart + size, 0); + + // Step 9. + var resultLength = std_Math_min(std_Math_max(end, 0), size - intStart) + + // Step 10. + if (resultLength <= 0) + return ""; + + // Step 11. + // While |intStart| and |resultLength| are bounded to the length of |str| + // and thus definitely in the int32 range, they can still be typed as + // double. Eagerly truncate since SubstringKernel only accepts int32. + return SubstringKernel(str, intStart | 0, resultLength | 0); +} + +function String_static_substr(string, start, length) { + if (arguments.length < 1) + ThrowError(JSMSG_MISSING_FUN_ARG, 0, 'String.substr'); + return callFunction(String_substr, string, start, length); +} + +/* ES6 Draft Oct 14, 2014 21.1.3.16 */ +function String_slice(start, end) { + // Steps 1-3. + CheckObjectCoercible(this); + var str = ToString(this); + + // Step 4. + var len = str.length; + + // Step 5. + var intStart = ToInteger(start); + + // Step 6. + var intEnd = (end === undefined) ? len : ToInteger(end); + + // Step 7. + var from = (intStart < 0) ? std_Math_max(len + intStart, 0) : std_Math_min(intStart, len); + + // Step 8. + var to = (intEnd < 0) ? std_Math_max(len + intEnd, 0) : std_Math_min(intEnd, len); + + // Step 9. + var span = std_Math_max(to - from, 0); + + // Step 10. + // While |from| and |span| are bounded to the length of |str| + // and thus definitely in the int32 range, they can still be typed as + // double. Eagerly truncate since SubstringKernel only accepts int32. + return SubstringKernel(str, from | 0, span | 0); +} + +function String_static_slice(string, start, end) { + if (arguments.length < 1) + ThrowError(JSMSG_MISSING_FUN_ARG, 0, 'String.slice'); + return callFunction(String_slice, string, start, end); +} + /* ES6 Draft September 5, 2013 21.1.3.3 */ function String_codePointAt(pos) { // Steps 1-3. diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 8425c12801ea..bd1c36ace1df 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -32,6 +32,7 @@ // The few items below here are either self-hosted or installing them under a // std_Foo name would require ugly contortions, so they just get aliased here. var std_Array_indexOf = ArrayIndexOf; +var std_String_substring = String_substring; // WeakMap is a bare constructor without properties or methods. var std_WeakMap = WeakMap; // StopIteration is a bare constructor without properties or methods. diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 4e83f5741625..7492d7d42eed 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -5944,6 +5944,111 @@ ConcatFatInlineString(MacroAssembler &masm, Register lhs, Register rhs, Register masm.ret(); } +typedef bool (*SubstringKernelFn)(JSContext *cx, HandleString str, int32_t begin, int32_t len, + MutableHandleString rstring); +static const VMFunction SubstringKernelInfo = + FunctionInfo(SubstringKernel); + +bool CodeGenerator::visitSubstr(LSubstr *lir) +{ + Register string = ToRegister(lir->string()); + Register begin = ToRegister(lir->begin()); + Register length = ToRegister(lir->length()); + Register output = ToRegister(lir->output()); + Register temp = ToRegister(lir->temp()); + Address stringFlags(string, JSString::offsetOfFlags()); + + Label isLatin1, notInline, nonZero, isInlinedLatin1; + + // For every edge case use the C++ variant. + // Note: we also use this upon allocation failure in newGCString and + // newGCFatInlineString. To squeeze out even more performance those failures + // can be handled by allocate in ool code and returning to jit code to fill + // in all data. + OutOfLineCode *ool = oolCallVM(SubstringKernelInfo, lir, + (ArgList(), string, begin, length), + StoreRegisterTo(output)); + if (!ool) + return false; + Label *slowPath = ool->entry(); + Label *done = ool->rejoin(); + + // Zero length, return emptystring. + masm.branchTest32(Assembler::NonZero, length, length, &nonZero); + const JSAtomState &names = GetIonContext()->runtime->names(); + masm.movePtr(ImmGCPtr(names.empty), output); + masm.jump(done); + + // Use slow path for ropes. + masm.bind(&nonZero); + static_assert(JSString::ROPE_FLAGS == 0, + "rope flags must be zero for (flags & TYPE_FLAGS_MASK) == 0 " + "to be a valid is-rope check"); + masm.branchTest32(Assembler::Zero, stringFlags, Imm32(JSString::TYPE_FLAGS_MASK), slowPath); + + // Handle inlined strings by creating a FatInlineString. + masm.branchTest32(Assembler::Zero, stringFlags, Imm32(JSString::INLINE_CHARS_BIT), ¬Inline); + masm.newGCFatInlineString(output, temp, slowPath); + masm.store32(length, Address(output, JSString::offsetOfLength())); + Address stringStorage(string, JSInlineString::offsetOfInlineStorage()); + Address outputStorage(output, JSInlineString::offsetOfInlineStorage()); + + masm.branchTest32(Assembler::NonZero, stringFlags, Imm32(JSString::LATIN1_CHARS_BIT), + &isInlinedLatin1); + { + masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS), + Address(output, JSString::offsetOfFlags())); + masm.computeEffectiveAddress(stringStorage, temp); + BaseIndex chars(temp, begin, ScaleFromElemWidth(sizeof(char16_t))); + masm.computeEffectiveAddress(chars, begin); + masm.computeEffectiveAddress(outputStorage, temp); + CopyStringChars(masm, temp, begin, length, string, sizeof(char16_t), sizeof(char16_t)); + masm.store16(Imm32(0), Address(temp, 0)); + masm.jump(done); + } + masm.bind(&isInlinedLatin1); + { + masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS | JSString::LATIN1_CHARS_BIT), + Address(output, JSString::offsetOfFlags())); + masm.computeEffectiveAddress(stringStorage, temp); + static_assert(sizeof(char) == 1, "begin index shouldn't need scaling"); + masm.addPtr(temp, begin); + masm.computeEffectiveAddress(outputStorage, temp); + CopyStringChars(masm, temp, begin, length, string, sizeof(char), sizeof(char)); + masm.store8(Imm32(0), Address(temp, 0)); + masm.jump(done); + } + + // Handle other cases with a DependentString. + masm.bind(¬Inline); + masm.newGCString(output, temp, slowPath); + masm.store32(length, Address(output, JSString::offsetOfLength())); + masm.storePtr(string, Address(output, JSDependentString::offsetOfBase())); + + masm.branchTest32(Assembler::NonZero, stringFlags, Imm32(JSString::LATIN1_CHARS_BIT), &isLatin1); + { + masm.store32(Imm32(JSString::DEPENDENT_FLAGS), Address(output, JSString::offsetOfFlags())); + masm.loadPtr(Address(string, JSString::offsetOfNonInlineChars()), temp); + BaseIndex chars(temp, begin, ScaleFromElemWidth(sizeof(char16_t))); + masm.computeEffectiveAddress(chars, temp); + masm.storePtr(temp, Address(output, JSString::offsetOfNonInlineChars())); + masm.jump(done); + } + masm.bind(&isLatin1); + { + masm.store32(Imm32(JSString::DEPENDENT_FLAGS | JSString::LATIN1_CHARS_BIT), + Address(output, JSString::offsetOfFlags())); + masm.loadPtr(Address(string, JSString::offsetOfNonInlineChars()), temp); + static_assert(sizeof(char) == 1, "begin index shouldn't need scaling"); + masm.addPtr(begin, temp); + masm.storePtr(temp, Address(output, JSString::offsetOfNonInlineChars())); + masm.jump(done); + } + + masm.bind(done); + return true; +} + JitCode * JitCompartment::generateStringConcatStub(JSContext *cx, ExecutionMode mode) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 40a0b38ac21c..fb4999455e89 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -186,6 +186,7 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitTypedObjectProto(LTypedObjectProto *ins); bool visitTypedObjectUnsizedLength(LTypedObjectUnsizedLength *ins); bool visitStringLength(LStringLength *lir); + bool visitSubstr(LSubstr *lir); bool visitInitializedLength(LInitializedLength *lir); bool visitSetInitializedLength(LSetInitializedLength *lir); bool visitNotO(LNotO *ins); diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 3b66ebd7f6b1..15f8677c57c7 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -765,6 +765,7 @@ class IonBuilder const Class *clasp3 = nullptr, const Class *clasp4 = nullptr); InliningStatus inlineIsConstructing(CallInfo &callInfo); + InliningStatus inlineSubstringKernel(CallInfo &callInfo); // Testing functions. InliningStatus inlineForceSequentialOrInParallelSection(CallInfo &callInfo); diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index 34ac5a5a1ca8..46c7c5a6f55d 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -3392,6 +3392,36 @@ class LStringSplit : public LCallInstructionHelper<1, 2, 0> } }; +class LSubstr : public LInstructionHelper<1, 3, 1> +{ + public: + LIR_HEADER(Substr) + + LSubstr(const LAllocation &string, const LAllocation &begin, const LAllocation &length, + const LDefinition &temp) + { + setOperand(0, string); + setOperand(1, begin); + setOperand(2, length); + setTemp(0, temp); + } + const LAllocation *string() { + return getOperand(0); + } + const LAllocation *begin() { + return getOperand(1); + } + const LAllocation *length() { + return getOperand(2); + } + const LDefinition *temp() { + return getTemp(0); + } + const MStringSplit *mir() const { + return mir_->toStringSplit(); + } +}; + // Convert a 32-bit integer to a double. class LInt32ToDouble : public LInstructionHelper<1, 1, 0> { diff --git a/js/src/jit/LOpcodes.h b/js/src/jit/LOpcodes.h index 5285616b66c9..fef23e917648 100644 --- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -185,6 +185,7 @@ _(RegExpTest) \ _(RegExpReplace) \ _(StringReplace) \ + _(Substr) \ _(Lambda) \ _(LambdaArrow) \ _(LambdaForSingleton) \ diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 2e1c807332b5..ccefbe721b85 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2153,6 +2153,16 @@ LIRGenerator::visitStringReplace(MStringReplace *ins) return defineReturn(lir, ins) && assignSafepoint(lir, ins); } +bool +LIRGenerator::visitSubstr(MSubstr *ins) +{ + LSubstr *lir = new (alloc()) LSubstr(useFixed(ins->string(), CallTempReg1), + useRegister(ins->begin()), + useRegister(ins->length()), + temp()); + return define(lir, ins) && assignSafepoint(lir, ins); +} + bool LIRGenerator::visitLambda(MLambda *ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index ca8523a0091d..81adf48f846b 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -144,6 +144,7 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitCharCodeAt(MCharCodeAt *ins); bool visitFromCharCode(MFromCharCode *ins); bool visitStringSplit(MStringSplit *ins); + bool visitSubstr(MSubstr *ins); bool visitStart(MStart *start); bool visitOsrEntry(MOsrEntry *entry); bool visitNop(MNop *nop); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index e3b9ff54c594..e0cc6e86415b 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -197,6 +197,8 @@ IonBuilder::inlineNativeCall(CallInfo &callInfo, JSFunction *target) return inlineToString(callInfo); if (native == intrinsic_IsConstructing) return inlineIsConstructing(callInfo); + if (native == intrinsic_SubstringKernel) + return inlineSubstringKernel(callInfo); // TypedObject intrinsics. if (native == intrinsic_ObjectIsTypedObject) @@ -1538,6 +1540,38 @@ IonBuilder::inlineStrReplace(CallInfo &callInfo) return InliningStatus_Inlined; } +IonBuilder::InliningStatus +IonBuilder::inlineSubstringKernel(CallInfo &callInfo) +{ + MOZ_ASSERT(callInfo.argc() == 3); + MOZ_ASSERT(!callInfo.constructing()); + + // Return: String. + if (getInlineReturnType() != MIRType_String) + return InliningStatus_NotInlined; + + // Arg 0: String. + if (callInfo.getArg(0)->type() != MIRType_String) + return InliningStatus_NotInlined; + + // Arg 1: Int. + if (callInfo.getArg(1)->type() != MIRType_Int32) + return InliningStatus_NotInlined; + + // Arg 2: Int. + if (callInfo.getArg(2)->type() != MIRType_Int32) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MSubstr *substr = MSubstr::New(alloc(), callInfo.getArg(0), callInfo.getArg(1), + callInfo.getArg(2)); + current->add(substr); + current->push(substr); + + return InliningStatus_Inlined; +} + IonBuilder::InliningStatus IonBuilder::inlineUnsafePutElements(CallInfo &callInfo) { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index cade1c276211..cd1c148b43e1 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -6854,6 +6854,47 @@ class MStringReplace } }; +class MSubstr + : public MTernaryInstruction, + public Mix3Policy, IntPolicy<1>, IntPolicy<2>> +{ + private: + + MSubstr(MDefinition *string, MDefinition *begin, MDefinition *length) + : MTernaryInstruction(string, begin, length) + { + setResultType(MIRType_String); + } + + public: + INSTRUCTION_HEADER(Substr); + + static MSubstr *New(TempAllocator &alloc, MDefinition *string, MDefinition *begin, + MDefinition *length) + { + return new(alloc) MSubstr(string, begin, length); + } + + MDefinition *string() { + return getOperand(0); + } + + MDefinition *begin() { + return getOperand(1); + } + + MDefinition *length() { + return getOperand(2); + } + + bool congruentTo(const MDefinition *ins) const { + return congruentIfOperandsEqual(ins); + } + AliasSet getAliasSet() const { + return AliasSet::None(); + } +}; + struct LambdaFunctionInfo { // The functions used in lambdas are the canonical original function in diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index e62645939f44..782fd42da38b 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -95,6 +95,7 @@ namespace jit { _(CharCodeAt) \ _(FromCharCode) \ _(StringSplit) \ + _(Substr) \ _(Return) \ _(Throw) \ _(Box) \ diff --git a/js/src/jit/ParallelSafetyAnalysis.cpp b/js/src/jit/ParallelSafetyAnalysis.cpp index dab075fccd88..3e2f848047b0 100644 --- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -320,6 +320,7 @@ class ParallelSafetyVisitor : public MDefinitionVisitor UNSAFE_OP(CallInstanceOf) UNSAFE_OP(ProfilerStackOp) UNSAFE_OP(GuardString) + UNSAFE_OP(Substr) UNSAFE_OP(NewDeclEnvObject) UNSAFE_OP(In) UNSAFE_OP(InArray) diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 91e1dd45f4b8..eb47e1df753b 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -977,6 +977,7 @@ bool intrinsic_IsCallable(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_ThrowError(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_NewDenseArray(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_IsConstructing(JSContext *cx, unsigned argc, Value *vp); +bool intrinsic_SubstringKernel(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_UnsafePutElements(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_DefineDataProperty(JSContext *cx, unsigned argc, Value *vp); diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index c74f06e6a0ae..9314131392a3 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -575,9 +575,18 @@ ValueToIntegerRange(JSContext *cx, HandleValue v, int32_t *out) return true; } -static JSString * -DoSubstr(JSContext *cx, JSString *str, size_t begin, size_t len) +bool +js::SubstringKernel(JSContext *cx, HandleString str, int32_t beginInt, int32_t lengthInt, + MutableHandleString substr) { + MOZ_ASSERT(0 <= beginInt); + MOZ_ASSERT(0 <= lengthInt); + MOZ_ASSERT(beginInt <= str->length()); + MOZ_ASSERT(lengthInt <= str->length() - beginInt); + + uint32_t begin = beginInt; + uint32_t len = lengthInt; + /* * Optimization for one level deep ropes. * This is common for the following pattern: @@ -592,15 +601,21 @@ DoSubstr(JSContext *cx, JSString *str, size_t begin, size_t len) /* Substring is totally in leftChild of rope. */ if (begin + len <= rope->leftChild()->length()) { - str = rope->leftChild(); - return NewDependentString(cx, str, begin, len); + JSLinearString *str = NewDependentString(cx, rope->leftChild(), begin, len); + if (!str) + return false; + substr.set(str); + return true; } /* Substring is totally in rightChild of rope. */ if (begin >= rope->leftChild()->length()) { - str = rope->rightChild(); begin -= rope->leftChild()->length(); - return NewDependentString(cx, str, begin, len); + JSLinearString *str = NewDependentString(cx, rope->rightChild(), begin, len); + if (!str) + return false; + substr.set(str); + return true; } /* @@ -616,74 +631,17 @@ DoSubstr(JSContext *cx, JSString *str, size_t begin, size_t len) Rooted ropeRoot(cx, rope); RootedString lhs(cx, NewDependentString(cx, ropeRoot->leftChild(), begin, lhsLength)); if (!lhs) - return nullptr; + return false; RootedString rhs(cx, NewDependentString(cx, ropeRoot->rightChild(), 0, rhsLength)); if (!rhs) - return nullptr; - - return JSRope::new_(cx, lhs, rhs, len); - } - - return NewDependentString(cx, str, begin, len); -} - -bool -js::str_substring(JSContext *cx, unsigned argc, Value *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - JSString *str = ThisToStringForStringProto(cx, args); - if (!str) - return false; - - int32_t length, begin, end; - if (args.length() > 0) { - end = length = int32_t(str->length()); - - if (args[0].isInt32()) { - begin = args[0].toInt32(); - } else { - RootedString strRoot(cx, str); - if (!ValueToIntegerRange(cx, args[0], &begin)) - return false; - str = strRoot; - } - - if (begin < 0) - begin = 0; - else if (begin > length) - begin = length; - - if (args.hasDefined(1)) { - if (args[1].isInt32()) { - end = args[1].toInt32(); - } else { - RootedString strRoot(cx, str); - if (!ValueToIntegerRange(cx, args[1], &end)) - return false; - str = strRoot; - } - - if (end > length) { - end = length; - } else { - if (end < 0) - end = 0; - if (end < begin) { - int32_t tmp = begin; - begin = end; - end = tmp; - } - } - } - - str = DoSubstr(cx, str, size_t(begin), size_t(end - begin)); - if (!str) return false; + + substr.set(JSRope::new_(cx, lhs, rhs, len)); + return true; } - args.rval().setString(str); + substr.set(NewDependentString(cx, str, begin, len)); return true; } @@ -3992,54 +3950,6 @@ js::str_split_string(JSContext *cx, HandleTypeObject type, HandleString str, Han return aobj; } -static bool -str_substr(JSContext *cx, unsigned argc, Value *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - RootedString str(cx, ThisToStringForStringProto(cx, args)); - if (!str) - return false; - - int32_t length, len, begin; - if (args.length() > 0) { - length = int32_t(str->length()); - if (!ValueToIntegerRange(cx, args[0], &begin)) - return false; - - if (begin >= length) { - args.rval().setString(cx->runtime()->emptyString); - return true; - } - if (begin < 0) { - begin += length; /* length + INT_MIN will always be less than 0 */ - if (begin < 0) - begin = 0; - } - - if (args.hasDefined(1)) { - if (!ValueToIntegerRange(cx, args[1], &len)) - return false; - - if (len <= 0) { - args.rval().setString(cx->runtime()->emptyString); - return true; - } - - if (uint32_t(length) < uint32_t(begin + len)) - len = length - begin; - } else { - len = length - begin; - } - - str = DoSubstr(cx, str, size_t(begin), size_t(len)); - if (!str) - return false; - } - - args.rval().setString(str); - return true; -} - /* * Python-esque sequence operations. */ @@ -4076,73 +3986,6 @@ str_concat(JSContext *cx, unsigned argc, Value *vp) return true; } -static bool -str_slice(JSContext *cx, unsigned argc, Value *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - if (args.length() == 1 && args.thisv().isString() && args[0].isInt32()) { - JSString *str = args.thisv().toString(); - size_t begin = args[0].toInt32(); - size_t end = str->length(); - if (begin <= end) { - size_t length = end - begin; - if (length == 0) { - str = cx->runtime()->emptyString; - } else { - str = (length == 1) - ? cx->staticStrings().getUnitStringForElement(cx, str, begin) - : NewDependentString(cx, str, begin, length); - if (!str) - return false; - } - args.rval().setString(str); - return true; - } - } - - RootedString str(cx, ThisToStringForStringProto(cx, args)); - if (!str) - return false; - - if (args.length() != 0) { - double begin, end, length; - - if (!ToInteger(cx, args[0], &begin)) - return false; - length = str->length(); - if (begin < 0) { - begin += length; - if (begin < 0) - begin = 0; - } else if (begin > length) { - begin = length; - } - - if (args.hasDefined(1)) { - if (!ToInteger(cx, args[1], &end)) - return false; - if (end < 0) { - end += length; - if (end < 0) - end = 0; - } else if (end > length) { - end = length; - } - if (end < begin) - end = begin; - } else { - end = length; - } - - str = NewDependentString(cx, str, size_t(begin), size_t(end - begin)); - if (!str) - return false; - } - args.rval().setString(str); - return true; -} - static const JSFunctionSpec string_methods[] = { #if JS_HAS_TOSOURCE JS_FN("quote", str_quote, 0,JSFUN_GENERIC_NATIVE), @@ -4152,11 +3995,11 @@ static const JSFunctionSpec string_methods[] = { /* Java-like methods. */ JS_FN(js_toString_str, js_str_toString, 0,0), JS_FN(js_valueOf_str, js_str_toString, 0,0), - JS_FN("substring", str_substring, 2,JSFUN_GENERIC_NATIVE), JS_FN("toLowerCase", str_toLowerCase, 0,JSFUN_GENERIC_NATIVE), JS_FN("toUpperCase", str_toUpperCase, 0,JSFUN_GENERIC_NATIVE), JS_FN("charAt", js_str_charAt, 1,JSFUN_GENERIC_NATIVE), JS_FN("charCodeAt", js_str_charCodeAt, 1,JSFUN_GENERIC_NATIVE), + JS_SELF_HOSTED_FN("substring", "String_substring", 2,0), JS_SELF_HOSTED_FN("codePointAt", "String_codePointAt", 1,0), JS_FN("contains", str_contains, 1,JSFUN_GENERIC_NATIVE), JS_FN("indexOf", str_indexOf, 1,JSFUN_GENERIC_NATIVE), @@ -4183,11 +4026,11 @@ static const JSFunctionSpec string_methods[] = { JS_FN("search", str_search, 1,JSFUN_GENERIC_NATIVE), JS_FN("replace", str_replace, 2,JSFUN_GENERIC_NATIVE), JS_FN("split", str_split, 2,JSFUN_GENERIC_NATIVE), - JS_FN("substr", str_substr, 2,JSFUN_GENERIC_NATIVE), + JS_SELF_HOSTED_FN("substr", "String_substr", 2,0), /* Python-esque sequence methods. */ JS_FN("concat", str_concat, 1,JSFUN_GENERIC_NATIVE), - JS_FN("slice", str_slice, 2,JSFUN_GENERIC_NATIVE), + JS_SELF_HOSTED_FN("slice", "String_slice", 2,0), /* HTML string methods. */ JS_SELF_HOSTED_FN("bold", "String_bold", 0,0), @@ -4293,13 +4136,17 @@ js::str_fromCharCode_one_arg(JSContext *cx, HandleValue code, MutableHandleValue static const JSFunctionSpec string_static_methods[] = { JS_FN("fromCharCode", js::str_fromCharCode, 1, 0), - JS_SELF_HOSTED_FN("fromCodePoint", "String_static_fromCodePoint", 1, 0), - JS_SELF_HOSTED_FN("raw", "String_static_raw", 2, 0), + + JS_SELF_HOSTED_FN("fromCodePoint", "String_static_fromCodePoint", 1,0), + JS_SELF_HOSTED_FN("raw", "String_static_raw", 2,0), + JS_SELF_HOSTED_FN("substring", "String_static_substring", 3,0), + JS_SELF_HOSTED_FN("substr", "String_static_substr", 3,0), + JS_SELF_HOSTED_FN("slice", "String_static_slice", 3,0), // This must be at the end because of bug 853075: functions listed after // self-hosted methods aren't available in self-hosted code. #if EXPOSE_INTL_API - JS_SELF_HOSTED_FN("localeCompare", "String_static_localeCompare", 2,0), + JS_SELF_HOSTED_FN("localeCompare", "String_static_localeCompare", 2,0), #endif JS_FS_END }; diff --git a/js/src/jsstr.h b/js/src/jsstr.h index 5231ab133698..3f17625ac7e6 100644 --- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -249,6 +249,15 @@ EqualChars(const Char1 *s1, const Char2 *s2, size_t len) return true; } +/* + * Computes |str|'s substring for the range [beginInt, beginInt + lengthInt). + * Negative, overlarge, swapped, etc. |beginInt| and |lengthInt| are forbidden + * and constitute API misuse. + */ +bool +SubstringKernel(JSContext *cx, HandleString str, int32_t beginInt, int32_t lengthInt, + MutableHandleString substr); + /* * Inflate bytes in ASCII encoding to char16_t code units. Return null on error, * otherwise return the char16_t buffer that was malloc'ed. length is updated to @@ -311,9 +320,6 @@ str_lastIndexOf(JSContext *cx, unsigned argc, Value *vp); extern bool str_startsWith(JSContext *cx, unsigned argc, Value *vp); -extern bool -str_substring(JSContext *cx, unsigned argc, Value *vp); - extern bool str_toLowerCase(JSContext *cx, unsigned argc, Value *vp); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 31e2951a747f..2865c0550339 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -118,6 +118,26 @@ intrinsic_IsConstructor(JSContext *cx, unsigned argc, Value *vp) return true; } +bool +js::intrinsic_SubstringKernel(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args[0].isString()); + MOZ_ASSERT(args[1].isInt32()); + MOZ_ASSERT(args[2].isInt32()); + + RootedString str(cx, args[0].toString()); + int32_t begin = args[1].toInt32(); + int32_t length = args[2].toInt32(); + + RootedString substr(cx); + if (!SubstringKernel(cx, str, begin, length, &substr)) + return false; + + args.rval().setString(substr); + return true; +} + static bool intrinsic_OwnPropertyKeys(JSContext *cx, unsigned argc, Value *vp) { @@ -1015,7 +1035,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("std_String_replace", str_replace, 2,0), JS_FN("std_String_split", str_split, 2,0), JS_FN("std_String_startsWith", str_startsWith, 1,0), - JS_FN("std_String_substring", str_substring, 2,0), JS_FN("std_String_toLowerCase", str_toLowerCase, 0,0), JS_FN("std_String_toUpperCase", str_toUpperCase, 0,0), @@ -1040,6 +1059,7 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("_IsConstructing", intrinsic_IsConstructing, 0,0), JS_FN("DecompileArg", intrinsic_DecompileArg, 2,0), JS_FN("RuntimeDefaultLocale", intrinsic_RuntimeDefaultLocale, 0,0), + JS_FN("SubstringKernel", intrinsic_SubstringKernel, 3,0), JS_FN("UnsafePutElements", intrinsic_UnsafePutElements, 3,0), JS_FN("_DefineDataProperty", intrinsic_DefineDataProperty, 4,0), diff --git a/js/src/vm/String.h b/js/src/vm/String.h index 5cd432afef6a..5f134309050e 100644 --- a/js/src/vm/String.h +++ b/js/src/vm/String.h @@ -475,6 +475,9 @@ class JSString : public js::gc::TenuredCell } static size_t offsetOfNonInlineChars() { + static_assert(offsetof(JSString, d.s.u2.nonInlineCharsTwoByte) == + offsetof(JSString, d.s.u2.nonInlineCharsLatin1), + "nonInlineCharsTwoByte and nonInlineCharsLatin1 must have same offset"); return offsetof(JSString, d.s.u2.nonInlineCharsTwoByte); }