From b90f317a8c40db94d7dfda37e3375c1af402596f Mon Sep 17 00:00:00 2001 From: Jan de Mooij Date: Sat, 31 May 2014 10:44:32 +0200 Subject: [PATCH] Bug 1015917 part 1 - Support string concatenation for Latin1 strings. r=luke --- js/src/gc/Statistics.cpp | 2 +- js/src/jit/CodeGenerator.cpp | 215 ++++++++++++------ js/src/jit/ParallelFunctions.cpp | 7 +- js/src/jit/arm/MacroAssembler-arm.cpp | 7 + js/src/jit/arm/MacroAssembler-arm.h | 1 + js/src/jit/shared/MacroAssembler-x86-shared.h | 3 + js/src/jsapi.cpp | 4 +- js/src/jsatom.cpp | 2 +- js/src/jsnum.cpp | 9 +- js/src/jsstr.cpp | 2 +- js/src/jsstr.h | 2 +- js/src/vm/CharacterEncoding.cpp | 4 +- js/src/vm/SelfHosting.cpp | 5 +- js/src/vm/String-inl.h | 38 +++- js/src/vm/String.cpp | 50 +++- js/src/vm/String.h | 47 +++- js/src/vm/StringBuffer.h | 2 +- 17 files changed, 284 insertions(+), 116 deletions(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 16b3a20310e5..01f625f74bf4 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -137,7 +137,7 @@ class gcstats::StatisticsSerializer return nullptr; } - InflateStringToBuffer(buf, nchars, out); + CopyAndInflateChars(out, buf, nchars); js_free(buf); out[nchars] = 0; diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index fb690251abb7..7654015fcf1e 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -5154,7 +5154,8 @@ CodeGenerator::visitConcatPar(LConcatPar *lir) } static void -CopyStringChars(MacroAssembler &masm, Register to, Register from, Register len, Register scratch) +CopyStringChars(MacroAssembler &masm, Register to, Register from, Register len, Register scratch, + size_t fromWidth, size_t toWidth) { // 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. @@ -5166,17 +5167,125 @@ CopyStringChars(MacroAssembler &masm, Register to, Register from, Register len, masm.bind(&ok); #endif - JS_STATIC_ASSERT(sizeof(jschar) == 2); + MOZ_ASSERT(fromWidth == 1 || fromWidth == 2); + MOZ_ASSERT(toWidth == 1 || toWidth == 2); + MOZ_ASSERT_IF(toWidth == 1, fromWidth == 1); 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); + if (fromWidth == 2) + masm.load16ZeroExtend(Address(from, 0), scratch); + else + masm.load8ZeroExtend(Address(from, 0), scratch); + if (toWidth == 2) + masm.store16(scratch, Address(to, 0)); + else + masm.store8(scratch, Address(to, 0)); + masm.addPtr(Imm32(fromWidth), from); + masm.addPtr(Imm32(toWidth), to); masm.branchSub32(Assembler::NonZero, Imm32(1), len, &start); } +static void +CopyStringCharsMaybeInflate(MacroAssembler &masm, Register input, Register destChars, + Register temp1, Register temp2) +{ + // destChars is TwoByte and input is a Latin1 or TwoByte string, so we may + // have to inflate. + + Label isLatin1, done; + masm.loadStringLength(input, temp1); + masm.branchTest32(Assembler::NonZero, Address(input, JSString::offsetOfFlags()), + Imm32(JSString::LATIN1_CHARS_BIT), &isLatin1); + { + masm.loadStringChars(input, input); + CopyStringChars(masm, destChars, input, temp1, temp2, sizeof(jschar), sizeof(jschar)); + masm.jump(&done); + } + masm.bind(&isLatin1); + { + masm.loadStringChars(input, input); + CopyStringChars(masm, destChars, input, temp1, temp2, sizeof(char), sizeof(jschar)); + } + masm.bind(&done); +} + +static void +ConcatFatInlineString(MacroAssembler &masm, Register lhs, Register rhs, Register output, + Register temp1, Register temp2, Register temp3, Register forkJoinContext, + ExecutionMode mode, Label *failure, Label *failurePopTemps, bool isTwoByte) +{ + // State: result length in temp2. + + // Ensure both strings are linear. + masm.branchIfRope(lhs, failure); + masm.branchIfRope(rhs, failure); + + // Allocate a JSFatInlineString. + switch (mode) { + case SequentialExecution: + masm.newGCFatInlineString(output, temp1, failure); + break; + case ParallelExecution: + masm.push(temp1); + masm.push(temp2); + masm.newGCFatInlineStringPar(output, forkJoinContext, temp1, temp2, failurePopTemps); + masm.pop(temp2); + masm.pop(temp1); + break; + default: + MOZ_ASSUME_UNREACHABLE("No such execution mode"); + } + + // Store length and flags. + uint32_t flags = JSString::INIT_FAT_INLINE_FLAGS; + if (!isTwoByte) + flags |= JSString::LATIN1_CHARS_BIT; + masm.store32(Imm32(flags), Address(output, JSString::offsetOfFlags())); + masm.store32(temp2, Address(output, JSString::offsetOfLength())); + + // Load chars pointer in temp2. + masm.computeEffectiveAddress(Address(output, JSInlineString::offsetOfInlineStorage()), temp2); + + { + // We use temp3 in this block, which in parallel execution also holds + // a live ForkJoinContext pointer. If we are compiling for parallel + // execution, be sure to save and restore the ForkJoinContext. + if (mode == ParallelExecution) + masm.push(temp3); + + // Copy lhs chars. Note that this advances temp2 to point to the next + // char. This also clobbers the lhs register. + if (isTwoByte) { + CopyStringCharsMaybeInflate(masm, lhs, temp2, temp1, temp3); + } else { + masm.loadStringLength(lhs, temp3); + masm.loadStringChars(lhs, lhs); + CopyStringChars(masm, temp2, lhs, temp3, temp1, sizeof(char), sizeof(char)); + } + + // Copy rhs chars. Clobbers the rhs register. + if (isTwoByte) { + CopyStringCharsMaybeInflate(masm, rhs, temp2, temp1, temp3); + } else { + masm.loadStringLength(rhs, temp3); + masm.loadStringChars(rhs, rhs); + CopyStringChars(masm, temp2, rhs, temp3, temp1, sizeof(char), sizeof(char)); + } + + // Null-terminate. + if (isTwoByte) + masm.store16(Imm32(0), Address(temp2, 0)); + else + masm.store8(Imm32(0), Address(temp2, 0)); + + if (mode == ParallelExecution) + masm.pop(temp3); + } + + masm.ret(); +} + JitCode * JitCompartment::generateStringConcatStub(JSContext *cx, ExecutionMode mode) { @@ -5208,10 +5317,27 @@ JitCompartment::generateStringConcatStub(JSContext *cx, ExecutionMode mode) masm.add32(temp1, temp2); - // Check if we can use a JSFatInlineString. - Label isFatInline; - masm.branch32(Assembler::BelowOrEqual, temp2, Imm32(JSFatInlineString::MAX_LENGTH_TWO_BYTE), - &isFatInline); + // Check if we can use a JSFatInlineString. The result is a Latin1 string if + // lhs and rhs are both Latin1, so we AND the flags. + Label isFatInlineTwoByte, isFatInlineLatin1; + masm.load32(Address(lhs, JSString::offsetOfFlags()), temp1); + masm.and32(Address(rhs, JSString::offsetOfFlags()), temp1); + + Label isLatin1, notInline; + masm.branchTest32(Assembler::NonZero, temp1, Imm32(JSString::LATIN1_CHARS_BIT), &isLatin1); + { + masm.branch32(Assembler::BelowOrEqual, temp2, Imm32(JSFatInlineString::MAX_LENGTH_TWO_BYTE), + &isFatInlineTwoByte); + masm.jump(¬Inline); + } + masm.bind(&isLatin1); + { + masm.branch32(Assembler::BelowOrEqual, temp2, Imm32(JSFatInlineString::MAX_LENGTH_LATIN1), + &isFatInlineLatin1); + } + masm.bind(¬Inline); + + // Keep AND'ed flags in temp1. // Ensure result length <= JSString::MAX_LENGTH. masm.branch32(Assembler::Above, temp2, Imm32(JSString::MAX_LENGTH), &failure); @@ -5232,8 +5358,12 @@ JitCompartment::generateStringConcatStub(JSContext *cx, ExecutionMode mode) MOZ_ASSUME_UNREACHABLE("No such execution mode"); } - // Store lengthAndFlags. - masm.store32(Imm32(JSString::ROPE_FLAGS), Address(output, JSString::offsetOfFlags())); + // Store rope length and flags. temp1 still holds the result of AND'ing the + // lhs and rhs flags, so we just have to clear the other flags to get our + // rope flags (Latin1 if both lhs and rhs are Latin1). + static_assert(JSString::ROPE_FLAGS == 0, "Rope flags must be 0"); + masm.and32(Imm32(JSString::LATIN1_CHARS_BIT), temp1); + masm.store32(temp1, Address(output, JSString::offsetOfFlags())); masm.store32(temp2, Address(output, JSString::offsetOfLength())); // Store left and right nodes. @@ -5249,62 +5379,13 @@ JitCompartment::generateStringConcatStub(JSContext *cx, ExecutionMode mode) masm.mov(lhs, output); masm.ret(); - masm.bind(&isFatInline); + masm.bind(&isFatInlineTwoByte); + ConcatFatInlineString(masm, lhs, rhs, output, temp1, temp2, temp3, forkJoinContext, + mode, &failure, &failurePopTemps, true); - // State: lhs length in temp1, result length in temp2. - - // Ensure both strings are linear. - masm.branchIfRope(lhs, &failure); - masm.branchIfRope(rhs, &failure); - - // Allocate a JSFatInlineString. - switch (mode) { - case SequentialExecution: - masm.newGCFatInlineString(output, temp3, &failure); - break; - case ParallelExecution: - masm.push(temp1); - masm.push(temp2); - masm.newGCFatInlineStringPar(output, forkJoinContext, temp1, temp2, &failurePopTemps); - masm.pop(temp2); - masm.pop(temp1); - break; - default: - MOZ_ASSUME_UNREACHABLE("No such execution mode"); - } - - // Set length and flags. - masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS), Address(output, JSString::offsetOfFlags())); - masm.store32(temp2, Address(output, JSString::offsetOfLength())); - - // Load chars pointer in temp2. - masm.computeEffectiveAddress(Address(output, JSInlineString::offsetOfInlineStorage()), temp2); - - { - // We use temp3 in this block, which in parallel execution also holds - // a live ForkJoinContext pointer. If we are compiling for parallel - // execution, be sure to save and restore the ForkJoinContext. - if (mode == ParallelExecution) - masm.push(temp3); - - // Copy lhs chars. Temp1 still holds the lhs length. Note that this - // advances temp2 to point to the next char. Note that this also - // repurposes the lhs register. - masm.loadStringChars(lhs, lhs); - CopyStringChars(masm, temp2, lhs, temp1, temp3); - - // Copy rhs chars. - masm.loadStringLength(rhs, temp1); - masm.loadStringChars(rhs, rhs); - CopyStringChars(masm, temp2, rhs, temp1, temp3); - - if (mode == ParallelExecution) - masm.pop(temp3); - } - - // Null-terminate. - masm.store16(Imm32(0), Address(temp2, 0)); - masm.ret(); + masm.bind(&isFatInlineLatin1); + ConcatFatInlineString(masm, lhs, rhs, output, temp1, temp2, temp3, forkJoinContext, + mode, &failure, &failurePopTemps, false); masm.bind(&failurePopTemps); masm.pop(temp2); diff --git a/js/src/jit/ParallelFunctions.cpp b/js/src/jit/ParallelFunctions.cpp index 84898746b28e..e28200357000 100644 --- a/js/src/jit/ParallelFunctions.cpp +++ b/js/src/jit/ParallelFunctions.cpp @@ -357,11 +357,12 @@ CompareStringsPar(ForkJoinContext *cx, JSString *left, JSString *right, int32_t { ScopedThreadSafeStringInspector leftInspector(left); ScopedThreadSafeStringInspector rightInspector(right); - if (!leftInspector.ensureChars(cx) || !rightInspector.ensureChars(cx)) + AutoCheckCannotGC nogc; + if (!leftInspector.ensureChars(cx, nogc) || !rightInspector.ensureChars(cx, nogc)) return false; - *res = CompareChars(leftInspector.chars(), left->length(), - rightInspector.chars(), right->length()); + *res = CompareChars(leftInspector.twoByteChars(), left->length(), + rightInspector.twoByteChars(), right->length()); return true; } diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index ecfb76179d00..1ca97d787821 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -1936,6 +1936,13 @@ MacroAssemblerARMCompat::and32(Imm32 imm, Register dest) ma_and(imm, dest, SetCond); } +void +MacroAssemblerARMCompat::and32(const Address &src, Register dest) +{ + load32(src, ScratchRegister); + ma_and(ScratchRegister, dest, SetCond); +} + void MacroAssemblerARMCompat::addPtr(Register src, Register dest) { diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index 063a5b071e76..fa754b06ff4e 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1299,6 +1299,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void and32(Register src, Register dest); void and32(Imm32 imm, Register dest); void and32(Imm32 imm, const Address &dest); + void and32(const Address &src, Register dest); void or32(Imm32 imm, const Address &dest); void xorPtr(Imm32 imm, Register dest); void xorPtr(Register src, Register dest); diff --git a/js/src/jit/shared/MacroAssembler-x86-shared.h b/js/src/jit/shared/MacroAssembler-x86-shared.h index 65293e99ad1b..610b0238250b 100644 --- a/js/src/jit/shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/shared/MacroAssembler-x86-shared.h @@ -111,6 +111,9 @@ class MacroAssemblerX86Shared : public Assembler void and32(Register src, Register dest) { andl(src, dest); } + void and32(const Address &src, Register dest) { + andl(Operand(src), dest); + } void and32(Imm32 imm, Register dest) { andl(imm, dest); } diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 691fadd736d2..d6d7fb2a69a1 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -5597,14 +5597,14 @@ JS_DecodeBytes(JSContext *cx, const char *src, size_t srclen, jschar *dst, size_ size_t dstlen = *dstlenp; if (srclen > dstlen) { - InflateStringToBuffer(src, dstlen, dst); + CopyAndInflateChars(dst, src, dstlen); AutoSuppressGC suppress(cx); JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL); return false; } - InflateStringToBuffer(src, srclen, dst); + CopyAndInflateChars(dst, src, srclen); *dstlenp = srclen; return true; } diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 1f6d8c2e8630..11445375280e 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -452,7 +452,7 @@ js::Atomize(ExclusiveContext *cx, const char *bytes, size_t length, InternBehavi * js::AtomizeString rarely has to copy the temp string we make. */ jschar inflated[ATOMIZE_BUF_MAX]; - InflateStringToBuffer(bytes, length, inflated); + CopyAndInflateChars(inflated, bytes, length); return AtomizeAndCopyChars(cx, inflated, length, ib); } diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 1620e55f4df5..43e4c9b0afcc 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -605,7 +605,7 @@ js::Int32ToString(ThreadSafeContext *cx, int32_t si) jschar *start = BackfillInt32InBuffer(si, buffer, JSFatInlineString::MAX_LENGTH_TWO_BYTE + 1, &length); - PodCopy(str->init(length), start, length + 1); + PodCopy(str->initTwoByte(length), start, length + 1); CacheNumber(cx, si, str); return str; @@ -1437,7 +1437,7 @@ js::IndexToString(JSContext *cx, uint32_t index) *end = '\0'; RangedPtr start = BackfillIndexInCharBuffer(index, end); - jschar *dst = str->init(end - start); + jschar *dst = str->initTwoByte(end - start); PodCopy(dst, start.get(), end - start + 1); c->dtoaCache.cache(10, index, str); @@ -1533,11 +1533,12 @@ CharsToNumber(ThreadSafeContext *cx, const jschar *chars, size_t length, double bool js::StringToNumber(ThreadSafeContext *cx, JSString *str, double *result) { + AutoCheckCannotGC nogc; ScopedThreadSafeStringInspector inspector(str); - if (!inspector.ensureChars(cx)) + if (!inspector.ensureChars(cx, nogc)) return false; - return CharsToNumber(cx, inspector.chars(), str->length(), result); + return CharsToNumber(cx, inspector.twoByteChars(), str->length(), result); } bool diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 5648bf3a894f..34ff5d3fb11a 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -2780,8 +2780,8 @@ FlattenSubstrings(JSContext *cx, const jschar *chars, JSFatInlineString *str = js_NewGCFatInlineString(cx); if (!str) return nullptr; - jschar *buf = str->init(outputLen); + jschar *buf = str->initTwoByte(outputLen); size_t pos = 0; for (size_t i = 0; i < rangesLen; i++) { PodCopy(buf + pos, chars + ranges[i].start, ranges[i].length); diff --git a/js/src/jsstr.h b/js/src/jsstr.h index ef94871a5361..11656c634171 100644 --- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -256,7 +256,7 @@ InflateString(ThreadSafeContext *cx, const char *bytes, size_t *length); * enough for 'srclen' jschars. The buffer is NOT null-terminated. */ inline void -InflateStringToBuffer(const char *src, size_t srclen, jschar *dst) +CopyAndInflateChars(jschar *dst, const char *src, size_t srclen) { for (size_t i = 0; i < srclen; i++) dst[i] = (unsigned char) src[i]; diff --git a/js/src/vm/CharacterEncoding.cpp b/js/src/vm/CharacterEncoding.cpp index b206c2f94a1a..69db3961b1e1 100644 --- a/js/src/vm/CharacterEncoding.cpp +++ b/js/src/vm/CharacterEncoding.cpp @@ -132,9 +132,11 @@ DeflateStringToUTF8Buffer(js::ThreadSafeContext *cx, const jschar *src, size_t s bufferTooSmall: *dstlenp = (origDstlen - dstlen); - if (cx->isJSContext()) + if (cx->isJSContext()) { + js::gc::AutoSuppressGC suppress(cx->asJSContext()); JS_ReportErrorNumber(cx->asJSContext(), js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL); + } return false; } diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 638532c28f8c..d71549e009c1 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -279,11 +279,12 @@ intrinsic_ParallelSpew(ThreadSafeContext *cx, unsigned argc, Value *vp) JS_ASSERT(args.length() == 1); JS_ASSERT(args[0].isString()); + AutoCheckCannotGC nogc; ScopedThreadSafeStringInspector inspector(args[0].toString()); - if (!inspector.ensureChars(cx)) + if (!inspector.ensureChars(cx, nogc)) return false; - ScopedJSFreePtr bytes(TwoByteCharsToNewUTF8CharsZ(cx, inspector.range()).c_str()); + ScopedJSFreePtr bytes(TwoByteCharsToNewUTF8CharsZ(cx, inspector.twoByteRange()).c_str()); parallel::Spew(parallel::SpewOps, bytes); args.rval().setUndefined(); diff --git a/js/src/vm/String-inl.h b/js/src/vm/String-inl.h index 57a567026c3e..be6c17ce0fb4 100644 --- a/js/src/vm/String-inl.h +++ b/js/src/vm/String-inl.h @@ -32,12 +32,12 @@ NewFatInlineString(ThreadSafeContext *cx, JS::Latin1Chars chars) str = JSInlineString::new_(cx); if (!str) return nullptr; - p = str->init(len); + p = str->initTwoByte(len); } else { JSFatInlineString *fatstr = JSFatInlineString::new_(cx); if (!fatstr) return nullptr; - p = fatstr->init(len); + p = fatstr->initTwoByte(len); str = fatstr; } @@ -65,12 +65,12 @@ NewFatInlineString(ExclusiveContext *cx, JS::TwoByteChars chars) str = JSInlineString::new_(cx); if (!str) return nullptr; - storage = str->init(len); + storage = str->initTwoByte(len); } else { JSFatInlineString *fatstr = JSFatInlineString::new_(cx); if (!fatstr) return nullptr; - storage = fatstr->init(len); + storage = fatstr->initTwoByte(len); str = fatstr; } @@ -107,6 +107,8 @@ JSRope::init(js::ThreadSafeContext *cx, JSString *left, JSString *right, size_t { d.u1.length = length; d.u1.flags = ROPE_FLAGS; + if (left->hasLatin1Chars() && right->hasLatin1Chars()) + d.u1.flags |= LATIN1_CHARS_BIT; d.s.u2.left = left; d.s.u3.right = right; js::StringWriteBarrierPost(cx, &d.s.u2.left); @@ -248,7 +250,7 @@ JSInlineString::new_(js::ThreadSafeContext *cx) } MOZ_ALWAYS_INLINE jschar * -JSInlineString::init(size_t length) +JSInlineString::initTwoByte(size_t length) { JS_ASSERT(twoByteLengthFits(length)); d.u1.length = length; @@ -256,8 +258,17 @@ JSInlineString::init(size_t length) return d.inlineStorageTwoByte; } +MOZ_ALWAYS_INLINE char * +JSInlineString::initLatin1(size_t length) +{ + JS_ASSERT(latin1LengthFits(length)); + d.u1.length = length; + d.u1.flags = INIT_INLINE_FLAGS | LATIN1_CHARS_BIT; + return d.inlineStorageLatin1; +} + MOZ_ALWAYS_INLINE jschar * -JSFatInlineString::init(size_t length) +JSFatInlineString::initTwoByte(size_t length) { JS_ASSERT(twoByteLengthFits(length)); d.u1.length = length; @@ -265,6 +276,15 @@ JSFatInlineString::init(size_t length) return d.inlineStorageTwoByte; } +MOZ_ALWAYS_INLINE char * +JSFatInlineString::initLatin1(size_t length) +{ + JS_ASSERT(latin1LengthFits(length)); + d.u1.length = length; + d.u1.flags = INIT_FAT_INLINE_FLAGS | LATIN1_CHARS_BIT; + return d.inlineStorageLatin1; +} + template MOZ_ALWAYS_INLINE JSFatInlineString * JSFatInlineString::new_(js::ThreadSafeContext *cx) @@ -339,7 +359,7 @@ JSFlatString::finalize(js::FreeOp *fop) JS_ASSERT(getAllocKind() != js::gc::FINALIZE_FAT_INLINE_STRING); if (!isInline()) - fop->free_(const_cast(nonInlineChars())); + fop->free_(nonInlineCharsRaw()); } inline void @@ -348,7 +368,7 @@ JSFatInlineString::finalize(js::FreeOp *fop) JS_ASSERT(getAllocKind() == js::gc::FINALIZE_FAT_INLINE_STRING); if (!isInline()) - fop->free_(const_cast(nonInlineChars())); + fop->free_(nonInlineCharsRaw()); } inline void @@ -358,7 +378,7 @@ JSAtom::finalize(js::FreeOp *fop) JS_ASSERT(JSString::isFlat()); if (!isInline()) - fop->free_(const_cast(nonInlineChars())); + fop->free_(nonInlineCharsRaw()); } inline void diff --git a/js/src/vm/String.cpp b/js/src/vm/String.cpp index f2c4129fe32a..9ecc5e3e9a8d 100644 --- a/js/src/vm/String.cpp +++ b/js/src/vm/String.cpp @@ -405,21 +405,39 @@ js::ConcatStrings(ThreadSafeContext *cx, if (!JSString::validateLength(cx, wholeLength)) return nullptr; - if (JSFatInlineString::twoByteLengthFits(wholeLength) && cx->isJSContext()) { + bool isLatin1 = left->hasLatin1Chars() && right->hasLatin1Chars(); + bool canUseFatInline = isLatin1 + ? JSFatInlineString::latin1LengthFits(wholeLength) + : JSFatInlineString::twoByteLengthFits(wholeLength); + if (canUseFatInline && cx->isJSContext()) { JSFatInlineString *str = js_NewGCFatInlineString(cx); if (!str) return nullptr; + AutoCheckCannotGC nogc; ScopedThreadSafeStringInspector leftInspector(left); ScopedThreadSafeStringInspector rightInspector(right); - if (!leftInspector.ensureChars(cx) || !rightInspector.ensureChars(cx)) + if (!leftInspector.ensureChars(cx, nogc) || !rightInspector.ensureChars(cx, nogc)) return nullptr; - jschar *buf = str->init(wholeLength); - PodCopy(buf, leftInspector.chars(), leftLen); - PodCopy(buf + leftLen, rightInspector.chars(), rightLen); + if (isLatin1) { + char *buf = str->initLatin1(wholeLength); + PodCopy(buf, leftInspector.latin1Chars(), leftLen); + PodCopy(buf + leftLen, rightInspector.latin1Chars(), rightLen); + buf[wholeLength] = 0; + } else { + jschar *buf = str->initTwoByte(wholeLength); + if (leftInspector.hasTwoByteChars()) + PodCopy(buf, leftInspector.twoByteChars(), leftLen); + else + CopyAndInflateChars(buf, leftInspector.latin1Chars(), leftLen); + if (rightInspector.hasTwoByteChars()) + PodCopy(buf + leftLen, rightInspector.twoByteChars(), rightLen); + else + CopyAndInflateChars(buf + leftLen, rightInspector.latin1Chars(), rightLen); + buf[wholeLength] = 0; + } - buf[wholeLength] = 0; return str; } @@ -530,27 +548,35 @@ JSFlatString::isIndexSlow(uint32_t *indexp) const } bool -ScopedThreadSafeStringInspector::ensureChars(ThreadSafeContext *cx) +ScopedThreadSafeStringInspector::ensureChars(ThreadSafeContext *cx, const AutoCheckCannotGC &nogc) { - if (chars_) + if (state_ != Uninitialized) return true; if (cx->isExclusiveContext()) { JSLinearString *linear = str_->ensureLinear(cx->asExclusiveContext()); if (!linear) return false; - chars_ = linear->chars(); + if (linear->hasTwoByteChars()) { + state_ = TwoByte; + twoByteChars_ = linear->twoByteChars(nogc); + } else { + state_ = Latin1; + latin1Chars_ = linear->latin1Chars(nogc); + } } else { if (str_->hasPureChars()) { - chars_ = str_->pureChars(); + state_ = TwoByte; + twoByteChars_ = str_->pureChars(); } else { if (!str_->copyNonPureChars(cx, scopedChars_)) return false; - chars_ = scopedChars_; + state_ = TwoByte; + twoByteChars_ = scopedChars_; } } - JS_ASSERT(chars_); + MOZ_ASSERT(state_ != Uninitialized); return true; } diff --git a/js/src/vm/String.h b/js/src/vm/String.h index 880ca85ff0e8..01af7693bda6 100644 --- a/js/src/vm/String.h +++ b/js/src/vm/String.h @@ -577,6 +577,17 @@ class JSLinearString : public JSString bool isLinear() const MOZ_DELETE; JSLinearString &asLinear() const MOZ_DELETE; + protected: + /* Returns void pointer to latin1/twoByte chars, for finalizers. */ + MOZ_ALWAYS_INLINE + void *nonInlineCharsRaw() const { + JS_ASSERT(!isInline()); + static_assert(offsetof(JSLinearString, d.s.u2.nonInlineCharsTwoByte) == + offsetof(JSLinearString, d.s.u2.nonInlineCharsLatin1), + "nonInlineCharsTwoByte and nonInlineCharsLatin1 must have same offset"); + return (void *)d.s.u2.nonInlineCharsTwoByte; + } + public: MOZ_ALWAYS_INLINE const jschar *nonInlineChars() const { @@ -734,7 +745,8 @@ class JSInlineString : public JSFlatString template static inline JSInlineString *new_(js::ThreadSafeContext *cx); - inline jschar *init(size_t length); + inline jschar *initTwoByte(size_t length); + inline char *initLatin1(size_t length); inline void resetLength(size_t length); @@ -822,7 +834,8 @@ class JSFatInlineString : public JSInlineString INLINE_EXTENSION_CHARS_TWO_BYTE -1 /* null terminator */; - inline jschar *init(size_t length); + inline jschar *initTwoByte(size_t length); + inline char *initLatin1(size_t length); static bool latin1LengthFits(size_t length) { return length <= MAX_LENGTH_LATIN1; @@ -924,24 +937,36 @@ class ScopedThreadSafeStringInspector private: JSString *str_; ScopedJSFreePtr scopedChars_; - const jschar *chars_; + union { + const jschar *twoByteChars_; + const char *latin1Chars_; + }; + enum State { Uninitialized, Latin1, TwoByte }; + State state_; public: explicit ScopedThreadSafeStringInspector(JSString *str) : str_(str), - chars_(nullptr) + state_(Uninitialized) { } - bool ensureChars(ThreadSafeContext *cx); + bool ensureChars(ThreadSafeContext *cx, const JS::AutoCheckCannotGC &nogc); - const jschar *chars() { - JS_ASSERT(chars_); - return chars_; + bool hasTwoByteChars() const { return state_ == TwoByte; } + bool hasLatin1Chars() const { return state_ == Latin1; } + + const jschar *twoByteChars() const { + MOZ_ASSERT(state_ == TwoByte); + return twoByteChars_; + } + const char *latin1Chars() const { + MOZ_ASSERT(state_ == Latin1); + return latin1Chars_; } - JS::TwoByteChars range() { - JS_ASSERT(chars_); - return JS::TwoByteChars(chars_, str_->length()); + JS::TwoByteChars twoByteRange() const { + MOZ_ASSERT(state_ == TwoByte); + return JS::TwoByteChars(twoByteChars_, str_->length()); } }; diff --git a/js/src/vm/StringBuffer.h b/js/src/vm/StringBuffer.h index 5b8b217302a3..df388dbdedc0 100644 --- a/js/src/vm/StringBuffer.h +++ b/js/src/vm/StringBuffer.h @@ -120,7 +120,7 @@ StringBuffer::appendInflated(const char *cstr, size_t cstrlen) size_t lengthBefore = length(); if (!cb.growByUninitialized(cstrlen)) return false; - InflateStringToBuffer(cstr, cstrlen, begin() + lengthBefore); + CopyAndInflateChars(begin() + lengthBefore, cstr, cstrlen); return true; }