[ext-code-space] Support external code space in serializers

... by updating the checks for not_mapped_symbol root and using proper
cage base values depending on the host object type.

Bug: v8:11880
Change-Id: I28908cbb5b1023addaee248028661d480734e29c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3222760
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/main@{#77435}
This commit is contained in:
Igor Sheludko 2021-10-18 16:05:05 +02:00 committed by V8 LUCI CQ
parent 761f892898
commit c0cf4f838d
10 changed files with 97 additions and 44 deletions

View File

@ -1890,6 +1890,7 @@ filegroup(
"src/snapshot/serializer-deserializer.h",
"src/snapshot/serializer.cc",
"src/snapshot/serializer.h",
"src/snapshot/serializer-inl.h",
"src/snapshot/snapshot-compression.cc",
"src/snapshot/snapshot-compression.h",
"src/snapshot/snapshot-data.cc",

View File

@ -3334,6 +3334,7 @@ v8_header_set("v8_internal_headers") {
"src/snapshot/references.h",
"src/snapshot/roots-serializer.h",
"src/snapshot/serializer-deserializer.h",
"src/snapshot/serializer-inl.h",
"src/snapshot/serializer.h",
"src/snapshot/shared-heap-deserializer.h",
"src/snapshot/shared-heap-serializer.h",

View File

@ -844,9 +844,9 @@ void UncompiledData::InitAfterBytecodeFlush(
set_end_position(end_position);
}
HeapObject SharedFunctionInfo::script() const {
HeapObject maybe_script = script_or_debug_info(kAcquireLoad);
if (maybe_script.IsDebugInfo()) {
DEF_GETTER(SharedFunctionInfo, script, HeapObject) {
HeapObject maybe_script = script_or_debug_info(cage_base, kAcquireLoad);
if (maybe_script.IsDebugInfo(cage_base)) {
return DebugInfo::cast(maybe_script).script();
}
return maybe_script;

View File

@ -378,7 +378,7 @@ class SharedFunctionInfo
// - a DebugInfo which holds the actual script [HasDebugInfo()].
DECL_RELEASE_ACQUIRE_ACCESSORS(script_or_debug_info, HeapObject)
inline HeapObject script() const;
DECL_GETTER(script, HeapObject)
inline void set_script(HeapObject script);
// True if the underlying script was parsed and compiled in REPL mode.

View File

@ -11,6 +11,7 @@
#include "src/heap/read-only-heap.h"
#include "src/objects/objects-inl.h"
#include "src/objects/slots.h"
#include "src/snapshot/serializer-inl.h"
#include "src/snapshot/startup-serializer.h"
namespace v8 {
@ -40,7 +41,7 @@ void ReadOnlySerializer::SerializeObjectImpl(Handle<HeapObject> obj) {
// in the root table, so don't try to serialize a reference and rely on the
// below CHECK(!did_serialize_not_mapped_symbol_) to make sure it doesn't
// serialize twice.
if (*obj != ReadOnlyRoots(isolate()).not_mapped_symbol()) {
if (!IsNotMappedSymbol(*obj)) {
if (SerializeHotObject(obj)) return;
if (IsRootAndHasBeenSerialized(*obj) && SerializeRoot(obj)) return;
if (SerializeBackReference(obj)) return;
@ -52,7 +53,7 @@ void ReadOnlySerializer::SerializeObjectImpl(Handle<HeapObject> obj) {
ObjectSerializer object_serializer(this, obj, &sink_);
object_serializer.Serialize();
#ifdef DEBUG
if (*obj == ReadOnlyRoots(isolate()).not_mapped_symbol()) {
if (IsNotMappedSymbol(*obj)) {
CHECK(!did_serialize_not_mapped_symbol_);
did_serialize_not_mapped_symbol_ = true;
} else {
@ -94,7 +95,7 @@ void ReadOnlySerializer::FinalizeSerialization() {
ReadOnlyHeapObjectIterator iterator(isolate()->read_only_heap());
for (HeapObject object = iterator.Next(); !object.is_null();
object = iterator.Next()) {
if (object == ReadOnlyRoots(isolate()).not_mapped_symbol()) {
if (IsNotMappedSymbol(object)) {
CHECK(did_serialize_not_mapped_symbol_);
} else {
CHECK_NOT_NULL(serialized_objects_.Find(object));

View File

@ -0,0 +1,32 @@
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_SNAPSHOT_SERIALIZER_INL_H_
#define V8_SNAPSHOT_SERIALIZER_INL_H_
#include "src/roots/roots-inl.h"
#include "src/snapshot/serializer.h"
namespace v8 {
namespace internal {
bool Serializer::IsNotMappedSymbol(HeapObject obj) const {
Object not_mapped_symbol = ReadOnlyRoots(isolate()).not_mapped_symbol();
if (V8_EXTERNAL_CODE_SPACE_BOOL) {
// It's possible that a Code object might have the same compressed value
// as the not_mapped_symbol, so we must compare full pointers.
// TODO(v8:11880): Avoid the need for this special case by never putting
// Code references anywhere except the CodeDadaContainer objects.
// In particular, the Code objects should not appear in serializer's
// identity map. This should be possible once the IsolateData::builtins
// table is migrated to contain CodeT references.
return obj.ptr() == not_mapped_symbol.ptr();
}
return obj == not_mapped_symbol;
}
} // namespace internal
} // namespace v8
#endif // V8_SNAPSHOT_SERIALIZER_INL_H_

View File

@ -19,6 +19,7 @@
#include "src/objects/slots-inl.h"
#include "src/objects/smi.h"
#include "src/snapshot/serializer-deserializer.h"
#include "src/snapshot/serializer-inl.h"
namespace v8 {
namespace internal {
@ -343,7 +344,7 @@ ExternalReferenceEncoder::Value Serializer::EncodeExternalReference(
}
void Serializer::RegisterObjectIsPending(Handle<HeapObject> obj) {
if (*obj == ReadOnlyRoots(isolate()).not_mapped_symbol()) return;
if (IsNotMappedSymbol(*obj)) return;
// Add the given object to the pending objects -> forward refs map.
auto find_result = forward_refs_per_pending_object_.FindOrInsert(obj);
@ -358,7 +359,7 @@ void Serializer::RegisterObjectIsPending(Handle<HeapObject> obj) {
}
void Serializer::ResolvePendingObject(Handle<HeapObject> obj) {
if (*obj == ReadOnlyRoots(isolate()).not_mapped_symbol()) return;
if (IsNotMappedSymbol(*obj)) return;
std::vector<int>* refs;
CHECK(forward_refs_per_pending_object_.Delete(obj, &refs));
@ -435,7 +436,7 @@ void Serializer::ObjectSerializer::SerializePrologue(SnapshotSpace space,
// Make sure the map serialization didn't accidentally recursively serialize
// this object.
DCHECK_IMPLIES(
*object_ != ReadOnlyRoots(isolate()).not_mapped_symbol(),
!serializer_->IsNotMappedSymbol(*object_),
serializer_->reference_map()->LookupReference(object_) == nullptr);
// Now that the object is allocated, we can resolve pending references to
@ -454,7 +455,7 @@ void Serializer::ObjectSerializer::SerializePrologue(SnapshotSpace space,
serializer_->back_refs_.Push(*object_);
DCHECK_EQ(serializer_->back_refs_.size(), serializer_->num_back_refs_);
#endif
if (*object_ != ReadOnlyRoots(isolate()).not_mapped_symbol()) {
if (!serializer_->IsNotMappedSymbol(*object_)) {
// Only add the object to the map if it's not not_mapped_symbol, else
// the reference IdentityMap has issues. We don't expect to have back
// references to the not_mapped_symbol anyway, so it's fine.
@ -575,7 +576,8 @@ void Serializer::ObjectSerializer::SerializeExternalStringAsSequentialString() {
// Instead of serializing this as an external string, we serialize
// an imaginary sequential string with the same content.
ReadOnlyRoots roots(isolate());
DCHECK(object_->IsExternalString());
PtrComprCageBase cage_base(isolate());
DCHECK(object_->IsExternalString(cage_base));
Handle<ExternalString> string = Handle<ExternalString>::cast(object_);
int length = string->length();
Map map;
@ -583,8 +585,8 @@ void Serializer::ObjectSerializer::SerializeExternalStringAsSequentialString() {
int allocation_size;
const byte* resource;
// Find the map and size for the imaginary sequential string.
bool internalized = object_->IsInternalizedString();
if (object_->IsExternalOneByteString()) {
bool internalized = object_->IsInternalizedString(cage_base);
if (object_->IsExternalOneByteString(cage_base)) {
map = internalized ? roots.one_byte_internalized_string_map()
: roots.one_byte_string_map();
allocation_size = SeqOneByteString::SizeFor(length);
@ -683,32 +685,33 @@ void Serializer::ObjectSerializer::Serialize() {
PrintF("\n");
}
if (object_->IsExternalString()) {
PtrComprCageBase cage_base(isolate());
if (object_->IsExternalString(cage_base)) {
SerializeExternalString();
return;
} else if (!ReadOnlyHeap::Contains(*object_)) {
// Only clear padding for strings outside the read-only heap. Read-only heap
// should have been cleared elsewhere.
if (object_->IsSeqOneByteString()) {
if (object_->IsSeqOneByteString(cage_base)) {
// Clear padding bytes at the end. Done here to avoid having to do this
// at allocation sites in generated code.
Handle<SeqOneByteString>::cast(object_)->clear_padding();
} else if (object_->IsSeqTwoByteString()) {
} else if (object_->IsSeqTwoByteString(cage_base)) {
Handle<SeqTwoByteString>::cast(object_)->clear_padding();
}
}
if (object_->IsJSTypedArray()) {
if (object_->IsJSTypedArray(cage_base)) {
SerializeJSTypedArray();
return;
} else if (object_->IsJSArrayBuffer()) {
} else if (object_->IsJSArrayBuffer(cage_base)) {
SerializeJSArrayBuffer();
return;
}
// We don't expect fillers.
DCHECK(!object_->IsFreeSpaceOrFiller());
DCHECK(!object_->IsFreeSpaceOrFiller(cage_base));
if (object_->IsScript()) {
if (object_->IsScript(cage_base)) {
// Clear cached line ends.
Oddball undefined = ReadOnlyRoots(isolate()).undefined_value();
Handle<Script>::cast(object_)->set_line_ends(undefined);
@ -827,11 +830,12 @@ void Serializer::ObjectSerializer::VisitPointers(HeapObject host,
MaybeObjectSlot start,
MaybeObjectSlot end) {
HandleScope scope(isolate());
PtrComprCageBase cage_base(isolate());
DisallowGarbageCollection no_gc;
MaybeObjectSlot current = start;
while (current < end) {
while (current < end && (*current)->IsSmi()) {
while (current < end && current.load(cage_base).IsSmi()) {
++current;
}
if (current < end) {
@ -839,15 +843,15 @@ void Serializer::ObjectSerializer::VisitPointers(HeapObject host,
}
// TODO(ishell): Revisit this change once we stick to 32-bit compressed
// tagged values.
while (current < end && (*current)->IsCleared()) {
while (current < end && current.load(cage_base).IsCleared()) {
sink_->Put(kClearedWeakReference, "ClearedWeakReference");
bytes_processed_so_far_ += kTaggedSize;
++current;
}
HeapObject current_contents;
HeapObjectReferenceType reference_type;
while (current < end &&
(*current)->GetHeapObject(&current_contents, &reference_type)) {
while (current < end && current.load(cage_base).GetHeapObject(
&current_contents, &reference_type)) {
// Write a weak prefix if we need it. This has to be done before the
// potential pending object serialization.
if (reference_type == HeapObjectReferenceType::WEAK) {
@ -896,8 +900,11 @@ void Serializer::ObjectSerializer::VisitCodePointer(HeapObject host,
HandleScope scope(isolate());
DisallowGarbageCollection no_gc;
// TODO(v8:11880): support external code space.
PtrComprCageBase code_cage_base = GetPtrComprCageBase(host);
#if V8_EXTERNAL_CODE_SPACE
PtrComprCageBase code_cage_base(isolate()->code_cage_base());
#else
PtrComprCageBase code_cage_base(isolate());
#endif
Object contents = slot.load(code_cage_base);
DCHECK(HAS_STRONG_HEAP_OBJECT_TAG(contents.ptr()));
DCHECK(contents.IsCode());
@ -1106,13 +1113,14 @@ void Serializer::ObjectSerializer::OutputRawData(Address up_to) {
__msan_check_mem_is_initialized(
reinterpret_cast<void*>(object_start + base), bytes_to_output);
#endif // MEMORY_SANITIZER
if (object_->IsBytecodeArray()) {
PtrComprCageBase cage_base(isolate_);
if (object_->IsBytecodeArray(cage_base)) {
// The bytecode age field can be changed by GC concurrently.
byte field_value = BytecodeArray::kNoAgeBytecodeAge;
OutputRawWithCustomField(sink_, object_start, base, bytes_to_output,
BytecodeArray::kBytecodeAgeOffset,
sizeof(field_value), &field_value);
} else if (object_->IsDescriptorArray()) {
} else if (object_->IsDescriptorArray(cage_base)) {
// The number of marked descriptors field can be changed by GC
// concurrently.
static byte field_value[2] = {0};
@ -1120,7 +1128,8 @@ void Serializer::ObjectSerializer::OutputRawData(Address up_to) {
sink_, object_start, base, bytes_to_output,
DescriptorArray::kRawNumberOfMarkedDescriptorsOffset,
sizeof(field_value), field_value);
} else if (V8_EXTERNAL_CODE_SPACE_BOOL && object_->IsCodeDataContainer()) {
} else if (V8_EXTERNAL_CODE_SPACE_BOOL &&
object_->IsCodeDataContainer(cage_base)) {
// The CodeEntryPoint field is just a cached value which will be
// recomputed after deserialization, so write zeros to keep the snapshot
// deterministic.

View File

@ -204,6 +204,10 @@ class Serializer : public SerializerDeserializer {
Serializer* serializer_;
};
// Compares obj with not_mapped_symbol root. When V8_EXTERNAL_CODE_SPACE is
// enabled it compares full pointers.
V8_INLINE bool IsNotMappedSymbol(HeapObject obj) const;
void SerializeDeferredObjects();
void SerializeObject(Handle<HeapObject> o);
virtual void SerializeObjectImpl(Handle<HeapObject> o) = 0;

View File

@ -222,6 +222,7 @@ MaybeHandle<Context> Snapshot::NewContextFromSnapshot(
void Snapshot::ClearReconstructableDataForSerialization(
Isolate* isolate, bool clear_recompilable_data) {
// Clear SFIs and JSRegExps.
PtrComprCageBase cage_base(isolate);
if (clear_recompilable_data) {
HandleScope scope(isolate);
@ -231,16 +232,17 @@ void Snapshot::ClearReconstructableDataForSerialization(
DisallowGarbageCollection disallow_gc;
i::HeapObjectIterator it(isolate->heap());
for (i::HeapObject o = it.Next(); !o.is_null(); o = it.Next()) {
if (o.IsSharedFunctionInfo()) {
if (o.IsSharedFunctionInfo(cage_base)) {
i::SharedFunctionInfo shared = i::SharedFunctionInfo::cast(o);
if (shared.script().IsScript() &&
Script::cast(shared.script()).type() == Script::TYPE_EXTENSION) {
if (shared.script(cage_base).IsScript(cage_base) &&
Script::cast(shared.script(cage_base)).type() ==
Script::TYPE_EXTENSION) {
continue; // Don't clear extensions, they cannot be recompiled.
}
if (shared.CanDiscardCompiled()) {
sfis_to_clear.emplace_back(shared, isolate);
}
} else if (o.IsJSRegExp()) {
} else if (o.IsJSRegExp(cage_base)) {
i::JSRegExp regexp = i::JSRegExp::cast(o);
if (regexp.HasCompiledCode()) {
regexp.DiscardCompiledCodeForSerialization();
@ -261,14 +263,15 @@ void Snapshot::ClearReconstructableDataForSerialization(
i::HeapObjectIterator it(isolate->heap());
for (i::HeapObject o = it.Next(); !o.is_null(); o = it.Next()) {
if (!o.IsJSFunction()) continue;
if (!o.IsJSFunction(cage_base)) continue;
i::JSFunction fun = i::JSFunction::cast(o);
fun.CompleteInobjectSlackTrackingIfActive();
i::SharedFunctionInfo shared = fun.shared();
if (shared.script().IsScript() &&
Script::cast(shared.script()).type() == Script::TYPE_EXTENSION) {
if (shared.script(cage_base).IsScript(cage_base) &&
Script::cast(shared.script(cage_base)).type() ==
Script::TYPE_EXTENSION) {
continue; // Don't clear extensions, they cannot be recompiled.
}
@ -276,8 +279,8 @@ void Snapshot::ClearReconstructableDataForSerialization(
if (fun.CanDiscardCompiled()) {
fun.set_code(*BUILTIN_CODE(isolate, CompileLazy));
}
if (!fun.raw_feedback_cell().value().IsUndefined()) {
fun.raw_feedback_cell().set_value(
if (!fun.raw_feedback_cell(cage_base).value(cage_base).IsUndefined()) {
fun.raw_feedback_cell(cage_base).set_value(
i::ReadOnlyRoots(isolate).undefined_value());
}
#ifdef DEBUG

View File

@ -134,8 +134,9 @@ bool IsUnexpectedCodeObject(Isolate* isolate, HeapObject obj) {
#endif // DEBUG
void StartupSerializer::SerializeObjectImpl(Handle<HeapObject> obj) {
PtrComprCageBase cage_base(isolate());
#ifdef DEBUG
if (obj->IsJSFunction()) {
if (obj->IsJSFunction(cage_base)) {
v8::base::OS::PrintError("Reference stack:\n");
PrintStack(std::cerr);
obj->Print(std::cerr);
@ -157,7 +158,7 @@ void StartupSerializer::SerializeObjectImpl(Handle<HeapObject> obj) {
use_simulator = true;
#endif
if (use_simulator && obj->IsAccessorInfo()) {
if (use_simulator && obj->IsAccessorInfo(cage_base)) {
// Wipe external reference redirects in the accessor info.
Handle<AccessorInfo> info = Handle<AccessorInfo>::cast(obj);
Address original_address =
@ -165,17 +166,18 @@ void StartupSerializer::SerializeObjectImpl(Handle<HeapObject> obj) {
Foreign::cast(info->js_getter())
.set_foreign_address(isolate(), original_address);
accessor_infos_.Push(*info);
} else if (use_simulator && obj->IsCallHandlerInfo()) {
} else if (use_simulator && obj->IsCallHandlerInfo(cage_base)) {
Handle<CallHandlerInfo> info = Handle<CallHandlerInfo>::cast(obj);
Address original_address =
Foreign::cast(info->callback()).foreign_address(isolate());
Foreign::cast(info->js_callback())
.set_foreign_address(isolate(), original_address);
call_handler_infos_.Push(*info);
} else if (obj->IsScript() && Handle<Script>::cast(obj)->IsUserJavaScript()) {
} else if (obj->IsScript(cage_base) &&
Handle<Script>::cast(obj)->IsUserJavaScript()) {
Handle<Script>::cast(obj)->set_context_data(
ReadOnlyRoots(isolate()).uninitialized_symbol());
} else if (obj->IsSharedFunctionInfo()) {
} else if (obj->IsSharedFunctionInfo(cage_base)) {
// Clear inferred name for native functions.
Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>::cast(obj);
if (!shared->IsSubjectToDebugging() && shared->HasUncompiledData()) {