// Copyright 2021 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/snapshot/shared-heap-serializer.h" #include "src/heap/heap-inl.h" #include "src/heap/read-only-heap.h" #include "src/objects/objects-inl.h" #include "src/snapshot/read-only-serializer.h" namespace v8 { namespace internal { // static bool SharedHeapSerializer::CanBeInSharedOldSpace(HeapObject obj) { if (ReadOnlyHeap::Contains(obj)) return false; if (obj.IsString()) { return obj.IsInternalizedString() || String::IsInPlaceInternalizable(String::cast(obj)); } return false; } // static bool SharedHeapSerializer::ShouldBeInSharedHeapObjectCache(HeapObject obj) { // To keep the shared heap object cache lean, only include objects that should // not be duplicated. Currently, that is only internalized strings. In-place // internalizable strings will still be allocated in the shared heap by the // deserializer, but do not need to be kept alive forever in the cache. if (CanBeInSharedOldSpace(obj)) { if (obj.IsInternalizedString()) return true; } return false; } SharedHeapSerializer::SharedHeapSerializer( Isolate* isolate, Snapshot::SerializerFlags flags, ReadOnlySerializer* read_only_serializer) : RootsSerializer(isolate, flags, RootIndex::kFirstStrongRoot), read_only_serializer_(read_only_serializer) #ifdef DEBUG , serialized_objects_(isolate->heap()) #endif { if (ShouldReconstructSharedHeapObjectCacheForTesting()) { ReconstructSharedHeapObjectCacheForTesting(); } } SharedHeapSerializer::~SharedHeapSerializer() { OutputStatistics("SharedHeapSerializer"); } void SharedHeapSerializer::FinalizeSerialization() { // This is called after serialization of the startup and context snapshots // which entries are added to the shared heap object cache. Terminate the // cache with an undefined. Object undefined = ReadOnlyRoots(isolate()).undefined_value(); VisitRootPointer(Root::kSharedHeapObjectCache, nullptr, FullObjectSlot(&undefined)); // When FLAG_shared_string_table is true, all internalized and // internalizable-in-place strings are in the shared heap. SerializeStringTable(isolate()->string_table()); SerializeDeferredObjects(); Pad(); #ifdef DEBUG // Check that all serialized object are in shared heap and not RO. RO objects // should be in the RO snapshot. IdentityMap::IteratableScope it_scope( &serialized_objects_); for (auto it = it_scope.begin(); it != it_scope.end(); ++it) { HeapObject obj = HeapObject::cast(it.key()); CHECK(CanBeInSharedOldSpace(obj)); CHECK(!ReadOnlyHeap::Contains(obj)); } #endif } bool SharedHeapSerializer::SerializeUsingReadOnlyObjectCache( SnapshotByteSink* sink, Handle obj) { return read_only_serializer_->SerializeUsingReadOnlyObjectCache(sink, obj); } bool SharedHeapSerializer::SerializeUsingSharedHeapObjectCache( SnapshotByteSink* sink, Handle obj) { if (!ShouldBeInSharedHeapObjectCache(*obj)) return false; int cache_index = SerializeInObjectCache(obj); // When testing deserialization of a snapshot from a live Isolate where there // is also a shared Isolate, the shared object cache needs to be extended // because the live isolate may have had new internalized strings that were // not present in the startup snapshot to be serialized. if (ShouldReconstructSharedHeapObjectCacheForTesting()) { std::vector* existing_cache = isolate()->shared_isolate()->shared_heap_object_cache(); const size_t existing_cache_size = existing_cache->size(); // This is strictly < because the existing cache contains the terminating // undefined value, which the reconstructed cache does not. DCHECK_LT(base::checked_cast(cache_index), existing_cache_size); if (base::checked_cast(cache_index) == existing_cache_size - 1) { ReadOnlyRoots roots(isolate()); DCHECK(existing_cache->back().IsUndefined(roots)); existing_cache->back() = *obj; existing_cache->push_back(roots.undefined_value()); } } sink->Put(kSharedHeapObjectCache, "SharedHeapObjectCache"); sink->PutInt(cache_index, "shared_heap_object_cache_index"); return true; } void SharedHeapSerializer::SerializeStringTable(StringTable* string_table) { // A StringTable is serialized as: // // N : int // string 1 // string 2 // ... // string N // // Notably, the hashmap structure, including empty and deleted elements, is // not serialized. sink_.PutInt(string_table->NumberOfElements(), "String table number of elements"); // Custom RootVisitor which walks the string table, but only serializes the // string entries. This is an inline class to be able to access the non-public // SerializeObject method. class SharedHeapSerializerStringTableVisitor : public RootVisitor { public: explicit SharedHeapSerializerStringTableVisitor( SharedHeapSerializer* serializer) : serializer_(serializer) {} void VisitRootPointers(Root root, const char* description, FullObjectSlot start, FullObjectSlot end) override { UNREACHABLE(); } void VisitRootPointers(Root root, const char* description, OffHeapObjectSlot start, OffHeapObjectSlot end) override { DCHECK_EQ(root, Root::kStringTable); Isolate* isolate = serializer_->isolate(); for (OffHeapObjectSlot current = start; current < end; ++current) { Object obj = current.load(isolate); if (obj.IsHeapObject()) { DCHECK(obj.IsInternalizedString()); serializer_->SerializeObject(handle(HeapObject::cast(obj), isolate)); } } } private: SharedHeapSerializer* serializer_; }; SharedHeapSerializerStringTableVisitor string_table_visitor(this); isolate()->string_table()->IterateElements(&string_table_visitor); } void SharedHeapSerializer::SerializeObjectImpl(Handle obj) { // Objects in the shared heap cannot depend on per-Isolate roots but can // depend on RO roots since sharing objects requires sharing the RO space. DCHECK(CanBeInSharedOldSpace(*obj) || ReadOnlyHeap::Contains(*obj)); { DisallowGarbageCollection no_gc; HeapObject raw = *obj; if (SerializeHotObject(raw)) return; if (IsRootAndHasBeenSerialized(raw) && SerializeRoot(raw)) return; } if (SerializeUsingReadOnlyObjectCache(&sink_, obj)) return; { DisallowGarbageCollection no_gc; HeapObject raw = *obj; if (SerializeBackReference(raw)) return; CheckRehashability(raw); DCHECK(!ReadOnlyHeap::Contains(raw)); } ObjectSerializer object_serializer(this, obj, &sink_); object_serializer.Serialize(); #ifdef DEBUG CHECK_NULL(serialized_objects_.Find(obj)); // There's no "IdentitySet", so use an IdentityMap with a value that is // later ignored. serialized_objects_.Insert(obj, 0); #endif } bool SharedHeapSerializer::ShouldReconstructSharedHeapObjectCacheForTesting() const { // When the live Isolate being serialized is not a client Isolate, there's no // need to reconstruct the shared heap object cache because it is not actually // shared. return reconstruct_read_only_and_shared_object_caches_for_testing() && isolate()->shared_isolate() != nullptr; } void SharedHeapSerializer::ReconstructSharedHeapObjectCacheForTesting() { std::vector* cache = isolate()->shared_isolate()->shared_heap_object_cache(); // Don't reconstruct the final element, which is always undefined and marks // the end of the cache, since serializing the live Isolate may extend the // shared object cache. for (size_t i = 0, size = cache->size(); i < size - 1; i++) { Handle obj(HeapObject::cast(cache->at(i)), isolate()); DCHECK(ShouldBeInSharedHeapObjectCache(*obj)); int cache_index = SerializeInObjectCache(obj); USE(cache_index); DCHECK_EQ(cache_index, i); } DCHECK(cache->back().IsUndefined(isolate())); } } // namespace internal } // namespace v8