☠☠ backed out by 672874702338 ☠ ☠ | |
author | Hannes Verschore <hv1989@gmail.com> |
Tue, 04 Nov 2014 09:43:00 +0100 | |
changeset 213906 | 88041cfff52074cd1f2fd6dfb53f0ce6d9d5ba3c |
parent 213905 | 53d51e7fbb9e234e7931875afb3ec7612ca7198f |
child 213907 | 619279d965d199fe13c028f49944371a9271e626 |
push id | 27768 |
push user | kwierso@gmail.com |
push date | Wed, 05 Nov 2014 02:19:03 +0000 |
treeherder | mozilla-central@a1823d3c7365 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | waldo, till |
bugs | 1052839 |
milestone | 36.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
|
--- a/js/src/builtin/String.js +++ b/js/src/builtin/String.js @@ -1,14 +1,134 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /*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. CheckObjectCoercible(this); var S = ToString(this); // Steps 4-5. var position = ToInteger(pos);
--- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -27,16 +27,17 @@ // All C++-implemented standard builtins library functions used in self-hosted // code are installed via the std_functions JSFunctionSpec[] in // SelfHosting.cpp. // // 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. var std_StopIteration = StopIteration; /********** List specification type **********/
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -5939,16 +5939,121 @@ ConcatFatInlineString(MacroAssembler &ma if (mode == ParallelExecution) masm.pop(temp3); } masm.ret(); } +typedef bool (*SubstringKernelFn)(JSContext *cx, HandleString str, int32_t begin, int32_t len, + MutableHandleString rstring); +static const VMFunction SubstringKernelInfo = + FunctionInfo<SubstringKernelFn>(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) { MacroAssembler masm(cx); Register lhs = CallTempReg0; Register rhs = CallTempReg1; Register temp1 = CallTempReg2;
--- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -181,16 +181,17 @@ class CodeGenerator : public CodeGenerat bool visitSetArrayLength(LSetArrayLength *lir); bool visitTypedArrayLength(LTypedArrayLength *lir); bool visitTypedArrayElements(LTypedArrayElements *lir); bool visitTypedObjectElements(LTypedObjectElements *lir); bool visitSetTypedObjectOffset(LSetTypedObjectOffset *lir); 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); bool visitNotV(LNotV *ins); bool visitBoundsCheck(LBoundsCheck *lir); bool visitBoundsCheckRange(LBoundsCheckRange *lir); bool visitBoundsCheckLower(LBoundsCheckLower *lir); bool visitLoadFixedSlotV(LLoadFixedSlotV *ins);
--- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -760,16 +760,17 @@ class IonBuilder InliningStatus inlineToInteger(CallInfo &callInfo); InliningStatus inlineToString(CallInfo &callInfo); InliningStatus inlineDump(CallInfo &callInfo); InliningStatus inlineHasClass(CallInfo &callInfo, const Class *clasp, const Class *clasp2 = nullptr, const Class *clasp3 = nullptr, const Class *clasp4 = nullptr); InliningStatus inlineIsConstructing(CallInfo &callInfo); + InliningStatus inlineSubstringKernel(CallInfo &callInfo); // Testing functions. InliningStatus inlineForceSequentialOrInParallelSection(CallInfo &callInfo); InliningStatus inlineBailout(CallInfo &callInfo); InliningStatus inlineAssertFloat32(CallInfo &callInfo); // Bind function. InliningStatus inlineBoundFunction(CallInfo &callInfo, JSFunction *target);
--- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -3387,16 +3387,46 @@ class LStringSplit : public LCallInstruc const LAllocation *separator() { return getOperand(1); } const MStringSplit *mir() const { return mir_->toStringSplit(); } }; +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> { public: LIR_HEADER(Int32ToDouble) explicit LInt32ToDouble(const LAllocation &input) { setOperand(0, input);
--- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -180,16 +180,17 @@ _(OsrScopeChain) \ _(OsrReturnValue) \ _(OsrArgumentsObject) \ _(RegExp) \ _(RegExpExec) \ _(RegExpTest) \ _(RegExpReplace) \ _(StringReplace) \ + _(Substr) \ _(Lambda) \ _(LambdaArrow) \ _(LambdaForSingleton) \ _(LambdaPar) \ _(Slots) \ _(Elements) \ _(ConvertElementsToDoubles) \ _(MaybeToDoubleElement) \
--- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2149,16 +2149,26 @@ LIRGenerator::visitStringReplace(MString LStringReplace *lir = new(alloc()) LStringReplace(useRegisterOrConstantAtStart(ins->string()), useRegisterAtStart(ins->pattern()), useRegisterOrConstantAtStart(ins->replacement())); 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) { if (ins->info().singletonType || ins->info().useNewTypeForClone) { // If the function has a singleton type, this instruction will only be // executed once so we don't bother inlining it. // // If UseNewTypeForClone is true, we will assign a singleton type to // the clone and we have to clone the script, we can't do that inline.
--- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -139,16 +139,17 @@ class LIRGenerator : public LIRGenerator bool visitMul(MMul *ins); bool visitDiv(MDiv *ins); bool visitMod(MMod *ins); bool visitConcat(MConcat *ins); bool visitConcatPar(MConcatPar *ins); 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); bool visitLimitedTruncate(MLimitedTruncate *nop); bool visitOsrValue(MOsrValue *value); bool visitOsrScopeChain(MOsrScopeChain *object); bool visitOsrReturnValue(MOsrReturnValue *value); bool visitOsrArgumentsObject(MOsrArgumentsObject *object);
--- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -192,16 +192,18 @@ IonBuilder::inlineNativeCall(CallInfo &c if (native == intrinsic_IsObject) return inlineIsObject(callInfo); if (native == intrinsic_ToInteger) return inlineToInteger(callInfo); if (native == intrinsic_ToString) return inlineToString(callInfo); if (native == intrinsic_IsConstructing) return inlineIsConstructing(callInfo); + if (native == intrinsic_SubstringKernel) + return inlineSubstringKernel(callInfo); // TypedObject intrinsics. if (native == intrinsic_ObjectIsTypedObject) return inlineHasClass(callInfo, &OutlineTransparentTypedObject::class_, &OutlineOpaqueTypedObject::class_, &InlineTransparentTypedObject::class_, &InlineOpaqueTypedObject::class_); @@ -1534,16 +1536,48 @@ IonBuilder::inlineStrReplace(CallInfo &c current->add(cte); current->push(cte); if (cte->isEffectful() && !resumeAfter(cte)) return InliningStatus_Error; 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) { uint32_t argc = callInfo.argc(); if (argc < 3 || (argc % 3) != 0 || callInfo.constructing()) return InliningStatus_NotInlined; /* Important: *
--- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -6849,16 +6849,57 @@ class MStringReplace return congruentIfOperandsEqual(ins); } AliasSet getAliasSet() const { return AliasSet::None(); } }; +class MSubstr + : public MTernaryInstruction, + public Mix3Policy<StringPolicy<0>, 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 // the script, and are immutable except for delazification. Record this // information while still on the main thread to avoid races. AlwaysTenuredFunction fun; uint16_t flags; gc::Cell *scriptOrLazyScript;
--- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -90,16 +90,17 @@ namespace jit { _(Mul) \ _(Div) \ _(Mod) \ _(Concat) \ _(ConcatPar) \ _(CharCodeAt) \ _(FromCharCode) \ _(StringSplit) \ + _(Substr) \ _(Return) \ _(Throw) \ _(Box) \ _(Unbox) \ _(GuardObject) \ _(GuardString) \ _(AssertRange) \ _(ToDouble) \
--- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -315,16 +315,17 @@ class ParallelSafetyVisitor : public MDe SAFE_OP(PowHalf) UNSAFE_OP(RegExpTest) UNSAFE_OP(RegExpExec) UNSAFE_OP(RegExpReplace) UNSAFE_OP(StringReplace) UNSAFE_OP(CallInstanceOf) UNSAFE_OP(ProfilerStackOp) UNSAFE_OP(GuardString) + UNSAFE_OP(Substr) UNSAFE_OP(NewDeclEnvObject) UNSAFE_OP(In) UNSAFE_OP(InArray) SAFE_OP(GuardThreadExclusive) SAFE_OP(InterruptCheckPar) SAFE_OP(CheckOverRecursedPar) SAFE_OP(FunctionDispatch) SAFE_OP(TypeObjectDispatch)
--- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -972,16 +972,17 @@ class AutoAssertNoException bool intrinsic_ToObject(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_IsObject(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_ToInteger(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_ToString(JSContext *cx, unsigned argc, Value *vp); 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); bool intrinsic_UnsafeSetReservedSlot(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_UnsafeGetReservedSlot(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_HaveSameClass(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_IsPackedArray(JSContext *cx, unsigned argc, Value *vp);
--- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -570,125 +570,83 @@ ValueToIntegerRange(JSContext *cx, Handl *out = INT32_MIN; else *out = int32_t(d); } 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: * * while() { * text = text.substr(0, x) + "bla" + text.substr(x) * test.charCodeAt(x + 1) * } */ if (str->isRope()) { JSRope *rope = &str->asRope(); /* 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; } /* * Requested substring is partly in the left and partly in right child. * Create a rope of substrings for both childs. */ MOZ_ASSERT(begin < rope->leftChild()->length() && begin + len > rope->leftChild()->length()); size_t lhsLength = rope->leftChild()->length() - begin; size_t rhsLength = begin + len - rope->leftChild()->length(); Rooted<JSRope *> 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_<CanGC>(cx, lhs, rhs, len); + return false; + + substr.set(JSRope::new_<CanGC>(cx, lhs, rhs, len)); + return true; } - 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; - } - - args.rval().setString(str); + substr.set(NewDependentString(cx, str, begin, len)); return true; } template <typename CharT> static JSString * ToLowerCase(JSContext *cx, JSLinearString *str) { // Unlike toUpperCase, toLowerCase has the nice invariant that if the input @@ -3987,64 +3945,16 @@ js::str_split_string(JSContext *cx, Hand if (!aobj) return nullptr; aobj->setType(type); 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. */ static bool str_concat(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); JSString *str = ThisToStringForStringProto(cx, args); @@ -4071,97 +3981,30 @@ str_concat(JSContext *cx, unsigned argc, return false; } } args.rval().setString(str); 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), JS_FN(js_toSource_str, str_toSource, 0,0), #endif /* 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), JS_FN("lastIndexOf", str_lastIndexOf, 1,JSFUN_GENERIC_NATIVE), JS_FN("startsWith", str_startsWith, 1,JSFUN_GENERIC_NATIVE), JS_FN("endsWith", str_endsWith, 1,JSFUN_GENERIC_NATIVE), JS_FN("trim", str_trim, 0,JSFUN_GENERIC_NATIVE), JS_FN("trimLeft", str_trimLeft, 0,JSFUN_GENERIC_NATIVE), @@ -4178,21 +4021,21 @@ static const JSFunctionSpec string_metho JS_FN("normalize", str_normalize, 0,JSFUN_GENERIC_NATIVE), #endif /* Perl-ish methods (search is actually Python-esque). */ JS_FN("match", str_match, 1,JSFUN_GENERIC_NATIVE), 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), JS_SELF_HOSTED_FN("italics", "String_italics", 0,0), JS_SELF_HOSTED_FN("fixed", "String_fixed", 0,0), JS_SELF_HOSTED_FN("strike", "String_strike", 0,0), JS_SELF_HOSTED_FN("small", "String_small", 0,0), JS_SELF_HOSTED_FN("big", "String_big", 0,0), @@ -4288,23 +4131,27 @@ js::str_fromCharCode_one_arg(JSContext * return false; rval.setString(str); return true; } 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 }; /* static */ Shape * StringObject::assignInitialShape(ExclusiveContext *cx, Handle<StringObject*> obj) { MOZ_ASSERT(obj->empty());
--- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -245,16 +245,25 @@ EqualChars(const Char1 *s1, const Char2 for (const Char1 *s1end = s1 + len; s1 < s1end; s1++, s2++) { if (*s1 != *s2) return false; } 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 * the length of the new string (in char16_t code units). A null char is * appended, but it is not included in the length. */ extern char16_t * InflateString(ThreadSafeContext *cx, const char *bytes, size_t *length); @@ -307,19 +316,16 @@ str_indexOf(JSContext *cx, unsigned argc extern bool 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); extern bool str_toUpperCase(JSContext *cx, unsigned argc, Value *vp); } /* namespace js */ extern bool
--- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -113,16 +113,36 @@ js::intrinsic_IsCallable(JSContext *cx, static bool intrinsic_IsConstructor(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setBoolean(IsConstructor(args[0])); 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) { CallArgs args = CallArgsFromVp(argc, vp); return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS); } @@ -1010,17 +1030,16 @@ static const JSFunctionSpec intrinsic_fu JS_FN("std_String_fromCharCode", str_fromCharCode, 1,0), JS_FN("std_String_charCodeAt", js_str_charCodeAt, 1,0), JS_FN("std_String_indexOf", str_indexOf, 1,0), JS_FN("std_String_lastIndexOf", str_lastIndexOf, 1,0), JS_FN("std_String_match", str_match, 1,0), 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), JS_FN("std_WeakMap_has", WeakMap_has, 1,0), JS_FN("std_WeakMap_get", WeakMap_get, 2,0), JS_FN("std_WeakMap_set", WeakMap_set, 2,0), JS_FN("std_WeakMap_delete", WeakMap_delete, 1,0), JS_FN("std_WeakMap_clear", WeakMap_clear, 0,0), @@ -1035,16 +1054,17 @@ static const JSFunctionSpec intrinsic_fu JS_FN("OwnPropertyKeys", intrinsic_OwnPropertyKeys, 1,0), JS_FN("ThrowError", intrinsic_ThrowError, 4,0), JS_FN("AssertionFailed", intrinsic_AssertionFailed, 1,0), JS_FN("SetScriptHints", intrinsic_SetScriptHints, 2,0), JS_FN("MakeConstructible", intrinsic_MakeConstructible, 2,0), 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), JS_FN("UnsafeSetReservedSlot", intrinsic_UnsafeSetReservedSlot, 3,0), JS_FN("UnsafeGetReservedSlot", intrinsic_UnsafeGetReservedSlot, 2,0), JS_FN("HaveSameClass", intrinsic_HaveSameClass, 2,0), JS_FN("IsPackedArray", intrinsic_IsPackedArray, 1,0),
--- a/js/src/vm/String.h +++ b/js/src/vm/String.h @@ -470,16 +470,19 @@ class JSString : public js::gc::TenuredC static size_t offsetOfLength() { return offsetof(JSString, d.u1.length); } static size_t offsetOfFlags() { return offsetof(JSString, d.u1.flags); } 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); } static inline js::ThingRootKind rootKind() { return js::THING_ROOT_STRING; } #ifdef DEBUG void dump(); void dumpCharsNoNewline(FILE *fp=stderr);