Diff
Modified: trunk/Source/_javascript_Core/ChangeLog (230129 => 230130)
--- trunk/Source/_javascript_Core/ChangeLog 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/_javascript_Core/ChangeLog 2018-03-31 15:55:38 UTC (rev 230130)
@@ -1,3 +1,20 @@
+2018-03-30 Filip Pizlo <[email protected]>
+
+ Strings and Vectors shouldn't do index masking
+ https://bugs.webkit.org/show_bug.cgi?id=184193
+
+ Reviewed by Mark Lam.
+
+ * dfg/DFGSpeculativeJIT.cpp:
+ (JSC::DFG::SpeculativeJIT::compileGetCharCodeAt):
+ (JSC::DFG::SpeculativeJIT::compileGetByValOnString):
+ * ftl/FTLAbstractHeapRepository.h:
+ * ftl/FTLLowerDFGToB3.cpp:
+ (JSC::FTL::DFG::LowerDFGToB3::compileStringCharAt):
+ (JSC::FTL::DFG::LowerDFGToB3::compileStringCharCodeAt):
+ * jit/ThunkGenerators.cpp:
+ (JSC::stringCharLoad):
+
2018-03-30 Mark Lam <[email protected]>
Add pointer profiling support in baseline JIT and supporting files.
Modified: trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp (230129 => 230130)
--- trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp 2018-03-31 15:55:38 UTC (rev 230130)
@@ -2110,7 +2110,6 @@
GPRReg scratchReg = scratch.gpr();
m_jit.loadPtr(MacroAssembler::Address(stringReg, JSString::offsetOfValue()), scratchReg);
- m_jit.and32(MacroAssembler::Address(scratchReg, StringImpl::maskOffset()), indexReg);
// Load the character into scratchReg
JITCompiler::Jump is16Bit = m_jit.branchTest32(MacroAssembler::Zero, MacroAssembler::Address(scratchReg, StringImpl::flagsOffset()), TrustedImm32(StringImpl::flagIs8Bit()));
@@ -2158,7 +2157,6 @@
speculationCheck(OutOfBounds, JSValueRegs(), 0, outOfBounds);
m_jit.loadPtr(MacroAssembler::Address(baseReg, JSString::offsetOfValue()), scratchReg);
- m_jit.and32(MacroAssembler::Address(scratchReg, StringImpl::maskOffset()), propertyReg);
// Load the character into scratchReg
JITCompiler::Jump is16Bit = m_jit.branchTest32(MacroAssembler::Zero, MacroAssembler::Address(scratchReg, StringImpl::flagsOffset()), TrustedImm32(StringImpl::flagIs8Bit()));
Modified: trunk/Source/_javascript_Core/ftl/FTLAbstractHeapRepository.h (230129 => 230130)
--- trunk/Source/_javascript_Core/ftl/FTLAbstractHeapRepository.h 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/_javascript_Core/ftl/FTLAbstractHeapRepository.h 2018-03-31 15:55:38 UTC (rev 230130)
@@ -112,7 +112,6 @@
macro(StringImpl_data, StringImpl::dataOffset()) \
macro(StringImpl_hashAndFlags, StringImpl::flagsOffset()) \
macro(StringImpl_length, StringImpl::lengthMemoryOffset()) \
- macro(StringImpl_mask, StringImpl::maskOffset()) \
macro(Structure_classInfo, Structure::classInfoOffset()) \
macro(Structure_globalObject, Structure::globalObjectOffset()) \
macro(Structure_prototype, Structure::prototypeOffset()) \
Modified: trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp (230129 => 230130)
--- trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp 2018-03-31 15:55:38 UTC (rev 230130)
@@ -6285,7 +6285,6 @@
LBasicBlock lastNext = m_out.appendTo(fastPath, slowPath);
LValue stringImpl = m_out.loadPtr(base, m_heaps.JSString_value);
- LValue indexForAccess = m_out.bitAnd(index, m_out.load32(stringImpl, m_heaps.StringImpl_mask));
LBasicBlock is8Bit = m_out.newBlock();
LBasicBlock is16Bit = m_out.newBlock();
@@ -6304,7 +6303,7 @@
// https://bugs.webkit.org/show_bug.cgi?id=174924
ValueFromBlock char8Bit = m_out.anchor(
m_out.load8ZeroExt32(m_out.baseIndex(
- m_heaps.characters8, storage, m_out.zeroExtPtr(indexForAccess),
+ m_heaps.characters8, storage, m_out.zeroExtPtr(index),
provenValue(m_graph.child(m_node, 1)))));
m_out.jump(bitsContinuation);
@@ -6312,7 +6311,7 @@
LValue char16BitValue = m_out.load16ZeroExt32(
m_out.baseIndex(
- m_heaps.characters16, storage, m_out.zeroExtPtr(indexForAccess),
+ m_heaps.characters16, storage, m_out.zeroExtPtr(index),
provenValue(m_graph.child(m_node, 1))));
ValueFromBlock char16Bit = m_out.anchor(char16BitValue);
m_out.branch(
@@ -6394,7 +6393,6 @@
index, m_out.load32NonNegative(base, m_heaps.JSString_length)));
LValue stringImpl = m_out.loadPtr(base, m_heaps.JSString_value);
- LValue indexForAccess = m_out.bitAnd(index, m_out.load32(stringImpl, m_heaps.StringImpl_mask));
m_out.branch(
m_out.testIsZero32(
@@ -6408,7 +6406,7 @@
// https://bugs.webkit.org/show_bug.cgi?id=174924
ValueFromBlock char8Bit = m_out.anchor(
m_out.load8ZeroExt32(m_out.baseIndex(
- m_heaps.characters8, storage, m_out.zeroExtPtr(indexForAccess),
+ m_heaps.characters8, storage, m_out.zeroExtPtr(index),
provenValue(m_node->child2()))));
m_out.jump(continuation);
@@ -6416,7 +6414,7 @@
ValueFromBlock char16Bit = m_out.anchor(
m_out.load16ZeroExt32(m_out.baseIndex(
- m_heaps.characters16, storage, m_out.zeroExtPtr(indexForAccess),
+ m_heaps.characters16, storage, m_out.zeroExtPtr(index),
provenValue(m_node->child2()))));
m_out.jump(continuation);
Modified: trunk/Source/_javascript_Core/jit/ThunkGenerators.cpp (230129 => 230130)
--- trunk/Source/_javascript_Core/jit/ThunkGenerators.cpp 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/_javascript_Core/jit/ThunkGenerators.cpp 2018-03-31 15:55:38 UTC (rev 230130)
@@ -633,7 +633,6 @@
SpecializedThunkJIT::JumpList cont8Bit;
// Load the string flags
jit.loadPtr(MacroAssembler::Address(SpecializedThunkJIT::regT0, StringImpl::flagsOffset()), SpecializedThunkJIT::regT2);
- jit.and32(MacroAssembler::Address(SpecializedThunkJIT::regT0, StringImpl::maskOffset()), SpecializedThunkJIT::regT1);
jit.loadPtr(MacroAssembler::Address(SpecializedThunkJIT::regT0, StringImpl::dataOffset()), SpecializedThunkJIT::regT0);
is16Bit.append(jit.branchTest32(MacroAssembler::Zero, SpecializedThunkJIT::regT2, MacroAssembler::TrustedImm32(StringImpl::flagIs8Bit())));
jit.load8(MacroAssembler::BaseIndex(SpecializedThunkJIT::regT0, SpecializedThunkJIT::regT1, MacroAssembler::TimesOne, 0), SpecializedThunkJIT::regT0);
Modified: trunk/Source/WTF/ChangeLog (230129 => 230130)
--- trunk/Source/WTF/ChangeLog 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/WTF/ChangeLog 2018-03-31 15:55:38 UTC (rev 230130)
@@ -1,3 +1,43 @@
+2018-03-30 Filip Pizlo <[email protected]>
+
+ Strings and Vectors shouldn't do index masking
+ https://bugs.webkit.org/show_bug.cgi?id=184193
+
+ Reviewed by Mark Lam.
+
+ * wtf/SizeLimits.cpp:
+ * wtf/Vector.h:
+ (WTF::VectorBufferBase::allocateBuffer):
+ (WTF::VectorBufferBase::tryAllocateBuffer):
+ (WTF::VectorBufferBase::reallocateBuffer):
+ (WTF::VectorBufferBase::deallocateBuffer):
+ (WTF::VectorBufferBase::releaseBuffer):
+ (WTF::VectorBufferBase::VectorBufferBase):
+ (WTF::VectorBuffer::allocateBuffer):
+ (WTF::VectorBuffer::tryAllocateBuffer):
+ (WTF::VectorBuffer::swap):
+ (WTF::VectorBuffer::restoreInlineBufferIfNeeded):
+ (WTF::Vector::at):
+ (WTF::Vector::at const):
+ (WTF::VectorBufferBase::updateMask): Deleted.
+ * wtf/text/StringImpl.h:
+ (WTF::StringImpl::flagIsSymbol):
+ (WTF::StringImpl::length const):
+ (WTF::StringImplShape::StringImplShape):
+ (WTF::StringImpl::at const):
+ (WTF::StringImpl::tailOffset):
+ (WTF::StringImpl::maskOffset): Deleted.
+ (WTF::StringImpl::mask const): Deleted.
+ * wtf/text/StringView.h:
+ (WTF::StringView::StringView):
+ (WTF::StringView::operator=):
+ (WTF::StringView::initialize):
+ (WTF::StringView::clear):
+ (WTF::StringView::operator[] const):
+ * wtf/text/WTFString.h:
+ (WTF::String::length const):
+ (WTF::String::mask const): Deleted.
+
2018-03-30 Mark Lam <[email protected]>
Add pointer profiling support in baseline JIT and supporting files.
Modified: trunk/Source/WTF/wtf/SizeLimits.cpp (230129 => 230130)
--- trunk/Source/WTF/wtf/SizeLimits.cpp 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/WTF/wtf/SizeLimits.cpp 2018-03-31 15:55:38 UTC (rev 230130)
@@ -68,7 +68,6 @@
void* bufferPointer;
unsigned capacity;
unsigned size;
- unsigned mask;
};
template<typename T>
Modified: trunk/Source/WTF/wtf/Vector.h (230129 => 230130)
--- trunk/Source/WTF/wtf/Vector.h 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/WTF/wtf/Vector.h 2018-03-31 15:55:38 UTC (rev 230130)
@@ -287,7 +287,6 @@
CRASH();
size_t sizeToAllocate = newCapacity * sizeof(T);
m_capacity = sizeToAllocate / sizeof(T);
- updateMask();
m_buffer = static_cast<T*>(Malloc::malloc(sizeToAllocate));
}
@@ -302,7 +301,6 @@
if (!newBuffer)
return false;
m_capacity = sizeToAllocate / sizeof(T);
- updateMask();
m_buffer = newBuffer;
return true;
}
@@ -319,7 +317,6 @@
CRASH();
size_t sizeToAllocate = newCapacity * sizeof(T);
m_capacity = sizeToAllocate / sizeof(T);
- updateMask();
m_buffer = static_cast<T*>(Malloc::realloc(m_buffer, sizeToAllocate));
}
@@ -331,7 +328,6 @@
if (m_buffer == bufferToDeallocate) {
m_buffer = 0;
m_capacity = 0;
- m_mask = 0;
}
Malloc::free(bufferToDeallocate);
@@ -347,7 +343,6 @@
T* buffer = m_buffer;
m_buffer = 0;
m_capacity = 0;
- m_mask = 0;
return adoptMallocPtr(buffer);
}
@@ -356,7 +351,6 @@
: m_buffer(0)
, m_capacity(0)
, m_size(0)
- , m_mask(0)
{
}
@@ -364,9 +358,7 @@
: m_buffer(buffer)
, m_capacity(capacity)
, m_size(size)
- , m_mask(0)
{
- updateMask();
}
~VectorBufferBase()
@@ -374,15 +366,9 @@
// FIXME: It would be nice to find a way to ASSERT that m_buffer hasn't leaked here.
}
- void updateMask()
- {
- m_mask = maskForSize(m_capacity);
- }
-
T* m_buffer;
unsigned m_capacity;
unsigned m_size; // Only used by the Vector subclass, but placed here to avoid padding the struct.
- unsigned m_mask;
};
template<typename T, size_t inlineCapacity, typename Malloc>
@@ -415,7 +401,6 @@
{
std::swap(m_buffer, other.m_buffer);
std::swap(m_capacity, other.m_capacity);
- std::swap(m_mask, other.m_mask);
}
void restoreInlineBufferIfNeeded() { }
@@ -440,7 +425,6 @@
using Base::releaseBuffer;
protected:
- using Base::m_mask;
using Base::m_size;
private:
@@ -479,7 +463,6 @@
else {
m_buffer = inlineBuffer();
m_capacity = inlineCapacity;
- updateMask();
}
}
@@ -489,7 +472,6 @@
return Base::tryAllocateBuffer(newCapacity);
m_buffer = inlineBuffer();
m_capacity = inlineCapacity;
- updateMask();
return true;
}
@@ -517,23 +499,19 @@
if (buffer() == inlineBuffer() && other.buffer() == other.inlineBuffer()) {
swapInlineBuffer(other, mySize, otherSize);
std::swap(m_capacity, other.m_capacity);
- std::swap(m_mask, other.m_mask);
} else if (buffer() == inlineBuffer()) {
m_buffer = other.m_buffer;
other.m_buffer = other.inlineBuffer();
swapInlineBuffer(other, mySize, 0);
std::swap(m_capacity, other.m_capacity);
- std::swap(m_mask, other.m_mask);
} else if (other.buffer() == other.inlineBuffer()) {
other.m_buffer = m_buffer;
m_buffer = inlineBuffer();
swapInlineBuffer(other, 0, otherSize);
std::swap(m_capacity, other.m_capacity);
- std::swap(m_mask, other.m_mask);
} else {
std::swap(m_buffer, other.m_buffer);
std::swap(m_capacity, other.m_capacity);
- std::swap(m_mask, other.m_mask);
}
}
@@ -543,7 +521,6 @@
return;
m_buffer = inlineBuffer();
m_capacity = inlineCapacity;
- updateMask();
}
#if ASAN_ENABLED
@@ -571,13 +548,11 @@
}
protected:
- using Base::m_mask;
using Base::m_size;
private:
using Base::m_buffer;
using Base::m_capacity;
- using Base::updateMask;
void swapInlineBuffer(VectorBuffer& other, size_t mySize, size_t otherSize)
{
@@ -713,23 +688,23 @@
{
if (UNLIKELY(i >= size()))
OverflowHandler::overflowed();
- return Base::buffer()[i & m_mask];
+ return Base::buffer()[i];
}
const T& at(size_t i) const
{
if (UNLIKELY(i >= size()))
OverflowHandler::overflowed();
- return Base::buffer()[i & m_mask];
+ return Base::buffer()[i];
}
T& at(Checked<size_t> i)
{
RELEASE_ASSERT(i < size());
- return Base::buffer()[i & m_mask];
+ return Base::buffer()[i];
}
const T& at(Checked<size_t> i) const
{
RELEASE_ASSERT(i < size());
- return Base::buffer()[i & m_mask];
+ return Base::buffer()[i];
}
T& operator[](size_t i) { return at(i); }
@@ -873,7 +848,6 @@
void asanBufferSizeWillChangeTo(size_t);
- using Base::m_mask;
using Base::m_size;
using Base::buffer;
using Base::capacity;
Modified: trunk/Source/WTF/wtf/text/StringImpl.cpp (230129 => 230130)
--- trunk/Source/WTF/wtf/text/StringImpl.cpp 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/WTF/wtf/text/StringImpl.cpp 2018-03-31 15:55:38 UTC (rev 230130)
@@ -48,7 +48,7 @@
using namespace Unicode;
-static_assert(sizeof(StringImpl) == 2 * sizeof(int) + sizeof(void*) + 2 * sizeof(int), "StringImpl should stay small");
+static_assert(sizeof(StringImpl) == 2 * sizeof(int) + 2 * sizeof(void*), "StringImpl should stay small");
#if STRING_STATS
StringStats StringImpl::m_stringStats;
Modified: trunk/Source/WTF/wtf/text/StringImpl.h (230129 => 230130)
--- trunk/Source/WTF/wtf/text/StringImpl.h 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/WTF/wtf/text/StringImpl.h 2018-03-31 15:55:38 UTC (rev 230130)
@@ -148,7 +148,6 @@
const char16_t* m_data16Char;
};
mutable unsigned m_hashAndFlags;
- unsigned m_mask;
};
// FIXME: Use of StringImpl and const is rather confused.
@@ -254,7 +253,6 @@
static unsigned flagIs8Bit() { return s_hashFlag8BitBuffer; }
static unsigned flagIsAtomic() { return s_hashFlagStringKindIsAtomic; }
static unsigned flagIsSymbol() { return s_hashFlagStringKindIsSymbol; }
- static unsigned maskOffset() { return OBJECT_OFFSETOF(StringImpl, m_mask); }
static unsigned maskStringKind() { return s_hashMaskStringKind; }
static unsigned dataOffset() { return OBJECT_OFFSETOF(StringImpl, m_data8); }
@@ -265,7 +263,6 @@
WTF_EXPORT_PRIVATE static Ref<StringImpl> adopt(StringBuffer<LChar>&&);
unsigned length() const { return m_length; }
- unsigned mask() const { return m_mask; }
static ptrdiff_t lengthMemoryOffset() { return OBJECT_OFFSETOF(StringImpl, m_length); }
bool isEmpty() const { return !m_length; }
@@ -758,7 +755,6 @@
, m_length(length)
, m_data8(data8)
, m_hashAndFlags(hashAndFlags)
- , m_mask(maskForSize(length))
{
}
@@ -767,7 +763,6 @@
, m_length(length)
, m_data16(data16)
, m_hashAndFlags(hashAndFlags)
- , m_mask(maskForSize(length))
{
}
@@ -776,7 +771,6 @@
, m_length(length)
, m_data8Char(characters)
, m_hashAndFlags(hashAndFlags)
- , m_mask(maskForSize(length))
{
}
@@ -785,7 +779,6 @@
, m_length(length)
, m_data16Char(characters)
, m_hashAndFlags(hashAndFlags)
- , m_mask(maskForSize(length))
{
}
@@ -1078,7 +1071,7 @@
inline UChar StringImpl::at(unsigned i) const
{
ASSERT_WITH_SECURITY_IMPLICATION(i < m_length);
- return is8Bit() ? m_data8[i & m_mask] : m_data16[i & m_mask];
+ return is8Bit() ? m_data8[i] : m_data16[i];
}
inline void StringImpl::assertCaged() const
@@ -1122,7 +1115,7 @@
// MSVC doesn't support alignof yet.
return roundUpToMultipleOf<sizeof(T)>(sizeof(StringImpl));
#else
- return roundUpToMultipleOf<alignof(T)>(offsetof(StringImpl, m_mask) + sizeof(StringImpl::m_mask));
+ return roundUpToMultipleOf<alignof(T)>(offsetof(StringImpl, m_hashAndFlags) + sizeof(StringImpl::m_hashAndFlags));
#endif
}
Modified: trunk/Source/WTF/wtf/text/StringView.h (230129 => 230130)
--- trunk/Source/WTF/wtf/text/StringView.h 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/WTF/wtf/text/StringView.h 2018-03-31 15:55:38 UTC (rev 230130)
@@ -159,8 +159,6 @@
void initialize(const LChar*, unsigned length);
void initialize(const UChar*, unsigned length);
- void initialize(const LChar*, unsigned length, unsigned mask);
- void initialize(const UChar*, unsigned length, unsigned mask);
template<typename CharacterType, typename MatchedCharacterPredicate>
StringView stripLeadingAndTrailingMatchedCharacters(const CharacterType*, const MatchedCharacterPredicate&);
@@ -178,7 +176,6 @@
const void* m_characters { nullptr };
unsigned m_length { 0 };
- unsigned m_mask { 0 };
bool m_is8Bit { true };
#if CHECK_STRINGVIEW_LIFETIME
@@ -231,7 +228,6 @@
inline StringView::StringView(StringView&& other)
: m_characters(other.m_characters)
, m_length(other.m_length)
- , m_mask(other.m_mask)
, m_is8Bit(other.m_is8Bit)
{
ASSERT(other.underlyingStringIsValid());
@@ -245,7 +241,6 @@
inline StringView::StringView(const StringView& other)
: m_characters(other.m_characters)
, m_length(other.m_length)
- , m_mask(other.m_mask)
, m_is8Bit(other.m_is8Bit)
{
ASSERT(other.underlyingStringIsValid());
@@ -259,7 +254,6 @@
m_characters = other.m_characters;
m_length = other.m_length;
- m_mask = other.m_mask;
m_is8Bit = other.m_is8Bit;
other.clear();
@@ -276,7 +270,6 @@
m_characters = other.m_characters;
m_length = other.m_length;
- m_mask = other.m_mask;
m_is8Bit = other.m_is8Bit;
setUnderlyingString(other);
@@ -287,27 +280,15 @@
inline void StringView::initialize(const LChar* characters, unsigned length)
{
- initialize(characters, length, maskForSize(length));
-}
-
-inline void StringView::initialize(const UChar* characters, unsigned length)
-{
- initialize(characters, length, maskForSize(length));
-}
-
-inline void StringView::initialize(const LChar* characters, unsigned length, unsigned mask)
-{
m_characters = characters;
m_length = length;
- m_mask = mask;
m_is8Bit = true;
}
-inline void StringView::initialize(const UChar* characters, unsigned length, unsigned mask)
+inline void StringView::initialize(const UChar* characters, unsigned length)
{
m_characters = characters;
m_length = length;
- m_mask = mask;
m_is8Bit = false;
}
@@ -330,9 +311,9 @@
{
setUnderlyingString(&string);
if (string.is8Bit())
- initialize(string.characters8(), string.length(), string.mask());
+ initialize(string.characters8(), string.length());
else
- initialize(string.characters16(), string.length(), string.mask());
+ initialize(string.characters16(), string.length());
}
inline StringView::StringView(const StringImpl* string)
@@ -342,9 +323,9 @@
setUnderlyingString(string);
if (string->is8Bit())
- initialize(string->characters8(), string->length(), string->mask());
+ initialize(string->characters8(), string->length());
else
- initialize(string->characters16(), string->length(), string->mask());
+ initialize(string->characters16(), string->length());
}
inline StringView::StringView(const String& string)
@@ -355,10 +336,10 @@
return;
}
if (string.is8Bit()) {
- initialize(string.characters8(), string.length(), string.mask());
+ initialize(string.characters8(), string.length());
return;
}
- initialize(string.characters16(), string.length(), string.mask());
+ initialize(string.characters16(), string.length());
}
inline StringView::StringView(const AtomicString& atomicString)
@@ -370,7 +351,6 @@
{
m_characters = nullptr;
m_length = 0;
- m_mask = 0;
m_is8Bit = true;
}
@@ -459,8 +439,8 @@
{
ASSERT(index < length());
if (is8Bit())
- return characters8()[index & m_mask];
- return characters16()[index & m_mask];
+ return characters8()[index];
+ return characters16()[index];
}
inline bool StringView::contains(UChar character) const
Modified: trunk/Source/WTF/wtf/text/WTFString.h (230129 => 230130)
--- trunk/Source/WTF/wtf/text/WTFString.h 2018-03-31 07:04:00 UTC (rev 230129)
+++ trunk/Source/WTF/wtf/text/WTFString.h 2018-03-31 15:55:38 UTC (rev 230130)
@@ -149,7 +149,6 @@
RefPtr<StringImpl> releaseImpl() { return WTFMove(m_impl); }
unsigned length() const { return m_impl ? m_impl->length() : 0; }
- unsigned mask() const { return m_impl ? m_impl->mask() : 0; }
const LChar* characters8() const { return m_impl ? m_impl->characters8() : nullptr; }
const UChar* characters16() const { return m_impl ? m_impl->characters16() : nullptr; }