From 21edc7c30bcd13f1293bbcd3e8980ffe1086de90 Mon Sep 17 00:00:00 2001 From: "yangguo@chromium.org" Date: Wed, 23 Nov 2011 09:58:58 +0000 Subject: [PATCH] Introduce short external strings without pointer cache. BUG= TEST= Review URL: http://codereview.chromium.org/8635011 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@10049 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/api.cc | 7 ++-- src/heap-inl.h | 7 +--- src/heap.cc | 9 +++++ src/heap.h | 10 ++++++ src/ia32/codegen-ia32.cc | 16 +++++---- src/objects-inl.h | 35 ++++++++++++------- src/objects-printer.cc | 6 ++++ src/objects-visiting.cc | 2 +- src/objects.cc | 61 +++++++++++++++------------------ src/objects.h | 69 +++++++++++++++++++++++++++++++++++--- src/serialize.cc | 9 ++++- test/cctest/test-api.cc | 8 ++--- test/mjsunit/string-externalize.js | 2 +- 13 files changed, 166 insertions(+), 75 deletions(-) diff --git a/src/api.cc b/src/api.cc index 1a47e09..d3a6f0b 100644 --- a/src/api.cc +++ b/src/api.cc @@ -4556,12 +4556,9 @@ bool v8::String::CanMakeExternal() { i::Handle obj = Utils::OpenHandle(this); i::Isolate* isolate = obj->GetIsolate(); if (IsDeadCheck(isolate, "v8::String::CanMakeExternal()")) return false; - if (isolate->string_tracker()->IsFreshUnusedString(obj)) { - return false; - } + if (isolate->string_tracker()->IsFreshUnusedString(obj)) return false; int size = obj->Size(); // Byte size of the original string. - if (size < i::ExternalString::kSize) - return false; + if (size < i::ExternalString::kShortSize) return false; i::StringShape shape(*obj); return !shape.IsExternal(); } diff --git a/src/heap-inl.h b/src/heap-inl.h index 396f678..8977cdb 100644 --- a/src/heap-inl.h +++ b/src/heap-inl.h @@ -255,16 +255,11 @@ void Heap::FinalizeExternalString(String* string) { ExternalString::kResourceOffset - kHeapObjectTag); - // Clear pointer cache. - ExternalString::cast(string)->clear_data_cache(); - // Dispose of the C++ object if it has not already been disposed. if (*resource_addr != NULL) { (*resource_addr)->Dispose(); + *resource_addr = NULL; } - - // Clear the resource pointer in the string. - *resource_addr = NULL; } diff --git a/src/heap.cc b/src/heap.cc index 8aa7580..02b4ca1 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -3997,6 +3997,15 @@ Map* Heap::SymbolMapForString(String* string) { if (map == external_string_with_ascii_data_map()) { return external_symbol_with_ascii_data_map(); } + if (map == short_external_string_map()) { + return short_external_symbol_map(); + } + if (map == short_external_ascii_string_map()) { + return short_external_ascii_symbol_map(); + } + if (map == short_external_string_with_ascii_data_map()) { + return short_external_symbol_with_ascii_data_map(); + } // No match found. return NULL; diff --git a/src/heap.h b/src/heap.h index cfd9625..9f4f505 100644 --- a/src/heap.h +++ b/src/heap.h @@ -110,6 +110,16 @@ inline Heap* _inline_get_heap_(); V(Map, external_string_map, ExternalStringMap) \ V(Map, external_string_with_ascii_data_map, ExternalStringWithAsciiDataMap) \ V(Map, external_ascii_string_map, ExternalAsciiStringMap) \ + V(Map, short_external_symbol_map, ShortExternalSymbolMap) \ + V(Map, \ + short_external_symbol_with_ascii_data_map, \ + ShortExternalSymbolWithAsciiDataMap) \ + V(Map, short_external_ascii_symbol_map, ShortExternalAsciiSymbolMap) \ + V(Map, short_external_string_map, ShortExternalStringMap) \ + V(Map, \ + short_external_string_with_ascii_data_map, \ + ShortExternalStringWithAsciiDataMap) \ + V(Map, short_external_ascii_string_map, ShortExternalAsciiStringMap) \ V(Map, undetectable_string_map, UndetectableStringMap) \ V(Map, undetectable_ascii_string_map, UndetectableAsciiStringMap) \ V(Map, external_pixel_array_map, ExternalPixelArrayMap) \ diff --git a/src/ia32/codegen-ia32.cc b/src/ia32/codegen-ia32.cc index 58eead7..d325938 100644 --- a/src/ia32/codegen-ia32.cc +++ b/src/ia32/codegen-ia32.cc @@ -563,15 +563,17 @@ void StringCharLoadGenerator::Generate(MacroAssembler* masm, __ Assert(zero, "external string expected, but not found"); } __ mov(result, FieldOperand(string, ExternalString::kResourceDataOffset)); - // Assert that the external string has not been finalized yet. - __ test(result, result); - __ j(zero, call_runtime); Register scratch = string; __ mov(scratch, FieldOperand(string, HeapObject::kMapOffset)); - __ cmp(scratch, Immediate(factory->external_ascii_string_map())); - __ j(equal, &ascii_external, Label::kNear); - __ cmp(scratch, Immediate(factory->external_ascii_symbol_map())); - __ j(equal, &ascii_external, Label::kNear); + __ movzx_b(scratch, FieldOperand(scratch, Map::kInstanceTypeOffset)); + // Rule out short external strings. + STATIC_CHECK(kShortExternalStringTag != 0); + __ test_b(scratch, kShortExternalStringMask); + __ j(not_zero, call_runtime); + // Check encoding. + STATIC_ASSERT(kTwoByteStringTag == 0); + __ test_b(scratch, kStringEncodingMask); + __ j(not_equal, &ascii_external, Label::kNear); // Two-byte string. __ movzx_w(result, Operand(result, index, times_2, 0)); __ jmp(&done); diff --git a/src/objects-inl.h b/src/objects-inl.h index f3a7fd3..8ef4f3c 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -2297,8 +2297,9 @@ void ConsString::set_second(String* value, WriteBarrierMode mode) { } -void ExternalString::clear_data_cache() { - WRITE_INTPTR_FIELD(this, kResourceDataOffset, 0); +bool ExternalString::is_short() { + InstanceType type = map()->instance_type(); + return (type & kShortExternalStringMask) == kShortExternalStringTag; } @@ -2307,19 +2308,24 @@ const ExternalAsciiString::Resource* ExternalAsciiString::resource() { } +void ExternalAsciiString::update_data_cache() { + if (is_short()) return; + const char** data_field = + reinterpret_cast(FIELD_ADDR(this, kResourceDataOffset)); + *data_field = resource()->data(); +} + + void ExternalAsciiString::set_resource( const ExternalAsciiString::Resource* resource) { *reinterpret_cast( FIELD_ADDR(this, kResourceOffset)) = resource; - clear_data_cache(); + if (resource != NULL) update_data_cache(); } const char* ExternalAsciiString::GetChars() { - const char** data_field = - reinterpret_cast(FIELD_ADDR(this, kResourceDataOffset)); - if (*data_field == NULL) *data_field = resource()->data(); - return *data_field; + return resource()->data(); } @@ -2334,19 +2340,24 @@ const ExternalTwoByteString::Resource* ExternalTwoByteString::resource() { } +void ExternalTwoByteString::update_data_cache() { + if (is_short()) return; + const uint16_t** data_field = + reinterpret_cast(FIELD_ADDR(this, kResourceDataOffset)); + *data_field = resource()->data(); +} + + void ExternalTwoByteString::set_resource( const ExternalTwoByteString::Resource* resource) { *reinterpret_cast( FIELD_ADDR(this, kResourceOffset)) = resource; - clear_data_cache(); + if (resource != NULL) update_data_cache(); } const uint16_t* ExternalTwoByteString::GetChars() { - const uint16_t** data_field = - reinterpret_cast(FIELD_ADDR(this, kResourceDataOffset)); - if (*data_field == NULL) *data_field = resource()->data(); - return *data_field; + return resource()->data(); } diff --git a/src/objects-printer.cc b/src/objects-printer.cc index c9f3f84..4b5d049 100644 --- a/src/objects-printer.cc +++ b/src/objects-printer.cc @@ -446,6 +446,9 @@ static const char* TypeToString(InstanceType type) { case EXTERNAL_ASCII_SYMBOL_TYPE: case EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE: case EXTERNAL_SYMBOL_TYPE: return "EXTERNAL_SYMBOL"; + case SHORT_EXTERNAL_ASCII_SYMBOL_TYPE: + case SHORT_EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE: + case SHORT_EXTERNAL_SYMBOL_TYPE: return "SHORT_EXTERNAL_SYMBOL"; case ASCII_STRING_TYPE: return "ASCII_STRING"; case STRING_TYPE: return "TWO_BYTE_STRING"; case CONS_STRING_TYPE: @@ -453,6 +456,9 @@ static const char* TypeToString(InstanceType type) { case EXTERNAL_ASCII_STRING_TYPE: case EXTERNAL_STRING_WITH_ASCII_DATA_TYPE: case EXTERNAL_STRING_TYPE: return "EXTERNAL_STRING"; + case SHORT_EXTERNAL_ASCII_STRING_TYPE: + case SHORT_EXTERNAL_STRING_WITH_ASCII_DATA_TYPE: + case SHORT_EXTERNAL_STRING_TYPE: return "SHORT_EXTERNAL_STRING"; case FIXED_ARRAY_TYPE: return "FIXED_ARRAY"; case BYTE_ARRAY_TYPE: return "BYTE_ARRAY"; case FREE_SPACE_TYPE: return "FREE_SPACE"; diff --git a/src/objects-visiting.cc b/src/objects-visiting.cc index a796283..9ca102b 100644 --- a/src/objects-visiting.cc +++ b/src/objects-visiting.cc @@ -64,7 +64,7 @@ StaticVisitorBase::VisitorId StaticVisitorBase::GetVisitorId( case kExternalStringTag: return GetVisitorIdForSize(kVisitDataObject, kVisitDataObjectGeneric, - ExternalString::kSize); + instance_size); } UNREACHABLE(); } diff --git a/src/objects.cc b/src/objects.cc index 2ebeb65..f3fe4cf 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -952,33 +952,32 @@ bool String::MakeExternal(v8::String::ExternalStringResource* resource) { #endif // DEBUG Heap* heap = GetHeap(); int size = this->Size(); // Byte size of the original string. - if (size < ExternalString::kSize) { + if (size < ExternalString::kShortSize) { return false; } - ASSERT(size >= ExternalString::kSize); bool is_ascii = this->IsAsciiRepresentation(); bool is_symbol = this->IsSymbol(); - int length = this->length(); - int hash_field = this->hash_field(); // Morph the object to an external string by adjusting the map and // reinitializing the fields. - this->set_map(is_ascii ? - heap->external_string_with_ascii_data_map() : - heap->external_string_map()); + if (size >= ExternalString::kSize) { + this->set_map( + is_symbol + ? (is_ascii ? heap->external_symbol_with_ascii_data_map() + : heap->external_symbol_map()) + : (is_ascii ? heap->external_string_with_ascii_data_map() + : heap->external_string_map())); + } else { + this->set_map( + is_symbol + ? (is_ascii ? heap->short_external_symbol_with_ascii_data_map() + : heap->short_external_symbol_map()) + : (is_ascii ? heap->short_external_string_with_ascii_data_map() + : heap->short_external_string_map())); + } ExternalTwoByteString* self = ExternalTwoByteString::cast(this); - self->set_length(length); - self->set_hash_field(hash_field); self->set_resource(resource); - // Additionally make the object into an external symbol if the original string - // was a symbol to start with. - if (is_symbol) { - self->Hash(); // Force regeneration of the hash value. - // Now morph this external string into a external symbol. - this->set_map(is_ascii ? - heap->external_symbol_with_ascii_data_map() : - heap->external_symbol_map()); - } + if (is_symbol) self->Hash(); // Force regeneration of the hash value. // Fill the remainder of the string with dead wood. int new_size = this->Size(); // Byte size of the external String object. @@ -1004,28 +1003,23 @@ bool String::MakeExternal(v8::String::ExternalAsciiStringResource* resource) { #endif // DEBUG Heap* heap = GetHeap(); int size = this->Size(); // Byte size of the original string. - if (size < ExternalString::kSize) { + if (size < ExternalString::kShortSize) { return false; } - ASSERT(size >= ExternalString::kSize); bool is_symbol = this->IsSymbol(); - int length = this->length(); - int hash_field = this->hash_field(); // Morph the object to an external string by adjusting the map and - // reinitializing the fields. - this->set_map(heap->external_ascii_string_map()); + // reinitializing the fields. Use short version if space is limited. + if (size >= ExternalString::kSize) { + this->set_map(is_symbol ? heap->external_ascii_symbol_map() + : heap->external_ascii_string_map()); + } else { + this->set_map(is_symbol ? heap->short_external_ascii_symbol_map() + : heap->short_external_ascii_string_map()); + } ExternalAsciiString* self = ExternalAsciiString::cast(this); - self->set_length(length); - self->set_hash_field(hash_field); self->set_resource(resource); - // Additionally make the object into an external symbol if the original string - // was a symbol to start with. - if (is_symbol) { - self->Hash(); // Force regeneration of the hash value. - // Now morph this external string into a external symbol. - this->set_map(heap->external_ascii_symbol_map()); - } + if (is_symbol) self->Hash(); // Force regeneration of the hash value. // Fill the remainder of the string with dead wood. int new_size = this->Size(); // Byte size of the external String object. @@ -1033,7 +1027,6 @@ bool String::MakeExternal(v8::String::ExternalAsciiStringResource* resource) { if (Marking::IsBlack(Marking::MarkBitFrom(this))) { MemoryChunk::IncrementLiveBytes(this->address(), new_size - size); } - return true; } diff --git a/src/objects.h b/src/objects.h index b376bcc..ed2b8b7 100644 --- a/src/objects.h +++ b/src/objects.h @@ -232,6 +232,9 @@ static const int kVariableSizeSentinel = 0; V(EXTERNAL_SYMBOL_TYPE) \ V(EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE) \ V(EXTERNAL_ASCII_SYMBOL_TYPE) \ + V(SHORT_EXTERNAL_SYMBOL_TYPE) \ + V(SHORT_EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE) \ + V(SHORT_EXTERNAL_ASCII_SYMBOL_TYPE) \ V(STRING_TYPE) \ V(ASCII_STRING_TYPE) \ V(CONS_STRING_TYPE) \ @@ -240,6 +243,9 @@ static const int kVariableSizeSentinel = 0; V(EXTERNAL_STRING_TYPE) \ V(EXTERNAL_STRING_WITH_ASCII_DATA_TYPE) \ V(EXTERNAL_ASCII_STRING_TYPE) \ + V(SHORT_EXTERNAL_STRING_TYPE) \ + V(SHORT_EXTERNAL_STRING_WITH_ASCII_DATA_TYPE) \ + V(SHORT_EXTERNAL_ASCII_STRING_TYPE) \ V(PRIVATE_EXTERNAL_ASCII_STRING_TYPE) \ \ V(MAP_TYPE) \ @@ -340,6 +346,18 @@ static const int kVariableSizeSentinel = 0; ExternalAsciiString::kSize, \ external_ascii_symbol, \ ExternalAsciiSymbol) \ + V(SHORT_EXTERNAL_SYMBOL_TYPE, \ + ExternalTwoByteString::kShortSize, \ + short_external_symbol, \ + ShortExternalSymbol) \ + V(SHORT_EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE, \ + ExternalTwoByteString::kShortSize, \ + short_external_symbol_with_ascii_data, \ + ShortExternalSymbolWithAsciiData) \ + V(SHORT_EXTERNAL_ASCII_SYMBOL_TYPE, \ + ExternalAsciiString::kShortSize, \ + short_external_ascii_symbol, \ + ShortExternalAsciiSymbol) \ V(STRING_TYPE, \ kVariableSizeSentinel, \ string, \ @@ -375,7 +393,19 @@ static const int kVariableSizeSentinel = 0; V(EXTERNAL_ASCII_STRING_TYPE, \ ExternalAsciiString::kSize, \ external_ascii_string, \ - ExternalAsciiString) + ExternalAsciiString) \ + V(SHORT_EXTERNAL_STRING_TYPE, \ + ExternalTwoByteString::kShortSize, \ + short_external_string, \ + ShortExternalString) \ + V(SHORT_EXTERNAL_STRING_WITH_ASCII_DATA_TYPE, \ + ExternalTwoByteString::kShortSize, \ + short_external_string_with_ascii_data, \ + ShortExternalStringWithAsciiData) \ + V(SHORT_EXTERNAL_ASCII_STRING_TYPE, \ + ExternalAsciiString::kShortSize, \ + short_external_ascii_string, \ + ShortExternalAsciiString) // A struct is a simple object a set of object-valued fields. Including an // object type in this causes the compiler to generate most of the boilerplate @@ -459,6 +489,11 @@ STATIC_ASSERT(IS_POWER_OF_TWO(kSlicedNotConsMask) && kSlicedNotConsMask != 0); const uint32_t kAsciiDataHintMask = 0x08; const uint32_t kAsciiDataHintTag = 0x08; +// If bit 7 is clear and string representation indicates an external string, +// then bit 4 indicates whether the data pointer is cached. +const uint32_t kShortExternalStringMask = 0x10; +const uint32_t kShortExternalStringTag = 0x10; + // A ConsString with an empty string as the right side is a candidate // for being shortcut by the garbage collector unless it is a @@ -478,6 +513,13 @@ enum InstanceType { ASCII_SYMBOL_TYPE = kAsciiStringTag | kSymbolTag | kSeqStringTag, CONS_SYMBOL_TYPE = kTwoByteStringTag | kSymbolTag | kConsStringTag, CONS_ASCII_SYMBOL_TYPE = kAsciiStringTag | kSymbolTag | kConsStringTag, + SHORT_EXTERNAL_SYMBOL_TYPE = kTwoByteStringTag | kSymbolTag | + kExternalStringTag | kShortExternalStringTag, + SHORT_EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE = + kTwoByteStringTag | kSymbolTag | kExternalStringTag | + kAsciiDataHintTag | kShortExternalStringTag, + SHORT_EXTERNAL_ASCII_SYMBOL_TYPE = kAsciiStringTag | kExternalStringTag | + kSymbolTag | kShortExternalStringTag, EXTERNAL_SYMBOL_TYPE = kTwoByteStringTag | kSymbolTag | kExternalStringTag, EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE = kTwoByteStringTag | kSymbolTag | kExternalStringTag | kAsciiDataHintTag, @@ -489,6 +531,13 @@ enum InstanceType { CONS_ASCII_STRING_TYPE = kAsciiStringTag | kConsStringTag, SLICED_STRING_TYPE = kTwoByteStringTag | kSlicedStringTag, SLICED_ASCII_STRING_TYPE = kAsciiStringTag | kSlicedStringTag, + SHORT_EXTERNAL_STRING_TYPE = + kTwoByteStringTag | kExternalStringTag | kShortExternalStringTag, + SHORT_EXTERNAL_STRING_WITH_ASCII_DATA_TYPE = + kTwoByteStringTag | kExternalStringTag | + kAsciiDataHintTag | kShortExternalStringTag, + SHORT_EXTERNAL_ASCII_STRING_TYPE = + kAsciiStringTag | kExternalStringTag | kShortExternalStringTag, EXTERNAL_STRING_TYPE = kTwoByteStringTag | kExternalStringTag, EXTERNAL_STRING_WITH_ASCII_DATA_TYPE = kTwoByteStringTag | kExternalStringTag | kAsciiDataHintTag, @@ -6755,12 +6804,12 @@ class ExternalString: public String { // Layout description. static const int kResourceOffset = POINTER_SIZE_ALIGN(String::kSize); + static const int kShortSize = kResourceOffset + kPointerSize; static const int kResourceDataOffset = kResourceOffset + kPointerSize; static const int kSize = kResourceDataOffset + kPointerSize; - // Clear the cached pointer to the character array provided by the resource. - // This cache is updated the first time the character array is accessed. - inline void clear_data_cache(); + // Return whether external string is short (data pointer is not cached). + inline bool is_short(); STATIC_CHECK(kResourceOffset == Internals::kStringResourceOffset); @@ -6781,6 +6830,12 @@ class ExternalAsciiString: public ExternalString { inline const Resource* resource(); inline void set_resource(const Resource* buffer); + // Update the pointer cache to the external character array. + // The cached pointer is always valid, as the external character array does = + // not move during lifetime. Deserialization is the only exception, after + // which the pointer cache has to be refreshed. + inline void update_data_cache(); + inline const char* GetChars(); // Dispatched behavior. @@ -6820,6 +6875,12 @@ class ExternalTwoByteString: public ExternalString { inline const Resource* resource(); inline void set_resource(const Resource* buffer); + // Update the pointer cache to the external character array. + // The cached pointer is always valid, as the external character array does = + // not move during lifetime. Deserialization is the only exception, after + // which the pointer cache has to be refreshed. + inline void update_data_cache(); + inline const uint16_t* GetChars(); // Dispatched behavior. diff --git a/src/serialize.cc b/src/serialize.cc index e3a84c8..d0a1a63 100644 --- a/src/serialize.cc +++ b/src/serialize.cc @@ -669,6 +669,14 @@ void Deserializer::Deserialize() { isolate_->heap()->set_global_contexts_list( isolate_->heap()->undefined_value()); + + // Update data pointers to the external strings containing natives sources. + for (int i = 0; i < Natives::GetBuiltinsCount(); i++) { + Object* source = isolate_->heap()->natives_source_cache()->get(i); + if (!source->IsUndefined()) { + ExternalAsciiString::cast(source)->update_data_cache(); + } + } } @@ -1564,7 +1572,6 @@ void Serializer::ObjectSerializer::VisitExternalAsciiString( sink_->Put(kNativesStringResource, "NativesStringResource"); sink_->PutSection(i, "NativesStringResourceEnd"); bytes_processed_so_far_ += sizeof(resource); - string->clear_data_cache(); return; } } diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 5e76de7..cc20b6f 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -491,7 +491,7 @@ TEST(MakingExternalStringConditions) { HEAP->CollectGarbage(i::NEW_SPACE); HEAP->CollectGarbage(i::NEW_SPACE); - uint16_t* two_byte_string = AsciiToTwoByteString("abcdefghi"); + uint16_t* two_byte_string = AsciiToTwoByteString("s1"); Local small_string = String::New(two_byte_string); i::DeleteArray(two_byte_string); @@ -503,7 +503,7 @@ TEST(MakingExternalStringConditions) { // Old space strings should be accepted. CHECK(small_string->CanMakeExternal()); - two_byte_string = AsciiToTwoByteString("abcdefghi"); + two_byte_string = AsciiToTwoByteString("small string 2"); small_string = String::New(two_byte_string); i::DeleteArray(two_byte_string); @@ -537,7 +537,7 @@ TEST(MakingExternalAsciiStringConditions) { HEAP->CollectGarbage(i::NEW_SPACE); HEAP->CollectGarbage(i::NEW_SPACE); - Local small_string = String::New("abcdefghi"); + Local small_string = String::New("s1"); // We should refuse to externalize newly created small string. CHECK(!small_string->CanMakeExternal()); // Trigger GCs so that the newly allocated string moves to old gen. @@ -546,7 +546,7 @@ TEST(MakingExternalAsciiStringConditions) { // Old space strings should be accepted. CHECK(small_string->CanMakeExternal()); - small_string = String::New("abcdefghi"); + small_string = String::New("small string 2"); // We should refuse externalizing newly created small string. CHECK(!small_string->CanMakeExternal()); for (int i = 0; i < 100; i++) { diff --git a/test/mjsunit/string-externalize.js b/test/mjsunit/string-externalize.js index 9edb615..449af59 100644 --- a/test/mjsunit/string-externalize.js +++ b/test/mjsunit/string-externalize.js @@ -39,7 +39,7 @@ function test() { assertTrue(isAsciiString(str)); var twoByteExternalWithAsciiData = - "AAAAAAAA" + (function() { return "A"; })(); + "AA" + (function() { return "A"; })(); externalizeString(twoByteExternalWithAsciiData, true /* force two-byte */); assertFalse(isAsciiString(twoByteExternalWithAsciiData)); -- 2.7.4