Commit 3cb4039c authored by Shu-yu Guo's avatar Shu-yu Guo Committed by V8 LUCI CQ

[string] Support shared strings in Value{Serializer,Deserializer}

When FLAG_shared_string_table is true, postMessaging strings will share
instead of copy.

Note that not all operations on shared strings are supported, and shared
strings may be slower than non-shared strings for some operations.

Bug: v8:12007
Change-Id: I3462128e15410d2568868143571571b3025722c1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3277250Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78614}
parent 87cf0bdd
...@@ -67,6 +67,17 @@ class V8_EXPORT ValueSerializer { ...@@ -67,6 +67,17 @@ class V8_EXPORT ValueSerializer {
virtual Maybe<uint32_t> GetWasmModuleTransferId( virtual Maybe<uint32_t> GetWasmModuleTransferId(
Isolate* isolate, Local<WasmModuleObject> module); Isolate* isolate, Local<WasmModuleObject> module);
/**
* 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<uint32_t> GetSharedValueId(Isolate* isolate,
Local<Value> shared_value);
/** /**
* Allocates memory for the buffer of at least the size provided. The actual * 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 * size (which may be greater or equal) is written to |actual_size|. If no
...@@ -166,17 +177,24 @@ class V8_EXPORT ValueDeserializer { ...@@ -166,17 +177,24 @@ class V8_EXPORT ValueDeserializer {
/** /**
* Get a WasmModuleObject given a transfer_id previously provided * Get a WasmModuleObject given a transfer_id previously provided
* by ValueSerializer::GetWasmModuleTransferId * by ValueSerializer::Delegate::GetWasmModuleTransferId
*/ */
virtual MaybeLocal<WasmModuleObject> GetWasmModuleFromId( virtual MaybeLocal<WasmModuleObject> GetWasmModuleFromId(
Isolate* isolate, uint32_t transfer_id); Isolate* isolate, uint32_t transfer_id);
/** /**
* Get a SharedArrayBuffer given a clone_id previously provided * Get a SharedArrayBuffer given a clone_id previously provided
* by ValueSerializer::GetSharedArrayBufferId * by ValueSerializer::Delegate::GetSharedArrayBufferId
*/ */
virtual MaybeLocal<SharedArrayBuffer> GetSharedArrayBufferFromId( virtual MaybeLocal<SharedArrayBuffer> GetSharedArrayBufferFromId(
Isolate* isolate, uint32_t clone_id); Isolate* isolate, uint32_t clone_id);
/**
* Get a value shared across Isolates given a shared_value_id provided by
* ValueSerializer::Delegate::GetSharedValueId.
*/
virtual MaybeLocal<Value> GetSharedValueFromId(Isolate* isolate,
uint32_t shared_value_id);
}; };
ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size); ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size);
......
...@@ -3391,6 +3391,11 @@ Maybe<uint32_t> ValueSerializer::Delegate::GetWasmModuleTransferId( ...@@ -3391,6 +3391,11 @@ Maybe<uint32_t> ValueSerializer::Delegate::GetWasmModuleTransferId(
return Nothing<uint32_t>(); return Nothing<uint32_t>();
} }
Maybe<uint32_t> ValueSerializer::Delegate::GetSharedValueId(
Isolate* v8_isolate, Local<Value> shared_value) {
return Nothing<uint32_t>();
}
void* ValueSerializer::Delegate::ReallocateBufferMemory(void* old_buffer, void* ValueSerializer::Delegate::ReallocateBufferMemory(void* old_buffer,
size_t size, size_t size,
size_t* actual_size) { size_t* actual_size) {
...@@ -3480,6 +3485,15 @@ MaybeLocal<WasmModuleObject> ValueDeserializer::Delegate::GetWasmModuleFromId( ...@@ -3480,6 +3485,15 @@ MaybeLocal<WasmModuleObject> ValueDeserializer::Delegate::GetWasmModuleFromId(
return MaybeLocal<WasmModuleObject>(); return MaybeLocal<WasmModuleObject>();
} }
MaybeLocal<Value> ValueDeserializer::Delegate::GetSharedValueFromId(
Isolate* v8_isolate, uint32_t shared_value_id) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
isolate->ScheduleThrow(*isolate->factory()->NewError(
isolate->error_function(),
i::MessageTemplate::kDataCloneDeserializationError));
return MaybeLocal<Value>();
}
MaybeLocal<SharedArrayBuffer> MaybeLocal<SharedArrayBuffer>
ValueDeserializer::Delegate::GetSharedArrayBufferFromId(Isolate* v8_isolate, ValueDeserializer::Delegate::GetSharedArrayBufferFromId(Isolate* v8_isolate,
uint32_t id) { uint32_t id) {
......
...@@ -4843,6 +4843,22 @@ class Serializer : public ValueSerializer::Delegate { ...@@ -4843,6 +4843,22 @@ class Serializer : public ValueSerializer::Delegate {
void FreeBufferMemory(void* buffer) override { base::Free(buffer); } void FreeBufferMemory(void* buffer) override { base::Free(buffer); }
Maybe<uint32_t> GetSharedValueId(Isolate* isolate,
Local<Value> 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<uint32_t>(static_cast<uint32_t>(index));
}
}
size_t index = data_->shared_values_.size();
// Note: the shared value is kept alive by the sender Isolate's
// GlobalHandles, so there's no race.
data_->shared_values_.emplace_back(isolate_, shared_value);
return Just<uint32_t>(static_cast<uint32_t>(index));
}
private: private:
Maybe<bool> PrepareTransfer(Local<Context> context, Local<Value> transfer) { Maybe<bool> PrepareTransfer(Local<Context> context, Local<Value> transfer) {
if (transfer->IsArray()) { if (transfer->IsArray()) {
...@@ -4955,6 +4971,15 @@ class Deserializer : public ValueDeserializer::Delegate { ...@@ -4955,6 +4971,15 @@ class Deserializer : public ValueDeserializer::Delegate {
isolate_, data_->compiled_wasm_modules().at(transfer_id)); isolate_, data_->compiled_wasm_modules().at(transfer_id));
} }
MaybeLocal<Value> 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<Value>();
}
private: private:
Isolate* isolate_; Isolate* isolate_;
ValueDeserializer deserializer_; ValueDeserializer deserializer_;
......
...@@ -151,6 +151,9 @@ class SerializationData { ...@@ -151,6 +151,9 @@ class SerializationData {
const std::vector<CompiledWasmModule>& compiled_wasm_modules() { const std::vector<CompiledWasmModule>& compiled_wasm_modules() {
return compiled_wasm_modules_; return compiled_wasm_modules_;
} }
const std::vector<v8::Global<v8::Value>>& shared_values() {
return shared_values_;
}
private: private:
struct DataDeleter { struct DataDeleter {
...@@ -162,6 +165,7 @@ class SerializationData { ...@@ -162,6 +165,7 @@ class SerializationData {
std::vector<std::shared_ptr<v8::BackingStore>> backing_stores_; std::vector<std::shared_ptr<v8::BackingStore>> backing_stores_;
std::vector<std::shared_ptr<v8::BackingStore>> sab_backing_stores_; std::vector<std::shared_ptr<v8::BackingStore>> sab_backing_stores_;
std::vector<CompiledWasmModule> compiled_wasm_modules_; std::vector<CompiledWasmModule> compiled_wasm_modules_;
std::vector<v8::Global<v8::Value>> shared_values_;
private: private:
friend class Serializer; friend class Serializer;
......
...@@ -153,6 +153,8 @@ enum class SerializationTag : uint8_t { ...@@ -153,6 +153,8 @@ enum class SerializationTag : uint8_t {
kArrayBufferView = 'V', kArrayBufferView = 'V',
// Shared array buffer. transferID:uint32_t // Shared array buffer. transferID:uint32_t
kSharedArrayBuffer = 'u', kSharedArrayBuffer = 'u',
// A HeapObject shared across Isolates. sharedValueID:uint32_t
kSharedObject = 'p',
// A wasm module object transfer. next value is its index. // A wasm module object transfer. next value is its index.
kWasmModuleTransfer = 'w', kWasmModuleTransfer = 'w',
// The delegate is responsible for processing all following data. // The delegate is responsible for processing all following data.
...@@ -443,7 +445,11 @@ Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) { ...@@ -443,7 +445,11 @@ Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
} }
default: default:
if (InstanceTypeChecker::IsString(instance_type)) { if (InstanceTypeChecker::IsString(instance_type)) {
WriteString(Handle<String>::cast(object)); auto string = Handle<String>::cast(object);
if (FLAG_shared_string_table) {
return WriteSharedObject(String::Share(isolate_, string));
}
WriteString(string);
return ThrowIfOutOfMemory(); return ThrowIfOutOfMemory();
} else if (InstanceTypeChecker::IsJSReceiver(instance_type)) { } else if (InstanceTypeChecker::IsJSReceiver(instance_type)) {
return WriteJSReceiver(Handle<JSReceiver>::cast(object)); return WriteJSReceiver(Handle<JSReceiver>::cast(object));
...@@ -1053,6 +1059,26 @@ Maybe<bool> ValueSerializer::WriteWasmMemory(Handle<WasmMemoryObject> object) { ...@@ -1053,6 +1059,26 @@ Maybe<bool> ValueSerializer::WriteWasmMemory(Handle<WasmMemoryObject> object) {
} }
#endif // V8_ENABLE_WEBASSEMBLY #endif // V8_ENABLE_WEBASSEMBLY
Maybe<bool> ValueSerializer::WriteSharedObject(Handle<HeapObject> object) {
// Currently only strings are shareable.
DCHECK(String::cast(*object).IsShared());
if (!delegate_) {
isolate_->Throw(*isolate_->factory()->NewError(
isolate_->error_function(), MessageTemplate::kDataCloneError, object));
return Nothing<bool>();
}
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
Maybe<uint32_t> index =
delegate_->GetSharedValueId(v8_isolate, Utils::ToLocal(object));
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
WriteTag(SerializationTag::kSharedObject);
WriteVarint(index.FromJust());
return ThrowIfOutOfMemory();
}
Maybe<bool> ValueSerializer::WriteHostObject(Handle<JSObject> object) { Maybe<bool> ValueSerializer::WriteHostObject(Handle<JSObject> object) {
WriteTag(SerializationTag::kHostObject); WriteTag(SerializationTag::kHostObject);
if (!delegate_) { if (!delegate_) {
...@@ -1395,6 +1421,8 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() { ...@@ -1395,6 +1421,8 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() {
case SerializationTag::kWasmMemoryTransfer: case SerializationTag::kWasmMemoryTransfer:
return ReadWasmMemory(); return ReadWasmMemory();
#endif // V8_ENABLE_WEBASSEMBLY #endif // V8_ENABLE_WEBASSEMBLY
case SerializationTag::kSharedObject:
return ReadSharedObject();
case SerializationTag::kHostObject: case SerializationTag::kHostObject:
return ReadHostObject(); return ReadHostObject();
default: default:
...@@ -2041,6 +2069,25 @@ MaybeHandle<WasmMemoryObject> ValueDeserializer::ReadWasmMemory() { ...@@ -2041,6 +2069,25 @@ MaybeHandle<WasmMemoryObject> ValueDeserializer::ReadWasmMemory() {
} }
#endif // V8_ENABLE_WEBASSEMBLY #endif // V8_ENABLE_WEBASSEMBLY
MaybeHandle<HeapObject> ValueDeserializer::ReadSharedObject() {
if (!delegate_) return MaybeHandle<HeapObject>();
STACK_CHECK(isolate_, MaybeHandle<HeapObject>());
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
uint32_t shared_value_id;
Local<Value> shared_value;
if (!ReadVarint<uint32_t>().To(&shared_value_id) ||
!delegate_->GetSharedValueFromId(v8_isolate, shared_value_id)
.ToLocal(&shared_value)) {
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, HeapObject);
return MaybeHandle<HeapObject>();
}
Handle<HeapObject> shared_object =
Handle<HeapObject>::cast(Utils::OpenHandle(*shared_value));
// Currently only strings are shareable.
DCHECK(String::cast(*shared_object).IsShared());
return shared_object;
}
MaybeHandle<JSObject> ValueDeserializer::ReadHostObject() { MaybeHandle<JSObject> ValueDeserializer::ReadHostObject() {
if (!delegate_) return MaybeHandle<JSObject>(); if (!delegate_) return MaybeHandle<JSObject>();
STACK_CHECK(isolate_, MaybeHandle<JSObject>()); STACK_CHECK(isolate_, MaybeHandle<JSObject>());
......
...@@ -137,6 +137,8 @@ class ValueSerializer { ...@@ -137,6 +137,8 @@ class ValueSerializer {
Maybe<bool> WriteWasmMemory(Handle<WasmMemoryObject> object) Maybe<bool> WriteWasmMemory(Handle<WasmMemoryObject> object)
V8_WARN_UNUSED_RESULT; V8_WARN_UNUSED_RESULT;
#endif // V8_ENABLE_WEBASSEMBLY #endif // V8_ENABLE_WEBASSEMBLY
Maybe<bool> WriteSharedObject(Handle<HeapObject> object)
V8_WARN_UNUSED_RESULT;
Maybe<bool> WriteHostObject(Handle<JSObject> object) V8_WARN_UNUSED_RESULT; Maybe<bool> WriteHostObject(Handle<JSObject> object) V8_WARN_UNUSED_RESULT;
/* /*
...@@ -286,6 +288,7 @@ class ValueDeserializer { ...@@ -286,6 +288,7 @@ class ValueDeserializer {
MaybeHandle<JSObject> ReadWasmModuleTransfer() V8_WARN_UNUSED_RESULT; MaybeHandle<JSObject> ReadWasmModuleTransfer() V8_WARN_UNUSED_RESULT;
MaybeHandle<WasmMemoryObject> ReadWasmMemory() V8_WARN_UNUSED_RESULT; MaybeHandle<WasmMemoryObject> ReadWasmMemory() V8_WARN_UNUSED_RESULT;
#endif // V8_ENABLE_WEBASSEMBLY #endif // V8_ENABLE_WEBASSEMBLY
MaybeHandle<HeapObject> ReadSharedObject() V8_WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadHostObject() V8_WARN_UNUSED_RESULT; MaybeHandle<JSObject> ReadHostObject() V8_WARN_UNUSED_RESULT;
/* /*
......
...@@ -1485,5 +1485,21 @@ RUNTIME_FUNCTION(Runtime_BigIntMaxLengthBits) { ...@@ -1485,5 +1485,21 @@ RUNTIME_FUNCTION(Runtime_BigIntMaxLengthBits) {
return *isolate->factory()->NewNumber(BigInt::kMaxLengthBits); 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<String>::cast(obj)->IsShared());
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -525,6 +525,8 @@ namespace internal { ...@@ -525,6 +525,8 @@ namespace internal {
F(IsConcurrentRecompilationSupported, 0, 1) \ F(IsConcurrentRecompilationSupported, 0, 1) \
F(IsDictPropertyConstTrackingEnabled, 0, 1) \ F(IsDictPropertyConstTrackingEnabled, 0, 1) \
F(IsMidTierTurboprop, 0, 1) \ F(IsMidTierTurboprop, 0, 1) \
F(IsSameHeapObject, 2, 1) \
F(IsSharedString, 1, 1) \
F(IsTopTierTurboprop, 0, 1) \ F(IsTopTierTurboprop, 0, 1) \
F(MapIteratorProtector, 0, 1) \ F(MapIteratorProtector, 0, 1) \
F(NeverOptimizeFunction, 1, 1) \ F(NeverOptimizeFunction, 1, 1) \
......
// 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();
})();
}
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