diff --git a/deps/v8/include/v8.h b/deps/v8/include/v8.h index 54bc4f08359125..0dcecf55f86634 100644 --- a/deps/v8/include/v8.h +++ b/deps/v8/include/v8.h @@ -9720,6 +9720,11 @@ class V8_EXPORT V8 { /** * Helper class to create a snapshot data blob. + * + * The Isolate used by a SnapshotCreator is owned by it, and will be entered + * and exited by the constructor and destructor, respectively; The destructor + * will also destroy the Isolate. Experimental language features, including + * those available by default, are not available while creating a snapshot. */ class V8_EXPORT SnapshotCreator { public: @@ -9748,6 +9753,10 @@ class V8_EXPORT SnapshotCreator { SnapshotCreator(const intptr_t* external_references = nullptr, StartupData* existing_blob = nullptr); + /** + * Destroy the snapshot creator, and exit and dispose of the Isolate + * associated with it. + */ ~SnapshotCreator(); /** diff --git a/deps/v8/src/objects/heap-object.h b/deps/v8/src/objects/heap-object.h index fcbb0ce8336bb7..c6cd4c3ae5480d 100644 --- a/deps/v8/src/objects/heap-object.h +++ b/deps/v8/src/objects/heap-object.h @@ -190,7 +190,7 @@ class HeapObject : public Object { bool CanBeRehashed() const; // Rehash the object based on the layout inferred from its map. - void RehashBasedOnMap(ReadOnlyRoots root); + void RehashBasedOnMap(Isolate* isolate); // Layout description. #define HEAP_OBJECT_FIELDS(V) \ diff --git a/deps/v8/src/objects/js-collection.h b/deps/v8/src/objects/js-collection.h index 17f9c3e198ba8b..a0350726c02db7 100644 --- a/deps/v8/src/objects/js-collection.h +++ b/deps/v8/src/objects/js-collection.h @@ -30,6 +30,7 @@ class JSSet : public TorqueGeneratedJSSet { public: static void Initialize(Handle set, Isolate* isolate); static void Clear(Isolate* isolate, Handle set); + void Rehash(Isolate* isolate); // Dispatched behavior. DECL_PRINTER(JSSet) @@ -56,6 +57,7 @@ class JSMap : public TorqueGeneratedJSMap { public: static void Initialize(Handle map, Isolate* isolate); static void Clear(Isolate* isolate, Handle map); + void Rehash(Isolate* isolate); // Dispatched behavior. DECL_PRINTER(JSMap) diff --git a/deps/v8/src/objects/objects.cc b/deps/v8/src/objects/objects.cc index 9b53019297a3c6..6b80bc8b1aea80 100644 --- a/deps/v8/src/objects/objects.cc +++ b/deps/v8/src/objects/objects.cc @@ -2285,9 +2285,8 @@ bool HeapObject::NeedsRehashing() const { case TRANSITION_ARRAY_TYPE: return TransitionArray::cast(*this).number_of_entries() > 1; case ORDERED_HASH_MAP_TYPE: - return OrderedHashMap::cast(*this).NumberOfElements() > 0; case ORDERED_HASH_SET_TYPE: - return OrderedHashSet::cast(*this).NumberOfElements() > 0; + return false; // We'll rehash from the JSMap or JSSet referencing them. case NAME_DICTIONARY_TYPE: case GLOBAL_DICTIONARY_TYPE: case NUMBER_DICTIONARY_TYPE: @@ -2297,6 +2296,8 @@ bool HeapObject::NeedsRehashing() const { case SMALL_ORDERED_HASH_MAP_TYPE: case SMALL_ORDERED_HASH_SET_TYPE: case SMALL_ORDERED_NAME_DICTIONARY_TYPE: + case JS_MAP_TYPE: + case JS_SET_TYPE: return true; default: return false; @@ -2306,10 +2307,13 @@ bool HeapObject::NeedsRehashing() const { bool HeapObject::CanBeRehashed() const { DCHECK(NeedsRehashing()); switch (map().instance_type()) { + case JS_MAP_TYPE: + case JS_SET_TYPE: + return true; case ORDERED_HASH_MAP_TYPE: case ORDERED_HASH_SET_TYPE: + UNREACHABLE(); // We'll rehash from the JSMap or JSSet referencing them. case ORDERED_NAME_DICTIONARY_TYPE: - // TODO(yangguo): actually support rehashing OrderedHash{Map,Set}. return false; case NAME_DICTIONARY_TYPE: case GLOBAL_DICTIONARY_TYPE: @@ -2333,7 +2337,8 @@ bool HeapObject::CanBeRehashed() const { return false; } -void HeapObject::RehashBasedOnMap(ReadOnlyRoots roots) { +void HeapObject::RehashBasedOnMap(Isolate* isolate) { + ReadOnlyRoots roots = ReadOnlyRoots(isolate); switch (map().instance_type()) { case HASH_TABLE_TYPE: UNREACHABLE(); @@ -2365,6 +2370,17 @@ void HeapObject::RehashBasedOnMap(ReadOnlyRoots roots) { case SMALL_ORDERED_HASH_SET_TYPE: DCHECK_EQ(0, SmallOrderedHashSet::cast(*this).NumberOfElements()); break; + case ORDERED_HASH_MAP_TYPE: + case ORDERED_HASH_SET_TYPE: + UNREACHABLE(); // We'll rehash from the JSMap or JSSet referencing them. + case JS_MAP_TYPE: { + JSMap::cast(*this).Rehash(isolate); + break; + } + case JS_SET_TYPE: { + JSSet::cast(*this).Rehash(isolate); + break; + } case SMALL_ORDERED_NAME_DICTIONARY_TYPE: DCHECK_EQ(0, SmallOrderedNameDictionary::cast(*this).NumberOfElements()); break; @@ -7740,6 +7756,13 @@ void JSSet::Clear(Isolate* isolate, Handle set) { set->set_table(*table); } +void JSSet::Rehash(Isolate* isolate) { + Handle table_handle(OrderedHashSet::cast(table()), isolate); + Handle new_table = + OrderedHashSet::Rehash(isolate, table_handle).ToHandleChecked(); + set_table(*new_table); +} + void JSMap::Initialize(Handle map, Isolate* isolate) { Handle table = isolate->factory()->NewOrderedHashMap(); map->set_table(*table); @@ -7751,6 +7774,13 @@ void JSMap::Clear(Isolate* isolate, Handle map) { map->set_table(*table); } +void JSMap::Rehash(Isolate* isolate) { + Handle table_handle(OrderedHashMap::cast(table()), isolate); + Handle new_table = + OrderedHashMap::Rehash(isolate, table_handle).ToHandleChecked(); + set_table(*new_table); +} + void JSWeakCollection::Initialize(Handle weak_collection, Isolate* isolate) { Handle table = EphemeronHashTable::New(isolate, 0); diff --git a/deps/v8/src/objects/ordered-hash-table.cc b/deps/v8/src/objects/ordered-hash-table.cc index 962224024ea805..a0a06e012ae0db 100644 --- a/deps/v8/src/objects/ordered-hash-table.cc +++ b/deps/v8/src/objects/ordered-hash-table.cc @@ -193,6 +193,13 @@ HeapObject OrderedHashMap::GetEmpty(ReadOnlyRoots ro_roots) { return ro_roots.empty_ordered_hash_map(); } +template +MaybeHandle OrderedHashTable::Rehash( + Isolate* isolate, Handle table) { + return OrderedHashTable::Rehash(isolate, table, + table->Capacity()); +} + template MaybeHandle OrderedHashTable::Rehash( Isolate* isolate, Handle table, int new_capacity) { @@ -249,6 +256,20 @@ MaybeHandle OrderedHashSet::Rehash(Isolate* isolate, new_capacity); } +MaybeHandle OrderedHashSet::Rehash( + Isolate* isolate, Handle table) { + return OrderedHashTable< + OrderedHashSet, OrderedHashSet::kEntrySizeWithoutChain>::Rehash(isolate, + table); +} + +MaybeHandle OrderedHashMap::Rehash( + Isolate* isolate, Handle table) { + return OrderedHashTable< + OrderedHashMap, OrderedHashMap::kEntrySizeWithoutChain>::Rehash(isolate, + table); +} + MaybeHandle OrderedHashMap::Rehash(Isolate* isolate, Handle table, int new_capacity) { diff --git a/deps/v8/src/objects/ordered-hash-table.h b/deps/v8/src/objects/ordered-hash-table.h index 590846f1302775..16609b8d1f50c8 100644 --- a/deps/v8/src/objects/ordered-hash-table.h +++ b/deps/v8/src/objects/ordered-hash-table.h @@ -138,6 +138,7 @@ class OrderedHashTable : public FixedArray { // The extra +1 is for linking the bucket chains together. static const int kEntrySize = entrysize + 1; + static const int kEntrySizeWithoutChain = entrysize; static const int kChainOffset = entrysize; static const int kNotFound = -1; @@ -200,6 +201,8 @@ class OrderedHashTable : public FixedArray { static MaybeHandle Allocate( Isolate* isolate, int capacity, AllocationType allocation = AllocationType::kYoung); + + static MaybeHandle Rehash(Isolate* isolate, Handle table); static MaybeHandle Rehash(Isolate* isolate, Handle table, int new_capacity); @@ -244,6 +247,8 @@ class V8_EXPORT_PRIVATE OrderedHashSet static MaybeHandle Rehash(Isolate* isolate, Handle table, int new_capacity); + static MaybeHandle Rehash(Isolate* isolate, + Handle table); static MaybeHandle Allocate( Isolate* isolate, int capacity, AllocationType allocation = AllocationType::kYoung); @@ -273,6 +278,8 @@ class V8_EXPORT_PRIVATE OrderedHashMap static MaybeHandle Rehash(Isolate* isolate, Handle table, int new_capacity); + static MaybeHandle Rehash(Isolate* isolate, + Handle table); Object ValueAt(int entry); // This takes and returns raw Address values containing tagged Object diff --git a/deps/v8/src/snapshot/deserializer.cc b/deps/v8/src/snapshot/deserializer.cc index 127cfae29e018c..93d01633293ede 100644 --- a/deps/v8/src/snapshot/deserializer.cc +++ b/deps/v8/src/snapshot/deserializer.cc @@ -70,7 +70,7 @@ void Deserializer::Initialize(Isolate* isolate) { void Deserializer::Rehash() { DCHECK(can_rehash() || deserializing_user_code()); for (HeapObject item : to_rehash_) { - item.RehashBasedOnMap(ReadOnlyRoots(isolate_)); + item.RehashBasedOnMap(isolate_); } } @@ -130,6 +130,14 @@ void Deserializer::DeserializeDeferredObjects() { } } } + + // When the deserialization of maps are deferred, they will be created + // as filler maps, and we postpone the post processing until the maps + // are also deserialized. + for (const auto& pair : fillers_to_post_process_) { + DCHECK(!pair.first.IsFiller()); + PostProcessNewObject(pair.first, pair.second); + } } void Deserializer::LogNewObjectEvents() { @@ -201,7 +209,11 @@ HeapObject Deserializer::PostProcessNewObject(HeapObject obj, DisallowHeapAllocation no_gc; if ((FLAG_rehash_snapshot && can_rehash_) || deserializing_user_code()) { - if (obj.IsString()) { + if (obj.IsFiller()) { + DCHECK_EQ(fillers_to_post_process_.find(obj), + fillers_to_post_process_.end()); + fillers_to_post_process_.insert({obj, space}); + } else if (obj.IsString()) { // Uninitialize hash field as we need to recompute the hash. String string = String::cast(obj); string.set_hash_field(String::kEmptyHashField); diff --git a/deps/v8/src/snapshot/deserializer.h b/deps/v8/src/snapshot/deserializer.h index 871aa6b3a77303..fcae577d8f79e5 100644 --- a/deps/v8/src/snapshot/deserializer.h +++ b/deps/v8/src/snapshot/deserializer.h @@ -107,6 +107,7 @@ class V8_EXPORT_PRIVATE Deserializer : public SerializerDeserializer { } std::shared_ptr backing_store(size_t i) { + DCHECK_LT(i, backing_stores_.size()); return backing_stores_[i]; } @@ -193,6 +194,11 @@ class V8_EXPORT_PRIVATE Deserializer : public SerializerDeserializer { // TODO(6593): generalize rehashing, and remove this flag. bool can_rehash_; std::vector to_rehash_; + // Store the objects whose maps are deferred and thus initialized as filler + // maps during deserialization, so that they can be processed later when the + // maps become available. + std::unordered_map + fillers_to_post_process_; #ifdef DEBUG uint32_t num_api_references_; diff --git a/deps/v8/src/snapshot/object-deserializer.cc b/deps/v8/src/snapshot/object-deserializer.cc index ab6ddf1d0c2234..e9fa501d6b7150 100644 --- a/deps/v8/src/snapshot/object-deserializer.cc +++ b/deps/v8/src/snapshot/object-deserializer.cc @@ -49,9 +49,10 @@ MaybeHandle ObjectDeserializer::Deserialize(Isolate* isolate) { LinkAllocationSites(); LogNewMapEvents(); result = handle(HeapObject::cast(root), isolate); - Rehash(); allocator()->RegisterDeserializedObjectsForBlackAllocation(); } + + Rehash(); CommitPostProcessedObjects(); return scope.CloseAndEscape(result); } diff --git a/deps/v8/src/snapshot/partial-deserializer.cc b/deps/v8/src/snapshot/partial-deserializer.cc index 8a215b6b871e94..e15cf6c678fbb8 100644 --- a/deps/v8/src/snapshot/partial-deserializer.cc +++ b/deps/v8/src/snapshot/partial-deserializer.cc @@ -57,12 +57,12 @@ MaybeHandle PartialDeserializer::Deserialize( // new code, which also has to be flushed from instruction cache. CHECK_EQ(start_address, code_space->top()); - if (FLAG_rehash_snapshot && can_rehash()) Rehash(); LogNewMapEvents(); result = handle(root, isolate); } + if (FLAG_rehash_snapshot && can_rehash()) Rehash(); SetupOffHeapArrayBufferBackingStores(); return result; diff --git a/deps/v8/src/snapshot/serializer-common.cc b/deps/v8/src/snapshot/serializer-common.cc index 1703af771769ac..5922139b8f9773 100644 --- a/deps/v8/src/snapshot/serializer-common.cc +++ b/deps/v8/src/snapshot/serializer-common.cc @@ -76,7 +76,8 @@ ExternalReferenceEncoder::Value ExternalReferenceEncoder::Encode( if (maybe_index.IsNothing()) { void* addr = reinterpret_cast(address); v8::base::OS::PrintError("Unknown external reference %p.\n", addr); - v8::base::OS::PrintError("%s", ExternalReferenceTable::ResolveSymbol(addr)); + v8::base::OS::PrintError("%s\n", + ExternalReferenceTable::ResolveSymbol(addr)); v8::base::OS::Abort(); } Value result(maybe_index.FromJust()); @@ -126,7 +127,14 @@ void SerializerDeserializer::Iterate(Isolate* isolate, RootVisitor* visitor) { } bool SerializerDeserializer::CanBeDeferred(HeapObject o) { - return !o.IsString() && !o.IsScript() && !o.IsJSTypedArray(); + // ArrayBuffer instances are serialized by first re-assigning a index + // to the backing store field, then serializing the object, and then + // storing the actual backing store address again (and the same for the + // ArrayBufferExtension). If serialization of the object itself is deferred, + // the real backing store address is written into the snapshot, which cannot + // be processed when deserializing. + return !o.IsString() && !o.IsScript() && !o.IsJSTypedArray() && + !o.IsJSArrayBuffer(); } void SerializerDeserializer::RestoreExternalReferenceRedirectors( diff --git a/deps/v8/src/snapshot/startup-serializer.cc b/deps/v8/src/snapshot/startup-serializer.cc index 4d6ce78b594dc3..8463373013d2d7 100644 --- a/deps/v8/src/snapshot/startup-serializer.cc +++ b/deps/v8/src/snapshot/startup-serializer.cc @@ -183,6 +183,7 @@ void SerializedHandleChecker::VisitRootPointers(Root root, PrintF("%s handle not serialized: ", root == Root::kGlobalHandles ? "global" : "eternal"); (*p).Print(); + PrintF("\n"); ok_ = false; } } diff --git a/deps/v8/test/cctest/test-serialize.cc b/deps/v8/test/cctest/test-serialize.cc index f37b6235041aaa..a7e74a159ef657 100644 --- a/deps/v8/test/cctest/test-serialize.cc +++ b/deps/v8/test/cctest/test-serialize.cc @@ -3715,7 +3715,7 @@ UNINITIALIZED_TEST(SnapshotCreatorIncludeGlobalProxy) { FreeCurrentEmbeddedBlob(); } -UNINITIALIZED_TEST(ReinitializeHashSeedNotRehashable) { +UNINITIALIZED_TEST(ReinitializeHashSeedJSCollectionRehashable) { DisableAlwaysOpt(); i::FLAG_rehash_snapshot = true; i::FLAG_hash_seed = 42; @@ -3733,13 +3733,16 @@ UNINITIALIZED_TEST(ReinitializeHashSeedNotRehashable) { CompileRun( "var m = new Map();" "m.set('a', 1);" - "m.set('b', 2);"); + "m.set('b', 2);" + "var s = new Set();" + "s.add(1)"); ExpectInt32("m.get('b')", 2); + ExpectTrue("s.has(1)"); creator.SetDefaultContext(context); } blob = creator.CreateBlob(v8::SnapshotCreator::FunctionCodeHandling::kClear); - CHECK(!blob.CanBeRehashed()); + CHECK(blob.CanBeRehashed()); } ReadOnlyHeap::ClearSharedHeapForTest(); @@ -3749,8 +3752,8 @@ UNINITIALIZED_TEST(ReinitializeHashSeedNotRehashable) { create_params.snapshot_blob = &blob; v8::Isolate* isolate = v8::Isolate::New(create_params); { - // Check that no rehashing has been performed. - CHECK_EQ(static_cast(42), + // Check that rehashing has been performed. + CHECK_EQ(static_cast(1337), HashSeed(reinterpret_cast(isolate))); v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); @@ -3758,6 +3761,7 @@ UNINITIALIZED_TEST(ReinitializeHashSeedNotRehashable) { CHECK(!context.IsEmpty()); v8::Context::Scope context_scope(context); ExpectInt32("m.get('b')", 2); + ExpectTrue("s.has(1)"); } isolate->Dispose(); delete[] blob.data; diff --git a/lib/buffer.js b/lib/buffer.js index 874227f0d44051..85e84fa64f37c6 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -56,7 +56,7 @@ const { swap64: _swap64, kMaxLength, kStringMaxLength, - zeroFill: bindingZeroFill + getZeroFillField } = internalBinding('buffer'); const { arraybuffer_untransferable_private_symbol, @@ -131,18 +131,22 @@ const constants = ObjectDefineProperties({}, { } }); +const encodingsMap = ObjectCreate(null); +for (let i = 0; i < encodings.length; ++i) + encodingsMap[encodings[i]] = i; + Buffer.poolSize = 8 * 1024; let poolSize, poolOffset, allocPool; // A toggle used to access the zero fill setting of the array buffer allocator // in C++. -// |zeroFill| can be undefined when running inside an isolate where we -// do not own the ArrayBuffer allocator. Zero fill is always on in that case. -const zeroFill = bindingZeroFill || [0]; - -const encodingsMap = ObjectCreate(null); -for (let i = 0; i < encodings.length; ++i) - encodingsMap[encodings[i]] = i; +// getZeroFillField() can return undefined when running inside an isolate where +// we do not own the ArrayBuffer allocator. Zero fill is always on in that case. +let zeroFill = new Uint32Array([0]); +function refreshZeroFillField() { + const fromBinding = getZeroFillField(); + if (fromBinding) zeroFill = fromBinding; +} function createUnsafeBuffer(size) { zeroFill[0] = 0; @@ -1207,7 +1211,9 @@ module.exports = { transcode, // Legacy kMaxLength, - kStringMaxLength + kStringMaxLength, + // Called and deleted by internal/bootstrap/pre_execution.js. + refreshZeroFillField }; ObjectDefineProperties(module.exports, { diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 2c58f912080733..c038e2aae81ff2 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -197,7 +197,17 @@ ObjectDefineProperty(process, 'features', { tls_sni: hasOpenSSL, tls_ocsp: hasOpenSSL, tls: hasOpenSSL, - cached_builtins: config.hasCachedBuiltins, + } +}); +ObjectDefineProperty(process.features, 'cached_builtins', { + enumerable: true, + configurable: true, + get() { + return config.hasCachedBuiltins(); + }, + set(v) { + delete this.cached_builtins; + this.cached_builtins = v; } }); @@ -302,7 +312,7 @@ function setupBuffer() { // Only after this point can C++ use Buffer::New() bufferBinding.setBufferPrototype(Buffer.prototype); delete bufferBinding.setBufferPrototype; - delete bufferBinding.zeroFill; + delete bufferBinding.getZeroFillField; ObjectDefineProperty(global, 'Buffer', { value: Buffer, diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index 3d5e0061daa8d1..012416ec2bc73a 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -7,7 +7,8 @@ const { } = primordials; const { getOptionValue } = require('internal/options'); -const { Buffer } = require('buffer'); +const buffer = require('buffer'); +const { Buffer } = buffer; const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes; const assert = require('internal/assert'); @@ -107,6 +108,9 @@ function patchProcessObject(expandArgv1) { addReadOnlyProcessAlias('traceDeprecation', '--trace-deprecation'); addReadOnlyProcessAlias('_breakFirstLine', '--inspect-brk', false); addReadOnlyProcessAlias('_breakNodeFirstLine', '--inspect-brk-node', false); + + buffer.refreshZeroFillField(); + delete buffer.refreshZeroFillField; } function addReadOnlyProcessAlias(name, option, enumerable = true) { diff --git a/node.gyp b/node.gyp index 3dadad15c9e193..9fe8874d691676 100644 --- a/node.gyp +++ b/node.gyp @@ -616,6 +616,7 @@ 'src/pipe_wrap.cc', 'src/process_wrap.cc', 'src/signal_wrap.cc', + 'src/snapshot_support.cc', 'src/spawn_sync.cc', 'src/stream_base.cc', 'src/stream_pipe.cc', @@ -707,6 +708,8 @@ 'src/pipe_wrap.h', 'src/req_wrap.h', 'src/req_wrap-inl.h', + 'src/snapshot_support.h', + 'src/snapshot_support-inl.h', 'src/spawn_sync.h', 'src/stream_base.h', 'src/stream_base-inl.h', @@ -1154,6 +1157,7 @@ 'test/cctest/test_per_process.cc', 'test/cctest/test_platform.cc', 'test/cctest/test_json_utils.cc', + 'test/cctest/test_snapshot_support.cc', 'test/cctest/test_sockaddr.cc', 'test/cctest/test_traced_value.cc', 'test/cctest/test_util.cc', diff --git a/src/aliased_buffer.h b/src/aliased_buffer.h index e762e8ede8ebee..6095bb6cc8efdd 100644 --- a/src/aliased_buffer.h +++ b/src/aliased_buffer.h @@ -4,6 +4,8 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include +// TODO(addaleax): There really, really should be an aliased_buffer-inl.h. +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" @@ -30,8 +32,10 @@ template ::value>> -class AliasedBufferBase { +class AliasedBufferBase final : public Snapshottable { public: + AliasedBufferBase() {} + AliasedBufferBase(v8::Isolate* isolate, const size_t count) : isolate_(isolate), count_(count), byte_offset_(0) { CHECK_GT(count, 0); @@ -243,11 +247,45 @@ class AliasedBufferBase { count_ = new_capacity; } + void Serialize(SnapshotCreateData* snapshot_data) const override { + v8::HandleScope handle_scope(isolate_); + snapshot_data->StartWriteEntry("AliasedBuffer"); + snapshot_data->WriteUint64(count_); + snapshot_data->WriteUint64(byte_offset_); + v8::Local arr = GetJSArray(); + snapshot_data->WriteObject(arr->CreationContext(), arr); + snapshot_data->EndWriteEntry(); + } + + AliasedBufferBase(v8::Local context, + SnapshotReadData* snapshot_data) + : isolate_(context->GetIsolate()) { + v8::HandleScope handle_scope(isolate_); + uint64_t count, byte_offset; + if (snapshot_data->StartReadEntry("AliasedBuffer").IsNothing() || + !snapshot_data->ReadUint64().To(&count) || + !snapshot_data->ReadUint64().To(&byte_offset)) { + return; + } + + count_ = count; + byte_offset_ = byte_offset; + + v8::Local field; + if (!snapshot_data->ReadObject(context).To(&field)) return; + js_array_.Reset(isolate_, field); + buffer_ = reinterpret_cast(static_cast( + field->Buffer()->GetBackingStore()->Data()) + + byte_offset_); + + snapshot_data->EndReadEntry(); + } + private: - v8::Isolate* isolate_; - size_t count_; - size_t byte_offset_; - NativeT* buffer_; + v8::Isolate* isolate_ = nullptr; + size_t count_ = 0; + size_t byte_offset_ = 0; + NativeT* buffer_ = nullptr; v8::Global js_array_; }; diff --git a/src/api/environment.cc b/src/api/environment.cc index b9ca6ca7451926..b2ff6a6b565c03 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -5,6 +5,7 @@ #include "node_native_module_env.h" #include "node_platform.h" #include "node_v8_platform-inl.h" +#include "snapshot_support-inl.h" #include "uv.h" #if HAVE_INSPECTOR @@ -14,6 +15,7 @@ namespace node { using errors::TryCatchScope; using v8::Array; +using v8::ArrayBuffer; using v8::Context; using v8::EscapableHandleScope; using v8::FinalizationGroup; @@ -91,6 +93,17 @@ static void HostCleanupFinalizationGroupCallback( env->RegisterFinalizationGroupForCleanup(group); } +std::shared_ptr NodeArrayBufferAllocator::zero_fill_field() { + if (!zero_fill_field_bs_) { + zero_fill_field_bs_ = + ArrayBuffer::NewBackingStore(&zero_fill_field_, + sizeof(zero_fill_field_), + [](void*, size_t, void*){}, + nullptr); + } + return zero_fill_field_bs_; +} + void* NodeArrayBufferAllocator::Allocate(size_t size) { void* ret; if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers) @@ -378,6 +391,12 @@ Environment* CreateEnvironment( env->set_abort_on_uncaught_exception(false); } + if (isolate_data->snapshot_data() != nullptr && + !isolate_data->snapshot_data()->errors().empty()) { + FreeEnvironment(env); + return nullptr; + } + #if HAVE_INSPECTOR if (inspector_parent_handle) { env->InitializeInspector( @@ -388,7 +407,8 @@ Environment* CreateEnvironment( } #endif - if (env->RunBootstrapping().IsEmpty()) { + if (env->isolate_data()->snapshot_data() == nullptr && + env->RunBootstrapping().IsEmpty()) { FreeEnvironment(env); return nullptr; } diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 42837e09818ec2..08111e248b5bd6 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -24,6 +24,7 @@ #include "env-inl.h" #include "node_errors.h" #include "tracing/traced_value.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" @@ -469,6 +470,7 @@ Local AsyncWrap::GetConstructorTemplate(Environment* env) { if (tmpl.IsEmpty()) { tmpl = env->NewFunctionTemplate(nullptr); tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "AsyncWrap")); + tmpl->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(tmpl, "getAsyncId", AsyncWrap::GetAsyncId); env->SetProtoMethod(tmpl, "asyncReset", AsyncWrap::AsyncReset); env->SetProtoMethod(tmpl, "getProviderType", AsyncWrap::GetProviderType); @@ -579,6 +581,23 @@ void AsyncWrap::Initialize(Local target, ->GetFunction(env->context()).ToLocalChecked()).Check(); } +static ExternalReferences external_references { + __FILE__, + SetupHooks, + AsyncWrap::PushAsyncContext, + AsyncWrap::PopAsyncContext, + AsyncWrap::QueueDestroyAsyncId, + EnablePromiseHook, + DisablePromiseHook, + RegisterDestroyHook, + AsyncWrapObject::New, + AsyncWrap::GetAsyncId, + // AsyncReset is overloaded, pick the right one + static_cast& args)>( + AsyncWrap::AsyncReset), + AsyncWrap::GetProviderType, + PromiseWrap::getIsChainedPromise, +}; AsyncWrap::AsyncWrap(Environment* env, Local object, diff --git a/src/base_object-inl.h b/src/base_object-inl.h index 60ccf81cc559c5..f318025fd0cd2d 100644 --- a/src/base_object-inl.h +++ b/src/base_object-inl.h @@ -101,6 +101,7 @@ Environment* BaseObject::env() const { } BaseObject* BaseObject::FromJSObject(v8::Local value) { + DCHECK(value->IsObject()); v8::Local obj = value.As(); DCHECK_GE(obj->InternalFieldCount(), BaseObject::kSlot); return static_cast( @@ -156,6 +157,7 @@ BaseObject::MakeLazilyInitializedJSTemplate(Environment* env) { }; v8::Local t = env->NewFunctionTemplate(constructor); + t->Inherit(BaseObject::GetConstructorTemplate(env)); t->InstanceTemplate()->SetInternalFieldCount( BaseObject::kInternalFieldCount); return t; diff --git a/src/base_object.h b/src/base_object.h index 03c69976ee4a68..b7f35dc547dc02 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -25,6 +25,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "memory_tracker.h" +#include "snapshot_support.h" #include "v8.h" #include // std::remove_reference @@ -34,7 +35,7 @@ class Environment; template class BaseObjectPtrImpl; -class BaseObject : public MemoryRetainer { +class BaseObject : public MemoryRetainer, public Snapshottable { public: enum InternalFields { kSlot, kInternalFieldCount }; @@ -98,6 +99,14 @@ class BaseObject : public MemoryRetainer { // a BaseObjectPtr to this object. inline void Detach(); + static v8::Local GetConstructorTemplate( + Environment* env); + + static v8::StartupData SerializeInternalFields( + v8::Local object, int index, void* data); + virtual v8::StartupData SerializeInternalFields( + int index, SnapshotCreateData* snapshot_data) const; + protected: virtual inline void OnGCCollect(); diff --git a/src/env-inl.h b/src/env-inl.h index 853ba78de6d548..d0059e1c3dc9c6 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -73,33 +73,14 @@ inline worker::Worker* IsolateData::worker_context() const { return worker_context_; } +SnapshotReadData* IsolateData::snapshot_data() const { + return snapshot_data_; +} + inline v8::Local IsolateData::async_wrap_provider(int index) const { return async_wrap_providers_[index].Get(isolate_); } -inline AsyncHooks::AsyncHooks() - : async_ids_stack_(env()->isolate(), 16 * 2), - fields_(env()->isolate(), kFieldsCount), - async_id_fields_(env()->isolate(), kUidFieldsCount) { - clear_async_id_stack(); - - // Always perform async_hooks checks, not just when async_hooks is enabled. - // TODO(AndreasMadsen): Consider removing this for LTS releases. - // See discussion in https://github.com/nodejs/node/pull/15454 - // When removing this, do it by reverting the commit. Otherwise the test - // and flag changes won't be included. - fields_[kCheck] = 1; - - // kDefaultTriggerAsyncId should be -1, this indicates that there is no - // specified default value and it should fallback to the executionAsyncId. - // 0 is not used as the magic value, because that indicates a missing context - // which is different from a default context. - async_id_fields_[AsyncHooks::kDefaultTriggerAsyncId] = -1; - - // kAsyncIdCounter should start at 1 because that'll be the id the execution - // context during bootstrap (code that runs before entering uv_run()). - async_id_fields_[AsyncHooks::kAsyncIdCounter] = 1; -} inline AliasedUint32Array& AsyncHooks::fields() { return fields_; } @@ -128,6 +109,10 @@ inline Environment* AsyncHooks::env() { return Environment::ForAsyncHooks(this); } +inline const Environment* AsyncHooks::env() const { + return Environment::ForAsyncHooks(const_cast(this)); +} + // Remember to keep this code aligned with pushAsyncContext() in JS. inline void AsyncHooks::push_async_context(double async_id, double trigger_async_id, @@ -238,9 +223,6 @@ inline void Environment::PopAsyncCallbackScope() { async_callback_scope_depth_--; } -inline ImmediateInfo::ImmediateInfo(v8::Isolate* isolate) - : fields_(isolate, kFieldsCount) {} - inline AliasedUint32Array& ImmediateInfo::fields() { return fields_; } @@ -265,9 +247,6 @@ inline void ImmediateInfo::ref_count_dec(uint32_t decrement) { fields_[kRefCount] -= decrement; } -inline TickInfo::TickInfo(v8::Isolate* isolate) - : fields_(isolate, kFieldsCount) {} - inline AliasedUint8Array& TickInfo::fields() { return fields_; } @@ -467,15 +446,27 @@ inline void Environment::set_is_in_inspector_console_call(bool value) { } #endif +inline const AsyncHooks* Environment::async_hooks() const { + return &async_hooks_; +} + inline AsyncHooks* Environment::async_hooks() { return &async_hooks_; } +inline const ImmediateInfo* Environment::immediate_info() const { + return &immediate_info_; +} + inline ImmediateInfo* Environment::immediate_info() { return &immediate_info_; } -inline TickInfo* Environment::tick_info() { +inline const TickInfo* Environment::tick_info() const { + return &tick_info_; +} + +inline TickInfo* Environment::tick_info() { return &tick_info_; } @@ -926,6 +917,10 @@ inline performance::PerformanceState* Environment::performance_state() { return performance_state_.get(); } +const performance::PerformanceState* Environment::performance_state() const { + return performance_state_.get(); +} + inline std::unordered_map* Environment::performance_marks() { return &performance_marks_; @@ -1213,7 +1208,7 @@ BaseObject* CleanupHookCallback::GetBaseObject() const { } template -void Environment::ForEachBaseObject(T&& iterator) { +void Environment::ForEachBaseObject(T&& iterator) const { for (const auto& hook : cleanup_hooks_) { BaseObject* obj = hook.GetBaseObject(); if (obj != nullptr) diff --git a/src/env.cc b/src/env.cc index 3efa5c3b9c98ab..103b5b208e9817 100644 --- a/src/env.cc +++ b/src/env.cc @@ -13,6 +13,7 @@ #include "node_v8_platform-inl.h" #include "node_worker.h" #include "req_wrap-inl.h" +#include "snapshot_support-inl.h" #include "stream_base.h" #include "tracing/agent.h" #include "tracing/traced_value.h" @@ -27,6 +28,7 @@ namespace node { using errors::TryCatchScope; +using v8::Array; using v8::ArrayBuffer; using v8::Boolean; using v8::Context; @@ -44,8 +46,8 @@ using v8::Number; using v8::Object; using v8::Private; using v8::Script; -using v8::SnapshotCreator; using v8::StackTrace; +using v8::StartupData; using v8::String; using v8::Symbol; using v8::TracingController; @@ -58,20 +60,15 @@ int const Environment::kNodeContextTag = 0x6e6f64; void* const Environment::kNodeContextTagPtr = const_cast( static_cast(&Environment::kNodeContextTag)); -std::vector IsolateData::Serialize(SnapshotCreator* creator) { - Isolate* isolate = creator->GetIsolate(); - std::vector indexes; - HandleScope handle_scope(isolate); - // XXX(joyeecheung): technically speaking, the indexes here should be - // consecutive and we could just return a range instead of an array, - // but that's not part of the V8 API contract so we use an array - // just to be safe. +void IsolateData::Serialize(SnapshotCreateData* snapshot_data) const { + HandleScope handle_scope(isolate_); + snapshot_data->StartWriteEntry("IsolateData"); #define VP(PropertyName, StringValue) V(Private, PropertyName) #define VY(PropertyName, StringValue) V(Symbol, PropertyName) #define VS(PropertyName, StringValue) V(String, PropertyName) #define V(TypeName, PropertyName) \ - indexes.push_back(creator->AddData(PropertyName##_.Get(isolate))); + snapshot_data->WriteContextIndependentObject(PropertyName()); PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) @@ -80,13 +77,14 @@ std::vector IsolateData::Serialize(SnapshotCreator* creator) { #undef VS #undef VP for (size_t i = 0; i < AsyncWrap::PROVIDERS_LENGTH; i++) - indexes.push_back(creator->AddData(async_wrap_provider(i))); + snapshot_data->WriteContextIndependentObject(async_wrap_provider(i)); - return indexes; + snapshot_data->EndWriteEntry(); } -void IsolateData::DeserializeProperties(const std::vector* indexes) { - size_t i = 0; +void IsolateData::DeserializeProperties() { + if (snapshot_data_->StartReadEntry("IsolateData").IsNothing()) return; + HandleScope handle_scope(isolate_); #define VP(PropertyName, StringValue) V(Private, PropertyName) @@ -94,12 +92,10 @@ void IsolateData::DeserializeProperties(const std::vector* indexes) { #define VS(PropertyName, StringValue) V(String, PropertyName) #define V(TypeName, PropertyName) \ do { \ - MaybeLocal field = \ - isolate_->GetDataFromSnapshotOnce((*indexes)[i++]); \ - if (field.IsEmpty()) { \ - fprintf(stderr, "Failed to deserialize " #PropertyName "\n"); \ - } \ - PropertyName##_.Set(isolate_, field.ToLocalChecked()); \ + Local field; \ + if (!snapshot_data_->ReadContextIndependentObject().To(&field)) \ + return; \ + PropertyName##_.Set(isolate_, field); \ } while (0); PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) @@ -110,13 +106,13 @@ void IsolateData::DeserializeProperties(const std::vector* indexes) { #undef VP for (size_t j = 0; j < AsyncWrap::PROVIDERS_LENGTH; j++) { - MaybeLocal field = - isolate_->GetDataFromSnapshotOnce((*indexes)[i++]); - if (field.IsEmpty()) { - fprintf(stderr, "Failed to deserialize AsyncWrap provider %zu\n", j); - } - async_wrap_providers_[j].Set(isolate_, field.ToLocalChecked()); + Local field; + if (!snapshot_data_->ReadContextIndependentObject().To(&field)) + return; + async_wrap_providers_[j].Set(isolate_, field); } + + snapshot_data_->EndReadEntry(); } void IsolateData::CreateProperties() { @@ -187,23 +183,25 @@ IsolateData::IsolateData(Isolate* isolate, uv_loop_t* event_loop, MultiIsolatePlatform* platform, ArrayBufferAllocator* node_allocator, - const std::vector* indexes) + SnapshotReadData* snapshot_data) : isolate_(isolate), event_loop_(event_loop), allocator_(isolate->GetArrayBufferAllocator()), node_allocator_(node_allocator == nullptr ? nullptr : node_allocator->GetImpl()), uses_node_allocator_(allocator_ == node_allocator_), - platform_(platform) { + platform_(platform), + snapshot_data_(snapshot_data) { CHECK_NOT_NULL(allocator_); options_.reset( new PerIsolateOptions(*(per_process::cli_options->per_isolate))); - if (indexes == nullptr) { + if (snapshot_data == nullptr) { CreateProperties(); } else { - DeserializeProperties(indexes); + snapshot_data->set_isolate(isolate); + DeserializeProperties(); } } @@ -270,6 +268,7 @@ void Environment::CreateProperties() { Local templ = FunctionTemplate::New(isolate()); templ->InstanceTemplate()->SetInternalFieldCount( BaseObject::kInternalFieldCount); + templ->Inherit(BaseObject::GetConstructorTemplate(this)); set_binding_data_ctor_template(templ); } @@ -287,6 +286,200 @@ void Environment::CreateProperties() { set_process_object(process_object); } +void Environment::DeserializeProperties() { + // If we have not run the bootstrapping code yet, serialization will fail, + // therefore this will always be true when deserializing. + set_has_run_bootstrapping_code(true); + + SnapshotReadData* snapshot_data = isolate_data()->snapshot_data(); + bool can_call_into_js; + if (!snapshot_data->ReadBool().To(&can_call_into_js)) return; + can_call_into_js_ = can_call_into_js; + + if (!snapshot_data->ReadUint32().To(&module_id_counter_)) return; + if (!snapshot_data->ReadUint32().To(&script_id_counter_)) return; + if (!snapshot_data->ReadUint32().To(&function_id_counter_)) return; + + Isolate* isolate = this->isolate(); + HandleScope handle_scope(isolate); + Local context = this->context(); + + if (snapshot_data->StartReadEntry("StrongPersistentTemplates").IsNothing()) + return; +#define V(PropertyName, TypeName) \ + do { \ + Local field; \ + if (!snapshot_data->ReadContextIndependentObject( \ + SnapshotReadData::kAllowEmpty).To(&field)) { \ + return; \ + } \ + PropertyName##_.Reset(isolate_, field); \ + } while (0); + ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) +#undef V + if (snapshot_data->EndReadEntry().IsNothing()) return; + + if (snapshot_data->StartReadEntry("StrongPersistentValues").IsNothing()) + return; +#define V(PropertyName, TypeName) \ + do { \ + Local field; \ + if (!snapshot_data->ReadObject(context, \ + SnapshotReadData::kAllowEmpty).To(&field)) { \ + return; \ + } \ + PropertyName##_.Reset(isolate_, field); \ + } while (0); + ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) +#undef V + if (snapshot_data->EndReadEntry().IsNothing()) return; + + if (snapshot_data->StartReadEntry("NativeModules").IsNothing()) return; + uint64_t expected_native_module_count; + if (!snapshot_data->ReadUint64().To(&expected_native_module_count)) return; + for (uint64_t i = 0; i < expected_native_module_count; i++) { + std::string str; + if (!snapshot_data->ReadString().To(&str)) return; + native_modules_in_snapshot.insert(str); + } + if (snapshot_data->EndReadEntry().IsNothing()) return; + + if (snapshot_data->StartReadEntry("BaseObjects").IsNothing()) return; + uint64_t expected_base_object_count; + if (!snapshot_data->ReadUint64().To(&expected_base_object_count)) return; + +#ifdef DEBUG + std::set deserialized_objects; +#endif + for (uint64_t i = 0; i < expected_base_object_count; i++) { + std::string name; + if (!snapshot_data->StartReadEntry(nullptr).To(&name)) return; + BaseObject* obj = BaseObjectDeserializer::Deserialize( + name, this, snapshot_data); + if (obj == nullptr) return; +#ifdef DEBUG + deserialized_objects.insert(obj); +#endif + } + +#ifdef DEBUG + // Make sure all BaseObject instances have been attached to this Environment. + ForEachBaseObject([&](const BaseObject* obj) { + deserialized_objects.erase(obj); + }); + CHECK(deserialized_objects.empty()); +#endif + if (snapshot_data->EndReadEntry().IsNothing()) return; + + if (snapshot_data->StartReadEntry("bindings").IsNothing()) return; + uint32_t num_bindings; + if (!snapshot_data->ReadUint32().To(&num_bindings)) return; + for (uint32_t i = 0; i < num_bindings; i++) { + std::string name; + BaseObjectPtr binding; + if (!snapshot_data->ReadString().To(&name) || + !snapshot_data->ReadBaseObjectPtr(context).To(&binding)) { + return; + } + bindings_.emplace(FastStringKey { name.c_str() }, binding); + // TODO(addaleax): Do this in a better way when moving BaseObject to the + // internal fields callback variant. + binding->Detach(); + } + + if (snapshot_data->EndReadEntry().IsNothing()) return; + + Local ctx; + if (!snapshot_data->ReadObject(context).To(&ctx)) return; + if (ctx != context) { + snapshot_data->add_error( + "Context from snapshot does not match context provided to Environment"); + } + snapshot_data->EndReadEntry(); +} + +void Environment::Serialize(SnapshotCreateData* snapshot_data) const { + // This method should only be called after CreateEnvironment() finished. + if (!has_run_bootstrapping_code()) { + snapshot_data->add_error("Environment has not been bootstrapped yet"); + return; + } + + snapshot_data->StartWriteEntry("Environment"); + async_hooks()->Serialize(snapshot_data); + immediate_info()->Serialize(snapshot_data); + tick_info()->Serialize(snapshot_data); + stream_base_state_.Serialize(snapshot_data); + should_abort_on_uncaught_toggle_.Serialize(snapshot_data); + performance_state()->Serialize(snapshot_data); + + snapshot_data->WriteBool(can_call_into_js()); + + snapshot_data->WriteUint32(module_id_counter_); + snapshot_data->WriteUint32(script_id_counter_); + snapshot_data->WriteUint32(function_id_counter_); + + Isolate* isolate = this->isolate(); + HandleScope handle_scope(isolate); + Local context = this->context(); + + snapshot_data->StartWriteEntry("StrongPersistentTemplates"); +#define V(PropertyName, TypeName) \ + snapshot_data->WriteContextIndependentObject(PropertyName()); + ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) +#undef V + snapshot_data->EndWriteEntry(); + + snapshot_data->StartWriteEntry("StrongPersistentValues"); +#define V(PropertyName, TypeName) \ + snapshot_data->WriteObject(context, PropertyName()); + ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) +#undef V + snapshot_data->EndWriteEntry(); + + snapshot_data->StartWriteEntry("NativeModules"); + std::vector native_modules; + native_modules.insert(native_modules.end(), + native_modules_with_cache.begin(), + native_modules_with_cache.end()); + native_modules.insert(native_modules.end(), + native_modules_without_cache.begin(), + native_modules_without_cache.end()); + // This one should currently always be empty, so this is just future-proofing. + native_modules.insert(native_modules.end(), + native_modules_in_snapshot.begin(), + native_modules_in_snapshot.end()); + snapshot_data->WriteUint64(native_modules.size()); + for (const std::string& str : native_modules) + snapshot_data->WriteString(str); + snapshot_data->EndWriteEntry(); + + snapshot_data->StartWriteEntry("BaseObjects"); + size_t expected_base_object_count = + initial_base_object_count_ + base_object_count(); + snapshot_data->WriteUint64(expected_base_object_count); + + size_t observed_base_object_count = 0; + ForEachBaseObject([&](const BaseObject* obj) { + observed_base_object_count++; + obj->Serialize(snapshot_data); + }); + CHECK_EQ(observed_base_object_count, expected_base_object_count); + snapshot_data->EndWriteEntry(); + + snapshot_data->StartWriteEntry("bindings"); + snapshot_data->WriteUint32(bindings_.size()); + for (const auto& binding : bindings_) { + snapshot_data->WriteString(binding.first.c_str()); + snapshot_data->WriteBaseObjectPtr(context, binding.second); + } + + snapshot_data->EndWriteEntry(); + + snapshot_data->WriteObject(context, context); + snapshot_data->EndWriteEntry(); +} + std::string GetExecPath(const std::vector& argv) { char exec_path_buf[2 * PATH_MAX]; size_t exec_path_len = sizeof(exec_path_buf); @@ -321,18 +514,27 @@ Environment::Environment(IsolateData* isolate_data, ThreadId thread_id) : isolate_(context->GetIsolate()), isolate_data_(isolate_data), - immediate_info_(context->GetIsolate()), - tick_info_(context->GetIsolate()), + context_(isolate_, context), + async_hooks_( + // Read the `Environment` start tag if deserializing from snapshot -- + // slightly awkward place for this, but it needs to happen before the + // AsyncHooks() constructor. Comma operator to the rescue! + (isolate_data->snapshot_data() != nullptr && + isolate_data->snapshot_data()->StartReadEntry("Environment") + .IsNothing(), + this)), + immediate_info_(this), + tick_info_(this), timer_base_(uv_now(isolate_data->event_loop())), exec_argv_(exec_args), argv_(args), exec_path_(GetExecPath(args)), - should_abort_on_uncaught_toggle_(isolate_, 1), - stream_base_state_(isolate_, StreamBase::kNumStreamBaseStateFields), flags_(flags), thread_id_(thread_id.id == static_cast(-1) ? - AllocateEnvironmentThreadId().id : thread_id.id), - context_(context->GetIsolate(), context) { + AllocateEnvironmentThreadId().id : thread_id.id) { + if (isolate_data->snapshot_data() != nullptr && + !isolate_data->snapshot_data()->errors().empty()) return; + // We'll be creating new objects so make sure we've entered the context. HandleScope handle_scope(isolate()); Context::Scope context_scope(context); @@ -382,8 +584,26 @@ Environment::Environment(IsolateData* isolate_data, }, this); - performance_state_ = - std::make_unique(isolate()); + if (isolate_data->snapshot_data() != nullptr) { + stream_base_state_ = AliasedInt32Array( + context, isolate_data->snapshot_data()); + should_abort_on_uncaught_toggle_ = AliasedUint32Array( + context, isolate_data->snapshot_data()); + performance_state_ = std::make_unique( + context, isolate_data->snapshot_data()); + } else { + stream_base_state_ = AliasedInt32Array( + isolate(), StreamBase::kNumStreamBaseStateFields); + should_abort_on_uncaught_toggle_ = AliasedUint32Array(isolate(), 1); + // By default, always abort when --abort-on-uncaught-exception was passed. + should_abort_on_uncaught_toggle_[0] = 1; + performance_state_ = std::make_unique( + isolate()); + } + + if (isolate_data->snapshot_data() != nullptr && + !isolate_data->snapshot_data()->errors().empty()) return; + performance_state_->Mark( performance::NODE_PERFORMANCE_MILESTONE_ENVIRONMENT); performance_state_->Mark(performance::NODE_PERFORMANCE_MILESTONE_NODE_START, @@ -408,16 +628,14 @@ Environment::Environment(IsolateData* isolate_data, std::move(traced_value)); } - // By default, always abort when --abort-on-uncaught-exception was passed. - should_abort_on_uncaught_toggle_[0] = 1; - if (options_->no_force_async_hooks_checks) { async_hooks_.no_force_checks(); } - // TODO(joyeecheung): deserialize when the snapshot covers the environment - // properties. - CreateProperties(); + if (isolate_data->snapshot_data() == nullptr) + CreateProperties(); + else + DeserializeProperties(); // This adjusts the return value of base_object_count() so that tests that // check the count do not have to account for internally created BaseObjects. @@ -604,7 +822,8 @@ void Environment::CleanupHandles() { Isolate::DisallowJavascriptExecutionScope disallow_js(isolate(), Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE); - RunAndClearNativeImmediates(true /* skip SetUnrefImmediate()s */); + if (immediate_info()->fields().Length() > 0) // Check whether initialized + RunAndClearNativeImmediates(true /* skip SetUnrefImmediate()s */); for (ReqWrapBase* request : req_wrap_queue_) request->Cancel(); @@ -1005,16 +1224,40 @@ void ImmediateInfo::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("fields", fields_); } +void ImmediateInfo::Serialize(SnapshotCreateData* snapshot_data) const { + snapshot_data->StartWriteEntry("ImmediateInfo"); + fields_.Serialize(snapshot_data); + snapshot_data->EndWriteEntry(); +} + void TickInfo::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("fields", fields_); } +void TickInfo::Serialize(SnapshotCreateData* snapshot_data) const { + snapshot_data->StartWriteEntry("TickInfo"); + fields_.Serialize(snapshot_data); + snapshot_data->EndWriteEntry(); +} + void AsyncHooks::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("async_ids_stack", async_ids_stack_); tracker->TrackField("fields", fields_); tracker->TrackField("async_id_fields", async_id_fields_); } +void AsyncHooks::Serialize(SnapshotCreateData* snapshot_data) const { + snapshot_data->StartWriteEntry("AsyncHooks"); + async_ids_stack_.Serialize(snapshot_data); + fields_.Serialize(snapshot_data); + async_id_fields_.Serialize(snapshot_data); + + snapshot_data->WriteObject( + env()->context(), execution_async_resources_.Get(env()->isolate())); + + snapshot_data->EndWriteEntry(); +} + void AsyncHooks::grow_async_ids_stack() { async_ids_stack_.reserve(async_ids_stack_.Length() * 3); @@ -1195,4 +1438,112 @@ Local BaseObject::WrappedObject() const { return object(); } +Local BaseObject::GetConstructorTemplate(Environment* env) { + Local tmpl = env->base_object_ctor_template(); + if (tmpl.IsEmpty()) { + tmpl = env->NewFunctionTemplate(nullptr); + tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "BaseObject")); + env->set_base_object_ctor_template(tmpl); + } + return tmpl; +} + +StartupData BaseObject::SerializeInternalFields( + Local object, int index, void* data) { + // TODO(addaleax): Not an ideal way to get the Environment. + Environment* env = Environment::GetCurrent(object->CreationContext()); + if (env == nullptr || !GetConstructorTemplate(env)->HasInstance(object)) + return {}; + const BaseObject* self = FromJSObject(object); + if (self == nullptr) return {}; // No BaseObject associated with `object`. + CHECK_EQ(self->object(), object); + return self->SerializeInternalFields( + index, static_cast(data)); +} + +StartupData BaseObject::SerializeInternalFields( + int index, SnapshotCreateData* snapshot_data) const { + if (index == BaseObject::kSlot) { + // The 0th slot points back to the BaseObject* itself. We don't need to + // serialize it, the serialization of the global handle should have already + // occurred, and when deserializing, the BaseObject* slot should be reset + // with the correct value anyway. + return {}; + } + snapshot_data->add_error( + SPrintF("Missing override of SerializeInternalFields() for BaseObject of " + "type %s for internal field %d", MemoryInfoName(), index)); + return {}; +} + +AsyncHooks::AsyncHooks(Environment* env) { + if (env->isolate_data()->snapshot_data() != nullptr) { + SnapshotReadData* snapshot_data = env->isolate_data()->snapshot_data(); + + if (snapshot_data->StartReadEntry("AsyncHooks").IsNothing()) return; + async_ids_stack_ = AliasedFloat64Array(env->context(), snapshot_data); + fields_ = AliasedUint32Array(env->context(), snapshot_data); + async_id_fields_ = AliasedFloat64Array(env->context(), snapshot_data); + + Local execution_async_resources; + if (!snapshot_data->ReadObject(env->context()) + .To(&execution_async_resources)) { + return; + } + execution_async_resources_.Reset(env->isolate(), execution_async_resources); + + snapshot_data->EndReadEntry(); + return; + } + + async_ids_stack_ = AliasedFloat64Array(env->isolate(), 16 * 2); + fields_ = AliasedUint32Array(env->isolate(), kFieldsCount); + async_id_fields_ = AliasedFloat64Array(env->isolate(), kUidFieldsCount); + + clear_async_id_stack(); + + // Always perform async_hooks checks, not just when async_hooks is enabled. + // TODO(AndreasMadsen): Consider removing this for LTS releases. + // See discussion in https://github.com/nodejs/node/pull/15454 + // When removing this, do it by reverting the commit. Otherwise the test + // and flag changes won't be included. + fields_[kCheck] = 1; + + // kDefaultTriggerAsyncId should be -1, this indicates that there is no + // specified default value and it should fallback to the executionAsyncId. + // 0 is not used as the magic value, because that indicates a missing context + // which is different from a default context. + async_id_fields_[AsyncHooks::kDefaultTriggerAsyncId] = -1; + + // kAsyncIdCounter should start at 1 because that'll be the id the execution + // context during bootstrap (code that runs before entering uv_run()). + async_id_fields_[AsyncHooks::kAsyncIdCounter] = 1; +} + +TickInfo::TickInfo(Environment* env) { + if (env->isolate_data()->snapshot_data() != nullptr) { + SnapshotReadData* snapshot_data = env->isolate_data()->snapshot_data(); + + if (snapshot_data->StartReadEntry("TickInfo").IsNothing()) return; + fields_ = AliasedUint8Array(env->context(), snapshot_data); + + snapshot_data->EndReadEntry(); + return; + } + fields_ = AliasedUint8Array(env->isolate(), kFieldsCount); +} + +ImmediateInfo::ImmediateInfo(Environment* env) { + if (env->isolate_data()->snapshot_data() != nullptr) { + SnapshotReadData* snapshot_data = env->isolate_data()->snapshot_data(); + + if (snapshot_data->StartReadEntry("ImmediateInfo").IsNothing()) return; + fields_ = AliasedUint32Array(env->context(), snapshot_data); + + snapshot_data->EndReadEntry(); + return; + } + fields_ = AliasedUint32Array(env->isolate(), kFieldsCount); +} + } // namespace node diff --git a/src/env.h b/src/env.h index 146754d03ab728..10cbcf6257115b 100644 --- a/src/env.h +++ b/src/env.h @@ -392,6 +392,7 @@ constexpr size_t kFsStatsBufferLength = #define ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) \ V(async_wrap_ctor_template, v8::FunctionTemplate) \ V(async_wrap_object_ctor_template, v8::FunctionTemplate) \ + V(base_object_ctor_template, v8::FunctionTemplate) \ V(binding_data_ctor_template, v8::FunctionTemplate) \ V(compiled_fn_entry_template, v8::ObjectTemplate) \ V(dir_instance_template, v8::ObjectTemplate) \ @@ -467,18 +468,19 @@ constexpr size_t kFsStatsBufferLength = V(url_constructor_function, v8::Function) class Environment; +class SnapshotReadData; -class IsolateData : public MemoryRetainer { +class IsolateData : public MemoryRetainer, public Snapshottable { public: IsolateData(v8::Isolate* isolate, uv_loop_t* event_loop, MultiIsolatePlatform* platform = nullptr, ArrayBufferAllocator* node_allocator = nullptr, - const std::vector* indexes = nullptr); + SnapshotReadData* snapshot_data = nullptr); SET_MEMORY_INFO_NAME(IsolateData) SET_SELF_SIZE(IsolateData) void MemoryInfo(MemoryTracker* tracker) const override; - std::vector Serialize(v8::SnapshotCreator* creator); + void Serialize(SnapshotCreateData* snapshot_data) const override; inline uv_loop_t* event_loop() const; inline MultiIsolatePlatform* platform() const; @@ -492,6 +494,8 @@ class IsolateData : public MemoryRetainer { inline worker::Worker* worker_context() const; inline void set_worker_context(worker::Worker* context); + inline SnapshotReadData* snapshot_data() const; + #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) #define VS(PropertyName, StringValue) V(v8::String, PropertyName) @@ -515,7 +519,7 @@ class IsolateData : public MemoryRetainer { IsolateData& operator=(IsolateData&&) = delete; private: - void DeserializeProperties(const std::vector* indexes); + void DeserializeProperties(); void CreateProperties(); #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) @@ -542,6 +546,7 @@ class IsolateData : public MemoryRetainer { MultiIsolatePlatform* platform_; std::shared_ptr options_; worker::Worker* worker_context_ = nullptr; + SnapshotReadData* const snapshot_data_; }; struct ContextInfo { @@ -617,7 +622,7 @@ namespace per_process { extern std::shared_ptr system_environment; } -class AsyncHooks : public MemoryRetainer { +class AsyncHooks : public MemoryRetainer, public Snapshottable { public: SET_MEMORY_INFO_NAME(AsyncHooks) SET_SELF_SIZE(AsyncHooks) @@ -654,6 +659,7 @@ class AsyncHooks : public MemoryRetainer { inline void no_force_checks(); inline Environment* env(); + inline const Environment* env() const; inline void push_async_context(double async_id, double trigger_async_id, v8::Local execution_async_resource_); @@ -688,9 +694,11 @@ class AsyncHooks : public MemoryRetainer { double old_default_trigger_async_id_; }; + void Serialize(SnapshotCreateData* snapshot_data) const override; + + explicit AsyncHooks(Environment* env); + private: - friend class Environment; // So we can call the constructor. - inline AsyncHooks(); // Stores the ids of the current execution context stack. AliasedFloat64Array async_ids_stack_; // Attached to a Uint32Array that tracks the number of active hooks for @@ -704,7 +712,7 @@ class AsyncHooks : public MemoryRetainer { v8::Global execution_async_resources_; }; -class ImmediateInfo : public MemoryRetainer { +class ImmediateInfo : public MemoryRetainer, public Snapshottable { public: inline AliasedUint32Array& fields(); inline uint32_t count() const; @@ -723,16 +731,17 @@ class ImmediateInfo : public MemoryRetainer { SET_SELF_SIZE(ImmediateInfo) void MemoryInfo(MemoryTracker* tracker) const override; - private: - friend class Environment; // So we can call the constructor. - inline explicit ImmediateInfo(v8::Isolate* isolate); + void Serialize(SnapshotCreateData* snapshot_data) const override; + explicit ImmediateInfo(Environment* env); + + private: enum Fields { kCount, kRefCount, kHasOutstanding, kFieldsCount }; AliasedUint32Array fields_; }; -class TickInfo : public MemoryRetainer { +class TickInfo : public MemoryRetainer, public Snapshottable { public: inline AliasedUint8Array& fields(); inline bool has_tick_scheduled() const; @@ -748,10 +757,11 @@ class TickInfo : public MemoryRetainer { TickInfo& operator=(TickInfo&&) = delete; ~TickInfo() = default; - private: - friend class Environment; // So we can call the constructor. - inline explicit TickInfo(v8::Isolate* isolate); + void Serialize(SnapshotCreateData* snapshot_data) const override; + + explicit TickInfo(Environment* env); + private: enum Fields { kHasTickScheduled = 0, kHasRejectionToWarn, kFieldsCount }; AliasedUint8Array fields_; @@ -822,7 +832,7 @@ class CleanupHookCallback { uint64_t insertion_order_counter_; }; -class Environment : public MemoryRetainer { +class Environment : public MemoryRetainer, public Snapshottable { public: Environment(const Environment&) = delete; Environment& operator=(const Environment&) = delete; @@ -835,7 +845,8 @@ class Environment : public MemoryRetainer { bool IsRootNode() const override { return true; } void MemoryInfo(MemoryTracker* tracker) const override; - void CreateProperties(); + void Serialize(SnapshotCreateData* snapshot_data) const override; + // Should be called before InitializeInspector() void InitializeDiagnostics(); #if HAVE_INSPECTOR @@ -943,8 +954,11 @@ class Environment : public MemoryRetainer { inline void IncreaseWaitingRequestCounter(); inline void DecreaseWaitingRequestCounter(); + inline const AsyncHooks* async_hooks() const; inline AsyncHooks* async_hooks(); + inline const ImmediateInfo* immediate_info() const; inline ImmediateInfo* immediate_info(); + inline const TickInfo* tick_info() const; inline TickInfo* tick_info(); inline uint64_t timer_base() const; inline std::shared_ptr env_vars(); @@ -989,6 +1003,7 @@ class Environment : public MemoryRetainer { std::set native_modules_with_cache; std::set native_modules_without_cache; + std::set native_modules_in_snapshot; std::unordered_multimap hash_to_module_map; std::unordered_map id_to_module_map; @@ -1003,6 +1018,7 @@ class Environment : public MemoryRetainer { EnabledDebugList* enabled_debug_list() { return &enabled_debug_list_; } inline performance::PerformanceState* performance_state(); + inline const performance::PerformanceState* performance_state() const; inline std::unordered_map* performance_marks(); void CollectUVExceptionInfo(v8::Local context, @@ -1250,6 +1266,9 @@ class Environment : public MemoryRetainer { void RunAndClearInterrupts(); private: + void CreateProperties(); + void DeserializeProperties(); + template inline void CreateImmediate(Fn&& cb, bool ref); @@ -1259,6 +1278,7 @@ class Environment : public MemoryRetainer { std::list loaded_addons_; v8::Isolate* const isolate_; IsolateData* const isolate_data_; + v8::Global context_; uv_timer_t timer_handle_; uv_check_t immediate_check_handle_; uv_idle_t immediate_idle_handle_; @@ -1442,15 +1462,13 @@ class Environment : public MemoryRetainer { DefaultProcessExitHandler }; template - void ForEachBaseObject(T&& iterator); + void ForEachBaseObject(T&& iterator) const; #define V(PropertyName, TypeName) v8::Global PropertyName ## _; ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) #undef V - v8::Global context_; - // Keeps the main script source alive is one was passed to LoadEnvironment(). // We should probably find a way to just use plain `v8::String`s created from // the source passed to LoadEnvironment() directly instead. diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 5c9ad5e946424b..a8b95fc40d3a9d 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -2,6 +2,7 @@ #include "inspector_agent.h" #include "inspector_io.h" #include "memory_tracker-inl.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" #include "v8-inspector.h" @@ -345,6 +346,31 @@ void Initialize(Local target, Local unused, JSBindingsConnection::Bind(env, target); } +static ExternalReferences external_references { + __FILE__, + InspectorConsoleCall, + SetConsoleExtensionInstaller, + CallAndPauseOnStart, + Open, + Url, + WaitForDebugger, + AsyncTaskScheduledWrapper, + InvokeAsyncTaskFnWithId<&Agent::AsyncTaskCanceled>, + InvokeAsyncTaskFnWithId<&Agent::AsyncTaskStarted>, + InvokeAsyncTaskFnWithId<&Agent::AsyncTaskFinished>, + RegisterAsyncHookWrapper, + IsEnabled, + JSBindingsConnection::New, + JSBindingsConnection::Dispatch, + // Disconnect is overloaded, pick the right one + static_cast& args)>( + JSBindingsConnection::Disconnect), + JSBindingsConnection::New, + JSBindingsConnection::Dispatch, + static_cast& args)>( + JSBindingsConnection::Disconnect), +}; + } // namespace } // namespace inspector } // namespace node diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 13dd331e6b23e1..d32dda14dcd4a1 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -691,6 +691,7 @@ void ModuleWrap::Initialize(Local target, tpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap")); tpl->InstanceTemplate()->SetInternalFieldCount( ModuleWrap::kInternalFieldCount); + tpl->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(tpl, "link", Link); env->SetProtoMethod(tpl, "instantiate", Instantiate); diff --git a/src/node.cc b/src/node.cc index 240f1f2e3b00f3..cc1b945a7650ca 100644 --- a/src/node.cc +++ b/src/node.cc @@ -39,6 +39,7 @@ #include "node_revert.h" #include "node_v8_platform-inl.h" #include "node_version.h" +#include "snapshot_support-inl.h" #if HAVE_OPENSSL #include "node_crypto.h" @@ -116,6 +117,7 @@ #include #include +#include namespace node { @@ -982,6 +984,18 @@ InitializationResult InitializeOncePerProcess(int argc, char** argv) { UNREACHABLE(); } + if (per_process::cli_options->dump_snapshot) { + SnapshotReadData* snapshot_data = NodeMainInstance::GetSnapshotData(); + if (snapshot_data == nullptr) { + fprintf(stderr, "No snapshot data provided\n"); + } else { + snapshot_data->DumpToStderr(); + } + result.exit_code = 0; + result.early_return = true; + return result; + } + #if HAVE_OPENSSL { std::string extra_ca_certs; @@ -1027,20 +1041,19 @@ int Start(int argc, char** argv) { { Isolate::CreateParams params; - const std::vector* indexes = nullptr; - std::vector external_references; + std::vector external_references = ExternalReferences::get_list(); + external_references.push_back(ExternalReferences::kEnd); + + SnapshotReadData* snapshot_data = nullptr; bool force_no_snapshot = per_process::cli_options->per_isolate->no_node_snapshot; if (!force_no_snapshot) { v8::StartupData* blob = NodeMainInstance::GetEmbeddedSnapshotBlob(); if (blob != nullptr) { - // TODO(joyeecheung): collect external references and set it in - // params.external_references. - external_references.push_back(reinterpret_cast(nullptr)); params.external_references = external_references.data(); params.snapshot_blob = blob; - indexes = NodeMainInstance::GetIsolateDataIndexes(); + snapshot_data = NodeMainInstance::GetSnapshotData(); } } @@ -1049,7 +1062,7 @@ int Start(int argc, char** argv) { per_process::v8_platform.Platform(), result.args, result.exec_args, - indexes); + snapshot_data); result.exit_code = main_instance.Run(); } @@ -1062,6 +1075,14 @@ int Stop(Environment* env) { return 0; } +static ExternalReferences external_references { + __FILE__, + binding::GetLinkedBinding, + binding::GetInternalBinding, + MarkBootstrapComplete, +}; + + } // namespace node #if !HAVE_INSPECTOR diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 1ff60ad721753e..4d0d2e79962452 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -28,6 +28,7 @@ #include "string_bytes.h" #include "string_search.h" #include "util-inl.h" +#include "snapshot_support-inl.h" #include "v8.h" #include @@ -1151,6 +1152,25 @@ void SetBufferPrototype(const FunctionCallbackInfo& args) { } +void GetZeroFillField(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + // This can be a nullptr when running inside an isolate where we + // do not own the ArrayBuffer allocator. + NodeArrayBufferAllocator* allocator = env->isolate_data()->node_allocator(); + if (allocator == nullptr) return; + + std::shared_ptr backing = allocator->zero_fill_field(); + Local array_buffer = + ArrayBuffer::New(env->isolate(), std::move(backing)); + array_buffer->SetPrivate( + env->context(), + env->arraybuffer_untransferable_private_symbol(), + True(env->isolate())).Check(); + args.GetReturnValue().Set(Uint32Array::New(array_buffer, 0, 1)); +} + + void Initialize(Local target, Local unused, Local context, @@ -1158,6 +1178,7 @@ void Initialize(Local target, Environment* env = Environment::GetCurrent(context); env->SetMethod(target, "setBufferPrototype", SetBufferPrototype); + env->SetMethod(target, "getZeroFillField", GetZeroFillField); env->SetMethodNoSideEffect(target, "createFromString", CreateFromString); env->SetMethodNoSideEffect(target, "byteLengthUtf8", ByteLengthUtf8); @@ -1197,31 +1218,40 @@ void Initialize(Local target, env->SetMethod(target, "hexWrite", StringWrite); env->SetMethod(target, "ucs2Write", StringWrite); env->SetMethod(target, "utf8Write", StringWrite); - - // It can be a nullptr when running inside an isolate where we - // do not own the ArrayBuffer allocator. - if (NodeArrayBufferAllocator* allocator = - env->isolate_data()->node_allocator()) { - uint32_t* zero_fill_field = allocator->zero_fill_field(); - std::unique_ptr backing = - ArrayBuffer::NewBackingStore(zero_fill_field, - sizeof(*zero_fill_field), - [](void*, size_t, void*){}, - nullptr); - Local array_buffer = - ArrayBuffer::New(env->isolate(), std::move(backing)); - array_buffer->SetPrivate( - env->context(), - env->arraybuffer_untransferable_private_symbol(), - True(env->isolate())).Check(); - CHECK(target - ->Set(env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "zeroFill"), - Uint32Array::New(array_buffer, 0, 1)) - .FromJust()); - } } +static ExternalReferences external_references { + __FILE__, + SetBufferPrototype, + GetZeroFillField, + CreateFromString, + ByteLengthUtf8, + Copy, + Compare, + CompareOffset, + Fill, + IndexOfBuffer, + IndexOfNumber, + IndexOfString, + Swap16, + Swap32, + Swap64, + EncodeInto, + EncodeUtf8String, + StringSlice, + StringSlice, + StringSlice, + StringSlice, + StringSlice, + StringSlice, + StringWrite, + StringWrite, + StringWrite, + StringWrite, + StringWrite, + StringWrite, +}; + } // anonymous namespace } // namespace Buffer } // namespace node diff --git a/src/node_config.cc b/src/node_config.cc index 6ee3164a134fe8..129f3c4f0a015d 100644 --- a/src/node_config.cc +++ b/src/node_config.cc @@ -4,17 +4,23 @@ #include "node_i18n.h" #include "node_native_module_env.h" #include "node_options.h" +#include "snapshot_support-inl.h" #include "util-inl.h" namespace node { using v8::Context; +using v8::FunctionCallbackInfo; using v8::Isolate; using v8::Local; using v8::Number; using v8::Object; using v8::Value; +static void HasCachedBuiltins(const FunctionCallbackInfo& args) { + args.GetReturnValue().Set(native_module::has_code_cache); +} + // The config binding is used to provide an internal view of compile time // config options that are required internally by lib/*.js code. This is an // alternative to dropping additional properties onto the process object as @@ -85,10 +91,16 @@ static void Initialize(Local target, READONLY_TRUE_PROPERTY(target, "hasDtrace"); #endif - READONLY_PROPERTY(target, "hasCachedBuiltins", - v8::Boolean::New(isolate, native_module::has_code_cache)); + // Make this a method instead of a constant in order for snapshotted builts + // to correctly report this. + env->SetMethodNoSideEffect(target, "hasCachedBuiltins", HasCachedBuiltins); } // InitConfig +static ExternalReferences external_references { + __FILE__, + HasCachedBuiltins +}; + } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(config, node::Initialize) diff --git a/src/node_credentials.cc b/src/node_credentials.cc index ad0e1dbb9bb68e..8bbdc0db1b1fdb 100644 --- a/src/node_credentials.cc +++ b/src/node_credentials.cc @@ -1,5 +1,6 @@ #include "env-inl.h" #include "node_internals.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS @@ -401,6 +402,25 @@ static void Initialize(Local target, #endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS } +static ExternalReferences external_references { + __FILE__, + // SafeGetenv is overloaded, pick the right one + static_cast& args)>(SafeGetenv), +#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS + GetUid, + GetEUid, + GetGid, + GetEGid, + GetGroups, + InitGroups, + SetEGid, + SetEUid, + SetGid, + SetUid, + SetGroups, +#endif +}; + } // namespace credentials } // namespace node diff --git a/src/node_crypto.cc b/src/node_crypto.cc index d53a6d2f2325fe..f08c89b427b307 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -454,6 +454,7 @@ void SecureContext::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); t->InstanceTemplate()->SetInternalFieldCount( SecureContext::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); Local secureContextString = FIXED_ONE_BYTE_STRING(env->isolate(), "SecureContext"); t->SetClassName(secureContextString); @@ -3242,6 +3243,7 @@ Local KeyObject::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); t->InstanceTemplate()->SetInternalFieldCount( KeyObject::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "init", Init); env->SetProtoMethodNoSideEffect(t, "getSymmetricKeySize", @@ -3476,6 +3478,7 @@ void CipherBase::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( CipherBase::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "init", Init); env->SetProtoMethod(t, "initiv", InitIv); @@ -4090,6 +4093,7 @@ void Hmac::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( Hmac::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "init", HmacInit); env->SetProtoMethod(t, "update", HmacUpdate); @@ -4202,6 +4206,7 @@ void Hash::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( Hash::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "update", HashUpdate); env->SetProtoMethod(t, "digest", HashDigest); @@ -4458,6 +4463,7 @@ void Sign::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( SignBase::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "init", SignInit); env->SetProtoMethod(t, "update", SignUpdate); @@ -4780,6 +4786,7 @@ void Verify::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( SignBase::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "init", VerifyInit); env->SetProtoMethod(t, "update", VerifyUpdate); @@ -5090,6 +5097,7 @@ void DiffieHellman::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( DiffieHellman::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "generateKeys", GenerateKeys); env->SetProtoMethod(t, "computeSecret", ComputeSecret); @@ -5448,6 +5456,7 @@ void ECDH::Initialize(Environment* env, Local target) { HandleScope scope(env->isolate()); Local t = env->NewFunctionTemplate(New); + t->Inherit(BaseObject::GetConstructorTemplate(env)); t->InstanceTemplate()->SetInternalFieldCount(ECDH::kInternalFieldCount); diff --git a/src/node_env_var.cc b/src/node_env_var.cc index 23eaad48586130..7922b07b5795b4 100644 --- a/src/node_env_var.cc +++ b/src/node_env_var.cc @@ -2,6 +2,7 @@ #include "env-inl.h" #include "node_errors.h" #include "node_process.h" +#include "snapshot_support-inl.h" #include // tzset(), _tzset() @@ -385,4 +386,15 @@ MaybeLocal CreateEnvVarProxy(Local context, Isolate* isolate) { PropertyHandlerFlags::kHasNoSideEffect)); return scope.EscapeMaybe(env_proxy_template->NewInstance(context)); } + + +static ExternalReferences external_references { + __FILE__, + EnvGetter, + EnvSetter, + EnvQuery, + EnvDeleter, + EnvEnumerator +}; + } // namespace node diff --git a/src/node_errors.cc b/src/node_errors.cc index 4e13c24e15e1d0..c010e0888fe956 100644 --- a/src/node_errors.cc +++ b/src/node_errors.cc @@ -7,6 +7,7 @@ #include "node_report.h" #include "node_process.h" #include "node_v8_platform-inl.h" +#include "snapshot_support-inl.h" #include "util-inl.h" namespace node { @@ -848,6 +849,16 @@ void Initialize(Local target, env->SetMethod(target, "triggerUncaughtException", TriggerUncaughtException); } +static ExternalReferences external_references { + __FILE__, + SetPrepareStackTraceCallback, + SetEnhanceStackForFatalException, + NoSideEffectsToString, + // TriggerUncaughtException is overloaded, pick the right one + static_cast& args)>( + TriggerUncaughtException), +}; + void DecorateErrorStack(Environment* env, const errors::TryCatchScope& try_catch) { Local exception = try_catch.Exception(); diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 169374aa5de441..4acc7f46053755 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -49,6 +49,7 @@ #include "node_buffer.h" #include "node_errors.h" #include "node_internals.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" @@ -811,6 +812,7 @@ void Initialize(Local target, // ConverterObject { Local t = FunctionTemplate::New(env->isolate()); + t->Inherit(BaseObject::GetConstructorTemplate(env)); t->InstanceTemplate()->SetInternalFieldCount( ConverterObject::kInternalFieldCount); Local converter_string = @@ -824,6 +826,20 @@ void Initialize(Local target, env->SetMethod(target, "hasConverter", ConverterObject::Has); } +static ExternalReferences external_references { + __FILE__, + // Pick the right variant for overloaded names. + static_cast& args)>(ToUnicode), + static_cast& args)>(ToASCII), + GetStringWidth, + ICUErrorName, + static_cast& args)>(Transcode), + ConverterObject::Create, + ConverterObject::Decode, + ConverterObject::Has, +}; + + } // namespace i18n } // namespace node diff --git a/src/node_internals.h b/src/node_internals.h index fa3ba022fe7e23..c962a33f5f8610 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -107,7 +107,7 @@ void PromiseRejectCallback(v8::PromiseRejectMessage message); class NodeArrayBufferAllocator : public ArrayBufferAllocator { public: - inline uint32_t* zero_fill_field() { return &zero_fill_field_; } + std::shared_ptr zero_fill_field(); void* Allocate(size_t size) override; // Defined in src/node.cc void* AllocateUninitialized(size_t size) override; @@ -127,6 +127,7 @@ class NodeArrayBufferAllocator : public ArrayBufferAllocator { private: uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land. + std::shared_ptr zero_fill_field_bs_; std::atomic total_mem_usage_ {0}; }; diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc index 033ab56188bfee..6e46d8a70a9d63 100644 --- a/src/node_main_instance.cc +++ b/src/node_main_instance.cc @@ -4,6 +4,7 @@ #include "node_internals.h" #include "node_options-inl.h" #include "node_v8_platform-inl.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #if defined(LEAK_SANITIZER) #include @@ -23,11 +24,12 @@ using v8::Locker; using v8::Object; using v8::SealHandleScope; -NodeMainInstance::NodeMainInstance(Isolate* isolate, - uv_loop_t* event_loop, - MultiIsolatePlatform* platform, - const std::vector& args, - const std::vector& exec_args) +NodeMainInstance::NodeMainInstance( + Isolate* isolate, + uv_loop_t* event_loop, + MultiIsolatePlatform* platform, + const std::vector& args, + const std::vector& exec_args) : args_(args), exec_args_(exec_args), array_buffer_allocator_(nullptr), @@ -36,8 +38,9 @@ NodeMainInstance::NodeMainInstance(Isolate* isolate, isolate_data_(nullptr), owns_isolate_(false), deserialize_mode_(false) { - isolate_data_ = - std::make_unique(isolate_, event_loop, platform, nullptr); + // TODO(addaleax): Use CreateIsolateData. + isolate_data_ = std::make_unique( + isolate_, event_loop, platform, nullptr, nullptr); IsolateSettings misc; SetIsolateMiscHandlers(isolate_, misc); @@ -49,8 +52,8 @@ std::unique_ptr NodeMainInstance::Create( MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args) { - return std::unique_ptr( - new NodeMainInstance(isolate, event_loop, platform, args, exec_args)); + return std::unique_ptr(new NodeMainInstance( + isolate, event_loop, platform, args, exec_args)); } NodeMainInstance::NodeMainInstance( @@ -59,7 +62,7 @@ NodeMainInstance::NodeMainInstance( MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args, - const std::vector* per_isolate_data_indexes) + SnapshotReadData* snapshot_data) : args_(args), exec_args_(exec_args), array_buffer_allocator_(ArrayBufferAllocator::Create()), @@ -70,20 +73,23 @@ NodeMainInstance::NodeMainInstance( params->array_buffer_allocator = array_buffer_allocator_.get(); isolate_ = Isolate::Allocate(); CHECK_NOT_NULL(isolate_); + if (snapshot_data != nullptr) + snapshot_data->set_isolate(isolate_); // Register the isolate on the platform before the isolate gets initialized, // so that the isolate can access the platform during initialization. platform->RegisterIsolate(isolate_, event_loop); SetIsolateCreateParamsForNode(params); Isolate::Initialize(isolate_, *params); - deserialize_mode_ = per_isolate_data_indexes != nullptr; + deserialize_mode_ = snapshot_data != nullptr; // If the indexes are not nullptr, we are not deserializing CHECK_IMPLIES(deserialize_mode_, params->external_references != nullptr); + // TODO(addaleax): Use CreateIsolateData. isolate_data_ = std::make_unique(isolate_, event_loop, platform, array_buffer_allocator_.get(), - per_isolate_data_indexes); + snapshot_data); IsolateSettings s; SetIsolateMiscHandlers(isolate_, s); if (!deserialize_mode_) { @@ -207,6 +213,15 @@ NodeMainInstance::CreateMainEnvironment(int* exit_code) { exec_args_, EnvironmentFlags::kDefaultFlags) }; + if (deserialize_mode_) { + SnapshotReadData* snapshot_data = isolate_data_->snapshot_data(); + CHECK_NOT_NULL(snapshot_data); + if (snapshot_data->errors().empty()) + snapshot_data->Finish(); + + snapshot_data->PrintErrorsAndAbortIfAny(); + } + if (*exit_code != 0) { return env; } diff --git a/src/node_main_instance.h b/src/node_main_instance.h index b8178c2774e795..ecbb5092bc7900 100644 --- a/src/node_main_instance.h +++ b/src/node_main_instance.h @@ -7,12 +7,15 @@ #include #include "node.h" +#include "snapshot_support.h" #include "util.h" #include "uv.h" #include "v8.h" namespace node { +class SnapshotReadData; + // TODO(joyeecheung): align this with the Worker/WorkerThreadData class. // We may be able to create an abstract class to reuse some of the routines. class NodeMainInstance { @@ -51,7 +54,7 @@ class NodeMainInstance { MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args, - const std::vector* per_isolate_data_indexes = nullptr); + SnapshotReadData* snapshot_data = nullptr); ~NodeMainInstance(); // Start running the Node.js instances, return the exit code when finished. @@ -64,7 +67,7 @@ class NodeMainInstance { // If nullptr is returned, the binary is not built with embedded // snapshot. - static const std::vector* GetIsolateDataIndexes(); + static SnapshotReadData* GetSnapshotData(); static v8::StartupData* GetEmbeddedSnapshotBlob(); static const size_t kNodeContextIndex = 0; diff --git a/src/node_native_module_env.cc b/src/node_native_module_env.cc index be647b01c640b0..402be4ab51d566 100644 --- a/src/node_native_module_env.cc +++ b/src/node_native_module_env.cc @@ -1,4 +1,5 @@ #include "node_native_module_env.h" +#include "snapshot_support-inl.h" #include "env-inl.h" namespace node { @@ -95,6 +96,11 @@ void NativeModuleEnv::GetCacheUsage(const FunctionCallbackInfo& args) { OneByteString(isolate, "compiledWithoutCache"), ToJsSet(context, env->native_modules_without_cache)) .FromJust(); + result + ->Set(env->context(), + OneByteString(isolate, "includedInSnapshot"), + ToJsSet(context, env->native_modules_in_snapshot)) + .FromJust(); args.GetReturnValue().Set(result); } @@ -202,6 +208,15 @@ void NativeModuleEnv::Initialize(Local target, target->SetIntegrityLevel(context, IntegrityLevel::kFrozen).FromJust(); } +static ExternalReferences external_references { + __FILE__, + NativeModuleEnv::ConfigStringGetter, + NativeModuleEnv::ModuleIdsGetter, + NativeModuleEnv::GetModuleCategories, + NativeModuleEnv::GetCacheUsage, + NativeModuleEnv::CompileFunction, +}; + } // namespace native_module } // namespace node diff --git a/src/node_native_module_env.h b/src/node_native_module_env.h index bc36be75109639..b9e8132adee0eb 100644 --- a/src/node_native_module_env.h +++ b/src/node_native_module_env.h @@ -37,7 +37,6 @@ class NativeModuleEnv { // in node_code_cache_stub.cc static void InitializeCodeCache(); - private: static void RecordResult(const char* id, NativeModuleLoader::Result result, Environment* env); diff --git a/src/node_options.cc b/src/node_options.cc index 3b9142c19e98a8..42f4e05094d315 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -649,6 +649,9 @@ PerProcessOptionsParser::PerProcessOptionsParser( AddOption("--v8-options", "print V8 command line options", &PerProcessOptions::print_v8_help); + AddOption("--dump-snapshot", + "", // No help because this is for Node.js core developers. + &PerProcessOptions::dump_snapshot); AddOption("--report-compact", "output compact single-line JSON", &PerProcessOptions::report_compact, diff --git a/src/node_options.h b/src/node_options.h index 539e41e67ac6ee..46316bec5bde27 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -216,6 +216,7 @@ class PerProcessOptions : public Options { bool print_help = false; bool print_v8_help = false; bool print_version = false; + bool dump_snapshot = false; #ifdef NODE_HAVE_I18N_SUPPORT std::string icu_data_dir; diff --git a/src/node_perf.cc b/src/node_perf.cc index 4b8bf2a8a7c913..13def082e251e6 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -4,6 +4,7 @@ #include "node_perf.h" #include "node_buffer.h" #include "node_process.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include @@ -53,6 +54,41 @@ void PerformanceState::Mark(enum PerformanceMilestone milestone, TRACE_EVENT_SCOPE_THREAD, ts / 1000); } +PerformanceState::PerformanceState(Isolate* isolate) + : root( + isolate, + sizeof(performance_state_internal)), + milestones( + isolate, + offsetof(performance_state_internal, milestones), + NODE_PERFORMANCE_MILESTONE_INVALID, + root), + observers( + isolate, + offsetof(performance_state_internal, observers), + NODE_PERFORMANCE_ENTRY_TYPE_INVALID, + root) { + for (size_t i = 0; i < milestones.Length(); i++) + milestones[i] = -1.; +} + +PerformanceState::PerformanceState(Local context, + SnapshotReadData* snapshot_data) { + if (snapshot_data->StartReadEntry("PerformanceState").IsNothing()) return; + root = AliasedUint8Array(context, snapshot_data); + milestones = AliasedFloat64Array(context, snapshot_data); + observers = AliasedUint32Array(context, snapshot_data); + snapshot_data->EndReadEntry(); +} + +void PerformanceState::Serialize(SnapshotCreateData* snapshot_data) const { + snapshot_data->StartWriteEntry("PerformanceState"); + root.Serialize(snapshot_data); + milestones.Serialize(snapshot_data); + observers.Serialize(snapshot_data); + snapshot_data->EndWriteEntry(); +} + // Initialize the performance entry object properties inline void InitObject(const PerformanceEntry& entry, Local obj) { Environment* env = entry.env(); @@ -647,6 +683,7 @@ void Initialize(Local target, eldh->SetClassName(eldh_classname); eldh->InstanceTemplate()->SetInternalFieldCount( ELDHistogram::kInternalFieldCount); + eldh->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(eldh, "exceeds", ELDHistogramExceeds); env->SetProtoMethod(eldh, "min", ELDHistogramMin); env->SetProtoMethod(eldh, "max", ELDHistogramMax); diff --git a/src/node_perf_common.h b/src/node_perf_common.h index 75d266afc257e9..fad3f8b9f71566 100644 --- a/src/node_perf_common.h +++ b/src/node_perf_common.h @@ -52,25 +52,11 @@ enum PerformanceEntryType { NODE_PERFORMANCE_ENTRY_TYPE_INVALID }; -class PerformanceState { +class PerformanceState final : public Snapshottable { public: - explicit PerformanceState(v8::Isolate* isolate) : - root( - isolate, - sizeof(performance_state_internal)), - milestones( - isolate, - offsetof(performance_state_internal, milestones), - NODE_PERFORMANCE_MILESTONE_INVALID, - root), - observers( - isolate, - offsetof(performance_state_internal, observers), - NODE_PERFORMANCE_ENTRY_TYPE_INVALID, - root) { - for (size_t i = 0; i < milestones.Length(); i++) - milestones[i] = -1.; - } + explicit PerformanceState(v8::Isolate* isolate); + PerformanceState(v8::Local context, + SnapshotReadData* snapshot_data); AliasedUint8Array root; AliasedFloat64Array milestones; @@ -81,6 +67,8 @@ class PerformanceState { void Mark(enum PerformanceMilestone milestone, uint64_t ts = PERFORMANCE_NOW()); + void Serialize(SnapshotCreateData* snapshot_data) const override; + private: struct performance_state_internal { // doubles first so that they are always sizeof(double)-aligned diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index 88f4c1cfbd0249..ac483029dc0df1 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -5,6 +5,7 @@ #include "node_errors.h" #include "node_internals.h" #include "node_process.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "uv.h" #include "v8.h" @@ -492,6 +493,33 @@ static void InitializeProcessMethods(Local target, env->SetMethod(target, "patchProcessObject", PatchProcessObject); } +static ExternalReferences external_references { + __FILE__, + DebugProcess, + DebugEnd, + // Abort is overloaded, pick the right one + static_cast& args)>(Abort), + CauseSegfault, + Chdir, + StartProfilerIdleNotifier, + StopProfilerIdleNotifier, + Umask, + RawDebug, + MemoryUsage, + CPUUsage, + Hrtime, + HrtimeBigInt, + ResourceUsage, + GetActiveHandles, + GetActiveRequests, + Kill, + Cwd, + binding::DLOpen, + ReallyExit, + Uptime, + PatchProcessObject, +}; + } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(process_methods, diff --git a/src/node_serdes.cc b/src/node_serdes.cc index 86fb822dd5bfa9..5a47be10a42e83 100644 --- a/src/node_serdes.cc +++ b/src/node_serdes.cc @@ -452,6 +452,7 @@ void Initialize(Local target, ser->InstanceTemplate()->SetInternalFieldCount( SerializerContext::kInternalFieldCount); + ser->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(ser, "writeHeader", SerializerContext::WriteHeader); env->SetProtoMethod(ser, "writeValue", SerializerContext::WriteValue); @@ -479,6 +480,7 @@ void Initialize(Local target, des->InstanceTemplate()->SetInternalFieldCount( DeserializerContext::kInternalFieldCount); + des->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(des, "readHeader", DeserializerContext::ReadHeader); env->SetProtoMethod(des, "readValue", DeserializerContext::ReadValue); diff --git a/src/node_snapshot_stub.cc b/src/node_snapshot_stub.cc index fac03b0c87af5d..0ae3b041b2e379 100644 --- a/src/node_snapshot_stub.cc +++ b/src/node_snapshot_stub.cc @@ -10,7 +10,7 @@ v8::StartupData* NodeMainInstance::GetEmbeddedSnapshotBlob() { return nullptr; } -const std::vector* NodeMainInstance::GetIsolateDataIndexes() { +SnapshotReadData* NodeMainInstance::GetSnapshotData() { return nullptr; } diff --git a/src/node_task_queue.cc b/src/node_task_queue.cc index 3c7d9bae0884c5..1b9144bfeb523a 100644 --- a/src/node_task_queue.cc +++ b/src/node_task_queue.cc @@ -3,6 +3,7 @@ #include "node_errors.h" #include "node_internals.h" #include "node_process.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" @@ -145,6 +146,14 @@ static void Initialize(Local target, SetPromiseRejectCallback); } +static ExternalReferences external_references { + __FILE__, + EnqueueMicrotask, + SetTickCallback, + RunMicrotasks, + SetPromiseRejectCallback, +}; + } // namespace task_queue } // namespace node diff --git a/src/node_trace_events.cc b/src/node_trace_events.cc index 9adee9e458ccc0..7fae020bbf0240 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -5,6 +5,7 @@ #include "node_internals.h" #include "node_v8_platform-inl.h" #include "tracing/agent.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include @@ -131,6 +132,7 @@ void NodeCategorySet::Initialize(Local target, env->NewFunctionTemplate(NodeCategorySet::New); category_set->InstanceTemplate()->SetInternalFieldCount( NodeCategorySet::kInternalFieldCount); + category_set->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(category_set, "enable", NodeCategorySet::Enable); env->SetProtoMethod(category_set, "disable", NodeCategorySet::Disable); @@ -153,6 +155,15 @@ void NodeCategorySet::Initialize(Local target, binding->Get(context, trace).ToLocalChecked()).Check(); } +static ExternalReferences external_references { + __FILE__, + GetEnabledCategories, + SetTraceCategoryStateUpdateHandler, + NodeCategorySet::New, + NodeCategorySet::Enable, + NodeCategorySet::Disable, +}; + } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(trace_events, diff --git a/src/node_types.cc b/src/node_types.cc index 9643a66668144e..c906aa5dfc8871 100644 --- a/src/node_types.cc +++ b/src/node_types.cc @@ -1,5 +1,6 @@ #include "env-inl.h" #include "node.h" +#include "snapshot_support-inl.h" using v8::Context; using v8::FunctionCallbackInfo; @@ -76,6 +77,15 @@ void InitializeTypes(Local target, env->SetMethodNoSideEffect(target, "isBoxedPrimitive", IsBoxedPrimitive); } +static ExternalReferences external_references { + __FILE__, +#define V(type) Is##type, + VALUE_METHOD_MAP(V) +#undef V + IsAnyArrayBuffer, + IsBoxedPrimitive, +}; + } // anonymous namespace } // namespace node diff --git a/src/node_url.cc b/src/node_url.cc index 27e89e8d9b7652..83b97acf3b9285 100644 --- a/src/node_url.cc +++ b/src/node_url.cc @@ -2,6 +2,7 @@ #include "base_object-inl.h" #include "node_errors.h" #include "node_i18n.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include @@ -2331,6 +2332,17 @@ void Initialize(Local target, PARSESTATES(XX) #undef XX } + +static ExternalReferences external_references { + __FILE__, + // Parse is overloaded, pick the right one + static_cast& args)>(Parse), + EncodeAuthSet, + ToUSVString, + DomainToASCII, + DomainToUnicode, + SetURLConstructor, +}; } // namespace std::string URL::ToFilePath() const { diff --git a/src/node_util.cc b/src/node_util.cc index db9b8ec8d65f51..11dd7cc82184ce 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -1,6 +1,7 @@ +#include "base_object-inl.h" #include "node_errors.h" +#include "snapshot_support-inl.h" #include "util-inl.h" -#include "base_object-inl.h" namespace node { namespace util { @@ -326,6 +327,7 @@ void Initialize(Local target, weak_ref->InstanceTemplate()->SetInternalFieldCount( WeakReference::kInternalFieldCount); weak_ref->SetClassName(weak_ref_string); + weak_ref->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(weak_ref, "get", WeakReference::Get); env->SetProtoMethod(weak_ref, "incRef", WeakReference::IncRef); env->SetProtoMethod(weak_ref, "decRef", WeakReference::DecRef); @@ -335,6 +337,24 @@ void Initialize(Local target, env->SetMethod(target, "guessHandleType", GuessHandleType); } +static ExternalReferences external_references { + __FILE__, + GetHiddenValue, + SetHiddenValue, + GetPromiseDetails, + GetProxyDetails, + PreviewEntries, + GetOwnNonIndexProperties, + GetConstructorName, + Sleep, + ArrayBufferViewHasBuffer, + WeakReference::New, + WeakReference::Get, + WeakReference::IncRef, + WeakReference::DecRef, + GuessHandleType, +}; + } // namespace util } // namespace node diff --git a/src/node_wasi.cc b/src/node_wasi.cc index 909023d84e0235..125f44f01d905d 100644 --- a/src/node_wasi.cc +++ b/src/node_wasi.cc @@ -1825,6 +1825,7 @@ static void Initialize(Local target, auto wasi_wrap_string = FIXED_ONE_BYTE_STRING(env->isolate(), "WASI"); tmpl->InstanceTemplate()->SetInternalFieldCount(WASI::kInternalFieldCount); tmpl->SetClassName(wasi_wrap_string); + tmpl->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(tmpl, "args_get", WASI::ArgsGet); env->SetProtoMethod(tmpl, "args_sizes_get", WASI::ArgsSizesGet); diff --git a/src/node_worker.cc b/src/node_worker.cc index 1e1d9434cddb4c..dc76f86493d13d 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -5,6 +5,7 @@ #include "node_buffer.h" #include "node_options-inl.h" #include "node_perf.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "async_wrap-inl.h" diff --git a/src/snapshot_support-inl.h b/src/snapshot_support-inl.h new file mode 100644 index 00000000000000..3d81839b5d2eb9 --- /dev/null +++ b/src/snapshot_support-inl.h @@ -0,0 +1,135 @@ +#ifndef SRC_SNAPSHOT_SUPPORT_INL_H_ +#define SRC_SNAPSHOT_SUPPORT_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "snapshot_support.h" + +namespace node { + +const std::vector& SnapshotDataBase::errors() const { + return state_.errors; +} + +std::vector SnapshotDataBase::storage() { + return storage_; +} + +SnapshotDataBase::SnapshotDataBase(std::vector&& storage) + : storage_(storage) {} + + +template +void SnapshotCreateData::WriteContextIndependentObject(v8::Local data) { + WriteTag(kContextIndependentObjectTag); + WriteIndex(data.IsEmpty() ? kEmptyIndex : creator()->AddData(data)); +} + +template +void SnapshotCreateData::WriteObject( + v8::Local context, v8::Local data) { + WriteTag(kObjectTag); + WriteIndex(data.IsEmpty() ? kEmptyIndex : creator()->AddData(context, data)); +} + +template +void SnapshotCreateData::WriteBaseObjectPtr( + v8::Local context, BaseObjectPtrImpl ptr) { + WriteObject(context, ptr ? ptr->object() : v8::Local()); +} + +v8::Isolate* SnapshotCreateData::isolate() { + return creator()->GetIsolate(); +} + +v8::SnapshotCreator* SnapshotCreateData::creator() { + return creator_; +} + +SnapshotCreateData::SnapshotCreateData(v8::SnapshotCreator* creator) + : creator_(creator) {} + +template +v8::Maybe> SnapshotReadData::ReadContextIndependentObject( + EmptyHandleMode mode) { + if (!ReadTag(kContextIndependentObjectTag)) + return v8::Nothing>(); + V8SnapshotIndex index; + if (!ReadIndex().To(&index)) return v8::Nothing>(); + if (index == kEmptyIndex) { + if (mode == kAllowEmpty) return v8::Just(v8::Local()); + add_error("Empty handle in serialized data was rejected"); + return v8::Nothing>(); + } + v8::MaybeLocal ret = isolate()->GetDataFromSnapshotOnce(index); + if (ret.IsEmpty()) { + add_error("Could not get context-independent object from snapshot"); + return v8::Nothing>(); + } + return v8::Just(ret.ToLocalChecked()); +} + +template +v8::Maybe> SnapshotReadData::ReadObject( + v8::Local context, EmptyHandleMode mode) { + if (!ReadTag(kObjectTag)) return v8::Nothing>(); + V8SnapshotIndex index; + if (!ReadIndex().To(&index)) return v8::Nothing>(); + if (index == kEmptyIndex) { + if (mode == kAllowEmpty) return v8::Just(v8::Local()); + add_error("Empty handle in serialized data was rejected"); + return v8::Nothing>(); + } + v8::MaybeLocal ret = context->GetDataFromSnapshotOnce(index); + if (ret.IsEmpty()) { + add_error("Could not get context-dependent object from snapshot"); + return v8::Nothing>(); + } + return v8::Just(ret.ToLocalChecked()); +} + +template +v8::Maybe> SnapshotReadData::ReadBaseObjectPtr( + v8::Local context, EmptyHandleMode mode) { + v8::Local obj; + if (!ReadObject(context, mode).To(&obj)) { + return v8::Nothing>(); + } + if (obj.IsEmpty()) return v8::Just(BaseObjectPtrImpl()); + BaseObject* base_obj; + if (!GetBaseObjectFromV8Object(context, obj).To(&base_obj)) { + return v8::Nothing>(); + } + return v8::Just(BaseObjectPtrImpl(base_obj)); +} + +v8::Isolate* SnapshotReadData::isolate() { + return isolate_; +} + +void SnapshotReadData::set_isolate(v8::Isolate* isolate) { + isolate_ = isolate; +} + +SnapshotReadData::SnapshotReadData(std::vector&& storage) + : SnapshotDataBase(std::move(storage)) {} + +template +ExternalReferences::ExternalReferences(const char* id, Args*... args) { + Register(id, this); + HandleArgs(args...); +} + +void ExternalReferences::HandleArgs() {} + +template +void ExternalReferences::HandleArgs(T* ptr, Args*... args) { + AddPointer(reinterpret_cast(ptr)); + HandleArgs(args...); +} + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_SNAPSHOT_SUPPORT_INL_H_ diff --git a/src/snapshot_support.cc b/src/snapshot_support.cc new file mode 100644 index 00000000000000..129e2e6bbeeae9 --- /dev/null +++ b/src/snapshot_support.cc @@ -0,0 +1,501 @@ +#include "snapshot_support.h" // NOLINT(build/include_inline) +#include "snapshot_support-inl.h" +#include "debug_utils-inl.h" +#include "env-inl.h" +#include "json_utils.h" // EscapeJsonChars +#include "util.h" +#include +#include // std::setw + +using v8::Context; +using v8::Just; +using v8::Local; +using v8::Maybe; +using v8::Nothing; +using v8::Object; + +namespace node { + +Snapshottable::~Snapshottable() {} + +void Snapshottable::Serialize(SnapshotCreateData* snapshot_data) const { + snapshot_data->add_error("Unserializable object encountered"); +} + +#define SNAPSHOT_TAGS(V) \ + V(kStartEntry) \ + V(kEndEntry) \ + V(kBool) \ + V(kInt32) \ + V(kInt64) \ + V(kUint32) \ + V(kUint64) \ + V(kIndex) \ + V(kString) \ + V(kContextIndependentObject) \ + V(kObject) \ + +enum Tag { +#define V(name) name, + SNAPSHOT_TAGS(V) +#undef V +}; + +static std::string TagName(int tag) { +#define V(name) if (tag == name) return #name; + SNAPSHOT_TAGS(V) +#undef V + return SPrintF("(unknown tag %d)", tag); +} + +const uint8_t SnapshotDataBase::kContextIndependentObjectTag = + kContextIndependentObject; +const uint8_t SnapshotDataBase::kObjectTag = kObject; + +SnapshotDataBase::SaveStateScope::SaveStateScope( + SnapshotDataBase* snapshot_data) + : snapshot_data_(snapshot_data), state_(snapshot_data->state_) { +} + +SnapshotDataBase::SaveStateScope::~SaveStateScope() { + snapshot_data_->state_ = std::move(state_); +} + + +bool SnapshotDataBase::HasSpace(size_t length) const { + return storage_.size() - state_.current_index >= length; +} + +void SnapshotCreateData::WriteRawData(const uint8_t* data, size_t length) { + storage_.resize(storage_.size() + length); + memcpy(storage_.data() + state_.current_index, data, length); + state_.current_index += length; + CHECK_EQ(state_.current_index, storage_.size()); +} + +bool SnapshotReadData::ReadRawData(uint8_t* data, size_t length) { + if (UNLIKELY(!HasSpace(length))) { + add_error("Unexpected end of snapshot data input"); + return false; + } + memcpy(data, storage_.data() + state_.current_index, length); + state_.current_index += length; + return true; +} + +void SnapshotCreateData::WriteTag(uint8_t tag) { + WriteRawData(&tag, 1); +} + +bool SnapshotReadData::ReadTag(uint8_t expected) { + uint8_t actual; + if (!ReadRawData(&actual, 1)) return false; + if (actual != expected) { + add_error(SPrintF("Tried to read object of type %s, found type %s instead", + TagName(expected), TagName(actual))); + return false; + } + return true; +} + +Maybe SnapshotReadData::PeekTag() { + SaveStateScope state_scope(this); + uint8_t tag; + if (!ReadRawData(&tag, 1)) return Nothing(); + return Just(tag); +} + +void SnapshotCreateData::StartWriteEntry(const char* name) { + WriteTag(kStartEntry); + WriteString(name); + state_.entry_stack.push_back(name); +} + +void SnapshotCreateData::EndWriteEntry() { + if (state_.entry_stack.empty()) { + add_error("Attempting to end entry on empty stack, " + "more EndWriteEntry() than StartWriteEntry() calls"); + return; + } + + state_.entry_stack.pop_back(); + WriteTag(kEndEntry); +} + +void SnapshotCreateData::WriteBool(bool value) { + WriteTag(kBool); + uint8_t data = value ? 1 : 0; + WriteRawData(&data, 1); +} + +void SnapshotCreateData::WriteInt32(int32_t value) { + WriteTag(kInt32); + WriteRawData(reinterpret_cast(&value), sizeof(value)); +} + +void SnapshotCreateData::WriteInt64(int64_t value) { + WriteTag(kInt64); + WriteRawData(reinterpret_cast(&value), sizeof(value)); +} + +void SnapshotCreateData::WriteUint32(uint32_t value) { + WriteTag(kUint32); + WriteRawData(reinterpret_cast(&value), sizeof(value)); +} + +void SnapshotCreateData::WriteUint64(uint64_t value) { + WriteTag(kUint64); + WriteRawData(reinterpret_cast(&value), sizeof(value)); +} + +void SnapshotCreateData::WriteIndex(V8SnapshotIndex value) { + WriteTag(kIndex); + WriteRawData(reinterpret_cast(&value), sizeof(value)); +} + +void SnapshotCreateData::WriteString(const char* str, size_t length) { + WriteTag(kString); + if (length == static_cast(-1)) length = strlen(str); + WriteUint64(length); + WriteRawData(reinterpret_cast(str), length); +} + +void SnapshotCreateData::WriteString(const std::string& str) { + WriteString(str.c_str(), str.size()); +} + +v8::Maybe SnapshotReadData::StartReadEntry(const char* expected) { + if (!ReadTag(kStartEntry)) return Nothing(); + std::string actual; + if (!ReadString().To(&actual)) return Nothing(); + if (expected != nullptr && actual != expected) { + add_error(SPrintF("Tried to read start of entry %s, found entry %s", + expected, actual)); + return Nothing(); + } + state_.entry_stack.push_back(actual); + return Just(std::move(actual)); +} + +v8::Maybe SnapshotReadData::EndReadEntry() { + if (!ReadTag(kEndEntry)) return Nothing(); + if (state_.entry_stack.empty()) { + add_error("Attempting to end entry on empty stack, " + "more EndReadEntry() than StartReadEntry() calls"); + return Nothing(); + } + state_.entry_stack.pop_back(); + return Just(true); +} + +v8::Maybe SnapshotReadData::ReadBool() { + if (!ReadTag(kBool)) return Nothing(); + uint8_t value; + if (!ReadRawData(&value, 1)) return Nothing(); + return Just(static_cast(value)); +} + +v8::Maybe SnapshotReadData::ReadInt32() { + if (!ReadTag(kInt32)) return Nothing(); + int32_t value; + if (!ReadRawData(reinterpret_cast(&value), sizeof(value))) + return Nothing(); + return Just(value); +} + +v8::Maybe SnapshotReadData::ReadInt64() { + if (!ReadTag(kInt64)) return Nothing(); + int64_t value; + if (!ReadRawData(reinterpret_cast(&value), sizeof(value))) + return Nothing(); + return Just(value); +} + +v8::Maybe SnapshotReadData::ReadUint32() { + if (!ReadTag(kUint32)) return Nothing(); + uint32_t value; + if (!ReadRawData(reinterpret_cast(&value), sizeof(value))) + return Nothing(); + return Just(static_cast(value)); +} + +v8::Maybe SnapshotReadData::ReadUint64() { + if (!ReadTag(kUint64)) return Nothing(); + uint64_t value; + if (!ReadRawData(reinterpret_cast(&value), sizeof(value))) + return Nothing(); + return Just(value); +} + +v8::Maybe SnapshotReadData::ReadIndex() { + if (!ReadTag(kIndex)) return Nothing(); + size_t value; + if (!ReadRawData(reinterpret_cast(&value), sizeof(value))) + return Nothing(); + return Just(value); +} + +v8::Maybe SnapshotReadData::ReadString() { + if (!ReadTag(kString)) return Nothing(); + uint64_t size; + if (!ReadUint64().To(&size)) return Nothing(); + std::string str(size, '\0'); + if (!ReadRawData(reinterpret_cast(&str[0]), size)) + return Nothing(); + return Just(std::move(str)); +} + +v8::Maybe SnapshotReadData::Finish() { + if (!state_.entry_stack.empty()) { + add_error("Entries left on snapshot stack, EndReadEntry() missing"); + return Nothing(); + } + + if (state_.current_index != storage_.size()) { + add_error("Unexpected data past the end of the snapshot data"); + return Nothing(); + } + + storage_.clear(); + storage_.shrink_to_fit(); + state_ = State{}; + return Just(true); +} + +void SnapshotDataBase::add_error(const std::string& error) { + std::string message = "At ["; + message += std::to_string(state_.current_index); + message += "] "; + for (const std::string& entry : state_.entry_stack) { + message += entry; + message += ':'; + } + message += " "; + message += error; + state_.errors.emplace_back( + Error { state_.current_index, std::move(message) }); +} + +std::string SnapshotReadData::DumpLine::ToString() const { + std::ostringstream os; + os << std::setw(6) << index << ' '; + for (size_t i = 0; i < depth; i++) os << " "; + os << description; + return os.str(); +} + +void SnapshotDataBase::DumpToStderr() { + std::vector lines; + std::vector errors; + std::tie(lines, errors) = SnapshotReadData(storage()).Dump(); + + for (const SnapshotReadData::DumpLine& line : lines) + fprintf(stderr, "%s\n", line.ToString().c_str()); + if (!errors.empty()) { + fprintf(stderr, "Encountered %zu snapshot errors:\n", errors.size()); + for (const Error& error : errors) + fprintf(stderr, "%s\n", error.message.c_str()); + } +} + +void SnapshotDataBase::PrintErrorsAndAbortIfAny() { + if (errors().empty()) return; + + std::vector lines; + std::tie(lines, std::ignore) = SnapshotReadData(storage()).Dump(); + + fprintf(stderr, "Encountered %zu snapshot errors:\n", errors().size()); + for (const Error& error : errors()) { + fprintf(stderr, "%s\nAround:\n", error.message.c_str()); + size_t i; + for (i = 0; i < lines.size(); i++) { + if (lines[i].index >= error.index) break; + } + size_t start_print_range = std::max(i - 5, 0); + size_t end_print_range = std::min(i + 5, lines.size() - 1); + for (size_t j = start_print_range; j <= end_print_range; j++) { + fprintf(stderr, + "%c %s\n", + i == j + 1 ? '*' : ' ', // Mark the line presumed to be at fault. + lines[j].ToString().c_str()); + } + } + fprintf(stderr, + "(`node --dump-snapshot` dumps the full snapshot data contained " + "in a node binary)\n"); + fflush(stderr); + Abort(); +} + +std::pair, + std::vector> SnapshotReadData::Dump() { + SaveStateScope state_scope(this); + state_ = State{}; + + std::vector ret; + + while (state_.current_index < storage_.size() && errors().empty()) { + uint8_t tag; + if (!PeekTag().To(&tag)) { + ReadTag(0); // PeekTag() failing means EOF, this re-generates that error. + break; + } + + DumpLine line = { state_.current_index, state_.entry_stack.size(), "" }; + + switch (tag) { + case kStartEntry: { + std::string str; + if (!StartReadEntry(nullptr).To(&str)) break; + line.description = SPrintF("StartEntry: [%s]", str); + break; + } + case kEndEntry: { + if (EndReadEntry().IsNothing()) break; + line.description = "EndEntry"; + break; + } + case kBool: { + bool value; + if (!ReadBool().To(&value)) break; + line.description = SPrintF("Bool: %s", value); + break; + } + case kInt32: { + int32_t value; + if (!ReadInt32().To(&value)) break; + line.description = SPrintF("Int32: %s", value); + break; + } + case kInt64: { + int64_t value; + if (!ReadInt64().To(&value)) break; + line.description = SPrintF("Int64: %s", value); + break; + } + case kUint32: { + uint32_t value; + if (!ReadUint32().To(&value)) break; + line.description = SPrintF("Uint32: %s", value); + break; + } + case kUint64: { + uint64_t value; + if (!ReadUint64().To(&value)) break; + line.description = SPrintF("Uint64: %s", value); + break; + } + case kString: { + std::string value; + if (!ReadString().To(&value)) break; + if (value.size() > 120) { + line.description = SPrintF("String: '%s' ... '%s'", + EscapeJsonChars(value.substr(0, 90)), + EscapeJsonChars(value.substr(value.size() - 20))); + } else { + line.description = SPrintF("String: '%s'", EscapeJsonChars(value)); + } + break; + } + case kContextIndependentObjectTag: + case kObjectTag: + CHECK(ReadTag(tag)); + // fall-through + case kIndex: { + V8SnapshotIndex index; + if (!ReadIndex().To(&index)) break; + const char* type = tag == kContextIndependentObjectTag ? + "Context-independent object index" : + "Object index"; + if (index == kEmptyIndex) + line.description = SPrintF("%s: (empty)", type); + else + line.description = SPrintF("%s: %s", type, index); + break; + } + default: { + // This will indicate that this is an unknown tag. + line.description = TagName(tag); + add_error(SPrintF("Encountered unknown type tag %d", tag)); + break; + } + } + + if (!line.description.empty()) + ret.emplace_back(std::move(line)); + } + + return std::make_pair(ret, errors()); +} + + +Maybe SnapshotReadData::GetBaseObjectFromV8Object( + Local context, Local obj) { + Environment* env = Environment::GetCurrent(context); + if (!BaseObject::GetConstructorTemplate(env)->HasInstance(obj)) { + add_error("Deserialized object is not a BaseObject"); + return v8::Nothing(); + } + BaseObject* base_obj = BaseObject::FromJSObject(obj); + if (base_obj == nullptr) { + add_error("Deserialized BaseObject is empty"); + return v8::Nothing(); + } + return Just(base_obj); +} + +void ExternalReferences::AddPointer(intptr_t ptr) { + DCHECK_NE(ptr, kEnd); + references_.push_back(ptr); +} + +std::map* ExternalReferences::map() { + static std::map map_; + return &map_; +} + +std::vector ExternalReferences::get_list() { + static std::vector list; + if (list.empty()) { + for (const auto& entry : *map()) { + std::vector* source = &entry.second->references_; + list.insert(list.end(), source->begin(), source->end()); + source->clear(); + source->shrink_to_fit(); + } + } + return list; +} + +void ExternalReferences::Register(const char* id, ExternalReferences* self) { + auto result = map()->insert({id, this}); + CHECK(result.second); +} + +const intptr_t ExternalReferences::kEnd = reinterpret_cast(nullptr); + +BaseObjectDeserializer::BaseObjectDeserializer( + const std::string& name, Callback callback) { + auto result = map()->insert({name, callback}); + CHECK(result.second); +} + +BaseObject* BaseObjectDeserializer::Deserialize( + const std::string& name, + Environment* env, + SnapshotReadData* snapshot_data) { + Callback callback = (*map())[name]; + if (callback == nullptr) { + snapshot_data->add_error(SPrintF("Unknown BaseObject type %s", name)); + return nullptr; + } + return callback(env, snapshot_data); +} + +std::map* +BaseObjectDeserializer::map() { + static std::map map_; + return &map_; +} + +} // namespace node diff --git a/src/snapshot_support.h b/src/snapshot_support.h new file mode 100644 index 00000000000000..d4a0d8abb04588 --- /dev/null +++ b/src/snapshot_support.h @@ -0,0 +1,259 @@ +#ifndef SRC_SNAPSHOT_SUPPORT_H_ +#define SRC_SNAPSHOT_SUPPORT_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "v8.h" +#include + +namespace node { + +class BaseObject; +template +class BaseObjectPtrImpl; +class Environment; + +// We use V8SnapshotIndex when referring to indices in the sense of the V8 +// snapshot data, to avoid confusion with other kinds of indices. +using V8SnapshotIndex = size_t; + +// This serves as the abstract base class for the snapshot reader and writer +// classes. It includes the current snapshot blob, and a list of errors that +// have occurred while reading/writing the snapshot data. +class SnapshotDataBase { + public: + virtual ~SnapshotDataBase() = default; + + // kEmptyIndex is used when we want to serialize an empty v8::Local<> handle. + static constexpr V8SnapshotIndex kEmptyIndex = static_cast(-1); + + struct Error { + // The byte index into the snapshot where the error occurred. + size_t index; + // An error message. Currently, this includes the index, as well as the + // stack of entries that are used for grouping data in the snapshot. + std::string message; + }; + + // Add a single new error. Index and entry stack are added automatically. + void add_error(const std::string& error); + // Return the list of current errors. + inline const std::vector& errors() const; + + // Return a copy of the underlying byte array. + inline std::vector storage(); + + // Print a human-readable representation of all errors, and abort the process + // if any exist. + void PrintErrorsAndAbortIfAny(); + // Dump the full snapshot, including errors, to stderr. + void DumpToStderr(); + + protected: + explicit inline SnapshotDataBase(std::vector&& storage); + SnapshotDataBase() = default; + + // Checks whether `length` more data is available in the byte array. + // This mostly makes sense for reading data. + bool HasSpace(size_t length) const; + + std::vector storage_; + + struct State { + size_t current_index = 0; + std::vector errors; + std::vector entry_stack; + }; + State state_; + + // Defined here so that they can be used by the v8::Local<> writing + // and reading functions. + static const uint8_t kContextIndependentObjectTag; + static const uint8_t kObjectTag; + + // Used internally to temporarily save the current State of this object. + class SaveStateScope { + public: + explicit SaveStateScope(SnapshotDataBase* snapshot_data); + ~SaveStateScope(); + + private: + SnapshotDataBase* snapshot_data_; + State state_; + }; +}; + +class SnapshotCreateData final : public SnapshotDataBase { + public: + // Start writing an entry. This must be matched by an EndWriteEntry() call. + // The name is mostly used for debugging, but can also be used to figure out + // how to deserialize the data for this entry. + void StartWriteEntry(const char* name); + void EndWriteEntry(); + + // Write data of a given type into the snapshot. When reading, the read + // function for the corresponding must be used. + void WriteBool(bool value); + void WriteInt32(int32_t value); + void WriteInt64(int64_t value); + void WriteUint32(uint32_t value); + void WriteUint64(uint64_t value); + void WriteIndex(V8SnapshotIndex value); + void WriteString(const char* str, size_t length = static_cast(-1)); + void WriteString(const std::string& str); + + // Write a V8 object into the snapshot. Use the ContextIndependent variant + // for writing ObjectTemplate, FunctionTemplate and External objects. + // For other values, their source context must be provided. + template + inline void WriteContextIndependentObject(v8::Local data); + template + inline void WriteObject(v8::Local context, v8::Local data); + template + inline void WriteBaseObjectPtr( + v8::Local context, BaseObjectPtrImpl ptr); + + inline v8::SnapshotCreator* creator(); + inline v8::Isolate* isolate(); + + explicit inline SnapshotCreateData(v8::SnapshotCreator* creator); + + private: + void WriteTag(uint8_t tag); + void WriteRawData(const uint8_t* data, size_t length); + + v8::SnapshotCreator* creator_; +}; + +class SnapshotReadData final : public SnapshotDataBase { + public: + enum EmptyHandleMode { + kAllowEmpty, + kRejectEmpty + }; + + // Start reading an entry. If `expected_name` is a string, it will be matched + // against the string provided to StartWriteEntry(), and if there is a + // mismatch, this function will fail. If no such check is desired, for example + // because the type of the next entry is dynamic, `nullptr` can be passed. + // The entry type as written by StartWriteEntry() is returned. + v8::Maybe StartReadEntry(const char* expected_name); + v8::Maybe EndReadEntry(); + + // Read data from the snapshot. If the read function doesn't match the write + // function at this position in the snapshot, the function will fail and + // return an empty Maybe. + v8::Maybe ReadBool(); + v8::Maybe ReadInt32(); + v8::Maybe ReadInt64(); + v8::Maybe ReadUint32(); + v8::Maybe ReadUint64(); + v8::Maybe ReadIndex(); + v8::Maybe ReadString(); + + // Read V8 objects from the snapshot, matching the writing counterparts of + // these functions. If `mode` is `kRejectEmpty`, reading empty handles is + // regarded as a failure. Pass `kAllowEmpty` if it is expected that the + // corresponding write call may write an empty object. + // These functions do not use `v8::MaybeLocal` in order to be able to + // distinguish between successfully reading an empty handle and unsuccessfully + // reading data. + template + inline v8::Maybe> ReadContextIndependentObject( + EmptyHandleMode mode = kRejectEmpty); + template + inline v8::Maybe> ReadObject( + v8::Local context, EmptyHandleMode mode = kRejectEmpty); + template + inline v8::Maybe> ReadBaseObjectPtr( + v8::Local context, EmptyHandleMode mode = kRejectEmpty); + + // Ensure that the snapshot is finished at this point, and no further data + // is included in the snapshot. + v8::Maybe Finish(); + + struct DumpLine { + size_t index; + size_t depth; + std::string description; + + std::string ToString() const; + }; + // Return a structured description of the snapshot contents. + std::pair, std::vector> Dump(); + + inline v8::Isolate* isolate(); + inline void set_isolate(v8::Isolate*); + + explicit inline SnapshotReadData(std::vector&& storage); + + private: + bool ReadTag(uint8_t tag); + v8::Maybe PeekTag(); + bool ReadRawData(uint8_t* data, size_t length); + + v8::Maybe GetBaseObjectFromV8Object( + v8::Local context, v8::Local obj); + + v8::Isolate* isolate_; +}; + +class Snapshottable { + public: + virtual ~Snapshottable() = 0; + + // Subclasses are expected to override this. The default implementation only + // adds an error to the snapshot data. + virtual void Serialize(SnapshotCreateData* snapshot_data) const; +}; + +class ExternalReferences { + public: + // Create static instances of this class to register a list of external + // references for usage in snapshotting. Usually, this includes all C++ + // binding functions. `id` can be any string, as long as it is unique + // (e.g. the current file name as retrieved by __FILE__). + template + inline ExternalReferences(const char* id, Args*... args); + + // Returns the list of all references collected so far, not yet terminated + // by kEnd. + static std::vector get_list(); + + static const intptr_t kEnd; // The end-of-list marker used by V8, nullptr. + + private: + void Register(const char* id, ExternalReferences* self); + static std::map* map(); + std::vector references_; + + void AddPointer(intptr_t ptr); + inline void HandleArgs(); + template + inline void HandleArgs(T* ptr, Args*... args); +}; + +class BaseObjectDeserializer { + public: + typedef BaseObject* (*Callback)(Environment*, SnapshotReadData*); + + // Create static instances of this class to register a callback used for + // deserializing specific types of BaseObjects. This instance will be used if + // `name` matches the name passed to `StartWriteEntry()` when creating the + // snapshot. `name` must be unique. + BaseObjectDeserializer(const std::string& name, Callback callback); + + static BaseObject* Deserialize( + const std::string& name, + Environment* env, + SnapshotReadData* snapshot_data); + + private: + static std::map* map(); +}; + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_SNAPSHOT_SUPPORT_H_ diff --git a/src/string_decoder.cc b/src/string_decoder.cc index 6ec84e0e11ed31..2c4eed7bb328d5 100644 --- a/src/string_decoder.cc +++ b/src/string_decoder.cc @@ -4,6 +4,7 @@ #include "env-inl.h" #include "node_buffer.h" #include "string_bytes.h" +#include "snapshot_support-inl.h" #include "util.h" using v8::Array; @@ -320,6 +321,12 @@ void InitializeStringDecoder(Local target, env->SetMethod(target, "flush", FlushData); } +static ExternalReferences external_references { + __FILE__, + DecodeData, + FlushData +}; + } // anonymous namespace } // namespace node diff --git a/src/timers.cc b/src/timers.cc index fab1b12018a921..9ce2b05524ff02 100644 --- a/src/timers.cc +++ b/src/timers.cc @@ -1,4 +1,5 @@ #include "env-inl.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" @@ -58,6 +59,14 @@ void Initialize(Local target, env->immediate_info()->fields().GetJSArray()).Check(); } +static ExternalReferences external_references { + __FILE__, + GetLibuvNow, + SetupTimers, + ScheduleTimer, + ToggleTimerRef, + ToggleImmediateRef, +}; } // anonymous namespace } // namespace node diff --git a/test/cctest/test_snapshot_support.cc b/test/cctest/test_snapshot_support.cc new file mode 100644 index 00000000000000..2a66e13412ac5c --- /dev/null +++ b/test/cctest/test_snapshot_support.cc @@ -0,0 +1,78 @@ +#include "snapshot_support-inl.h" +#include "gtest/gtest.h" + +TEST(SnapshotSupportTest, DumpWithoutErrors) { + node::SnapshotCreateData data_w(nullptr); + + data_w.StartWriteEntry("Outer"); + data_w.WriteUint32(10); + data_w.StartWriteEntry("Inner"); + data_w.WriteString("Hello!"); + data_w.EndWriteEntry(); + data_w.EndWriteEntry(); + + node::SnapshotReadData data_r(data_w.storage()); + + std::vector lines; + std::vector errors; + std::tie(lines, errors) = data_r.Dump(); + + EXPECT_TRUE(errors.empty()); + EXPECT_EQ(lines.size(), 6u); + while (lines.size() < 6) lines.emplace_back(); // Avoid OOB test crahes. + EXPECT_EQ(lines[0].index, 0u); + EXPECT_EQ(lines[0].depth, 0u); + EXPECT_EQ(lines[0].description, "StartEntry: [Outer]"); + EXPECT_EQ(lines[1].depth, 1u); + EXPECT_EQ(lines[1].description, "Uint32: 10"); + EXPECT_EQ(lines[2].depth, 1u); + EXPECT_EQ(lines[2].description, "StartEntry: [Inner]"); + EXPECT_EQ(lines[3].depth, 2u); + EXPECT_EQ(lines[3].description, "String: 'Hello!'"); + EXPECT_EQ(lines[4].depth, 2u); + EXPECT_EQ(lines[4].description, "EndEntry"); + EXPECT_EQ(lines[5].depth, 1u); + EXPECT_EQ(lines[5].description, "EndEntry"); +} + +TEST(SnapshotSupportTest, DumpWithErrors) { + node::SnapshotCreateData data_w(nullptr); + + data_w.StartWriteEntry("Outer"); + data_w.WriteUint32(10); + data_w.StartWriteEntry("Inner"); + data_w.WriteString("Hello!"); + data_w.EndWriteEntry(); + data_w.EndWriteEntry(); + + std::vector storage = data_w.storage(); + storage.at(21)++; // Invalidate storage data in some way. + node::SnapshotReadData data_r(std::move(storage)); + + data_r.DumpToStderr(); + std::vector lines; + std::vector errors; + std::tie(lines, errors) = data_r.Dump(); + + // The expectations here may need to be updated if the snapshot format + // changes. + EXPECT_EQ(lines.size(), 5u); + while (lines.size() < 5) lines.emplace_back(); // Avoid OOB test crahes. + EXPECT_EQ(lines[0].index, 0u); + EXPECT_EQ(lines[0].depth, 0u); + EXPECT_EQ(lines[0].description, "StartEntry: [Outer]"); + EXPECT_EQ(lines[1].depth, 1u); + EXPECT_EQ(lines[1].description, "Uint32: 10"); + EXPECT_EQ(lines[2].depth, 1u); + EXPECT_EQ(lines[2].description, "EndEntry"); + EXPECT_EQ(lines[3].depth, 0u); + EXPECT_EQ(lines[3].description, "String: 'Inner'"); + EXPECT_EQ(lines[4].depth, 0u); + EXPECT_EQ(lines[4].description, "String: 'Hello!'"); + + EXPECT_EQ(errors.size(), 1u); + while (errors.size() < 1) errors.emplace_back(); // Avoid OOB test crashes. + EXPECT_EQ(errors[0].message, + "At [54] Attempting to end entry on empty stack, more EndReadEntry() " + "than StartReadEntry() calls"); +} diff --git a/test/parallel/test-code-cache.js b/test/parallel/test-code-cache.js index 3c4488c557d524..fe3d242a86e96e 100644 --- a/test/parallel/test-code-cache.js +++ b/test/parallel/test-code-cache.js @@ -22,7 +22,8 @@ for (const key of canBeRequired) { // The computation has to be delayed until we have done loading modules const { compiledWithoutCache, - compiledWithCache + compiledWithCache, + includedInSnapshot } = getCacheUsage(); const loadedModules = process.moduleLoadList @@ -59,6 +60,7 @@ if (!process.features.cached_builtins) { const wrong = []; for (const key of loadedModules) { + if (includedInSnapshot.has(key)) continue; if (cannotBeRequired.has(key) && !compiledWithoutCache.has(key)) { wrong.push(`"${key}" should've been compiled **without** code cache`); } diff --git a/tools/snapshot/README.md b/tools/snapshot/README.md index 34dc574d56cc30..5ab1df698d26d7 100644 --- a/tools/snapshot/README.md +++ b/tools/snapshot/README.md @@ -23,7 +23,7 @@ into the Node.js executable, `libnode` is first built with these unresolved symbols: - `node::NodeMainInstance::GetEmbeddedSnapshotBlob` -- `node::NodeMainInstance::GetIsolateDataIndexes` +- `node::NodeMainInstance::GetSnapshotData` Then the `node_mksnapshot` executable is built with C++ files in this directory, as well as `src/node_snapshot_stub.cc` which defines the unresolved diff --git a/tools/snapshot/snapshot_builder.cc b/tools/snapshot/snapshot_builder.cc index 8a97513ba905fc..5f16ecc3a6fbce 100644 --- a/tools/snapshot/snapshot_builder.cc +++ b/tools/snapshot/snapshot_builder.cc @@ -4,6 +4,7 @@ #include "node_internals.h" #include "node_main_instance.h" #include "node_v8_platform-inl.h" +#include "snapshot_support-inl.h" namespace node { @@ -21,12 +22,13 @@ void WriteVector(std::stringstream* ss, const T* vec, size_t size) { } std::string FormatBlob(v8::StartupData* blob, - const std::vector& isolate_data_indexes) { + SnapshotCreateData* snapshot_data) { std::stringstream ss; ss << R"(#include #include "node_main_instance.h" -#include "v8.h" +#include "env.h" +#include "snapshot_support-inl.h" // This file is generated by tools/snapshot. Do not edit. @@ -46,13 +48,17 @@ static v8::StartupData blob = { blob_data, blob_size }; return &blob; } -static const std::vector isolate_data_indexes { +static SnapshotReadData snapshot_data { + { )"; - WriteVector(&ss, isolate_data_indexes.data(), isolate_data_indexes.size()); - ss << R"(}; + std::vector raw_data = snapshot_data->storage(); + WriteVector(&ss, raw_data.data(), raw_data.size()); + ss << R"( + } +}; -const std::vector* NodeMainInstance::GetIsolateDataIndexes() { - return &isolate_data_indexes; +SnapshotReadData* NodeMainInstance::GetSnapshotData() { + return &snapshot_data; } } // namespace node )"; @@ -63,19 +69,20 @@ const std::vector* NodeMainInstance::GetIsolateDataIndexes() { std::string SnapshotBuilder::Generate( const std::vector args, const std::vector exec_args) { - // TODO(joyeecheung): collect external references and set it in - // params.external_references. - std::vector external_references = { - reinterpret_cast(nullptr)}; Isolate* isolate = Isolate::Allocate(); per_process::v8_platform.Platform()->RegisterIsolate(isolate, uv_default_loop()); std::unique_ptr main_instance; std::string result; + int exit_code = 0; + + std::vector external_references = ExternalReferences::get_list(); + external_references.push_back(ExternalReferences::kEnd); { - std::vector isolate_data_indexes; SnapshotCreator creator(isolate, external_references.data()); + SnapshotCreateData snapshot_data(&creator); + DeleteFnPtr env; { main_instance = NodeMainInstance::Create(isolate, @@ -85,20 +92,27 @@ std::string SnapshotBuilder::Generate( exec_args); HandleScope scope(isolate); creator.SetDefaultContext(Context::New(isolate)); - isolate_data_indexes = main_instance->isolate_data()->Serialize(&creator); - size_t index = creator.AddContext(NewContext(isolate)); + env = main_instance->CreateMainEnvironment(&exit_code); + env->isolate_data()->Serialize(&snapshot_data); + env->Serialize(&snapshot_data); + snapshot_data.PrintErrorsAndAbortIfAny(); + size_t index = creator.AddContext(env->context(), + { BaseObject::SerializeInternalFields, &snapshot_data }); CHECK_EQ(index, NodeMainInstance::kNodeContextIndex); + CHECK_EQ(exit_code, 0); } // Must be out of HandleScope StartupData blob = creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kClear); + snapshot_data.PrintErrorsAndAbortIfAny(); CHECK(blob.CanBeRehashed()); // Must be done while the snapshot creator isolate is entered i.e. the // creator is still alive. + env.reset(); main_instance->Dispose(); - result = FormatBlob(&blob, isolate_data_indexes); + result = FormatBlob(&blob, &snapshot_data); delete[] blob.data; }