Commit 200dcb05 authored by Ben L. Titzer's avatar Ben L. Titzer Committed by Commit Bot

Add size estimate to Managed<T>

Extends the functionality of Managed<T> to track an estimated size
for the external memory associated with an instance of Managed<T>
in order to allow for proper accounting in the garbage collector.

R=mstarzinger@chromium.org
CC=ulan@chromium.org

Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng
Change-Id: I8c49c6245eaf267c9264ebb93b43d5dfbf4671fd
Reviewed-on: https://chromium-review.googlesource.com/1076332
Commit-Queue: Ben Titzer <titzer@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53433}
parent 70b5fd3b
...@@ -952,7 +952,7 @@ bool Collator::InitializeCollator(Isolate* isolate, ...@@ -952,7 +952,7 @@ bool Collator::InitializeCollator(Isolate* isolate,
} }
Handle<Managed<icu::Collator>> managed = Handle<Managed<icu::Collator>> managed =
Managed<icu::Collator>::FromRawPtr(isolate, collator); Managed<icu::Collator>::FromRawPtr(isolate, 0, collator);
collator_holder->SetEmbedderField(0, *managed); collator_holder->SetEmbedderField(0, *managed);
return true; return true;
......
...@@ -13,8 +13,10 @@ namespace { ...@@ -13,8 +13,10 @@ namespace {
void ManagedObjectFinalizerSecondPass(const v8::WeakCallbackInfo<void>& data) { void ManagedObjectFinalizerSecondPass(const v8::WeakCallbackInfo<void>& data) {
auto destructor = auto destructor =
reinterpret_cast<ManagedPtrDestructor*>(data.GetParameter()); reinterpret_cast<ManagedPtrDestructor*>(data.GetParameter());
int64_t adjustment = 0 - static_cast<int64_t>(destructor->estimated_size_);
destructor->destructor_(destructor->shared_ptr_ptr_); destructor->destructor_(destructor->shared_ptr_ptr_);
delete destructor; delete destructor;
data.GetIsolate()->AdjustAmountOfExternalAllocatedMemory(adjustment);
} }
} // namespace } // namespace
......
...@@ -16,14 +16,21 @@ namespace internal { ...@@ -16,14 +16,21 @@ namespace internal {
// Implements a doubly-linked lists of destructors for the isolate. // Implements a doubly-linked lists of destructors for the isolate.
struct ManagedPtrDestructor { struct ManagedPtrDestructor {
// Estimated size of external memory associated with the managed object.
// This is used to adjust the garbage collector's heuristics upon
// allocation and deallocation of a managed object.
size_t estimated_size_ = 0;
ManagedPtrDestructor* prev_ = nullptr; ManagedPtrDestructor* prev_ = nullptr;
ManagedPtrDestructor* next_ = nullptr; ManagedPtrDestructor* next_ = nullptr;
void* shared_ptr_ptr_ = nullptr; void* shared_ptr_ptr_ = nullptr;
void (*destructor_)(void* shared_ptr) = nullptr; void (*destructor_)(void* shared_ptr) = nullptr;
Object** global_handle_location_ = nullptr; Object** global_handle_location_ = nullptr;
ManagedPtrDestructor(void* shared_ptr_ptr, void (*destructor)(void*)) ManagedPtrDestructor(size_t estimated_size, void* shared_ptr_ptr,
: shared_ptr_ptr_(shared_ptr_ptr), destructor_(destructor) {} void (*destructor)(void*))
: estimated_size_(estimated_size),
shared_ptr_ptr_(shared_ptr_ptr),
destructor_(destructor) {}
}; };
// The GC finalizer of a managed object, which does not depend on // The GC finalizer of a managed object, which does not depend on
...@@ -53,30 +60,40 @@ class Managed : public Foreign { ...@@ -53,30 +60,40 @@ class Managed : public Foreign {
// Allocate a new {CppType} and wrap it in a {Managed<CppType>}. // Allocate a new {CppType} and wrap it in a {Managed<CppType>}.
template <typename... Args> template <typename... Args>
static Handle<Managed<CppType>> Allocate(Isolate* isolate, Args&&... args) { static Handle<Managed<CppType>> Allocate(Isolate* isolate,
size_t estimated_size,
Args&&... args) {
CppType* ptr = new CppType(std::forward<Args>(args)...); CppType* ptr = new CppType(std::forward<Args>(args)...);
return FromSharedPtr(isolate, std::shared_ptr<CppType>(ptr)); return FromSharedPtr(isolate, estimated_size,
std::shared_ptr<CppType>(ptr));
} }
// Create a {Managed<CppType>} from an existing raw {CppType*}. The returned // Create a {Managed<CppType>} from an existing raw {CppType*}. The returned
// object will now own the memory pointed to by {CppType}. // object will now own the memory pointed to by {CppType}.
static Handle<Managed<CppType>> FromRawPtr(Isolate* isolate, CppType* ptr) { static Handle<Managed<CppType>> FromRawPtr(Isolate* isolate,
return FromSharedPtr(isolate, std::shared_ptr<CppType>(ptr)); size_t estimated_size,
CppType* ptr) {
return FromSharedPtr(isolate, estimated_size,
std::shared_ptr<CppType>(ptr));
} }
// Create a {Managed<CppType>} from an existing {std::unique_ptr<CppType>}. // Create a {Managed<CppType>} from an existing {std::unique_ptr<CppType>}.
// The returned object will now own the memory pointed to by {CppType}, and // The returned object will now own the memory pointed to by {CppType}, and
// the unique pointer will be released. // the unique pointer will be released.
static Handle<Managed<CppType>> FromUniquePtr( static Handle<Managed<CppType>> FromUniquePtr(
Isolate* isolate, std::unique_ptr<CppType> unique_ptr) { Isolate* isolate, size_t estimated_size,
return FromSharedPtr(isolate, std::move(unique_ptr)); std::unique_ptr<CppType> unique_ptr) {
return FromSharedPtr(isolate, estimated_size, std::move(unique_ptr));
} }
// Create a {Managed<CppType>} from an existing {std::shared_ptr<CppType>}. // Create a {Managed<CppType>} from an existing {std::shared_ptr<CppType>}.
static Handle<Managed<CppType>> FromSharedPtr( static Handle<Managed<CppType>> FromSharedPtr(
Isolate* isolate, std::shared_ptr<CppType> shared_ptr) { Isolate* isolate, size_t estimated_size,
std::shared_ptr<CppType> shared_ptr) {
reinterpret_cast<v8::Isolate*>(isolate)
->AdjustAmountOfExternalAllocatedMemory(estimated_size);
auto destructor = new ManagedPtrDestructor( auto destructor = new ManagedPtrDestructor(
new std::shared_ptr<CppType>(shared_ptr), Destructor); estimated_size, new std::shared_ptr<CppType>(shared_ptr), Destructor);
Handle<Managed<CppType>> handle = Handle<Managed<CppType>>::cast( Handle<Managed<CppType>> handle = Handle<Managed<CppType>>::cast(
isolate->factory()->NewForeign(reinterpret_cast<Address>(destructor))); isolate->factory()->NewForeign(reinterpret_cast<Address>(destructor)));
Handle<Object> global_handle = isolate->global_handles()->Create(*handle); Handle<Object> global_handle = isolate->global_handles()->Create(*handle);
......
...@@ -849,7 +849,7 @@ Address CompileLazy(Isolate* isolate, ...@@ -849,7 +849,7 @@ Address CompileLazy(Isolate* isolate,
if (indirectly_called) { if (indirectly_called) {
DCHECK(!caller_instance.is_null()); DCHECK(!caller_instance.is_null());
if (!caller_instance->has_managed_indirect_patcher()) { if (!caller_instance->has_managed_indirect_patcher()) {
auto patcher = Managed<IndirectPatcher>::Allocate(isolate); auto patcher = Managed<IndirectPatcher>::Allocate(isolate, 0);
caller_instance->set_managed_indirect_patcher(*patcher); caller_instance->set_managed_indirect_patcher(*patcher);
} }
IndirectPatcher* patcher = Managed<IndirectPatcher>::cast( IndirectPatcher* patcher = Managed<IndirectPatcher>::cast(
...@@ -1332,8 +1332,10 @@ MaybeHandle<WasmModuleObject> CompileToModuleObjectInternal( ...@@ -1332,8 +1332,10 @@ MaybeHandle<WasmModuleObject> CompileToModuleObjectInternal(
// The {managed_module} will take ownership of the {WasmModule} object, // The {managed_module} will take ownership of the {WasmModule} object,
// and it will be destroyed when the GC reclaims the wrapper object. // and it will be destroyed when the GC reclaims the wrapper object.
size_t module_size = 0; // TODO(titzer): estimate size of decoded module.
Handle<Managed<WasmModule>> managed_module = Handle<Managed<WasmModule>> managed_module =
Managed<WasmModule>::FromUniquePtr(isolate, std::move(module)); Managed<WasmModule>::FromUniquePtr(isolate, module_size,
std::move(module));
// Create the shared module data. // Create the shared module data.
// TODO(clemensh): For the same module (same bytes / same hash), we should // TODO(clemensh): For the same module (same bytes / same hash), we should
...@@ -2830,8 +2832,10 @@ void AsyncCompileJob::FinishCompile() { ...@@ -2830,8 +2832,10 @@ void AsyncCompileJob::FinishCompile() {
// The {managed_module} will take ownership of the {WasmModule} object, // The {managed_module} will take ownership of the {WasmModule} object,
// and it will be destroyed when the GC reclaims the wrapper object. // and it will be destroyed when the GC reclaims the wrapper object.
size_t module_size = 0; // TODO(titzer): estimate size of decoded module.
Handle<Managed<WasmModule>> managed_module = Handle<Managed<WasmModule>> managed_module =
Managed<WasmModule>::FromUniquePtr(isolate_, std::move(module_)); Managed<WasmModule>::FromUniquePtr(isolate_, module_size,
std::move(module_));
// Create the shared module data. // Create the shared module data.
// TODO(clemensh): For the same module (same bytes / same hash), we should // TODO(clemensh): For the same module (same bytes / same hash), we should
......
...@@ -533,8 +533,9 @@ wasm::InterpreterHandle* GetOrCreateInterpreterHandle( ...@@ -533,8 +533,9 @@ wasm::InterpreterHandle* GetOrCreateInterpreterHandle(
Isolate* isolate, Handle<WasmDebugInfo> debug_info) { Isolate* isolate, Handle<WasmDebugInfo> debug_info) {
Handle<Object> handle(debug_info->interpreter_handle(), isolate); Handle<Object> handle(debug_info->interpreter_handle(), isolate);
if (handle->IsUndefined(isolate)) { if (handle->IsUndefined(isolate)) {
handle = Managed<wasm::InterpreterHandle>::Allocate(isolate, isolate, size_t interpreter_size = 0; // TODO(titzer): estimate size properly.
*debug_info); handle = Managed<wasm::InterpreterHandle>::Allocate(
isolate, interpreter_size, isolate, *debug_info);
debug_info->set_interpreter_handle(*handle); debug_info->set_interpreter_handle(*handle);
} }
...@@ -633,8 +634,9 @@ wasm::WasmInterpreter* WasmDebugInfo::SetupForTesting( ...@@ -633,8 +634,9 @@ wasm::WasmInterpreter* WasmDebugInfo::SetupForTesting(
Handle<WasmInstanceObject> instance_obj) { Handle<WasmInstanceObject> instance_obj) {
Handle<WasmDebugInfo> debug_info = WasmDebugInfo::New(instance_obj); Handle<WasmDebugInfo> debug_info = WasmDebugInfo::New(instance_obj);
Isolate* isolate = instance_obj->GetIsolate(); Isolate* isolate = instance_obj->GetIsolate();
auto interp_handle = size_t interpreter_size = 0; // TODO(titzer): estimate size properly.
Managed<wasm::InterpreterHandle>::Allocate(isolate, isolate, *debug_info); auto interp_handle = Managed<wasm::InterpreterHandle>::Allocate(
isolate, interpreter_size, isolate, *debug_info);
debug_info->set_interpreter_handle(*interp_handle); debug_info->set_interpreter_handle(*interp_handle);
auto ret = interp_handle->raw()->interpreter(); auto ret = interp_handle->raw()->interpreter();
ret->SetCallIndirectTestMode(); ret->SetCallIndirectTestMode();
...@@ -753,7 +755,8 @@ Handle<JSFunction> WasmDebugInfo::GetCWasmEntry( ...@@ -753,7 +755,8 @@ Handle<JSFunction> WasmDebugInfo::GetCWasmEntry(
if (!debug_info->has_c_wasm_entries()) { if (!debug_info->has_c_wasm_entries()) {
auto entries = isolate->factory()->NewFixedArray(4, TENURED); auto entries = isolate->factory()->NewFixedArray(4, TENURED);
debug_info->set_c_wasm_entries(*entries); debug_info->set_c_wasm_entries(*entries);
auto managed_map = Managed<wasm::SignatureMap>::Allocate(isolate); size_t map_size = 0; // size estimate not so important here.
auto managed_map = Managed<wasm::SignatureMap>::Allocate(isolate, map_size);
debug_info->set_c_wasm_entry_map(*managed_map); debug_info->set_c_wasm_entry_map(*managed_map);
} }
Handle<FixedArray> entries(debug_info->c_wasm_entries(), isolate); Handle<FixedArray> entries(debug_info->c_wasm_entries(), isolate);
......
...@@ -782,8 +782,10 @@ Handle<WasmInstanceObject> WasmInstanceObject::New( ...@@ -782,8 +782,10 @@ Handle<WasmInstanceObject> WasmInstanceObject::New(
module_object->shared()->module()->num_imported_functions; module_object->shared()->module()->num_imported_functions;
auto num_imported_mutable_globals = auto num_imported_mutable_globals =
module_object->shared()->module()->num_imported_mutable_globals; module_object->shared()->module()->num_imported_mutable_globals;
size_t native_allocations_size = 0; // TODO(titzer): estimate properly.
auto native_allocations = Managed<WasmInstanceNativeAllocations>::Allocate( auto native_allocations = Managed<WasmInstanceNativeAllocations>::Allocate(
isolate, instance, num_imported_functions, num_imported_mutable_globals); isolate, native_allocations_size, instance, num_imported_functions,
num_imported_mutable_globals);
instance->set_managed_native_allocations(*native_allocations); instance->set_managed_native_allocations(*native_allocations);
Handle<FixedArray> imported_function_instances = Handle<FixedArray> imported_function_instances =
...@@ -1357,8 +1359,10 @@ Handle<WasmCompiledModule> WasmCompiledModule::New(Isolate* isolate, ...@@ -1357,8 +1359,10 @@ Handle<WasmCompiledModule> WasmCompiledModule::New(Isolate* isolate,
{ {
auto native_module = auto native_module =
isolate->wasm_engine()->code_manager()->NewNativeModule(*module, env); isolate->wasm_engine()->code_manager()->NewNativeModule(*module, env);
size_t native_module_size =
0; // TODO(titzer): estimate native module size.
Handle<Foreign> native_module_wrapper = Handle<Foreign> native_module_wrapper =
Managed<wasm::NativeModule>::FromUniquePtr(isolate, Managed<wasm::NativeModule>::FromUniquePtr(isolate, native_module_size,
std::move(native_module)); std::move(native_module));
compiled_module->set_native_module(*native_module_wrapper); compiled_module->set_native_module(*native_module_wrapper);
} }
...@@ -1378,9 +1382,10 @@ Handle<WasmCompiledModule> WasmCompiledModule::Clone( ...@@ -1378,9 +1382,10 @@ Handle<WasmCompiledModule> WasmCompiledModule::Clone(
// construct the wrapper in 2 steps, because its construction may trigger GC, // construct the wrapper in 2 steps, because its construction may trigger GC,
// which would shift the this pointer in set_native_module. // which would shift the this pointer in set_native_module.
size_t native_module_size = 0;
Handle<Foreign> native_module_wrapper = Handle<Foreign> native_module_wrapper =
Managed<wasm::NativeModule>::FromSharedPtr( Managed<wasm::NativeModule>::FromSharedPtr(
isolate, isolate, native_module_size,
Managed<wasm::NativeModule>::cast(module->native_module())->get()); Managed<wasm::NativeModule>::cast(module->native_module())->get());
ret->set_native_module(*native_module_wrapper); ret->set_native_module(*native_module_wrapper);
......
...@@ -552,8 +552,10 @@ MaybeHandle<WasmModuleObject> DeserializeNativeModule( ...@@ -552,8 +552,10 @@ MaybeHandle<WasmModuleObject> DeserializeNativeModule(
DCHECK(module_bytes->IsSeqOneByteString()); DCHECK(module_bytes->IsSeqOneByteString());
// The {managed_module} will take ownership of the {WasmModule} object, // The {managed_module} will take ownership of the {WasmModule} object,
// and it will be destroyed when the GC reclaims the wrapper object. // and it will be destroyed when the GC reclaims the wrapper object.
size_t module_size = 0; // TODO(titzer): estimate size properly.
Handle<Managed<WasmModule>> managed_module = Handle<Managed<WasmModule>> managed_module =
Managed<WasmModule>::FromUniquePtr(isolate, std::move(decode_result.val)); Managed<WasmModule>::FromUniquePtr(isolate, module_size,
std::move(decode_result.val));
Handle<Script> script = CreateWasmScript(isolate, wire_bytes); Handle<Script> script = CreateWasmScript(isolate, wire_bytes);
Handle<WasmSharedModuleData> shared = WasmSharedModuleData::New( Handle<WasmSharedModuleData> shared = WasmSharedModuleData::New(
isolate, managed_module, Handle<SeqOneByteString>::cast(module_bytes), isolate, managed_module, Handle<SeqOneByteString>::cast(module_bytes),
......
...@@ -34,7 +34,7 @@ TEST(GCCausesDestruction) { ...@@ -34,7 +34,7 @@ TEST(GCCausesDestruction) {
DeleteCounter* d2 = new DeleteCounter(&deleted2); DeleteCounter* d2 = new DeleteCounter(&deleted2);
{ {
HandleScope scope(isolate); HandleScope scope(isolate);
auto handle = Managed<DeleteCounter>::FromRawPtr(isolate, d1); auto handle = Managed<DeleteCounter>::FromRawPtr(isolate, 0, d1);
USE(handle); USE(handle);
} }
...@@ -58,7 +58,7 @@ TEST(DisposeCausesDestruction1) { ...@@ -58,7 +58,7 @@ TEST(DisposeCausesDestruction1) {
DeleteCounter* d1 = new DeleteCounter(&deleted1); DeleteCounter* d1 = new DeleteCounter(&deleted1);
{ {
HandleScope scope(i_isolate); HandleScope scope(i_isolate);
auto handle = Managed<DeleteCounter>::FromRawPtr(i_isolate, d1); auto handle = Managed<DeleteCounter>::FromRawPtr(i_isolate, 0, d1);
USE(handle); USE(handle);
} }
isolate->Exit(); isolate->Exit();
...@@ -80,11 +80,11 @@ TEST(DisposeCausesDestruction2) { ...@@ -80,11 +80,11 @@ TEST(DisposeCausesDestruction2) {
DeleteCounter* d2 = new DeleteCounter(&deleted2); DeleteCounter* d2 = new DeleteCounter(&deleted2);
{ {
HandleScope scope(i_isolate); HandleScope scope(i_isolate);
auto handle = Managed<DeleteCounter>::FromRawPtr(i_isolate, d1); auto handle = Managed<DeleteCounter>::FromRawPtr(i_isolate, 0, d1);
USE(handle); USE(handle);
} }
ManagedPtrDestructor* destructor = ManagedPtrDestructor* destructor =
new ManagedPtrDestructor(d2, DeleteCounter::Deleter); new ManagedPtrDestructor(0, d2, DeleteCounter::Deleter);
i_isolate->RegisterManagedPtrDestructor(destructor); i_isolate->RegisterManagedPtrDestructor(destructor);
isolate->Exit(); isolate->Exit();
...@@ -107,7 +107,8 @@ TEST(DisposeWithAnotherSharedPtr) { ...@@ -107,7 +107,8 @@ TEST(DisposeWithAnotherSharedPtr) {
std::shared_ptr<DeleteCounter> shared1(d1); std::shared_ptr<DeleteCounter> shared1(d1);
{ {
HandleScope scope(i_isolate); HandleScope scope(i_isolate);
auto handle = Managed<DeleteCounter>::FromSharedPtr(i_isolate, shared1); auto handle =
Managed<DeleteCounter>::FromSharedPtr(i_isolate, 0, shared1);
USE(handle); USE(handle);
} }
isolate->Exit(); isolate->Exit();
...@@ -132,7 +133,7 @@ TEST(DisposeAcrossIsolates) { ...@@ -132,7 +133,7 @@ TEST(DisposeAcrossIsolates) {
{ {
HandleScope scope1(i_isolate1); HandleScope scope1(i_isolate1);
auto handle1 = auto handle1 =
Managed<DeleteCounter>::FromRawPtr(i_isolate1, delete_counter); Managed<DeleteCounter>::FromRawPtr(i_isolate1, 0, delete_counter);
v8::Isolate* isolate2 = v8::Isolate::New(create_params); v8::Isolate* isolate2 = v8::Isolate::New(create_params);
Isolate* i_isolate2 = reinterpret_cast<i::Isolate*>(isolate2); Isolate* i_isolate2 = reinterpret_cast<i::Isolate*>(isolate2);
...@@ -140,7 +141,7 @@ TEST(DisposeAcrossIsolates) { ...@@ -140,7 +141,7 @@ TEST(DisposeAcrossIsolates) {
{ {
HandleScope scope(i_isolate2); HandleScope scope(i_isolate2);
auto handle2 = auto handle2 =
Managed<DeleteCounter>::FromSharedPtr(i_isolate2, handle1->get()); Managed<DeleteCounter>::FromSharedPtr(i_isolate2, 0, handle1->get());
USE(handle2); USE(handle2);
} }
isolate2->Exit(); isolate2->Exit();
...@@ -167,7 +168,7 @@ TEST(CollectAcrossIsolates) { ...@@ -167,7 +168,7 @@ TEST(CollectAcrossIsolates) {
{ {
HandleScope scope1(i_isolate1); HandleScope scope1(i_isolate1);
auto handle1 = auto handle1 =
Managed<DeleteCounter>::FromRawPtr(i_isolate1, delete_counter); Managed<DeleteCounter>::FromRawPtr(i_isolate1, 0, delete_counter);
v8::Isolate* isolate2 = v8::Isolate::New(create_params); v8::Isolate* isolate2 = v8::Isolate::New(create_params);
Isolate* i_isolate2 = reinterpret_cast<i::Isolate*>(isolate2); Isolate* i_isolate2 = reinterpret_cast<i::Isolate*>(isolate2);
...@@ -175,7 +176,7 @@ TEST(CollectAcrossIsolates) { ...@@ -175,7 +176,7 @@ TEST(CollectAcrossIsolates) {
{ {
HandleScope scope(i_isolate2); HandleScope scope(i_isolate2);
auto handle2 = auto handle2 =
Managed<DeleteCounter>::FromSharedPtr(i_isolate2, handle1->get()); Managed<DeleteCounter>::FromSharedPtr(i_isolate2, 0, handle1->get());
USE(handle2); USE(handle2);
} }
i_isolate2->heap()->CollectAllAvailableGarbage( i_isolate2->heap()->CollectAllAvailableGarbage(
......
...@@ -210,8 +210,9 @@ const WasmGlobal* TestingModuleBuilder::AddGlobal(ValueType type) { ...@@ -210,8 +210,9 @@ const WasmGlobal* TestingModuleBuilder::AddGlobal(ValueType type) {
Handle<WasmInstanceObject> TestingModuleBuilder::InitInstanceObject() { Handle<WasmInstanceObject> TestingModuleBuilder::InitInstanceObject() {
Handle<SeqOneByteString> empty_string = Handle<SeqOneByteString>::cast( Handle<SeqOneByteString> empty_string = Handle<SeqOneByteString>::cast(
isolate_->factory()->NewStringFromOneByte({}).ToHandleChecked()); isolate_->factory()->NewStringFromOneByte({}).ToHandleChecked());
size_t module_size = 0;
auto managed_module = auto managed_module =
Managed<WasmModule>::FromSharedPtr(isolate_, test_module_); Managed<WasmModule>::FromSharedPtr(isolate_, module_size, test_module_);
DCHECK_EQ(test_module_ptr_, managed_module->raw()); DCHECK_EQ(test_module_ptr_, managed_module->raw());
Handle<Script> script = Handle<Script> script =
isolate_->factory()->NewScript(isolate_->factory()->empty_string()); isolate_->factory()->NewScript(isolate_->factory()->empty_string());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment