[log] Improve --trace-maps logging

Only log incrementally deserialized maps with --trace-maps instead of
iterating the whole heap and print all existing maps on every partial
deserialization for new contexts. This should greatly improve
performance of --trace-maps on websites with many iframes.

- Add helpers to share code: LogNewObjectEvents, LogScriptEvents,
  LogNewMapEvents
- Link AllocationSites before any GC

Change-Id: I5322421a83e057518f871540691511c80bc7786a
Reviewed-on: https://chromium-review.googlesource.com/c/1342029
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#57874}
This commit is contained in:
Camillo Bruni 2018-11-27 09:08:59 +01:00 committed by Commit Bot
parent 22f9a8e158
commit 8e80210fe8
15 changed files with 354 additions and 93 deletions

View File

@ -348,8 +348,7 @@ Handle<Context> Bootstrapper::CreateEnvironment(
return Handle<Context>();
}
}
// Log all maps created during bootstrapping.
if (FLAG_trace_maps) LOG(isolate_, LogMaps());
LogAllMaps();
return scope.CloseAndEscape(env);
}
@ -363,11 +362,18 @@ Handle<JSGlobalProxy> Bootstrapper::NewRemoteContext(
global_proxy = genesis.global_proxy();
if (global_proxy.is_null()) return Handle<JSGlobalProxy>();
}
// Log all maps created during bootstrapping.
if (FLAG_trace_maps) LOG(isolate_, LogMaps());
LogAllMaps();
return scope.CloseAndEscape(global_proxy);
}
void Bootstrapper::LogAllMaps() {
if (!FLAG_trace_maps || isolate_->initialized_from_snapshot()) return;
// Log all created Map objects that are on the heap. For snapshots the Map
// logging happens during deserialization in order to avoid printing Maps
// multiple times during partial deserialization.
LOG(isolate_, LogAllMaps());
}
void Bootstrapper::DetachGlobal(Handle<Context> env) {
isolate_->counters()->errors_thrown_per_context()->AddSample(
env->GetErrorsThrown());

View File

@ -100,6 +100,9 @@ class Bootstrapper final {
static void ExportFromRuntime(Isolate* isolate, Handle<JSObject> container);
private:
// Log newly created Map objects if no snapshot was used.
void LogAllMaps();
Isolate* isolate_;
typedef int NestingCounterType;
NestingCounterType nesting_;

View File

@ -2970,6 +2970,7 @@ Handle<JSGlobalObject> Factory::NewJSGlobalObject(
Handle<Map> new_map = Map::CopyDropDescriptors(isolate(), map);
new_map->set_may_have_interesting_symbols(true);
new_map->set_is_dictionary_map(true);
LOG(isolate(), MapDetails(*new_map));
// Set up the global object as a normalized object.
global->set_global_dictionary(*dictionary);
@ -3507,6 +3508,7 @@ Handle<JSGlobalProxy> Factory::NewUninitializedJSGlobalProxy(int size) {
// Maintain invariant expected from any JSGlobalProxy.
map->set_is_access_check_needed(true);
map->set_may_have_interesting_symbols(true);
LOG(isolate(), MapDetails(*map));
return Handle<JSGlobalProxy>::cast(NewJSObjectFromMap(map, NOT_TENURED));
}
@ -4087,6 +4089,7 @@ Handle<Map> Factory::CreateSloppyFunctionMap(
map->AppendDescriptor(&d);
}
DCHECK_EQ(inobject_properties_count, field_index);
LOG(isolate(), MapDetails(*map));
return map;
}
@ -4163,6 +4166,7 @@ Handle<Map> Factory::CreateStrictFunctionMap(
map->AppendDescriptor(&d);
}
DCHECK_EQ(inobject_properties_count, field_index);
LOG(isolate(), MapDetails(*map));
return map;
}
@ -4197,6 +4201,7 @@ Handle<Map> Factory::CreateClassFunctionMap(Handle<JSFunction> empty_function) {
prototype_string(), function_prototype_accessor(), ro_attribs);
map->AppendDescriptor(&d);
}
LOG(isolate(), MapDetails(*map));
return map;
}

View File

@ -1714,9 +1714,6 @@ void Logger::MapCreate(Map map) {
void Logger::MapDetails(Map map) {
if (!log_->IsEnabled() || !FLAG_trace_maps) return;
// Disable logging Map details during bootstrapping since we use LogMaps() to
// log all creating
if (isolate_->bootstrapper()->IsActive()) return;
DisallowHeapAllocation no_gc;
Log::MessageBuilder msg(log_);
msg << "map-details" << kNext << timer_.Elapsed().InMicroseconds() << kNext
@ -1860,14 +1857,16 @@ void Logger::LogAccessorCallbacks() {
}
}
void Logger::LogMaps() {
void Logger::LogAllMaps() {
DisallowHeapAllocation no_gc;
Heap* heap = isolate_->heap();
HeapIterator iterator(heap);
DisallowHeapAllocation no_gc;
for (HeapObject* obj = iterator.next(); obj != nullptr;
obj = iterator.next()) {
if (!obj->IsMap()) continue;
MapDetails(Map::cast(obj));
Map map = Map::cast(obj);
MapCreate(map);
MapDetails(map);
}
}

View File

@ -274,8 +274,8 @@ class Logger : public CodeEventListener {
void LogAccessorCallbacks();
// Used for logging stubs found in the snapshot.
void LogCodeObjects();
// Logs all Mpas foind in the heap.
void LogMaps();
// Logs all Maps found on the heap.
void LogAllMaps();
// Converts tag to a corresponding NATIVE_... if the script is native.
V8_INLINE static CodeEventListener::LogEventsAndTags ToNativeByScript(

View File

@ -6677,9 +6677,6 @@ void JSObject::MigrateSlowToFast(Handle<JSObject> object,
NotifyMapChange(old_map, new_map, isolate);
if (FLAG_trace_maps) {
LOG(isolate, MapEvent("SlowToFast", *old_map, *new_map, reason));
}
if (instance_descriptor_length == 0) {
DisallowHeapAllocation no_gc;
@ -6690,6 +6687,9 @@ void JSObject::MigrateSlowToFast(Handle<JSObject> object,
object->SetProperties(ReadOnlyRoots(isolate).empty_fixed_array());
// Check that it really works.
DCHECK(object->HasFastProperties());
if (FLAG_trace_maps) {
LOG(isolate, MapEvent("SlowToFast", *old_map, *new_map, reason));
}
return;
}
@ -6784,6 +6784,9 @@ void JSObject::MigrateSlowToFast(Handle<JSObject> object,
new_map->SetOutOfObjectUnusedPropertyFields(unused_property_fields);
}
if (FLAG_trace_maps) {
LOG(isolate, MapEvent("SlowToFast", *old_map, *new_map, reason));
}
// Transform the object.
object->synchronized_set_map(*new_map);
@ -9695,6 +9698,7 @@ Handle<Map> Map::AddMissingTransitions(
Handle<Map> new_map = CopyDropDescriptors(isolate, map);
InstallDescriptors(isolate, map, new_map, i, descriptors,
full_layout_descriptor);
map = new_map;
}
map->NotifyLeafMapLayoutChange(isolate);
@ -9775,7 +9779,7 @@ Handle<Map> Map::CopyAsElementsKind(Isolate* isolate, Handle<Map> map,
maybe_elements_transition_map.is_null();
if (insert_transition) {
Handle<Map> new_map = CopyForTransition(isolate, map, "CopyAsElementsKind");
Handle<Map> new_map = CopyForElementsTransition(isolate, map);
new_map->set_elements_kind(kind);
Handle<Name> name = isolate->factory()->elements_transition_symbol();
@ -9829,8 +9833,7 @@ Handle<Map> Map::AsLanguageMode(Isolate* isolate, Handle<Map> initial_map,
return map;
}
Handle<Map> Map::CopyForTransition(Isolate* isolate, Handle<Map> map,
const char* reason) {
Handle<Map> Map::CopyForElementsTransition(Isolate* isolate, Handle<Map> map) {
DCHECK(!map->is_prototype_map());
Handle<Map> new_map = CopyDropDescriptors(isolate, map);
@ -9851,10 +9854,6 @@ Handle<Map> Map::CopyForTransition(Isolate* isolate, Handle<Map> map,
isolate);
new_map->InitializeDescriptors(*new_descriptors, *new_layout_descriptor);
}
if (FLAG_trace_maps) {
LOG(isolate, MapEvent("CopyForTransition", *map, *new_map, reason));
}
return new_map;
}

View File

@ -735,8 +735,8 @@ class Map : public HeapObjectPtr {
// Returns a copy of the map, prepared for inserting into the transition
// tree (if the |map| owns descriptors then the new one will share
// descriptors with |map|).
static Handle<Map> CopyForTransition(Isolate* isolate, Handle<Map> map,
const char* reason);
static Handle<Map> CopyForElementsTransition(Isolate* isolate,
Handle<Map> map);
// Returns a copy of the map, with all transitions dropped from the
// instance descriptors.

View File

@ -8,6 +8,7 @@
#include "src/heap/heap-write-barrier-inl.h"
#include "src/interpreter/interpreter.h"
#include "src/isolate.h"
#include "src/log.h"
#include "src/objects/api-callbacks.h"
#include "src/objects/hash-table.h"
#include "src/objects/js-array-buffer-inl.h"
@ -152,6 +153,34 @@ void Deserializer::DeserializeDeferredObjects() {
}
}
void Deserializer::LogNewObjectEvents() {
{
// {new_maps_} and {new_code_objects_} are vectors containing raw
// pointers, hence there should be no GC happening.
DisallowHeapAllocation no_gc;
// Issue code events for newly deserialized code objects.
LOG_CODE_EVENT(isolate_, LogCodeObjects());
}
LOG_CODE_EVENT(isolate_, LogCompiledFunctions());
LogNewMapEvents();
}
void Deserializer::LogNewMapEvents() {
DisallowHeapAllocation no_gc;
for (Map map : new_maps()) {
DCHECK(FLAG_trace_maps);
LOG(isolate_, MapCreate(map));
LOG(isolate_, MapDetails(map));
}
}
void Deserializer::LogScriptEvents(Script* script) {
DisallowHeapAllocation no_gc;
LOG(isolate_,
ScriptEvent(Logger::ScriptEventType::kDeserialize, script->id()));
LOG(isolate_, ScriptDetails(script));
}
StringTableInsertionKey::StringTableInsertionKey(String string)
: StringTableKey(ComputeHashField(string)), string_(string) {
DCHECK(string->IsInternalizedString());
@ -203,28 +232,29 @@ HeapObject* Deserializer::PostProcessNewObject(HeapObject* obj, int space) {
}
} else if (obj->IsScript()) {
new_scripts_.push_back(handle(Script::cast(obj), isolate_));
} else if (obj->IsAllocationSite()) {
// We should link new allocation sites, but we can't do this immediately
// because |AllocationSite::HasWeakNext()| internally accesses
// |Heap::roots_| that may not have been initialized yet. So defer this to
// |ObjectDeserializer::CommitPostProcessedObjects()|.
new_allocation_sites_.push_back(AllocationSite::cast(obj));
} else {
DCHECK(CanBeDeferred(obj));
}
} else if (obj->IsScript()) {
LOG(isolate_, ScriptEvent(Logger::ScriptEventType::kDeserialize,
Script::cast(obj)->id()));
LOG(isolate_, ScriptDetails(Script::cast(obj)));
}
if (obj->IsAllocationSite()) {
// We should link new allocation sites, but we can't do this immediately
// because |AllocationSite::HasWeakNext()| internally accesses
// |Heap::roots_| that may not have been initialized yet. So defer this to
// |ObjectDeserializer::CommitPostProcessedObjects()|.
new_allocation_sites_.push_back(AllocationSite::cast(obj));
if (obj->IsScript()) {
LogScriptEvents(Script::cast(obj));
} else if (obj->IsCode()) {
// We flush all code pages after deserializing the startup snapshot. In that
// case, we only need to remember code objects in the large object space.
// When deserializing user code, remember each individual code object.
// We flush all code pages after deserializing the startup snapshot.
// Hence we only remember each individual code object when deserializing
// user code.
if (deserializing_user_code() || space == LO_SPACE) {
new_code_objects_.push_back(Code::cast(obj));
}
} else if (FLAG_trace_maps && obj->IsMap()) {
// Keep track of all seen Maps to log them later since they might be only
// partially initialized at this point.
new_maps_.push_back(Map::cast(obj));
} else if (obj->IsAccessorInfo()) {
#ifdef USE_SIMULATOR
accessor_infos_.push_back(AccessorInfo::cast(obj));

View File

@ -7,7 +7,9 @@
#include <vector>
#include "src/objects/code.h"
#include "src/objects/js-array.h"
#include "src/objects/map.h"
#include "src/objects/string.h"
#include "src/snapshot/deserializer-allocator.h"
#include "src/snapshot/serializer-common.h"
@ -58,6 +60,11 @@ class Deserializer : public SerializerDeserializer {
void Initialize(Isolate* isolate);
void DeserializeDeferredObjects();
// Create Log events for newly deserialized objects.
void LogNewObjectEvents();
void LogScriptEvents(Script* script);
void LogNewMapEvents();
// This returns the address of an object that has been described in the
// snapshot by chunk index and offset.
HeapObject* GetBackReferencedObject(int space);
@ -76,6 +83,7 @@ class Deserializer : public SerializerDeserializer {
const std::vector<Code>& new_code_objects() const {
return new_code_objects_;
}
const std::vector<Map>& new_maps() const { return new_maps_; }
const std::vector<AccessorInfo*>& accessor_infos() const {
return accessor_infos_;
}
@ -95,6 +103,9 @@ class Deserializer : public SerializerDeserializer {
void Rehash();
// Cached current isolate.
Isolate* isolate_;
private:
void VisitRootPointers(Root root, const char* description, ObjectSlot start,
ObjectSlot end) override;
@ -130,9 +141,6 @@ class Deserializer : public SerializerDeserializer {
// Special handling for serialized code like hooking up internalized strings.
HeapObject* PostProcessNewObject(HeapObject* obj, int space);
// Cached current isolate.
Isolate* isolate_;
// Objects from the attached object descriptions in the serialized user code.
std::vector<Handle<HeapObject>> attached_objects_;
@ -141,6 +149,7 @@ class Deserializer : public SerializerDeserializer {
ExternalReferenceTable* external_reference_table_;
std::vector<Map> new_maps_;
std::vector<AllocationSite*> new_allocation_sites_;
std::vector<Code> new_code_objects_;
std::vector<AccessorInfo*> accessor_infos_;

View File

@ -49,7 +49,9 @@ MaybeHandle<HeapObject> ObjectDeserializer::Deserialize(Isolate* isolate) {
Object* root;
VisitRootPointer(Root::kPartialSnapshotCache, nullptr, ObjectSlot(&root));
DeserializeDeferredObjects();
FlushICacheForNewCodeObjectsAndRecordEmbeddedObjects();
FlushICache();
LinkAllocationSites();
LogNewMapEvents();
result = handle(HeapObject::cast(root), isolate);
Rehash();
allocator()->RegisterDeserializedObjectsForBlackAllocation();
@ -58,8 +60,7 @@ MaybeHandle<HeapObject> ObjectDeserializer::Deserialize(Isolate* isolate) {
return scope.CloseAndEscape(result);
}
void ObjectDeserializer::
FlushICacheForNewCodeObjectsAndRecordEmbeddedObjects() {
void ObjectDeserializer::FlushICache() {
DCHECK(deserializing_user_code());
for (Code code : new_code_objects()) {
// Record all references to embedded objects in the new code object.
@ -86,16 +87,18 @@ void ObjectDeserializer::CommitPostProcessedObjects() {
for (Handle<Script> script : new_scripts()) {
// Assign a new script id to avoid collision.
script->set_id(isolate()->heap()->NextScriptId());
LOG(isolate(),
ScriptEvent(Logger::ScriptEventType::kDeserialize, script->id()));
LOG(isolate(), ScriptDetails(*script));
LogScriptEvents(*script);
// Add script to list.
Handle<WeakArrayList> list = factory->script_list();
list = WeakArrayList::AddToEnd(isolate(), list,
MaybeObjectHandle::Weak(script));
heap->SetRootScriptList(*list);
}
}
void ObjectDeserializer::LinkAllocationSites() {
DisallowHeapAllocation no_gc;
Heap* heap = isolate()->heap();
// Allocation sites are present in the snapshot, and must be linked into
// a list at deserialization time.
for (AllocationSite* site : new_allocation_sites()) {

View File

@ -25,7 +25,8 @@ class ObjectDeserializer final : public Deserializer {
// Deserialize an object graph. Fail gracefully.
MaybeHandle<HeapObject> Deserialize(Isolate* isolate);
void FlushICacheForNewCodeObjectsAndRecordEmbeddedObjects();
void FlushICache();
void LinkAllocationSites();
void CommitPostProcessedObjects();
};

View File

@ -54,6 +54,7 @@ MaybeHandle<Object> PartialDeserializer::Deserialize(
CHECK_EQ(start_address, code_space->top());
if (FLAG_rehash_snapshot && can_rehash()) Rehash();
LogNewMapEvents();
return Handle<Object>(root, isolate);
}

View File

@ -46,7 +46,7 @@ void StartupDeserializer::DeserializeInto(Isolate* isolate) {
// Flush the instruction cache for the entire code-space. Must happen after
// builtins deserialization.
FlushICacheForNewIsolate();
FlushICache();
}
isolate->heap()->set_native_contexts_list(
@ -58,12 +58,11 @@ void StartupDeserializer::DeserializeInto(Isolate* isolate) {
ReadOnlyRoots(isolate).undefined_value());
}
// Issue code events for newly deserialized code objects.
LOG_CODE_EVENT(isolate, LogCodeObjects());
LOG_CODE_EVENT(isolate, LogCompiledFunctions());
isolate->builtins()->MarkInitialized();
LogNewMapEvents();
if (FLAG_rehash_snapshot && can_rehash()) {
isolate->heap()->InitializeHashSeed();
read_only_deserializer.RehashHeap();
@ -71,7 +70,11 @@ void StartupDeserializer::DeserializeInto(Isolate* isolate) {
}
}
void StartupDeserializer::FlushICacheForNewIsolate() {
void StartupDeserializer::LogNewMapEvents() {
if (FLAG_trace_maps) LOG(isolate_, LogAllMaps());
}
void StartupDeserializer::FlushICache() {
DCHECK(!deserializing_user_code());
// The entire isolate is newly deserialized. Simply flush all code pages.
for (Page* p : *isolate()->heap()->code_space()) {

View File

@ -22,7 +22,8 @@ class StartupDeserializer final : public Deserializer {
void DeserializeInto(Isolate* isolate);
private:
void FlushICacheForNewIsolate();
void FlushICache();
void LogNewMapEvents();
const SnapshotData* read_only_data_;
};

View File

@ -34,6 +34,7 @@
#include "src/log-utils.h"
#include "src/log.h"
#include "src/objects-inl.h"
#include "src/ostreams.h"
#include "src/profiler/cpu-profiler.h"
#include "src/snapshot/natives.h"
#include "src/v8.h"
@ -104,6 +105,11 @@ class ScopedLoggerInitializer {
.ToLocalChecked();
}
void PrintLog() {
i::StdoutStream os;
os << raw_log_ << std::flush;
}
void StopLogging() {
bool exists = false;
raw_log_ = i::ReadFile(StopLoggingGetTempFile(), &exists, true);
@ -146,16 +152,19 @@ class ScopedLoggerInitializer {
return true;
}
std::unordered_set<uintptr_t> ExtractAllAddresses(std::string search_term,
size_t address_column) {
std::unordered_set<uintptr_t> ExtractLogAddresses(std::string search_term,
size_t address_column,
bool allow_duplicates) {
CHECK_GT(log_.size(), 0);
std::unordered_set<uintptr_t> result;
size_t start = 0;
// Map addresses of Maps to log_lines.
std::unordered_map<uintptr_t, std::string> map;
size_t current = 0;
while (true) {
start = IndexOfLine({search_term}, start);
if (start == std::string::npos) break;
std::vector<std::string> columns = Split(log_.at(start), ',');
++start; // Skip the found line.
current = IndexOfLine({search_term}, current);
if (current == std::string::npos) break;
std::string current_line = log_.at(current);
std::vector<std::string> columns = Split(current_line, ',');
++current; // Skip the found line.
// TODO(crbug.com/v8/8084): These two continue lines should really be
// errors. But on Windows the log is sometimes mysteriously cut off at the
// end. If the cut-off point happens to fall in the address field, the
@ -164,7 +173,25 @@ class ScopedLoggerInitializer {
uintptr_t address =
strtoll(columns.at(address_column).c_str(), nullptr, 16);
if (address == 0) continue;
result.insert(address);
if (!allow_duplicates) {
auto match = map.find(address);
// Ignore same address but different log line.
if (match != map.end() && match->second.compare(current_line) == 0) {
for (size_t i = 0; i < current; i++) {
printf("%s\n", log_.at(i).c_str());
}
printf("%zu\n", current);
V8_Fatal(__FILE__, __LINE__, "%s, ... %p apperead twice:\n %s",
search_term.c_str(), reinterpret_cast<void*>(address),
current_line.c_str());
}
}
map.insert({address, current_line});
}
// Extract all keys.
std::unordered_set<uintptr_t> result;
for (auto key_value : map) {
result.insert(key_value.first);
}
return result;
}
@ -723,15 +750,25 @@ TEST(TraceMaps) {
ScopedLoggerInitializer logger(saved_log, saved_prof, isolate);
// Try to create many different kind of maps to make sure the logging won't
// crash. More detailed tests are implemented separately.
const char* source_text =
"let a = {};"
"for (let i = 0; i < 500; i++) { a['p'+i] = i };"
"class Test { constructor(i) { this.a = 1; this['p'+i] = 1; }};"
"let t = new Test();"
"t.b = 1; t.c = 1; t.d = 3;"
"for (let i = 0; i < 100; i++) { t = new Test(i) };"
"t.b = {};";
CompileRun(source_text);
const char* source_text = R"(
let a = {};
for (let i = 0; i < 500; i++) {
a['p'+i] = i
};
class Test {
constructor(i) {
this.a = 1;
this['p'+i] = 1;
}
};
let t = new Test();
t.b = 1; t.c = 1; t.d = 3;
for (let i = 0; i < 100; i++) {
t = new Test(i)
};
t.b = {};
)";
CompileRunChecked(isolate, source_text);
logger.StopLogging();
@ -744,7 +781,56 @@ TEST(TraceMaps) {
isolate->Dispose();
}
TEST(LogMaps) {
namespace {
// Ensure that all Maps found on the heap have a single corresponding map-create
// and map-details entry in the v8.log.
void ValidateMapDetailsLogging(v8::Isolate* isolate,
ScopedLoggerInitializer* logger) {
// map-create might have duplicates if a Map address is reused after a gc.
std::unordered_set<uintptr_t> map_create_addresses =
logger->ExtractLogAddresses("map-create", 2, true);
std::unordered_set<uintptr_t> map_details_addresses =
logger->ExtractLogAddresses("map-details", 2, false);
// Iterate over all maps on the heap.
i::Heap* heap = reinterpret_cast<i::Isolate*>(isolate)->heap();
i::HeapIterator iterator(heap);
i::DisallowHeapAllocation no_gc;
size_t i = 0;
for (i::HeapObject* obj = iterator.next(); obj != nullptr;
obj = iterator.next()) {
if (!obj->IsMap()) continue;
i++;
uintptr_t address = reinterpret_cast<uintptr_t>(obj);
if (map_create_addresses.find(address) == map_create_addresses.end()) {
// logger->PrintLog();
i::Map::cast(obj)->Print();
V8_Fatal(__FILE__, __LINE__,
"Map (%p, #%zu) creation not logged during startup with "
"--trace-maps!"
"\n# Expected Log Line: map-create, ... %p",
reinterpret_cast<void*>(obj), i, reinterpret_cast<void*>(obj));
} else if (map_details_addresses.find(address) ==
map_details_addresses.end()) {
// logger->PrintLog();
i::Map::cast(obj)->Print();
V8_Fatal(__FILE__, __LINE__,
"Map (%p, #%zu) details not logged during startup with "
"--trace-maps!"
"\n# Expected Log Line: map-details, ... %p",
reinterpret_cast<void*>(obj), i, reinterpret_cast<void*>(obj));
}
}
}
} // namespace
TEST(LogMapsDetailsStartup) {
// Reusing map addresses might cause these tests to fail.
if (i::FLAG_gc_global || i::FLAG_stress_compaction ||
i::FLAG_stress_incremental_marking) {
return;
}
// Test that all Map details from Maps in the snapshot are logged properly.
SETUP_FLAGS();
i::FLAG_trace_maps = true;
@ -754,27 +840,142 @@ TEST(LogMaps) {
{
ScopedLoggerInitializer logger(saved_log, saved_prof, isolate);
logger.StopLogging();
std::unordered_set<uintptr_t> map_addresses =
logger.ExtractAllAddresses("map-details", 2);
i::Heap* heap = reinterpret_cast<i::Isolate*>(isolate)->heap();
i::HeapIterator iterator(heap);
i::DisallowHeapAllocation no_gc;
// Iterate over all maps on the heap.
size_t i = 0;
for (i::HeapObject* obj = iterator.next(); obj != nullptr;
obj = iterator.next()) {
i++;
if (!obj->IsMap()) continue;
uintptr_t address = reinterpret_cast<uintptr_t>(obj);
if (map_addresses.find(address) != map_addresses.end()) continue;
i::Map::cast(obj)->Print();
V8_Fatal(__FILE__, __LINE__,
"Map (%p, #%zu) was not logged during startup with --trace-maps!"
"\n# Expected Log Line: map_details, ... %p",
reinterpret_cast<void*>(obj), i, reinterpret_cast<void*>(obj));
}
ValidateMapDetailsLogging(isolate, &logger);
}
i::FLAG_log_function_events = false;
isolate->Dispose();
}
TEST(LogMapsDetailsCode) {
// Reusing map addresses might cause these tests to fail.
if (i::FLAG_gc_global || i::FLAG_stress_compaction ||
i::FLAG_stress_incremental_marking) {
return;
}
SETUP_FLAGS();
i::FLAG_retain_maps_for_n_gc = 0xFFFFFFF;
i::FLAG_trace_maps = true;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
const char* source = R"(
// Normal properties overflowing into dict-mode.
let a = {};
for (let i = 0; i < 500; i++) {
a['p'+i] = i
};
// Constructor / initial maps
function Constructor(dictElements=false) {
this.a = 1;
this.b = 2;
this.c = 3;
if (dictElements) {
this[0xFFFFF] = 1;
}
this.d = 4;
this.e = 5;
this.f = 5;
}
// Keep objects and their maps alive to avoid reusing map addresses.
let instances = [];
let instance;
for (let i =0; i < 500; i++) {
instances.push(new Constructor());
}
// Map deprecation.
for (let i =0; i < 500; i++) {
instance = new Constructor();
instance.d = 1.1;
instances.push(instance);
}
for (let i =0; i < 500; i++) {
instance = new Constructor();
instance.b = 1.1;
instances.push(instance);
}
for (let i =0; i < 500; i++) {
instance = new Constructor();
instance.c = Object;
instances.push(instance);
}
// Create instance with dict-elements.
instances.push(new Constructor(true));
// Class
class Test {
constructor(i) {
this.a = 1;
this['p'+i] = 1;
}
};
let t = new Test();
t.b = 1; t.c = 1; t.d = 3;
for (let i = 0; i < 100; i++) {
t = new Test(i);
instances.push(t);
}
t.b = {};
// Anonymous classes
function create(value) {
return new class {
constructor() {
this.value = value;
}
}
}
for (let i = 0; i < 100; i++) {
instances.push(create(i));
};
// Modifying some protoypes.
Array.prototype.helper = () => 1;
[1,2,3].helper();
)";
{
ScopedLoggerInitializer logger(saved_log, saved_prof, isolate);
CompileRunChecked(isolate, source);
logger.StopLogging();
ValidateMapDetailsLogging(isolate, &logger);
}
i::FLAG_log_function_events = false;
isolate->Dispose();
}
TEST(LogMapsDetailsContexts) {
// Reusing map addresses might cause these tests to fail.
if (i::FLAG_gc_global || i::FLAG_stress_compaction ||
i::FLAG_stress_incremental_marking) {
return;
}
// Test that all Map details from Maps in the snapshot are logged properly.
SETUP_FLAGS();
i::FLAG_trace_maps = true;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
ScopedLoggerInitializer logger(saved_log, saved_prof, isolate);
// Use the default context.
CompileRunChecked(isolate, "{a:1}");
// Create additional contexts.
v8::Local<v8::Context> env1 = v8::Context::New(isolate);
env1->Enter();
CompileRun(env1, "{b:1}").ToLocalChecked();
v8::Local<v8::Context> env2 = v8::Context::New(isolate);
env2->Enter();
CompileRun(env2, "{c:1}").ToLocalChecked();
env2->Exit();
env1->Exit();
logger.StopLogging();
ValidateMapDetailsLogging(isolate, &logger);
}
i::FLAG_log_function_events = false;
isolate->Dispose();
}