Commit 1bf2c740 authored by iposva@chromium.org's avatar iposva@chromium.org

Allow the morphing of strings to external strings to avoid having to

create copies in the embedding code (aka WebKit V8 bindings) on every
external use.

Review URL: http://codereview.chromium.org/21117

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1252 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent c621bbbe
......@@ -834,6 +834,16 @@ class EXPORT String : public Primitive {
* external string resource.
*/
static Local<String> NewExternal(ExternalStringResource* resource);
/**
* Associate an external string resource with this string by transforming it
* in place so that existing references to this string in the JavaScript heap
* will use the external string resource. The external string resource's
* character contents needs to be equivalent to this string.
* Returns true if the string has been changed to be an external string.
* The string is not modified if the operation fails.
*/
bool MakeExternal(ExternalStringResource* resource);
/**
* Creates a new external string using the ascii data defined in the given
......@@ -844,6 +854,16 @@ class EXPORT String : public Primitive {
* external string resource.
*/
static Local<String> NewExternal(ExternalAsciiStringResource* resource);
/**
* Associate an external string resource with this string by transforming it
* in place so that existing references to this string in the JavaScript heap
* will use the external string resource. The external string resource's
* character contents needs to be equivalent to this string.
* Returns true if the string has been changed to be an external string.
* The string is not modified if the operation fails.
*/
bool MakeExternal(ExternalAsciiStringResource* resource);
/** Creates an undetectable string from the supplied ascii or utf-8 data.*/
static Local<String> NewUndetectable(const char* data, int length = -1);
......
......@@ -2440,22 +2440,58 @@ i::Handle<i::String> NewExternalAsciiStringHandle(
static void DisposeExternalString(v8::Persistent<v8::Value> obj,
void* parameter) {
v8::String::ExternalStringResource* resource =
reinterpret_cast<v8::String::ExternalStringResource*>(parameter);
const size_t total_size = resource->length() * sizeof(*resource->data());
i::Counters::total_external_string_memory.Decrement(total_size);
delete resource;
i::ExternalTwoByteString* str =
i::ExternalTwoByteString::cast(*Utils::OpenHandle(*obj));
// External symbols are deleted when they are pruned out of the symbol
// table. Generally external symbols are not registered with the weak handle
// callbacks unless they are upgraded to a symbol after being externalized.
if (!str->IsSymbol()) {
v8::String::ExternalStringResource* resource =
reinterpret_cast<v8::String::ExternalStringResource*>(parameter);
if (resource != NULL) {
const size_t total_size = resource->length() * sizeof(*resource->data());
i::Counters::total_external_string_memory.Decrement(total_size);
// The object will continue to live in the JavaScript heap until the
// handle is entirely cleaned out by the next GC. For example the
// destructor for the resource below could bring it back to life again.
// Which is why we make sure to not have a dangling pointer here.
str->set_resource(NULL);
delete resource;
}
}
// In any case we do not need this handle any longer.
obj.Dispose();
}
static void DisposeExternalAsciiString(v8::Persistent<v8::Value> obj,
void* parameter) {
v8::String::ExternalAsciiStringResource* resource =
reinterpret_cast<v8::String::ExternalAsciiStringResource*>(parameter);
const size_t total_size = resource->length() * sizeof(*resource->data());
i::Counters::total_external_string_memory.Decrement(total_size);
delete resource;
i::ExternalAsciiString* str =
i::ExternalAsciiString::cast(*Utils::OpenHandle(*obj));
// External symbols are deleted when they are pruned out of the symbol
// table. Generally external symbols are not registered with the weak handle
// callbacks unless they are upgraded to a symbol after being externalized.
if (!str->IsSymbol()) {
v8::String::ExternalAsciiStringResource* resource =
reinterpret_cast<v8::String::ExternalAsciiStringResource*>(parameter);
if (resource != NULL) {
const size_t total_size = resource->length() * sizeof(*resource->data());
i::Counters::total_external_string_memory.Decrement(total_size);
// The object will continue to live in the JavaScript heap until the
// handle is entirely cleaned out by the next GC. For example the
// destructor for the resource below could bring it back to life again.
// Which is why we make sure to not have a dangling pointer here.
str->set_resource(NULL);
delete resource;
}
}
// In any case we do not need this handle any longer.
obj.Dispose();
}
......@@ -2475,6 +2511,24 @@ Local<String> v8::String::NewExternal(
}
bool v8::String::MakeExternal(v8::String::ExternalStringResource* resource) {
if (IsDeadCheck("v8::String::MakeExternal()")) return false;
if (this->IsExternal()) return false; // Already an external string.
i::Handle <i::String> obj = Utils::OpenHandle(this);
bool result = obj->MakeExternal(resource);
if (result && !obj->IsSymbol()) {
// Operation was successful and the string is not a symbol. In this case
// we need to make sure that the we call the destructor for the external
// resource when no strong references to the string remain.
i::Handle<i::Object> handle = i::GlobalHandles::Create(*obj);
i::GlobalHandles::MakeWeak(handle.location(),
resource,
&DisposeExternalString);
}
return result;
}
Local<String> v8::String::NewExternal(
v8::String::ExternalAsciiStringResource* resource) {
EnsureInitialized("v8::String::NewExternal()");
......@@ -2490,6 +2544,25 @@ Local<String> v8::String::NewExternal(
}
bool v8::String::MakeExternal(
v8::String::ExternalAsciiStringResource* resource) {
if (IsDeadCheck("v8::String::MakeExternal()")) return false;
if (this->IsExternal()) return false; // Already an external string.
i::Handle <i::String> obj = Utils::OpenHandle(this);
bool result = obj->MakeExternal(resource);
if (result && !obj->IsSymbol()) {
// Operation was successful and the string is not a symbol. In this case
// we need to make sure that the we call the destructor for the external
// resource when no strong references to the string remain.
i::Handle<i::Object> handle = i::GlobalHandles::Create(*obj);
i::GlobalHandles::MakeWeak(handle.location(),
resource,
&DisposeExternalAsciiString);
}
return result;
}
Local<v8::Object> v8::Object::New() {
EnsureInitialized("v8::Object::New()");
LOG_API("Object::New");
......
......@@ -1519,16 +1519,9 @@ Object* Heap::AllocateExternalStringFromAscii(
Object* Heap::AllocateExternalStringFromTwoByte(
ExternalTwoByteString::Resource* resource) {
Map* map;
int length = resource->length();
if (length <= String::kMaxShortStringSize) {
map = short_external_string_map();
} else if (length <= String::kMaxMediumStringSize) {
map = medium_external_string_map();
} else {
map = long_external_string_map();
}
Map* map = ExternalTwoByteString::StringMap(length);
Object* result = Allocate(map, NEW_SPACE);
if (result->IsFailure()) return result;
......@@ -1542,16 +1535,9 @@ Object* Heap::AllocateExternalStringFromTwoByte(
Object* Heap::AllocateExternalSymbolFromTwoByte(
ExternalTwoByteString::Resource* resource) {
Map* map;
int length = resource->length();
if (length <= String::kMaxShortStringSize) {
map = short_external_symbol_map();
} else if (length <= String::kMaxMediumStringSize) {
map = medium_external_symbol_map();
} else {
map = long_external_symbol_map();
}
Map* map = ExternalTwoByteString::SymbolMap(length);
Object* result = Allocate(map, OLD_DATA_SPACE);
if (result->IsFailure()) return result;
......@@ -1618,6 +1604,18 @@ Object* Heap::AllocateByteArray(int length) {
}
void Heap::CreateFillerObjectAt(Address addr, int size) {
if (size == 0) return;
HeapObject* filler = HeapObject::FromAddress(addr);
if (size == kPointerSize) {
filler->set_map(Heap::one_word_filler_map());
} else {
filler->set_map(Heap::byte_array_map());
ByteArray::cast(filler)->set_length(ByteArray::LengthFor(size));
}
}
Object* Heap::CreateCode(const CodeDesc& desc,
ScopeInfo<>* sinfo,
Code::Flags flags,
......@@ -2069,18 +2067,24 @@ Map* Heap::SymbolMapForString(String* string) {
return long_sliced_ascii_symbol_map();
}
if (map == short_external_string_map()) return short_external_string_map();
if (map == medium_external_string_map()) return medium_external_string_map();
if (map == long_external_string_map()) return long_external_string_map();
if (map == short_external_string_map()) {
return short_external_symbol_map();
}
if (map == medium_external_string_map()) {
return medium_external_symbol_map();
}
if (map == long_external_string_map()) {
return long_external_symbol_map();
}
if (map == short_external_ascii_string_map()) {
return short_external_ascii_string_map();
return short_external_ascii_symbol_map();
}
if (map == medium_external_ascii_string_map()) {
return medium_external_ascii_string_map();
return medium_external_ascii_symbol_map();
}
if (map == long_external_ascii_string_map()) {
return long_external_ascii_string_map();
return long_external_ascii_symbol_map();
}
// No match found.
......
......@@ -556,6 +556,10 @@ class Heap : public AllStatic {
AllocationSpace space,
AllocationSpace retry_space);
// Initialize a filler object to keep the ability to iterate over the heap
// when shortening objects.
static void CreateFillerObjectAt(Address addr, int size);
// Makes a new native code object
// Returns Failure::RetryAfterGC(requested_bytes, space) if the allocation
// failed. On success, the pointer to the Code object is stored in the
......
......@@ -390,6 +390,34 @@ class SymbolTableCleaner : public ObjectVisitor {
// Visit all HeapObject pointers in [start, end).
for (Object** p = start; p < end; p++) {
if ((*p)->IsHeapObject() && !HeapObject::cast(*p)->IsMarked()) {
// Check if the symbol being pruned is an external symbol. We need to
// delete the associated external data as this symbol is going away.
// Since the object is not marked we can access its map word safely
// without having to worry about marking bits in the object header.
Map* map = HeapObject::cast(*p)->map();
// Since no objects have yet been moved we can safely access the map of
// the object.
uint32_t type = map->instance_type();
bool is_external = (type & kStringRepresentationMask) ==
kExternalStringTag;
if (is_external) {
bool is_two_byte = (type & kStringEncodingMask) == kTwoByteStringTag;
byte* resource_addr = reinterpret_cast<byte*>(*p) +
ExternalString::kResourceOffset -
kHeapObjectTag;
if (is_two_byte) {
v8::String::ExternalStringResource* resource =
*reinterpret_cast<v8::String::ExternalStringResource**>
(resource_addr);
delete resource;
} else {
v8::String::ExternalAsciiStringResource* resource =
*reinterpret_cast<v8::String::ExternalAsciiStringResource**>
(resource_addr);
delete resource;
}
}
// Set the entry to null_value (as deleted).
*p = Heap::null_value();
pointers_removed_++;
......
......@@ -1639,6 +1639,34 @@ void ExternalAsciiString::set_resource(
}
Map* ExternalAsciiString::StringMap(int length) {
Map* map;
// Number of characters: determines the map.
if (length <= String::kMaxShortStringSize) {
map = Heap::short_external_ascii_string_map();
} else if (length <= String::kMaxMediumStringSize) {
map = Heap::medium_external_ascii_string_map();
} else {
map = Heap::long_external_ascii_string_map();
}
return map;
}
Map* ExternalAsciiString::SymbolMap(int length) {
Map* map;
// Number of characters: determines the map.
if (length <= String::kMaxShortStringSize) {
map = Heap::short_external_ascii_symbol_map();
} else if (length <= String::kMaxMediumStringSize) {
map = Heap::medium_external_ascii_symbol_map();
} else {
map = Heap::long_external_ascii_symbol_map();
}
return map;
}
ExternalTwoByteString::Resource* ExternalTwoByteString::resource() {
return *reinterpret_cast<Resource**>(FIELD_ADDR(this, kResourceOffset));
}
......@@ -1650,6 +1678,34 @@ void ExternalTwoByteString::set_resource(
}
Map* ExternalTwoByteString::StringMap(int length) {
Map* map;
// Number of characters: determines the map.
if (length <= String::kMaxShortStringSize) {
map = Heap::short_external_string_map();
} else if (length <= String::kMaxMediumStringSize) {
map = Heap::medium_external_string_map();
} else {
map = Heap::long_external_string_map();
}
return map;
}
Map* ExternalTwoByteString::SymbolMap(int length) {
Map* map;
// Number of characters: determines the map.
if (length <= String::kMaxShortStringSize) {
map = Heap::short_external_symbol_map();
} else if (length <= String::kMaxMediumStringSize) {
map = Heap::medium_external_symbol_map();
} else {
map = Heap::long_external_symbol_map();
}
return map;
}
byte ByteArray::get(int index) {
ASSERT(index >= 0 && index < this->length());
return READ_BYTE_FIELD(this, kHeaderSize + index * kCharSize);
......
......@@ -667,6 +667,92 @@ Object* String::TryFlatten(StringShape shape) {
}
bool String::MakeExternal(v8::String::ExternalStringResource* resource) {
#ifdef DEBUG
{ // NOLINT (presubmit.py gets confused about if and braces)
// Assert that the resource and the string are equivalent.
ASSERT(static_cast<size_t>(this->length()) == resource->length());
SmartPointer<uc16> smart_chars = this->ToWideCString();
ASSERT(memcmp(*smart_chars,
resource->data(),
resource->length()*sizeof(**smart_chars)) == 0);
}
#endif // DEBUG
int size = this->Size(); // Byte size of the original string.
if (size < ExternalString::kSize) {
// The string is too small to fit an external String in its place. This can
// only happen for zero length strings.
return false;
}
ASSERT(size >= ExternalString::kSize);
bool is_symbol = this->IsSymbol();
int length = this->length();
// Morph the object to an external string by adjusting the map and
// reinitializing the fields.
this->set_map(ExternalTwoByteString::StringMap(length));
ExternalTwoByteString* self = ExternalTwoByteString::cast(this);
self->set_length(length);
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.
self->set_map(ExternalTwoByteString::SymbolMap(length));
}
// Fill the remainder of the string with dead wood.
int new_size = this->Size(); // Byte size of the external String object.
Heap::CreateFillerObjectAt(this->address() + new_size, size - new_size);
return true;
}
bool String::MakeExternal(v8::String::ExternalAsciiStringResource* resource) {
#ifdef DEBUG
{ // NOLINT (presubmit.py gets confused about if and braces)
// Assert that the resource and the string are equivalent.
ASSERT(static_cast<size_t>(this->length()) == resource->length());
SmartPointer<char> smart_chars = this->ToCString();
ASSERT(memcmp(*smart_chars,
resource->data(),
resource->length()*sizeof(**smart_chars)) == 0);
}
#endif // DEBUG
int size = this->Size(); // Byte size of the original string.
if (size < ExternalString::kSize) {
// The string is too small to fit an external String in its place. This can
// only happen for zero length strings.
return false;
}
ASSERT(size >= ExternalString::kSize);
bool is_symbol = this->IsSymbol();
int length = this->length();
// Morph the object to an external string by adjusting the map and
// reinitializing the fields.
this->set_map(ExternalAsciiString::StringMap(length));
ExternalAsciiString* self = ExternalAsciiString::cast(this);
self->set_length(length);
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.
self->set_map(ExternalAsciiString::SymbolMap(length));
}
// Fill the remainder of the string with dead wood.
int new_size = this->Size(); // Byte size of the external String object.
Heap::CreateFillerObjectAt(this->address() + new_size, size - new_size);
return true;
}
void String::StringShortPrint(StringStream* accumulator) {
StringShape shape(this);
int len = length(shape);
......@@ -1927,23 +2013,15 @@ Object* JSObject::NormalizeProperties(PropertyNormalizationMode mode) {
if (obj->IsFailure()) return obj;
Map* new_map = Map::cast(obj);
// Clear inobject properties if needed by adjusting the instance
// size and putting in a filler or byte array instead of the
// inobject properties.
// Clear inobject properties if needed by adjusting the instance size and
// putting in a filler object instead of the inobject properties.
if (mode == CLEAR_INOBJECT_PROPERTIES && map()->inobject_properties() > 0) {
int instance_size_delta = map()->inobject_properties() * kPointerSize;
int new_instance_size = map()->instance_size() - instance_size_delta;
new_map->set_inobject_properties(0);
new_map->set_instance_size(new_instance_size);
if (instance_size_delta == kPointerSize) {
WRITE_FIELD(this, new_instance_size, Heap::one_word_filler_map());
} else {
int byte_array_length = ByteArray::LengthFor(instance_size_delta);
int byte_array_length_offset = new_instance_size + kPointerSize;
WRITE_FIELD(this, new_instance_size, Heap::byte_array_map());
WRITE_INT_FIELD(this, byte_array_length_offset, byte_array_length);
}
WRITE_BARRIER(this, new_instance_size);
Heap::CreateFillerObjectAt(this->address() + new_instance_size,
instance_size_delta);
}
new_map->set_unused_property_fields(0);
......
......@@ -945,7 +945,7 @@ class MapWord BASE_EMBEDDED {
// Bits used by the marking phase of the garbage collector.
//
// The first word of a heap object is normall a map pointer. The last two
// The first word of a heap object is normally a map pointer. The last two
// bits are tagged as '01' (kHeapObjectTag). We reuse the last two bits to
// mark an object as live and/or overflowed:
// last bit = 0, marked as alive
......@@ -3205,6 +3205,10 @@ class String: public HeapObject {
uint32_t* index,
int length);
// Externalization.
bool MakeExternal(v8::String::ExternalStringResource* resource);
bool MakeExternal(v8::String::ExternalAsciiStringResource* resource);
// Conversion.
inline bool AsArrayIndex(uint32_t* index);
......@@ -3584,6 +3588,9 @@ class ExternalAsciiString: public ExternalString {
unsigned* offset,
unsigned chars);
// Identify the map for the external string/symbol with a particular length.
static inline Map* StringMap(int length);
static inline Map* SymbolMap(int length);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ExternalAsciiString);
};
......@@ -3613,6 +3620,9 @@ class ExternalTwoByteString: public ExternalString {
unsigned* offset_ptr,
unsigned chars);
// Identify the map for the external string/symbol with a particular length.
static inline Map* StringMap(int length);
static inline Map* SymbolMap(int length);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ExternalTwoByteString);
};
......
......@@ -491,6 +491,76 @@ THREADED_TEST(ScriptUsingAsciiStringResource) {
}
THREADED_TEST(ScriptMakingExternalString) {
TestResource::dispose_count = 0;
uint16_t* two_byte_source = AsciiToTwoByteString("1 + 2 * 3");
{
v8::HandleScope scope;
LocalContext env;
Local<String> source = String::New(two_byte_source);
bool success = source->MakeExternal(new TestResource(two_byte_source));
CHECK(success);
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(7, value->Int32Value());
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(0, TestResource::dispose_count);
}
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(1, TestResource::dispose_count);
}
THREADED_TEST(ScriptMakingExternalAsciiString) {
TestAsciiResource::dispose_count = 0;
const char* c_source = "1 + 2 * 3";
{
v8::HandleScope scope;
LocalContext env;
Local<String> source = v8_str(c_source);
bool success = source->MakeExternal(
new TestAsciiResource(i::StrDup(c_source)));
CHECK(success);
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(7, value->Int32Value());
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(0, TestAsciiResource::dispose_count);
}
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(1, TestAsciiResource::dispose_count);
}
THREADED_TEST(UsingExternalString) {
v8::HandleScope scope;
uint16_t* two_byte_string = AsciiToTwoByteString("test string");
Local<String> string = String::NewExternal(new TestResource(two_byte_string));
i::Handle<i::String> istring = v8::Utils::OpenHandle(*string);
// Trigger GCs so that the newly allocated string moves to old gen.
i::Heap::CollectGarbage(0, i::NEW_SPACE); // in survivor space now
i::Heap::CollectGarbage(0, i::NEW_SPACE); // in old gen now
i::Handle<i::String> isymbol = i::Factory::SymbolFromString(istring);
CHECK(isymbol->IsSymbol());
}
THREADED_TEST(UsingExternalAsciiString) {
v8::HandleScope scope;
const char* one_byte_string = "test string";
Local<String> string = String::NewExternal(
new TestAsciiResource(i::StrDup(one_byte_string)));
i::Handle<i::String> istring = v8::Utils::OpenHandle(*string);
// Trigger GCs so that the newly allocated string moves to old gen.
i::Heap::CollectGarbage(0, i::NEW_SPACE); // in survivor space now
i::Heap::CollectGarbage(0, i::NEW_SPACE); // in old gen now
i::Handle<i::String> isymbol = i::Factory::SymbolFromString(istring);
CHECK(isymbol->IsSymbol());
}
THREADED_TEST(GlobalProperties) {
v8::HandleScope scope;
LocalContext env;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment