* Returns true if the string is both external and ascii
*/
bool IsExternalAscii() const;
+
+ class V8EXPORT ExternalStringResourceBase {
+ public:
+ virtual ~ExternalStringResourceBase() {}
+ protected:
+ ExternalStringResourceBase() {}
+ private:
+ // Disallow copying and assigning.
+ ExternalStringResourceBase(const ExternalStringResourceBase&);
+ void operator=(const ExternalStringResourceBase&);
+ };
+
/**
* An ExternalStringResource is a wrapper around a two-byte string
* buffer that resides outside V8's heap. Implement an
* ExternalStringResource to manage the life cycle of the underlying
* buffer. Note that the string data must be immutable.
*/
- class V8EXPORT ExternalStringResource { // NOLINT
+ class V8EXPORT ExternalStringResource
+ : public ExternalStringResourceBase {
public:
/**
* Override the destructor to manage the life cycle of the underlying
virtual size_t length() const = 0;
protected:
ExternalStringResource() {}
- private:
- // Disallow copying and assigning.
- ExternalStringResource(const ExternalStringResource&);
- void operator=(const ExternalStringResource&);
};
/**
* Use String::New or convert to 16 bit data for non-ASCII.
*/
- class V8EXPORT ExternalAsciiStringResource { // NOLINT
+ class V8EXPORT ExternalAsciiStringResource
+ : public ExternalStringResourceBase {
public:
/**
* Override the destructor to manage the life cycle of the underlying
virtual size_t length() const = 0;
protected:
ExternalAsciiStringResource() {}
- private:
- // Disallow copying and assigning.
- ExternalAsciiStringResource(const ExternalAsciiStringResource&);
- void operator=(const ExternalAsciiStringResource&);
};
/**
}
-static void DisposeExternalString(v8::Persistent<v8::Value> obj,
- void* parameter) {
- ENTER_V8;
- 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 int total_size =
- static_cast<int>(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) {
- ENTER_V8;
- 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 int total_size =
- static_cast<int>(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();
-}
-
-
Local<String> v8::String::NewExternal(
v8::String::ExternalStringResource* resource) {
EnsureInitialized("v8::String::NewExternal()");
LOG_API("String::NewExternal");
ENTER_V8;
- const int total_size =
- static_cast<int>(resource->length() * sizeof(*resource->data()));
- i::Counters::total_external_string_memory.Increment(total_size);
i::Handle<i::String> result = NewExternalStringHandle(resource);
- i::Handle<i::Object> handle = i::GlobalHandles::Create(*result);
- i::GlobalHandles::MakeWeak(handle.location(),
- resource,
- &DisposeExternalString);
+ i::ExternalStringTable::AddString(*result);
return Utils::ToLocal(result);
}
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);
+ i::ExternalStringTable::AddString(*obj);
}
return result;
}
EnsureInitialized("v8::String::NewExternal()");
LOG_API("String::NewExternal");
ENTER_V8;
- const int total_size =
- static_cast<int>(resource->length() * sizeof(*resource->data()));
- i::Counters::total_external_string_memory.Increment(total_size);
i::Handle<i::String> result = NewExternalAsciiStringHandle(resource);
- i::Handle<i::Object> handle = i::GlobalHandles::Create(*result);
- i::GlobalHandles::MakeWeak(handle.location(),
- resource,
- &DisposeExternalAsciiString);
+ i::ExternalStringTable::AddString(*result);
return Utils::ToLocal(result);
}
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);
+ i::ExternalStringTable::AddString(*obj);
}
return result;
}
if (first_deallocated()) {
first_deallocated()->set_next(head());
}
+ // Check that we are not passing a finalized external string to
+ // the callback.
+ ASSERT(!object_->IsExternalAsciiString() ||
+ ExternalAsciiString::cast(object_)->resource() != NULL);
+ ASSERT(!object_->IsExternalTwoByteString() ||
+ ExternalTwoByteString::cast(object_)->resource() != NULL);
// Leaving V8.
VMState state(EXTERNAL);
func(object, par);
object_groups->Clear();
}
-
} } // namespace v8::internal
enum Executability { NOT_EXECUTABLE, EXECUTABLE };
-enum VisitMode { VISIT_ALL, VISIT_ONLY_STRONG };
+enum VisitMode { VISIT_ALL, VISIT_ALL_IN_SCAVENGE, VISIT_ONLY_STRONG };
// A CodeDesc describes a buffer holding instructions and relocation
}
+void Heap::FinalizeExternalString(String* string) {
+ ASSERT(string->IsExternalString());
+ v8::String::ExternalStringResourceBase** resource_addr =
+ reinterpret_cast<v8::String::ExternalStringResourceBase**>(
+ reinterpret_cast<byte*>(string) +
+ ExternalString::kResourceOffset -
+ kHeapObjectTag);
+ delete *resource_addr;
+ // Clear the resource pointer in the string.
+ *resource_addr = NULL;
+}
+
+
Object* Heap::AllocateRawMap() {
#ifdef DEBUG
Counters::objs_since_last_full.Increment();
#endif
+void ExternalStringTable::AddString(String* string) {
+ ASSERT(string->IsExternalString());
+ if (Heap::InNewSpace(string)) {
+ new_space_strings_.Add(string);
+ } else {
+ old_space_strings_.Add(string);
+ }
+}
+
+
+void ExternalStringTable::Iterate(ObjectVisitor* v) {
+ if (!new_space_strings_.is_empty()) {
+ Object** start = &new_space_strings_[0];
+ v->VisitPointers(start, start + new_space_strings_.length());
+ }
+ if (!old_space_strings_.is_empty()) {
+ Object** start = &old_space_strings_[0];
+ v->VisitPointers(start, start + old_space_strings_.length());
+ }
+}
+
+
+// Verify() is inline to avoid ifdef-s around its calls in release
+// mode.
+void ExternalStringTable::Verify() {
+#ifdef DEBUG
+ for (int i = 0; i < new_space_strings_.length(); ++i) {
+ ASSERT(Heap::InNewSpace(new_space_strings_[i]));
+ ASSERT(new_space_strings_[i] != Heap::raw_unchecked_null_value());
+ }
+ for (int i = 0; i < old_space_strings_.length(); ++i) {
+ ASSERT(!Heap::InNewSpace(old_space_strings_[i]));
+ ASSERT(old_space_strings_[i] != Heap::raw_unchecked_null_value());
+ }
+#endif
+}
+
+
+void ExternalStringTable::AddOldString(String* string) {
+ ASSERT(string->IsExternalString());
+ ASSERT(!Heap::InNewSpace(string));
+ old_space_strings_.Add(string);
+}
+
+
+void ExternalStringTable::ShrinkNewStrings(int position) {
+ new_space_strings_.Rewind(position);
+ Verify();
+}
+
} } // namespace v8::internal
#endif // V8_HEAP_INL_H_
ScavengeVisitor scavenge_visitor;
// Copy roots.
- IterateRoots(&scavenge_visitor, VISIT_ALL);
+ IterateRoots(&scavenge_visitor, VISIT_ALL_IN_SCAVENGE);
// Copy objects reachable from the old generation. By definition,
// there are no intergenerational pointers in code or data spaces.
}
}
+ new_space_front = DoScavenge(&scavenge_visitor, new_space_front);
+
+ ScavengeExternalStringTable();
+ ASSERT(new_space_front == new_space_.top());
+
+ // Set age mark.
+ new_space_.set_age_mark(new_space_.top());
+
+ // Update how much has survived scavenge.
+ survived_since_last_expansion_ +=
+ (PromotedSpaceSize() - survived_watermark) + new_space_.Size();
+
+ LOG(ResourceEvent("scavenge", "end"));
+
+ gc_state_ = NOT_IN_GC;
+}
+
+
+void Heap::ScavengeExternalStringTable() {
+ ExternalStringTable::Verify();
+
+ if (ExternalStringTable::new_space_strings_.is_empty()) return;
+
+ Object** start = &ExternalStringTable::new_space_strings_[0];
+ Object** end = start + ExternalStringTable::new_space_strings_.length();
+ Object** last = start;
+
+ for (Object** p = start; p < end; ++p) {
+ ASSERT(Heap::InFromSpace(*p));
+ MapWord first_word = HeapObject::cast(*p)->map_word();
+
+ if (!first_word.IsForwardingAddress()) {
+ // Unreachable external string can be finalized.
+ FinalizeExternalString(String::cast(*p));
+ continue;
+ }
+
+ // String is still reachable.
+ String* target = String::cast(first_word.ToForwardingAddress());
+ ASSERT(target->IsExternalString());
+
+ if (Heap::InNewSpace(target)) {
+ // String is still in new space. Update the table entry.
+ *last = target;
+ ++last;
+ } else {
+ // String got promoted. Move it to the old string list.
+ ExternalStringTable::AddOldString(target);
+ }
+ }
+
+ ExternalStringTable::ShrinkNewStrings(last - start);
+}
+
+
+Address Heap::DoScavenge(ObjectVisitor* scavenge_visitor,
+ Address new_space_front) {
do {
ASSERT(new_space_front <= new_space_.top());
// queue is empty.
while (new_space_front < new_space_.top()) {
HeapObject* object = HeapObject::FromAddress(new_space_front);
- object->Iterate(&scavenge_visitor);
+ object->Iterate(scavenge_visitor);
new_space_front += object->Size();
}
RecordCopiedObject(target);
#endif
// Visit the newly copied object for pointers to new space.
- target->Iterate(&scavenge_visitor);
+ target->Iterate(scavenge_visitor);
UpdateRSet(target);
}
// (there are currently no more unswept promoted objects).
} while (new_space_front < new_space_.top());
- // Set age mark.
- new_space_.set_age_mark(new_space_.top());
-
- // Update how much has survived scavenge.
- survived_since_last_expansion_ +=
- (PromotedSpaceSize() - survived_watermark) + new_space_.Size();
-
- LOG(ResourceEvent("scavenge", "end"));
-
- gc_state_ = NOT_IN_GC;
+ return new_space_front;
}
IterateStrongRoots(v, mode);
v->VisitPointer(reinterpret_cast<Object**>(&roots_[kSymbolTableRootIndex]));
v->Synchronize("symbol_table");
+ if (mode != VISIT_ALL_IN_SCAVENGE) {
+ // Scavenge collections have special processing for this.
+ ExternalStringTable::Iterate(v);
+ }
+ v->Synchronize("external_string_table");
}
HandleScopeImplementer::Iterate(v);
v->Synchronize("handlescope");
- // Iterate over the builtin code objects and code stubs in the heap. Note
- // that it is not strictly necessary to iterate over code objects on
- // scavenge collections. We still do it here because this same function
- // is used by the mark-sweep collector and the deserializer.
- Builtins::IterateBuiltins(v);
+ // Iterate over the builtin code objects and code stubs in the
+ // heap. Note that it is not necessary to iterate over code objects
+ // on scavenge collections.
+ if (mode != VISIT_ALL_IN_SCAVENGE) {
+ Builtins::IterateBuiltins(v);
+ }
v->Synchronize("builtins");
// Iterate over global handles.
void Heap::TearDown() {
GlobalHandles::TearDown();
+ ExternalStringTable::TearDown();
+
new_space_.TearDown();
if (old_pointer_space_ != NULL) {
// Triggers a depth-first traversal of reachable objects from roots
// and finds a path to a specific heap object and prints it.
-void Heap::TracePathToObject() {
- search_target = NULL;
+void Heap::TracePathToObject(Object* target) {
+ search_target = target;
search_for_any_global = false;
MarkRootVisitor root_visitor;
}
+void ExternalStringTable::CleanUp() {
+ int last = 0;
+ for (int i = 0; i < new_space_strings_.length(); ++i) {
+ if (new_space_strings_[i] == Heap::raw_unchecked_null_value()) continue;
+ if (Heap::InNewSpace(new_space_strings_[i])) {
+ new_space_strings_[last++] = new_space_strings_[i];
+ } else {
+ old_space_strings_.Add(new_space_strings_[i]);
+ }
+ }
+ new_space_strings_.Rewind(last);
+ last = 0;
+ for (int i = 0; i < old_space_strings_.length(); ++i) {
+ if (old_space_strings_[i] == Heap::raw_unchecked_null_value()) continue;
+ ASSERT(!Heap::InNewSpace(old_space_strings_[i]));
+ old_space_strings_[last++] = old_space_strings_[i];
+ }
+ old_space_strings_.Rewind(last);
+ Verify();
+}
+
+
+void ExternalStringTable::TearDown() {
+ new_space_strings_.Free();
+ old_space_strings_.Free();
+}
+
+
+List<Object*> ExternalStringTable::new_space_strings_;
+List<Object*> ExternalStringTable::old_space_strings_;
+
} } // namespace v8::internal
static Object* AllocateExternalStringFromTwoByte(
ExternalTwoByteString::Resource* resource);
+ // Finalizes an external string by deleting the associated external
+ // data and clearing the resource pointer.
+ static inline void FinalizeExternalString(String* string);
+
// Allocates an uninitialized object. The memory is non-executable if the
// hardware and OS allow.
// Returns Failure::RetryAfterGC(requested_bytes, space) if the allocation
return disallow_allocation_failure_;
}
- static void TracePathToObject();
+ static void TracePathToObject(Object* target);
static void TracePathToGlobal();
#endif
// Performs a minor collection in new generation.
static void Scavenge();
+ static void ScavengeExternalStringTable();
+ static Address DoScavenge(ObjectVisitor* scavenge_visitor,
+ Address new_space_front);
// Performs a major collection in the whole heap.
static void MarkCompact(GCTracer* tracer);
};
+// External strings table is a place where all external strings are
+// registered. We need to keep track of such strings to properly
+// finalize them.
+class ExternalStringTable : public AllStatic {
+ public:
+ // Registers an external string.
+ inline static void AddString(String* string);
+
+ inline static void Iterate(ObjectVisitor* v);
+
+ // Restores internal invariant and gets rid of collected strings.
+ // Must be called after each Iterate() that modified the strings.
+ static void CleanUp();
+
+ // Destroys all allocated memory.
+ static void TearDown();
+
+ private:
+ friend class Heap;
+
+ inline static void Verify();
+
+ inline static void AddOldString(String* string);
+
+ // Notifies the table that only a prefix of the new list is valid.
+ inline static void ShrinkNewStrings(int position);
+
+ // To speed up scavenge collections new space string are kept
+ // separate from old space strings.
+ static List<Object*> new_space_strings_;
+ static List<Object*> old_space_strings_;
+};
+
} } // namespace v8::internal
#endif // V8_HEAP_H_
// objects (empty string, illegal builtin).
StubCache::Clear();
+ ExternalStringTable::CleanUp();
+
// If we've just compacted old space there's no reason to check the
// fragmentation limit. Just return.
if (HasCompacted()) return;
class SymbolTableCleaner : public ObjectVisitor {
public:
SymbolTableCleaner() : pointers_removed_(0) { }
- void VisitPointers(Object** start, Object** end) {
+
+ virtual void VisitPointers(Object** start, Object** end) {
// 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;
- // Clear the resource pointer in the symbol.
- *resource = NULL;
- } else {
- v8::String::ExternalAsciiStringResource** resource =
- reinterpret_cast<v8::String::ExternalAsciiStringResource**>
- (resource_addr);
- delete *resource;
- // Clear the resource pointer in the symbol.
- *resource = NULL;
- }
+ if ((*p)->IsExternalString()) {
+ Heap::FinalizeExternalString(String::cast(*p));
}
// Set the entry to null_value (as deleted).
*p = Heap::raw_unchecked_null_value();
}
-class SymbolMarkingVisitor : public ObjectVisitor {
- public:
- void VisitPointers(Object** start, Object** end) {
- MarkingVisitor marker;
- for (Object** p = start; p < end; p++) {
- if (!(*p)->IsHeapObject()) continue;
-
- HeapObject* object = HeapObject::cast(*p);
- // If the object is marked, we have marked or are in the process
- // of marking subparts.
- if (object->IsMarked()) continue;
-
- // The object is unmarked, we do not need to unmark to use its
- // map.
- Map* map = object->map();
- object->IterateBody(map->instance_type(),
- object->SizeFromMap(map),
- &marker);
- }
- }
-};
-
-
void MarkCompactCollector::MarkSymbolTable() {
- // Objects reachable from symbols are marked as live so as to ensure
- // that if the symbol itself remains alive after GC for any reason,
- // and if it is a cons string backed by an external string (even indirectly),
- // then the external string does not receive a weak reference callback.
SymbolTable* symbol_table = Heap::raw_unchecked_symbol_table();
// Mark the symbol table itself.
SetMark(symbol_table);
MarkingVisitor marker;
symbol_table->IteratePrefix(&marker);
ProcessMarkingStack(&marker);
- // Mark subparts of the symbols but not the symbols themselves
- // (unless reachable from another symbol).
- SymbolMarkingVisitor symbol_marker;
- symbol_table->IterateElements(&symbol_marker);
- ProcessMarkingStack(&marker);
}
SymbolTableCleaner v;
symbol_table->IterateElements(&v);
symbol_table->ElementsRemoved(v.PointersRemoved());
+ ExternalStringTable::Iterate(&v);
+ ExternalStringTable::CleanUp();
// Remove object groups after marking phase.
GlobalHandles::RemoveObjectGroups();
SC(objs_since_last_full, V8.ObjsSinceLastFull) \
SC(symbol_table_capacity, V8.SymbolTableCapacity) \
SC(number_of_symbols, V8.NumberOfSymbols) \
- /* Current amount of memory in external string buffers. */ \
- SC(total_external_string_memory, V8.TotalExternalStringMemory) \
SC(script_wrappers, V8.ScriptWrappers) \
SC(call_initialize_stubs, V8.CallInitializeStubs) \
SC(call_premonomorphic_stubs, V8.CallPreMonomorphicStubs) \
}
+THREADED_TEST(ScavengeExternalString) {
+ TestResource::dispose_count = 0;
+ {
+ 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);
+ i::Heap::CollectGarbage(0, i::NEW_SPACE);
+ CHECK(i::Heap::InNewSpace(*istring));
+ CHECK_EQ(0, TestResource::dispose_count);
+ }
+ i::Heap::CollectGarbage(0, i::NEW_SPACE);
+ CHECK_EQ(1, TestResource::dispose_count);
+}
+
+
+THREADED_TEST(ScavengeExternalAsciiString) {
+ TestAsciiResource::dispose_count = 0;
+ {
+ 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);
+ i::Heap::CollectGarbage(0, i::NEW_SPACE);
+ CHECK(i::Heap::InNewSpace(*istring));
+ CHECK_EQ(0, TestAsciiResource::dispose_count);
+ }
+ i::Heap::CollectGarbage(0, i::NEW_SPACE);
+ CHECK_EQ(1, TestAsciiResource::dispose_count);
+}
+
+
THREADED_TEST(StringConcat) {
{
v8::HandleScope scope;