X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/9dae56ea45a0f5f8136a5c93d6f3a7f99399ca73..fb8617cde5834786bd4e4afd579883e4acf5666e:/runtime/JSString.h diff --git a/runtime/JSString.h b/runtime/JSString.h index e4baa73..af03025 100644 --- a/runtime/JSString.h +++ b/runtime/JSString.h @@ -23,10 +23,11 @@ #ifndef JSString_h #define JSString_h -#include "CommonIdentifiers.h" #include "CallFrame.h" +#include "CommonIdentifiers.h" #include "Identifier.h" #include "JSNumberCell.h" +#include "PropertyDescriptor.h" #include "PropertySlot.h" namespace JSC { @@ -58,14 +59,117 @@ namespace JSC { JSString* jsOwnedString(JSGlobalData*, const UString&); JSString* jsOwnedString(ExecState*, const UString&); - class JSString : public JSCell { - friend class JIT; - friend class Interpreter; + typedef void (*JSStringFinalizerCallback)(JSString*, void* context); + JSString* jsStringWithFinalizer(ExecState*, const UString&, JSStringFinalizerCallback callback, void* context); + class JS_EXPORTCLASS JSString : public JSCell { public: - JSString(JSGlobalData* globalData, const UString& value) + friend class JIT; + friend class JSGlobalData; + + // A Rope is a string composed of a set of substrings. + class Rope : public RefCounted { + public: + // A Rope is composed from a set of smaller strings called Fibers. + // Each Fiber in a rope is either UString::Rep or another Rope. + class Fiber { + public: + Fiber() : m_value(0) {} + Fiber(UString::Rep* string) : m_value(reinterpret_cast(string)) {} + Fiber(Rope* rope) : m_value(reinterpret_cast(rope) | 1) {} + + Fiber(void* nonFiber) : m_value(reinterpret_cast(nonFiber)) {} + + void deref() + { + if (isRope()) + rope()->deref(); + else + string()->deref(); + } + + Fiber& ref() + { + if (isString()) + string()->ref(); + else + rope()->ref(); + return *this; + } + + unsigned refAndGetLength() + { + if (isString()) { + UString::Rep* rep = string(); + return rep->ref()->size(); + } else { + Rope* r = rope(); + r->ref(); + return r->stringLength(); + } + } + + bool isRope() { return m_value & 1; } + Rope* rope() { return reinterpret_cast(m_value & ~1); } + bool isString() { return !isRope(); } + UString::Rep* string() { return reinterpret_cast(m_value); } + + void* nonFiber() { return reinterpret_cast(m_value); } + private: + intptr_t m_value; + }; + + // Creates a Rope comprising of 'ropeLength' Fibers. + // The Rope is constructed in an uninitialized state - initialize must be called for each Fiber in the Rope. + static PassRefPtr createOrNull(unsigned ropeLength) + { + void* allocation; + if (tryFastMalloc(sizeof(Rope) + (ropeLength - 1) * sizeof(Fiber)).getValue(allocation)) + return adoptRef(new (allocation) Rope(ropeLength)); + return 0; + } + + ~Rope(); + void destructNonRecursive(); + + void append(unsigned &index, Fiber& fiber) + { + m_fibers[index++] = fiber; + m_stringLength += fiber.refAndGetLength(); + } + void append(unsigned &index, const UString& string) + { + UString::Rep* rep = string.rep(); + m_fibers[index++] = Fiber(rep); + m_stringLength += rep->ref()->size(); + } + void append(unsigned& index, JSString* jsString) + { + if (jsString->isRope()) { + for (unsigned i = 0; i < jsString->m_ropeLength; ++i) + append(index, jsString->m_fibers[i]); + } else + append(index, jsString->string()); + } + + unsigned ropeLength() { return m_ropeLength; } + unsigned stringLength() { return m_stringLength; } + Fiber& fibers(unsigned index) { return m_fibers[index]; } + + private: + Rope(unsigned ropeLength) : m_ropeLength(ropeLength), m_stringLength(0) {} + void* operator new(size_t, void* inPlace) { return inPlace; } + + unsigned m_ropeLength; + unsigned m_stringLength; + Fiber m_fibers[1]; + }; + + ALWAYS_INLINE JSString(JSGlobalData* globalData, const UString& value) : JSCell(globalData->stringStructure.get()) + , m_stringLength(value.size()) , m_value(value) + , m_ropeLength(0) { Heap::heap(this)->reportExtraMemoryCost(value.cost()); } @@ -73,34 +177,168 @@ namespace JSC { enum HasOtherOwnerType { HasOtherOwner }; JSString(JSGlobalData* globalData, const UString& value, HasOtherOwnerType) : JSCell(globalData->stringStructure.get()) + , m_stringLength(value.size()) , m_value(value) + , m_ropeLength(0) { } JSString(JSGlobalData* globalData, PassRefPtr value, HasOtherOwnerType) : JSCell(globalData->stringStructure.get()) + , m_stringLength(value->size()) , m_value(value) + , m_ropeLength(0) { } - - const UString& value() const { return m_value; } + JSString(JSGlobalData* globalData, PassRefPtr rope) + : JSCell(globalData->stringStructure.get()) + , m_stringLength(rope->stringLength()) + , m_ropeLength(1) + { + m_fibers[0] = rope.releaseRef(); + } + // This constructor constructs a new string by concatenating s1 & s2. + // This should only be called with ropeLength <= 3. + JSString(JSGlobalData* globalData, unsigned ropeLength, JSString* s1, JSString* s2) + : JSCell(globalData->stringStructure.get()) + , m_stringLength(s1->length() + s2->length()) + , m_ropeLength(ropeLength) + { + ASSERT(ropeLength <= s_maxInternalRopeLength); + unsigned index = 0; + appendStringInConstruct(index, s1); + appendStringInConstruct(index, s2); + ASSERT(ropeLength == index); + } + // This constructor constructs a new string by concatenating s1 & s2. + // This should only be called with ropeLength <= 3. + JSString(JSGlobalData* globalData, unsigned ropeLength, JSString* s1, const UString& u2) + : JSCell(globalData->stringStructure.get()) + , m_stringLength(s1->length() + u2.size()) + , m_ropeLength(ropeLength) + { + ASSERT(ropeLength <= s_maxInternalRopeLength); + unsigned index = 0; + appendStringInConstruct(index, s1); + appendStringInConstruct(index, u2); + ASSERT(ropeLength == index); + } + // This constructor constructs a new string by concatenating s1 & s2. + // This should only be called with ropeLength <= 3. + JSString(JSGlobalData* globalData, unsigned ropeLength, const UString& u1, JSString* s2) + : JSCell(globalData->stringStructure.get()) + , m_stringLength(u1.size() + s2->length()) + , m_ropeLength(ropeLength) + { + ASSERT(ropeLength <= s_maxInternalRopeLength); + unsigned index = 0; + appendStringInConstruct(index, u1); + appendStringInConstruct(index, s2); + ASSERT(ropeLength == index); + } + // This constructor constructs a new string by concatenating v1, v2 & v3. + // This should only be called with ropeLength <= 3 ... which since every + // value must require a ropeLength of at least one implies that the length + // for each value must be exactly 1! + JSString(ExecState* exec, JSValue v1, JSValue v2, JSValue v3) + : JSCell(exec->globalData().stringStructure.get()) + , m_stringLength(0) + , m_ropeLength(s_maxInternalRopeLength) + { + unsigned index = 0; + appendValueInConstructAndIncrementLength(exec, index, v1); + appendValueInConstructAndIncrementLength(exec, index, v2); + appendValueInConstructAndIncrementLength(exec, index, v3); + ASSERT(index == s_maxInternalRopeLength); + } + + JSString(JSGlobalData* globalData, const UString& value, JSStringFinalizerCallback finalizer, void* context) + : JSCell(globalData->stringStructure.get()) + , m_stringLength(value.size()) + , m_value(value) + , m_ropeLength(0) + { + // nasty hack because we can't union non-POD types + m_fibers[0] = reinterpret_cast(reinterpret_cast(finalizer)); + m_fibers[1] = context; + Heap::heap(this)->reportExtraMemoryCost(value.cost()); + } + + ~JSString() + { + ASSERT(vptr() == JSGlobalData::jsStringVPtr); + for (unsigned i = 0; i < m_ropeLength; ++i) + m_fibers[i].deref(); + + if (!m_ropeLength && m_fibers[0].nonFiber()) { + JSStringFinalizerCallback finalizer = reinterpret_cast(m_fibers[0].nonFiber()); + finalizer(this, m_fibers[1].nonFiber()); + } + } + + const UString& value(ExecState* exec) const + { + if (isRope()) + resolveRope(exec); + return m_value; + } + const UString tryGetValue() const + { + if (isRope()) + UString(); + return m_value; + } + unsigned length() { return m_stringLength; } bool getStringPropertySlot(ExecState*, const Identifier& propertyName, PropertySlot&); bool getStringPropertySlot(ExecState*, unsigned propertyName, PropertySlot&); + bool getStringPropertyDescriptor(ExecState*, const Identifier& propertyName, PropertyDescriptor&); - bool canGetIndex(unsigned i) { return i < static_cast(m_value.size()); } - JSString* getIndex(JSGlobalData*, unsigned); + bool canGetIndex(unsigned i) { return i < m_stringLength; } + JSString* getIndex(ExecState*, unsigned); - static PassRefPtr createStructure(JSValuePtr proto) { return Structure::create(proto, TypeInfo(StringType, NeedsThisConversion)); } + static PassRefPtr createStructure(JSValue proto) { return Structure::create(proto, TypeInfo(StringType, OverridesGetOwnPropertySlot | NeedsThisConversion), AnonymousSlotCount); } private: enum VPtrStealingHackType { VPtrStealingHack }; JSString(VPtrStealingHackType) : JSCell(0) + , m_ropeLength(0) { } - virtual JSValuePtr toPrimitive(ExecState*, PreferredPrimitiveType) const; - virtual bool getPrimitiveNumber(ExecState*, double& number, JSValuePtr& value); + void resolveRope(ExecState*) const; + + void appendStringInConstruct(unsigned& index, const UString& string) + { + m_fibers[index++] = Rope::Fiber(string.rep()->ref()); + } + + void appendStringInConstruct(unsigned& index, JSString* jsString) + { + if (jsString->isRope()) { + for (unsigned i = 0; i < jsString->m_ropeLength; ++i) + m_fibers[index++] = jsString->m_fibers[i].ref(); + } else + appendStringInConstruct(index, jsString->string()); + } + + void appendValueInConstructAndIncrementLength(ExecState* exec, unsigned& index, JSValue v) + { + if (v.isString()) { + ASSERT(asCell(v)->isString()); + JSString* s = static_cast(asCell(v)); + ASSERT(s->ropeLength() == 1); + appendStringInConstruct(index, s); + m_stringLength += s->length(); + } else { + UString u(v.toString(exec)); + m_fibers[index++] = Rope::Fiber(u.rep()->ref()); + m_stringLength += u.size(); + } + } + + virtual JSValue toPrimitive(ExecState*, PreferredPrimitiveType) const; + virtual bool getPrimitiveNumber(ExecState*, double& number, JSValue& value); virtual bool toBoolean(ExecState*) const; virtual double toNumber(ExecState*) const; virtual JSObject* toObject(ExecState*) const; @@ -113,13 +351,41 @@ namespace JSC { // Actually getPropertySlot, not getOwnPropertySlot (see JSCell). virtual bool getOwnPropertySlot(ExecState*, const Identifier& propertyName, PropertySlot&); virtual bool getOwnPropertySlot(ExecState*, unsigned propertyName, PropertySlot&); - - UString m_value; + virtual bool getOwnPropertyDescriptor(ExecState*, const Identifier&, PropertyDescriptor&); + + static const unsigned s_maxInternalRopeLength = 3; + + // A string is represented either by a UString or a Rope. + unsigned m_stringLength; + mutable UString m_value; + mutable unsigned m_ropeLength; + mutable Rope::Fiber m_fibers[s_maxInternalRopeLength]; + + bool isRope() const { return m_ropeLength; } + UString& string() { ASSERT(!isRope()); return m_value; } + unsigned ropeLength() { return m_ropeLength ? m_ropeLength : 1; } + + friend JSValue jsString(ExecState* exec, JSString* s1, JSString* s2); + friend JSValue jsString(ExecState* exec, const UString& u1, JSString* s2); + friend JSValue jsString(ExecState* exec, JSString* s1, const UString& u2); + friend JSValue jsString(ExecState* exec, Register* strings, unsigned count); + friend JSValue jsString(ExecState* exec, JSValue thisValue, const ArgList& args); + friend JSString* jsStringWithFinalizer(ExecState*, const UString&, JSStringFinalizerCallback callback, void* context); }; - JSString* asString(JSValuePtr); + JSString* asString(JSValue); + + // When an object is created from a different DLL, MSVC changes vptr to a "local" one right after invoking a constructor, + // see . + // This breaks isJSString(), and we don't need that hack anyway, so we change vptr back to primary one. + // The below function must be called by any inline function that invokes a JSString constructor. +#if COMPILER(MSVC) && !defined(BUILDING_JavaScriptCore) + inline JSString* fixupVPtr(JSGlobalData* globalData, JSString* string) { string->setVPtr(globalData->jsStringVPtr); return string; } +#else + inline JSString* fixupVPtr(JSGlobalData*, JSString* string) { return string; } +#endif - inline JSString* asString(JSValuePtr value) + inline JSString* asString(JSValue value) { ASSERT(asCell(value)->isString()); return static_cast(asCell(value)); @@ -134,7 +400,7 @@ namespace JSC { { if (c <= 0xFF) return globalData->smallStrings.singleCharacterString(globalData, c); - return new (globalData) JSString(globalData, UString(&c, 1)); + return fixupVPtr(globalData, new (globalData) JSString(globalData, UString(&c, 1))); } inline JSString* jsSingleCharacterSubstring(JSGlobalData* globalData, const UString& s, unsigned offset) @@ -143,7 +409,7 @@ namespace JSC { UChar c = s.data()[offset]; if (c <= 0xFF) return globalData->smallStrings.singleCharacterString(globalData, c); - return new (globalData) JSString(globalData, UString::Rep::create(s.rep(), offset, 1)); + return fixupVPtr(globalData, new (globalData) JSString(globalData, UString(UString::Rep::create(s.rep(), offset, 1)))); } inline JSString* jsNontrivialString(JSGlobalData* globalData, const char* s) @@ -151,19 +417,67 @@ namespace JSC { ASSERT(s); ASSERT(s[0]); ASSERT(s[1]); - return new (globalData) JSString(globalData, s); + return fixupVPtr(globalData, new (globalData) JSString(globalData, s)); } inline JSString* jsNontrivialString(JSGlobalData* globalData, const UString& s) { ASSERT(s.size() > 1); - return new (globalData) JSString(globalData, s); + return fixupVPtr(globalData, new (globalData) JSString(globalData, s)); } - inline JSString* JSString::getIndex(JSGlobalData* globalData, unsigned i) + inline JSString* JSString::getIndex(ExecState* exec, unsigned i) { ASSERT(canGetIndex(i)); - return jsSingleCharacterSubstring(globalData, m_value, i); + return jsSingleCharacterSubstring(&exec->globalData(), value(exec), i); + } + + inline JSString* jsString(JSGlobalData* globalData, const UString& s) + { + int size = s.size(); + if (!size) + return globalData->smallStrings.emptyString(globalData); + if (size == 1) { + UChar c = s.data()[0]; + if (c <= 0xFF) + return globalData->smallStrings.singleCharacterString(globalData, c); + } + return fixupVPtr(globalData, new (globalData) JSString(globalData, s)); + } + + inline JSString* jsStringWithFinalizer(ExecState* exec, const UString& s, JSStringFinalizerCallback callback, void* context) + { + ASSERT(s.size() && (s.size() > 1 || s.data()[0] > 0xFF)); + JSGlobalData* globalData = &exec->globalData(); + return fixupVPtr(globalData, new (globalData) JSString(globalData, s, callback, context)); + } + + inline JSString* jsSubstring(JSGlobalData* globalData, const UString& s, unsigned offset, unsigned length) + { + ASSERT(offset <= static_cast(s.size())); + ASSERT(length <= static_cast(s.size())); + ASSERT(offset + length <= static_cast(s.size())); + if (!length) + return globalData->smallStrings.emptyString(globalData); + if (length == 1) { + UChar c = s.data()[offset]; + if (c <= 0xFF) + return globalData->smallStrings.singleCharacterString(globalData, c); + } + return fixupVPtr(globalData, new (globalData) JSString(globalData, UString(UString::Rep::create(s.rep(), offset, length)), JSString::HasOtherOwner)); + } + + inline JSString* jsOwnedString(JSGlobalData* globalData, const UString& s) + { + int size = s.size(); + if (!size) + return globalData->smallStrings.emptyString(globalData); + if (size == 1) { + UChar c = s.data()[0]; + if (c <= 0xFF) + return globalData->smallStrings.singleCharacterString(globalData, c); + } + return fixupVPtr(globalData, new (globalData) JSString(globalData, s, JSString::HasOtherOwner)); } inline JSString* jsEmptyString(ExecState* exec) { return jsEmptyString(&exec->globalData()); } @@ -178,14 +492,14 @@ namespace JSC { ALWAYS_INLINE bool JSString::getStringPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot) { if (propertyName == exec->propertyNames().length) { - slot.setValue(jsNumber(exec, m_value.size())); + slot.setValue(jsNumber(exec, m_stringLength)); return true; } bool isStrictUInt32; unsigned i = propertyName.toStrictUInt32(&isStrictUInt32); - if (isStrictUInt32 && i < static_cast(m_value.size())) { - slot.setValue(jsSingleCharacterSubstring(exec, m_value, i)); + if (isStrictUInt32 && i < m_stringLength) { + slot.setValue(jsSingleCharacterSubstring(exec, value(exec), i)); return true; } @@ -194,19 +508,61 @@ namespace JSC { ALWAYS_INLINE bool JSString::getStringPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot) { - if (propertyName < static_cast(m_value.size())) { - slot.setValue(jsSingleCharacterSubstring(exec, m_value, propertyName)); + if (propertyName < m_stringLength) { + slot.setValue(jsSingleCharacterSubstring(exec, value(exec), propertyName)); return true; } return false; } + inline bool isJSString(JSGlobalData* globalData, JSValue v) { return v.isCell() && v.asCell()->vptr() == globalData->jsStringVPtr; } + // --- JSValue inlines ---------------------------- - inline JSString* JSValuePtr::toThisJSString(ExecState* exec) + inline JSString* JSValue::toThisJSString(ExecState* exec) + { + return isCell() ? asCell()->toThisJSString(exec) : jsString(exec, toString(exec)); + } + + inline UString JSValue::toString(ExecState* exec) const + { + if (isString()) + return static_cast(asCell())->value(exec); + if (isInt32()) + return exec->globalData().numericStrings.add(asInt32()); + if (isDouble()) + return exec->globalData().numericStrings.add(asDouble()); + if (isTrue()) + return "true"; + if (isFalse()) + return "false"; + if (isNull()) + return "null"; + if (isUndefined()) + return "undefined"; + ASSERT(isCell()); + return asCell()->toString(exec); + } + + inline UString JSValue::toPrimitiveString(ExecState* exec) const { - return JSImmediate::isImmediate(asValue()) ? jsString(exec, JSImmediate::toString(asValue())) : asCell()->toThisJSString(exec); + if (isString()) + return static_cast(asCell())->value(exec); + if (isInt32()) + return exec->globalData().numericStrings.add(asInt32()); + if (isDouble()) + return exec->globalData().numericStrings.add(asDouble()); + if (isTrue()) + return "true"; + if (isFalse()) + return "false"; + if (isNull()) + return "null"; + if (isUndefined()) + return "undefined"; + ASSERT(isCell()); + return asCell()->toPrimitive(exec, NoPreference).toString(exec); } } // namespace JSC