| // Copyright 2016 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/snapshot/context-serializer.h" |
| #include "src/snapshot/startup-serializer.h" |
| |
| #include "src/api/api-inl.h" |
| #include "src/execution/microtask-queue.h" |
| #include "src/heap/combined-heap.h" |
| #include "src/numbers/math-random.h" |
| #include "src/objects/objects-inl.h" |
| #include "src/objects/slots.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| namespace { |
| |
| // During serialization, puts the native context into a state understood by the |
| // serializer (e.g. by clearing lists of Code objects). After serialization, |
| // the original state is restored. |
| class SanitizeNativeContextScope final { |
| public: |
| SanitizeNativeContextScope(Isolate* isolate, NativeContext native_context, |
| bool allow_active_isolate_for_testing, |
| const DisallowGarbageCollection& no_gc) |
| : isolate_(isolate), |
| native_context_(native_context), |
| microtask_queue_(native_context.microtask_queue()), |
| optimized_code_list_(native_context.OptimizedCodeListHead()), |
| deoptimized_code_list_(native_context.DeoptimizedCodeListHead()) { |
| #ifdef DEBUG |
| if (!allow_active_isolate_for_testing) { |
| // Microtasks. |
| DCHECK_EQ(0, microtask_queue_->size()); |
| DCHECK(!microtask_queue_->HasMicrotasksSuppressions()); |
| DCHECK_EQ(0, microtask_queue_->GetMicrotasksScopeDepth()); |
| DCHECK(microtask_queue_->DebugMicrotasksScopeDepthIsZero()); |
| // Code lists. |
| DCHECK(optimized_code_list_.IsUndefined(isolate)); |
| DCHECK(deoptimized_code_list_.IsUndefined(isolate)); |
| } |
| #endif |
| Object undefined = ReadOnlyRoots(isolate).undefined_value(); |
| native_context.set_microtask_queue(isolate, nullptr); |
| native_context.SetOptimizedCodeListHead(undefined); |
| native_context.SetDeoptimizedCodeListHead(undefined); |
| } |
| |
| ~SanitizeNativeContextScope() { |
| // Restore saved fields. |
| native_context_.SetDeoptimizedCodeListHead(optimized_code_list_); |
| native_context_.SetOptimizedCodeListHead(deoptimized_code_list_); |
| native_context_.set_microtask_queue(isolate_, microtask_queue_); |
| } |
| |
| private: |
| Isolate* isolate_; |
| NativeContext native_context_; |
| MicrotaskQueue* const microtask_queue_; |
| const Object optimized_code_list_; |
| const Object deoptimized_code_list_; |
| }; |
| |
| } // namespace |
| |
| ContextSerializer::ContextSerializer( |
| Isolate* isolate, Snapshot::SerializerFlags flags, |
| StartupSerializer* startup_serializer, |
| v8::SerializeEmbedderFieldsCallback callback) |
| : Serializer(isolate, flags), |
| startup_serializer_(startup_serializer), |
| serialize_embedder_fields_(callback), |
| can_be_rehashed_(true) { |
| InitializeCodeAddressMap(); |
| } |
| |
| ContextSerializer::~ContextSerializer() { |
| OutputStatistics("ContextSerializer"); |
| } |
| |
| void ContextSerializer::Serialize(Context* o, |
| const DisallowGarbageCollection& no_gc) { |
| context_ = *o; |
| DCHECK(context_.IsNativeContext()); |
| |
| // Upon deserialization, references to the global proxy and its map will be |
| // replaced. |
| reference_map()->AddAttachedReference(context_.global_proxy()); |
| reference_map()->AddAttachedReference(context_.global_proxy().map()); |
| |
| // The bootstrap snapshot has a code-stub context. When serializing the |
| // context snapshot, it is chained into the weak context list on the isolate |
| // and it's next context pointer may point to the code-stub context. Clear |
| // it before serializing, it will get re-added to the context list |
| // explicitly when it's loaded. |
| // TODO(v8:10416): These mutations should not observably affect the running |
| // context. |
| context_.set(Context::NEXT_CONTEXT_LINK, |
| ReadOnlyRoots(isolate()).undefined_value()); |
| DCHECK(!context_.global_object().IsUndefined()); |
| // Reset math random cache to get fresh random numbers. |
| MathRandom::ResetContext(context_); |
| |
| SanitizeNativeContextScope sanitize_native_context( |
| isolate(), context_.native_context(), allow_active_isolate_for_testing(), |
| no_gc); |
| |
| VisitRootPointer(Root::kStartupObjectCache, nullptr, FullObjectSlot(o)); |
| SerializeDeferredObjects(); |
| |
| // Add section for embedder-serialized embedder fields. |
| if (!embedder_fields_sink_.data()->empty()) { |
| sink_.Put(kEmbedderFieldsData, "embedder fields data"); |
| sink_.Append(embedder_fields_sink_); |
| sink_.Put(kSynchronize, "Finished with embedder fields data"); |
| } |
| |
| Pad(); |
| } |
| |
| void ContextSerializer::SerializeObjectImpl(Handle<HeapObject> obj) { |
| DCHECK(!ObjectIsBytecodeHandler(obj)); // Only referenced in dispatch table. |
| |
| if (!allow_active_isolate_for_testing()) { |
| // When serializing a snapshot intended for real use, we should not end up |
| // at another native context. |
| // But in test scenarios there is no way to avoid this. Since we only |
| // serialize a single context in these cases, and this context does not |
| // have to be executable, we can simply ignore this. |
| DCHECK_IMPLIES(obj->IsNativeContext(), *obj == context_); |
| } |
| |
| if (SerializeHotObject(obj)) return; |
| |
| if (SerializeRoot(obj)) return; |
| |
| if (SerializeBackReference(obj)) return; |
| |
| if (startup_serializer_->SerializeUsingReadOnlyObjectCache(&sink_, obj)) { |
| return; |
| } |
| |
| if (ShouldBeInTheStartupObjectCache(*obj)) { |
| startup_serializer_->SerializeUsingStartupObjectCache(&sink_, obj); |
| return; |
| } |
| |
| // Pointers from the context snapshot to the objects in the startup snapshot |
| // should go through the root array or through the startup object cache. |
| // If this is not the case you may have to add something to the root array. |
| DCHECK(!startup_serializer_->ReferenceMapContains(obj)); |
| // All the internalized strings that the context snapshot needs should be |
| // either in the root table or in the startup object cache. |
| DCHECK(!obj->IsInternalizedString()); |
| // Function and object templates are not context specific. |
| DCHECK(!obj->IsTemplateInfo()); |
| |
| // Clear literal boilerplates and feedback. |
| if (obj->IsFeedbackVector()) { |
| Handle<FeedbackVector>::cast(obj)->ClearSlots(isolate()); |
| } |
| |
| // Clear InterruptBudget when serializing FeedbackCell. |
| if (obj->IsFeedbackCell()) { |
| Handle<FeedbackCell>::cast(obj)->SetInitialInterruptBudget(); |
| } |
| |
| if (SerializeJSObjectWithEmbedderFields(obj)) { |
| return; |
| } |
| |
| if (obj->IsJSFunction()) { |
| // Unconditionally reset the JSFunction to its SFI's code, since we can't |
| // serialize optimized code anyway. |
| Handle<JSFunction> closure = Handle<JSFunction>::cast(obj); |
| closure->ResetIfBytecodeFlushed(); |
| if (closure->is_compiled()) closure->set_code(closure->shared().GetCode()); |
| } |
| |
| CheckRehashability(*obj); |
| |
| // Object has not yet been serialized. Serialize it here. |
| ObjectSerializer serializer(this, obj, &sink_); |
| serializer.Serialize(); |
| } |
| |
| bool ContextSerializer::ShouldBeInTheStartupObjectCache(HeapObject o) { |
| // Scripts should be referred only through shared function infos. We can't |
| // allow them to be part of the context snapshot because they contain a |
| // unique ID, and deserializing several context snapshots containing script |
| // would cause dupes. |
| DCHECK(!o.IsScript()); |
| return o.IsName() || o.IsSharedFunctionInfo() || o.IsHeapNumber() || |
| o.IsCode() || o.IsScopeInfo() || o.IsAccessorInfo() || |
| o.IsTemplateInfo() || o.IsClassPositions() || |
| o.map() == ReadOnlyRoots(isolate()).fixed_cow_array_map(); |
| } |
| |
| namespace { |
| bool DataIsEmpty(const StartupData& data) { return data.raw_size == 0; } |
| } // anonymous namespace |
| |
| bool ContextSerializer::SerializeJSObjectWithEmbedderFields( |
| Handle<HeapObject> obj) { |
| if (!obj->IsJSObject()) return false; |
| Handle<JSObject> js_obj = Handle<JSObject>::cast(obj); |
| int embedder_fields_count = js_obj->GetEmbedderFieldCount(); |
| if (embedder_fields_count == 0) return false; |
| CHECK_GT(embedder_fields_count, 0); |
| DCHECK(!js_obj->NeedsRehashing()); |
| |
| DisallowGarbageCollection no_gc; |
| DisallowJavascriptExecution no_js(isolate()); |
| DisallowCompilation no_compile(isolate()); |
| |
| v8::Local<v8::Object> api_obj = v8::Utils::ToLocal(js_obj); |
| |
| std::vector<EmbedderDataSlot::RawData> original_embedder_values; |
| std::vector<StartupData> serialized_data; |
| |
| // 1) Iterate embedder fields. Hold onto the original value of the fields. |
| // Ignore references to heap objects since these are to be handled by the |
| // serializer. For aligned pointers, call the serialize callback. Hold |
| // onto the result. |
| for (int i = 0; i < embedder_fields_count; i++) { |
| EmbedderDataSlot embedder_data_slot(*js_obj, i); |
| original_embedder_values.emplace_back( |
| embedder_data_slot.load_raw(isolate(), no_gc)); |
| Object object = embedder_data_slot.load_tagged(); |
| if (object.IsHeapObject()) { |
| DCHECK(IsValidHeapObject(isolate()->heap(), HeapObject::cast(object))); |
| serialized_data.push_back({nullptr, 0}); |
| } else { |
| // If no serializer is provided and the field was empty, we serialize it |
| // by default to nullptr. |
| if (serialize_embedder_fields_.callback == nullptr && |
| object == Smi::zero()) { |
| serialized_data.push_back({nullptr, 0}); |
| } else { |
| DCHECK_NOT_NULL(serialize_embedder_fields_.callback); |
| StartupData data = serialize_embedder_fields_.callback( |
| api_obj, i, serialize_embedder_fields_.data); |
| serialized_data.push_back(data); |
| } |
| } |
| } |
| |
| // 2) Embedder fields for which the embedder callback produced non-zero |
| // serialized data should be considered aligned pointers to objects owned |
| // by the embedder. Clear these memory addresses to avoid non-determism |
| // in the snapshot. This is done separately to step 1 to no not interleave |
| // with embedder callbacks. |
| for (int i = 0; i < embedder_fields_count; i++) { |
| if (!DataIsEmpty(serialized_data[i])) { |
| EmbedderDataSlot(*js_obj, i).store_raw(isolate(), kNullAddress, no_gc); |
| } |
| } |
| |
| // 3) Serialize the object. References from embedder fields to heap objects or |
| // smis are serialized regularly. |
| ObjectSerializer(this, js_obj, &sink_).Serialize(); |
| |
| // 4) Obtain back reference for the serialized object. |
| const SerializerReference* reference = |
| reference_map()->LookupReference(js_obj); |
| DCHECK_NOT_NULL(reference); |
| DCHECK(reference->is_back_reference()); |
| |
| // 5) Write data returned by the embedder callbacks into a separate sink, |
| // headed by the back reference. Restore the original embedder fields. |
| for (int i = 0; i < embedder_fields_count; i++) { |
| StartupData data = serialized_data[i]; |
| if (DataIsEmpty(data)) continue; |
| // Restore original values from cleared fields. |
| EmbedderDataSlot(*js_obj, i) |
| .store_raw(isolate(), original_embedder_values[i], no_gc); |
| embedder_fields_sink_.Put(kNewObject, "embedder field holder"); |
| embedder_fields_sink_.PutInt(reference->back_ref_index(), "BackRefIndex"); |
| embedder_fields_sink_.PutInt(i, "embedder field index"); |
| embedder_fields_sink_.PutInt(data.raw_size, "embedder fields data size"); |
| embedder_fields_sink_.PutRaw(reinterpret_cast<const byte*>(data.data), |
| data.raw_size, "embedder fields data"); |
| delete[] data.data; |
| } |
| |
| // 6) The content of the separate sink is appended eventually to the default |
| // sink. The ensures that during deserialization, we call the deserializer |
| // callback at the end, and can guarantee that the deserialized objects are |
| // in a consistent state. See ContextSerializer::Serialize. |
| return true; |
| } |
| |
| void ContextSerializer::CheckRehashability(HeapObject obj) { |
| if (!can_be_rehashed_) return; |
| if (!obj.NeedsRehashing()) return; |
| if (obj.CanBeRehashed()) return; |
| can_be_rehashed_ = false; |
| } |
| |
| } // namespace internal |
| } // namespace v8 |