diff --git a/include/v8-value-serializer-version.h b/include/v8-value-serializer-version.h index 6cd0b0e3c9..25eb19ca75 100644 --- a/include/v8-value-serializer-version.h +++ b/include/v8-value-serializer-version.h @@ -17,7 +17,7 @@ namespace v8 { -constexpr uint32_t CurrentValueSerializerFormatVersion() { return 14; } +constexpr uint32_t CurrentValueSerializerFormatVersion() { return 15; } } // namespace v8 diff --git a/include/v8-value-serializer.h b/include/v8-value-serializer.h index 574567bd5a..078f367c64 100644 --- a/include/v8-value-serializer.h +++ b/include/v8-value-serializer.h @@ -67,6 +67,23 @@ class V8_EXPORT ValueSerializer { virtual Maybe GetWasmModuleTransferId( Isolate* isolate, Local module); + + /** + * Returns whether shared values are supported. GetSharedValueId is only + * called if SupportsSharedValues() returns true. + */ + virtual bool SupportsSharedValues() const; + + /** + * Called when the ValueSerializer serializes a value that is shared across + * Isolates. The embedder must return an ID for the object. This function + * must be idempotent for the same object. When deserializing, the ID will + * be passed to ValueDeserializer::Delegate::GetSharedValueFromId as + * |shared_value_id|. + */ + virtual Maybe GetSharedValueId(Isolate* isolate, + Local shared_value); + /** * Allocates memory for the buffer of at least the size provided. The actual * size (which may be greater or equal) is written to |actual_size|. If no @@ -166,17 +183,30 @@ class V8_EXPORT ValueDeserializer { /** * Get a WasmModuleObject given a transfer_id previously provided - * by ValueSerializer::GetWasmModuleTransferId + * by ValueSerializer::Delegate::GetWasmModuleTransferId */ virtual MaybeLocal GetWasmModuleFromId( Isolate* isolate, uint32_t transfer_id); /** * Get a SharedArrayBuffer given a clone_id previously provided - * by ValueSerializer::GetSharedArrayBufferId + * by ValueSerializer::Delegate::GetSharedArrayBufferId */ virtual MaybeLocal GetSharedArrayBufferFromId( Isolate* isolate, uint32_t clone_id); + + /** + * Returns whether shared values are supported. GetSharedValueFromId is only + * called if SupportsSharedValues() returns true. + */ + virtual bool SupportsSharedValues() const; + + /** + * Get a value shared across Isolates given a shared_value_id provided by + * ValueSerializer::Delegate::GetSharedValueId. + */ + virtual MaybeLocal GetSharedValueFromId(Isolate* isolate, + uint32_t shared_value_id); }; ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size); diff --git a/src/api/api.cc b/src/api/api.cc index 0e3370e845..4d912ba495 100644 --- a/src/api/api.cc +++ b/src/api/api.cc @@ -3306,6 +3306,17 @@ Maybe ValueSerializer::Delegate::GetWasmModuleTransferId( return Nothing(); } +bool ValueSerializer::Delegate::SupportsSharedValues() const { return false; } + +Maybe ValueSerializer::Delegate::GetSharedValueId( + Isolate* v8_isolate, Local shared_value) { + i::Isolate* isolate = reinterpret_cast(v8_isolate); + isolate->ScheduleThrow(*isolate->factory()->NewError( + isolate->error_function(), i::MessageTemplate::kDataCloneError, + Utils::OpenHandle(*shared_value))); + return Nothing(); +} + void* ValueSerializer::Delegate::ReallocateBufferMemory(void* old_buffer, size_t size, size_t* actual_size) { @@ -3395,6 +3406,17 @@ MaybeLocal ValueDeserializer::Delegate::GetWasmModuleFromId( return MaybeLocal(); } +bool ValueDeserializer::Delegate::SupportsSharedValues() const { return false; } + +MaybeLocal ValueDeserializer::Delegate::GetSharedValueFromId( + Isolate* v8_isolate, uint32_t shared_value_id) { + i::Isolate* isolate = reinterpret_cast(v8_isolate); + isolate->ScheduleThrow(*isolate->factory()->NewError( + isolate->error_function(), + i::MessageTemplate::kDataCloneDeserializationError)); + return MaybeLocal(); +} + MaybeLocal ValueDeserializer::Delegate::GetSharedArrayBufferFromId(Isolate* v8_isolate, uint32_t id) { diff --git a/src/d8/d8.cc b/src/d8/d8.cc index 610c05c9ce..9f27cf20a6 100644 --- a/src/d8/d8.cc +++ b/src/d8/d8.cc @@ -28,6 +28,7 @@ #include "include/v8-initialization.h" #include "include/v8-inspector.h" #include "include/v8-json.h" +#include "include/v8-locker.h" #include "include/v8-profiler.h" #include "include/v8-wasm.h" #include "src/api/api-inl.h" @@ -45,6 +46,7 @@ #include "src/debug/debug-interface.h" #include "src/deoptimizer/deoptimizer.h" #include "src/diagnostics/basic-block-profiler.h" +#include "src/execution/v8threads.h" #include "src/execution/vm-state-inl.h" #include "src/flags/flags.h" #include "src/handles/maybe-handles.h" @@ -2594,11 +2596,18 @@ void Shell::QuitOnce(v8::FunctionCallbackInfo* args) { ->Int32Value(args->GetIsolate()->GetCurrentContext()) .FromMaybe(0); WaitForRunningWorkers(); - args->GetIsolate()->Exit(); + Isolate* isolate = args->GetIsolate(); + isolate->Exit(); + // As we exit the process anyway, we do not dispose the platform and other - // global data. Other isolates might still be running, so disposing here can - // cause them to crash. - OnExit(args->GetIsolate(), false); + // global data and manually unlock to quell DCHECKs. Other isolates might + // still be running, so disposing here can cause them to crash. + i::Isolate* i_isolate = reinterpret_cast(isolate); + if (i_isolate->thread_manager()->IsLockedByCurrentThread()) { + i_isolate->thread_manager()->Unlock(); + } + + OnExit(isolate, false); base::OS::ExitProcess(exit_code); } @@ -4862,6 +4871,30 @@ class Serializer : public ValueSerializer::Delegate { void FreeBufferMemory(void* buffer) override { base::Free(buffer); } + bool SupportsSharedValues() const override { return true; } + + Maybe GetSharedValueId(Isolate* isolate, + Local shared_value) override { + DCHECK_NOT_NULL(data_); + for (size_t index = 0; index < data_->shared_values_.size(); ++index) { + if (data_->shared_values_[index] == shared_value) { + return Just(static_cast(index)); + } + } + + size_t index = data_->shared_values_.size(); + // Shared values in transit are kept alive by global handles in the shared + // isolate. No code ever runs in the shared Isolate, so locking it does not + // contend with long-running tasks. + { + DCHECK_EQ(reinterpret_cast(isolate)->shared_isolate(), + reinterpret_cast(Shell::shared_isolate)); + v8::Locker locker(Shell::shared_isolate); + data_->shared_values_.emplace_back(Shell::shared_isolate, shared_value); + } + return Just(static_cast(index)); + } + private: Maybe PrepareTransfer(Local context, Local transfer) { if (transfer->IsArray()) { @@ -4928,6 +4961,12 @@ class Serializer : public ValueSerializer::Delegate { size_t current_memory_usage_; }; +void SerializationData::ClearSharedValuesUnderLockIfNeeded() { + if (shared_values_.empty()) return; + v8::Locker locker(Shell::shared_isolate); + shared_values_.clear(); +} + class Deserializer : public ValueDeserializer::Delegate { public: Deserializer(Isolate* isolate, std::unique_ptr data) @@ -4937,6 +4976,12 @@ class Deserializer : public ValueDeserializer::Delegate { deserializer_.SetSupportsLegacyWireFormat(true); } + ~Deserializer() { + DCHECK_EQ(reinterpret_cast(isolate_)->shared_isolate(), + reinterpret_cast(Shell::shared_isolate)); + data_->ClearSharedValuesUnderLockIfNeeded(); + } + Deserializer(const Deserializer&) = delete; Deserializer& operator=(const Deserializer&) = delete; @@ -4974,6 +5019,17 @@ class Deserializer : public ValueDeserializer::Delegate { isolate_, data_->compiled_wasm_modules().at(transfer_id)); } + bool SupportsSharedValues() const override { return true; } + + MaybeLocal GetSharedValueFromId(Isolate* isolate, + uint32_t id) override { + DCHECK_NOT_NULL(data_); + if (id < data_->shared_values().size()) { + return data_->shared_values().at(id).Get(isolate); + } + return MaybeLocal(); + } + private: Isolate* isolate_; ValueDeserializer deserializer_; diff --git a/src/d8/d8.h b/src/d8/d8.h index 40174cf029..f524d13aa7 100644 --- a/src/d8/d8.h +++ b/src/d8/d8.h @@ -151,6 +151,11 @@ class SerializationData { const std::vector& compiled_wasm_modules() { return compiled_wasm_modules_; } + const std::vector>& shared_values() { + return shared_values_; + } + + void ClearSharedValuesUnderLockIfNeeded(); private: struct DataDeleter { @@ -162,6 +167,7 @@ class SerializationData { std::vector> backing_stores_; std::vector> sab_backing_stores_; std::vector compiled_wasm_modules_; + std::vector> shared_values_; private: friend class Serializer; diff --git a/src/execution/v8threads.h b/src/execution/v8threads.h index ccaa6b1bef..69fb91f91b 100644 --- a/src/execution/v8threads.h +++ b/src/execution/v8threads.h @@ -60,7 +60,7 @@ class ThreadVisitor { class ThreadManager { public: void Lock(); - void Unlock(); + V8_EXPORT_PRIVATE void Unlock(); void InitThread(const ExecutionAccess&); void ArchiveThread(); diff --git a/src/heap/heap.cc b/src/heap/heap.cc index aefbbd9b31..60df269541 100644 --- a/src/heap/heap.cc +++ b/src/heap/heap.cc @@ -1388,7 +1388,7 @@ class V8_NODISCARD GCCallbacksScope { }; void Heap::HandleGCRequest() { - if (FLAG_stress_scavenge > 0 && stress_scavenge_observer_->HasRequestedGC()) { + if (IsStressingScavenge() && stress_scavenge_observer_->HasRequestedGC()) { CollectAllGarbage(NEW_SPACE, GarbageCollectionReason::kTesting); stress_scavenge_observer_->RequestedGCDone(); } else if (HighMemoryPressure()) { @@ -5772,7 +5772,7 @@ void Heap::SetUpSpaces(LinearAllocationArea* new_allocation_info, AddAllocationObserversToAllSpaces(stress_marking_observer_, stress_marking_observer_); } - if (FLAG_stress_scavenge > 0 && new_space()) { + if (IsStressingScavenge()) { stress_scavenge_observer_ = new StressScavengeObserver(this); new_space()->AddAllocationObserver(stress_scavenge_observer_); } @@ -6006,7 +6006,7 @@ void Heap::TearDown() { if (FLAG_stress_marking > 0) { PrintMaxMarkingLimitReached(); } - if (FLAG_stress_scavenge > 0) { + if (IsStressingScavenge()) { PrintMaxNewSpaceSizeReached(); } } @@ -6031,7 +6031,7 @@ void Heap::TearDown() { delete stress_marking_observer_; stress_marking_observer_ = nullptr; } - if (FLAG_stress_scavenge > 0 && new_space()) { + if (IsStressingScavenge()) { new_space()->RemoveAllocationObserver(stress_scavenge_observer_); delete stress_scavenge_observer_; stress_scavenge_observer_ = nullptr; @@ -7372,6 +7372,10 @@ void Heap::IncrementObjectCounters() { } #endif // DEBUG +bool Heap::IsStressingScavenge() { + return FLAG_stress_scavenge > 0 && new_space(); +} + // StrongRootBlocks are allocated as a block of addresses, prefixed with a // StrongRootsEntry pointer: // diff --git a/src/heap/heap.h b/src/heap/heap.h index e674217b7b..d08e3b41b4 100644 --- a/src/heap/heap.h +++ b/src/heap/heap.h @@ -2189,6 +2189,8 @@ class Heap { return allocation_type_for_in_place_internalizable_strings_; } + bool IsStressingScavenge(); + ExternalMemoryAccounting external_memory_; // This can be calculated directly from a pointer to the heap; however, it is diff --git a/src/objects/value-serializer.cc b/src/objects/value-serializer.cc index 022ceaad84..320fa1afa2 100644 --- a/src/objects/value-serializer.cc +++ b/src/objects/value-serializer.cc @@ -50,6 +50,7 @@ namespace internal { // Version 13: host objects have an explicit tag (rather than handling all // unknown tags) // Version 14: flags for JSArrayBufferViews +// Version 15: support for shared objects with an explicit tag // // WARNING: Increasing this value is a change which cannot safely be rolled // back without breaking compatibility with data stored on disk. It is @@ -58,7 +59,7 @@ namespace internal { // // Recent changes are routinely reverted in preparation for branch, and this // has been the cause of at least one bug in the past. -static const uint32_t kLatestVersion = 14; +static const uint32_t kLatestVersion = 15; static_assert(kLatestVersion == v8::CurrentValueSerializerFormatVersion(), "Exported format version must match latest version."); @@ -154,6 +155,8 @@ enum class SerializationTag : uint8_t { kArrayBufferView = 'V', // Shared array buffer. transferID:uint32_t kSharedArrayBuffer = 'u', + // A HeapObject shared across Isolates. sharedValueID:uint32_t + kSharedObject = 'p', // A wasm module object transfer. next value is its index. kWasmModuleTransfer = 'w', // The delegate is responsible for processing all following data. @@ -245,6 +248,7 @@ ValueSerializer::ValueSerializer(Isolate* isolate, v8::ValueSerializer::Delegate* delegate) : isolate_(isolate), delegate_(delegate), + supports_shared_values_(delegate && delegate->SupportsSharedValues()), zone_(isolate->allocator(), ZONE_NAME), id_map_(isolate->heap(), ZoneAllocationPolicy(&zone_)), array_buffer_transfer_map_(isolate->heap(), @@ -444,7 +448,11 @@ Maybe ValueSerializer::WriteObject(Handle object) { } default: if (InstanceTypeChecker::IsString(instance_type)) { - WriteString(Handle::cast(object)); + auto string = Handle::cast(object); + if (FLAG_shared_string_table && supports_shared_values_) { + return WriteSharedObject(String::Share(isolate_, string)); + } + WriteString(string); return ThrowIfOutOfMemory(); } else if (InstanceTypeChecker::IsJSReceiver(instance_type)) { return WriteJSReceiver(Handle::cast(object)); @@ -1050,6 +1058,23 @@ Maybe ValueSerializer::WriteWasmMemory(Handle object) { } #endif // V8_ENABLE_WEBASSEMBLY +Maybe ValueSerializer::WriteSharedObject(Handle object) { + // Currently only strings are shareable. + DCHECK(String::cast(*object).IsShared()); + DCHECK(supports_shared_values_); + DCHECK_NOT_NULL(delegate_); + DCHECK(delegate_->SupportsSharedValues()); + + v8::Isolate* v8_isolate = reinterpret_cast(isolate_); + Maybe index = + delegate_->GetSharedValueId(v8_isolate, Utils::ToLocal(object)); + RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing()); + + WriteTag(SerializationTag::kSharedObject); + WriteVarint(index.FromJust()); + return ThrowIfOutOfMemory(); +} + Maybe ValueSerializer::WriteHostObject(Handle object) { WriteTag(SerializationTag::kHostObject); if (!delegate_) { @@ -1127,6 +1152,7 @@ ValueDeserializer::ValueDeserializer(Isolate* isolate, delegate_(delegate), position_(data.begin()), end_(data.end()), + supports_shared_values_(delegate && delegate->SupportsSharedValues()), id_map_(isolate->global_handles()->Create( ReadOnlyRoots(isolate_).empty_fixed_array())) {} @@ -1136,6 +1162,7 @@ ValueDeserializer::ValueDeserializer(Isolate* isolate, const uint8_t* data, delegate_(nullptr), position_(data), end_(data + size), + supports_shared_values_(false), id_map_(isolate->global_handles()->Create( ReadOnlyRoots(isolate_).empty_fixed_array())) {} @@ -1394,6 +1421,13 @@ MaybeHandle ValueDeserializer::ReadObjectInternal() { #endif // V8_ENABLE_WEBASSEMBLY case SerializationTag::kHostObject: return ReadHostObject(); + case SerializationTag::kSharedObject: + if (version_ >= 15 && supports_shared_values_) { + return ReadSharedObject(); + } + // If the delegate doesn't support shared values (e.g. older version, or + // is for deserializing from storage), treat the tag as unknown. + V8_FALLTHROUGH; default: // Before there was an explicit tag for host objects, all unknown tags // were delegated to the host. @@ -2039,6 +2073,28 @@ MaybeHandle ValueDeserializer::ReadWasmMemory() { } #endif // V8_ENABLE_WEBASSEMBLY +MaybeHandle ValueDeserializer::ReadSharedObject() { + STACK_CHECK(isolate_, MaybeHandle()); + DCHECK_GE(version_, 15); + DCHECK(supports_shared_values_); + DCHECK_NOT_NULL(delegate_); + DCHECK(delegate_->SupportsSharedValues()); + v8::Isolate* v8_isolate = reinterpret_cast(isolate_); + uint32_t shared_value_id; + Local shared_value; + if (!ReadVarint().To(&shared_value_id) || + !delegate_->GetSharedValueFromId(v8_isolate, shared_value_id) + .ToLocal(&shared_value)) { + RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, HeapObject); + return MaybeHandle(); + } + Handle shared_object = + Handle::cast(Utils::OpenHandle(*shared_value)); + // Currently only strings are shareable. + DCHECK(String::cast(*shared_object).IsShared()); + return shared_object; +} + MaybeHandle ValueDeserializer::ReadHostObject() { if (!delegate_) return MaybeHandle(); STACK_CHECK(isolate_, MaybeHandle()); diff --git a/src/objects/value-serializer.h b/src/objects/value-serializer.h index 4cf13130d8..853b7168cb 100644 --- a/src/objects/value-serializer.h +++ b/src/objects/value-serializer.h @@ -137,6 +137,8 @@ class ValueSerializer { Maybe WriteWasmMemory(Handle object) V8_WARN_UNUSED_RESULT; #endif // V8_ENABLE_WEBASSEMBLY + Maybe WriteSharedObject(Handle object) + V8_WARN_UNUSED_RESULT; Maybe WriteHostObject(Handle object) V8_WARN_UNUSED_RESULT; /* @@ -162,6 +164,7 @@ class ValueSerializer { uint8_t* buffer_ = nullptr; size_t buffer_size_ = 0; size_t buffer_capacity_ = 0; + const bool supports_shared_values_; bool treat_array_buffer_views_as_host_objects_ = false; bool out_of_memory_ = false; Zone zone_; @@ -286,6 +289,7 @@ class ValueDeserializer { MaybeHandle ReadWasmModuleTransfer() V8_WARN_UNUSED_RESULT; MaybeHandle ReadWasmMemory() V8_WARN_UNUSED_RESULT; #endif // V8_ENABLE_WEBASSEMBLY + MaybeHandle ReadSharedObject() V8_WARN_UNUSED_RESULT; MaybeHandle ReadHostObject() V8_WARN_UNUSED_RESULT; /* @@ -305,6 +309,7 @@ class ValueDeserializer { v8::ValueDeserializer::Delegate* const delegate_; const uint8_t* position_; const uint8_t* const end_; + const bool supports_shared_values_; uint32_t version_ = 0; uint32_t next_id_ = 0; diff --git a/src/runtime/runtime-test.cc b/src/runtime/runtime-test.cc index 4e372b95f2..61de76ef08 100644 --- a/src/runtime/runtime-test.cc +++ b/src/runtime/runtime-test.cc @@ -1460,5 +1460,21 @@ RUNTIME_FUNCTION(Runtime_BigIntMaxLengthBits) { return *isolate->factory()->NewNumber(BigInt::kMaxLengthBits); } +RUNTIME_FUNCTION(Runtime_IsSameHeapObject) { + HandleScope scope(isolate); + DCHECK_EQ(2, args.length()); + CONVERT_ARG_HANDLE_CHECKED(HeapObject, obj1, 0); + CONVERT_ARG_HANDLE_CHECKED(HeapObject, obj2, 1); + return isolate->heap()->ToBoolean(obj1->address() == obj2->address()); +} + +RUNTIME_FUNCTION(Runtime_IsSharedString) { + HandleScope scope(isolate); + DCHECK_EQ(1, args.length()); + CONVERT_ARG_HANDLE_CHECKED(HeapObject, obj, 0); + return isolate->heap()->ToBoolean(obj->IsString() && + Handle::cast(obj)->IsShared()); +} + } // namespace internal } // namespace v8 diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 951c6dfe9d..99dc183b0a 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -521,6 +521,8 @@ namespace internal { F(IsConcatSpreadableProtector, 0, 1) \ F(IsConcurrentRecompilationSupported, 0, 1) \ F(IsDictPropertyConstTrackingEnabled, 0, 1) \ + F(IsSameHeapObject, 2, 1) \ + F(IsSharedString, 1, 1) \ F(MapIteratorProtector, 0, 1) \ F(NeverOptimizeFunction, 1, 1) \ F(NewRegExpWithBacktrackLimit, 3, 1) \ diff --git a/src/snapshot/deserializer.cc b/src/snapshot/deserializer.cc index 1c50125d5d..9b2141ff59 100644 --- a/src/snapshot/deserializer.cc +++ b/src/snapshot/deserializer.cc @@ -431,7 +431,6 @@ void Deserializer::PostProcessNewObject(Handle map, isolate()->string_table()->LookupKey(isolate(), &key); if (*result != *string) { - DCHECK(!string->IsShared()); string->MakeThin(isolate(), *result); // Mutate the given object handle so that the backreference entry is // also updated. diff --git a/test/mjsunit/shared-string.js b/test/mjsunit/shared-string.js new file mode 100644 index 0000000000..da6fb39f43 --- /dev/null +++ b/test/mjsunit/shared-string.js @@ -0,0 +1,37 @@ +// 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. +// +// Flags: --shared-string-table --allow-natives-syntax + +if (this.Worker) { + +(function TestSharedStringPostMessage() { + let workerScript = + `postMessage("started"); + onmessage = function(str) { + if (!%IsSharedString(str)) { + throw new Error("str isn't shared"); + } + postMessage(str); + };`; + + let worker = new Worker(workerScript, { type: 'string' }); + let started = worker.getMessage(); + assertTrue(%IsSharedString(started)); + assertEquals("started", started); + + // The string literal appears in source and is internalized, so should + // already be shared. + let str_to_send = 'foo'; + assertTrue(%IsSharedString(str_to_send)); + worker.postMessage(str_to_send); + let str_received = worker.getMessage(); + assertTrue(%IsSharedString(str_received)); + // Object.is and === won't check pointer equality of Strings. + assertTrue(%IsSameHeapObject(str_to_send, str_received)); + + worker.terminate(); +})(); + +}