#include "src/snapshot/context-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/cpp-heap-object-wrapper-inl.h"
#include "src/objects/embedder-data-array-inl.h"
#include "src/objects/js-objects.h"
#include "src/objects/objects-inl.h"
#include "src/objects/slots.h"
#include "src/snapshot/serializer-deserializer.h"
#include "src/snapshot/startup-serializer.h"
namespace v8 {
namespace internal {
namespace {
class V8_NODISCARD SanitizeNativeContextScope final {
public:
SanitizeNativeContextScope(Isolate* isolate,
Tagged<NativeContext> native_context,
bool allow_active_isolate_for_testing,
const DisallowGarbageCollection& no_gc)
: native_context_(native_context), no_gc_(no_gc) {
#ifdef DEBUG
if (!allow_active_isolate_for_testing) {
MicrotaskQueue* microtask_queue = native_context_->microtask_queue();
DCHECK_EQ(0, microtask_queue->size());
DCHECK(!microtask_queue->HasMicrotasksSuppressions());
DCHECK_EQ(0, microtask_queue->GetMicrotasksScopeDepth());
DCHECK(microtask_queue->DebugMicrotasksScopeDepthIsZero());
}
#endif
microtask_queue_external_pointer_ =
native_context
->RawExternalPointerField(NativeContext::kMicrotaskQueueOffset,
kNativeContextMicrotaskQueueTag)
.GetAndClearContentForSerialization(no_gc);
}
~SanitizeNativeContextScope() {
native_context_
->RawExternalPointerField(NativeContext::kMicrotaskQueueOffset,
kNativeContextMicrotaskQueueTag)
.RestoreContentAfterSerialization(microtask_queue_external_pointer_,
no_gc_);
}
private:
Tagged<NativeContext> native_context_;
ExternalPointerSlot::RawContent microtask_queue_external_pointer_;
const DisallowGarbageCollection& no_gc_;
};
}
ContextSerializer::ContextSerializer(Isolate* isolate,
Snapshot::SerializerFlags flags,
StartupSerializer* startup_serializer,
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(Tagged<Context>* o,
const DisallowGarbageCollection& no_gc) {
context_ = *o;
DCHECK(IsNativeContext(context_));
reference_map()->AddAttachedReference(context_->global_proxy());
reference_map()->AddAttachedReference(context_->global_proxy()->map());
DCHECK(!IsUndefined(context_->global_object()));
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();
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");
}
if (!api_wrapper_sink_.data()->empty()) {
sink_.Put(kApiWrapperFieldsData, "api wrapper fields data");
sink_.Append(api_wrapper_sink_);
sink_.Put(kSynchronize, "Finished with api wrapper fields data");
}
Pad();
}
v8::StartupData InternalFieldSerializeWrapper(
int index, bool field_is_nullptr,
v8::SerializeInternalFieldsCallback user_callback,
v8::Local<v8::Object> api_obj) {
if (user_callback.callback == nullptr && field_is_nullptr) {
return StartupData{nullptr, 0};
}
DCHECK(user_callback.callback);
return user_callback.callback(api_obj, index, user_callback.data);
}
v8::StartupData ContextDataSerializeWrapper(
int index, bool field_is_nullptr,
v8::SerializeContextDataCallback user_callback,
v8::Local<v8::Context> api_obj) {
if (user_callback.callback == nullptr) {
return StartupData{nullptr, 0};
}
return user_callback.callback(api_obj, index, user_callback.data);
}
void ContextSerializer::SerializeObjectImpl(Handle<HeapObject> obj,
SlotType slot_type) {
DCHECK(!ObjectIsBytecodeHandler(*obj));
if (!allow_active_isolate_for_testing()) {
DCHECK_IMPLIES(IsNativeContext(*obj), *obj == context_);
}
{
DisallowGarbageCollection no_gc;
Tagged<HeapObject> raw = *obj;
if (SerializeHotObject(raw)) return;
if (SerializeRoot(raw)) return;
if (SerializeBackReference(raw)) return;
if (SerializeReadOnlyObjectReference(raw, &sink_)) return;
}
if (startup_serializer_->SerializeUsingSharedHeapObjectCache(&sink_, obj)) {
return;
}
if (ShouldBeInTheStartupObjectCache(*obj)) {
startup_serializer_->SerializeUsingStartupObjectCache(&sink_, obj);
return;
}
DCHECK(!startup_serializer_->ReferenceMapContains(obj));
DCHECK(!IsInternalizedString(*obj));
DCHECK(!IsTemplateInfo(*obj));
InstanceType instance_type = obj->map()->instance_type();
if (InstanceTypeChecker::IsFeedbackVector(instance_type)) {
Cast<FeedbackVector>(obj)->ClearSlots(isolate());
} else if (InstanceTypeChecker::IsJSObject(instance_type)) {
Handle<JSObject> js_obj = Cast<JSObject>(obj);
int embedder_fields_count = js_obj->GetEmbedderFieldCount();
if (embedder_fields_count > 0) {
DCHECK(!js_obj->NeedsRehashing(cage_base()));
v8::Local<v8::Object> api_obj = v8::Utils::ToLocal(js_obj);
v8::SerializeInternalFieldsCallback user_callback =
serialize_embedder_fields_.js_object_callback;
SerializeObjectWithEmbedderFields(js_obj, embedder_fields_count,
InternalFieldSerializeWrapper,
user_callback, api_obj);
if (IsJSApiWrapperObject(*js_obj)) {
SerializeApiWrapperFields(js_obj);
}
return;
}
if (InstanceTypeChecker::IsJSFunction(instance_type)) {
DisallowGarbageCollection no_gc;
Tagged<JSFunction> closure = Cast<JSFunction>(*obj);
if (closure->shared()->HasBytecodeArray()) {
closure->SetInterruptBudget(isolate(), BudgetModification::kReset);
}
closure->ResetIfCodeFlushed(isolate());
if (closure->is_compiled(isolate())) {
if (closure->shared()->HasBaselineCode()) {
closure->shared()->FlushBaselineCode();
}
Tagged<Code> sfi_code = closure->shared()->GetCode(isolate());
if (!sfi_code.SafeEquals(closure->code(isolate()))) {
closure->UpdateCode(isolate(), sfi_code);
}
}
}
} else if (InstanceTypeChecker::IsEmbedderDataArray(instance_type) &&
!allow_active_isolate_for_testing()) {
DCHECK_EQ(*obj, context_->embedder_data());
Handle<EmbedderDataArray> embedder_data = Cast<EmbedderDataArray>(obj);
int embedder_fields_count = embedder_data->length();
if (embedder_data->length() > 0) {
DirectHandle<Context> context_handle(context_, isolate());
v8::Local<v8::Context> api_obj =
v8::Utils::ToLocal(Cast<NativeContext>(context_handle));
v8::SerializeContextDataCallback user_callback =
serialize_embedder_fields_.context_callback;
SerializeObjectWithEmbedderFields(embedder_data, embedder_fields_count,
ContextDataSerializeWrapper,
user_callback, api_obj);
return;
}
}
CheckRehashability(*obj);
ObjectSerializer serializer(this, obj, &sink_);
serializer.Serialize(slot_type);
if (InstanceTypeChecker::IsJSApiWrapperObject(instance_type)) {
SerializeApiWrapperFields(Cast<JSObject>(obj));
}
}
bool ContextSerializer::ShouldBeInTheStartupObjectCache(Tagged<HeapObject> o) {
return IsName(o) || IsScript(o) || IsSharedFunctionInfo(o) ||
IsHeapNumber(o) || IsCode(o) || IsInstructionStream(o) ||
IsScopeInfo(o) || IsAccessorInfo(o) || IsTemplateInfo(o) ||
IsClassPositions(o) ||
o->map() == ReadOnlyRoots(isolate()).fixed_cow_array_map();
}
namespace {
bool DataIsEmpty(const StartupData& data) { return data.raw_size == 0; }
}
void ContextSerializer::SerializeApiWrapperFields(
DirectHandle<JSObject> js_object) {
DCHECK(IsJSApiWrapperObject(*js_object));
auto* cpp_heap_pointer =
CppHeapObjectWrapper(*js_object)
.GetCppHeapWrappable(isolate(), kAnyCppHeapPointer);
const auto& callback_data = serialize_embedder_fields_.api_wrapper_callback;
if (callback_data.callback == nullptr && cpp_heap_pointer == nullptr) {
return;
}
DCHECK_NOT_NULL(callback_data.callback);
const auto data = callback_data.callback(
v8::Utils::ToLocal(js_object), cpp_heap_pointer, callback_data.data);
if (DataIsEmpty(data)) {
return;
}
const SerializerReference* reference =
reference_map()->LookupReference(*js_object);
DCHECK_NOT_NULL(reference);
DCHECK(reference->is_back_reference());
api_wrapper_sink_.Put(kNewObject, "api wrapper field holder");
api_wrapper_sink_.PutUint30(reference->back_ref_index(), "BackRefIndex");
api_wrapper_sink_.PutUint30(data.raw_size, "api wrapper raw field data size");
api_wrapper_sink_.PutRaw(reinterpret_cast<const uint8_t*>(data.data),
data.raw_size, "api wrapper raw field data");
}
template <typename V8Type, typename UserSerializerWrapper,
typename UserCallback, typename ApiObjectType>
void ContextSerializer::SerializeObjectWithEmbedderFields(
Handle<V8Type> data_holder, int embedder_fields_count,
UserSerializerWrapper wrapper, UserCallback user_callback,
ApiObjectType api_obj) {
DisallowGarbageCollection no_gc;
CHECK_GT(embedder_fields_count, 0);
DisallowJavascriptExecution no_js(isolate());
DisallowCompilation no_compile(isolate());
auto raw_obj = *data_holder;
std::vector<EmbedderDataSlot::RawData> original_embedder_values;
std::vector<StartupData> serialized_data;
std::vector<bool> should_clear_slot;
for (int i = 0; i < embedder_fields_count; i++) {
EmbedderDataSlot slot(raw_obj, i);
original_embedder_values.emplace_back(slot.load_raw(isolate(), no_gc));
Tagged<Object> object = slot.load_tagged();
if (IsHeapObject(object)) {
DCHECK(IsValidHeapObject(isolate()->heap(), Cast<HeapObject>(object)));
serialized_data.push_back({nullptr, 0});
should_clear_slot.push_back(false);
} else {
StartupData data =
wrapper(i, object == Smi::zero(), user_callback, api_obj);
serialized_data.push_back(data);
bool clear_slot =
!DataIsEmpty(data) || slot.MustClearDuringSerialization(no_gc);
should_clear_slot.push_back(clear_slot);
}
}
for (int i = 0; i < embedder_fields_count; i++) {
if (should_clear_slot[i]) {
EmbedderDataSlot(raw_obj, i).store_raw(isolate(), kNullAddress, no_gc);
}
}
{
AllowGarbageCollection allow_gc;
ObjectSerializer(this, data_holder, &sink_).Serialize(SlotType::kAnySlot);
raw_obj = *data_holder;
}
const SerializerReference* reference =
reference_map()->LookupReference(raw_obj);
DCHECK_NOT_NULL(reference);
DCHECK(reference->is_back_reference());
for (int i = 0; i < embedder_fields_count; i++) {
StartupData data = serialized_data[i];
if (!should_clear_slot[i]) continue;
EmbedderDataSlot(raw_obj, i)
.store_raw(isolate(), original_embedder_values[i], no_gc);
if (DataIsEmpty(data)) continue;
embedder_fields_sink_.Put(kNewObject, "embedder field holder");
embedder_fields_sink_.PutUint30(reference->back_ref_index(),
"BackRefIndex");
embedder_fields_sink_.PutUint30(i, "embedder field index");
embedder_fields_sink_.PutUint30(data.raw_size, "embedder fields data size");
embedder_fields_sink_.PutRaw(reinterpret_cast<const uint8_t*>(data.data),
data.raw_size, "embedder fields data");
delete[] data.data;
}
}
void ContextSerializer::CheckRehashability(Tagged<HeapObject> obj) {
if (!can_be_rehashed_) return;
if (!obj->NeedsRehashing(cage_base())) return;
if (obj->CanBeRehashed(cage_base())) return;
can_be_rehashed_ = false;
}
}
}