Skip to content

Commit dd8795f

Browse files
committed
worker: enable transferring WASM modules
Enable in-memory transfer of WASM modules without recompilation. Previously, the serialization step worked, but deserialization failed because we did not explicitly enable decoding inline WASM modules, and so the message was not successfully received. PR-URL: #25314 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
1 parent 7f78137 commit dd8795f

4 files changed

+63
-10
lines changed

src/node_messaging.cc

+30-8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ using v8::String;
3030
using v8::Value;
3131
using v8::ValueDeserializer;
3232
using v8::ValueSerializer;
33+
using v8::WasmCompiledModule;
3334

3435
namespace node {
3536
namespace worker {
@@ -43,13 +44,15 @@ namespace {
4344
// `MessagePort`s and `SharedArrayBuffer`s, and make new JS objects out of them.
4445
class DeserializerDelegate : public ValueDeserializer::Delegate {
4546
public:
46-
DeserializerDelegate(Message* m,
47-
Environment* env,
48-
const std::vector<MessagePort*>& message_ports,
49-
const std::vector<Local<SharedArrayBuffer>>&
50-
shared_array_buffers)
51-
: message_ports_(message_ports),
52-
shared_array_buffers_(shared_array_buffers) {}
47+
DeserializerDelegate(
48+
Message* m,
49+
Environment* env,
50+
const std::vector<MessagePort*>& message_ports,
51+
const std::vector<Local<SharedArrayBuffer>>& shared_array_buffers,
52+
const std::vector<WasmCompiledModule::TransferrableModule>& wasm_modules)
53+
: message_ports_(message_ports),
54+
shared_array_buffers_(shared_array_buffers),
55+
wasm_modules_(wasm_modules) {}
5356

5457
MaybeLocal<Object> ReadHostObject(Isolate* isolate) override {
5558
// Currently, only MessagePort hosts objects are supported, so identifying
@@ -67,11 +70,19 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
6770
return shared_array_buffers_[clone_id];
6871
}
6972

73+
MaybeLocal<WasmCompiledModule> GetWasmModuleFromId(
74+
Isolate* isolate, uint32_t transfer_id) override {
75+
CHECK_LE(transfer_id, wasm_modules_.size());
76+
return WasmCompiledModule::FromTransferrableModule(
77+
isolate, wasm_modules_[transfer_id]);
78+
}
79+
7080
ValueDeserializer* deserializer = nullptr;
7181

7282
private:
7383
const std::vector<MessagePort*>& message_ports_;
7484
const std::vector<Local<SharedArrayBuffer>>& shared_array_buffers_;
85+
const std::vector<WasmCompiledModule::TransferrableModule>& wasm_modules_;
7586
};
7687

7788
} // anonymous namespace
@@ -109,7 +120,8 @@ MaybeLocal<Value> Message::Deserialize(Environment* env,
109120
}
110121
shared_array_buffers_.clear();
111122

112-
DeserializerDelegate delegate(this, env, ports, shared_array_buffers);
123+
DeserializerDelegate delegate(
124+
this, env, ports, shared_array_buffers, wasm_modules_);
113125
ValueDeserializer deserializer(
114126
env->isolate(),
115127
reinterpret_cast<const uint8_t*>(main_message_buf_.data),
@@ -143,6 +155,11 @@ void Message::AddMessagePort(std::unique_ptr<MessagePortData>&& data) {
143155
message_ports_.emplace_back(std::move(data));
144156
}
145157

158+
uint32_t Message::AddWASMModule(WasmCompiledModule::TransferrableModule&& mod) {
159+
wasm_modules_.emplace_back(std::move(mod));
160+
return wasm_modules_.size() - 1;
161+
}
162+
146163
namespace {
147164

148165
void ThrowDataCloneException(Environment* env, Local<String> message) {
@@ -202,6 +219,11 @@ class SerializerDelegate : public ValueSerializer::Delegate {
202219
return Just(i);
203220
}
204221

222+
Maybe<uint32_t> GetWasmModuleTransferId(
223+
Isolate* isolate, Local<WasmCompiledModule> module) override {
224+
return Just(msg_->AddWASMModule(module->GetTransferrableModule()));
225+
}
226+
205227
void Finish() {
206228
// Only close the MessagePort handles and actually transfer them
207229
// once we know that serialization succeeded.

src/node_messaging.h

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ class Message : public MemoryRetainer {
4747
// Internal method of Message that is called once serialization finishes
4848
// and that transfers ownership of `data` to this message.
4949
void AddMessagePort(std::unique_ptr<MessagePortData>&& data);
50+
// Internal method of Message that is called when a new WebAssembly.Module
51+
// object is encountered in the incoming value's structure.
52+
uint32_t AddWASMModule(v8::WasmCompiledModule::TransferrableModule&& mod);
5053

5154
// The MessagePorts that will be transferred, as recorded by Serialize().
5255
// Used for warning user about posting the target MessagePort to itself,
@@ -65,6 +68,7 @@ class Message : public MemoryRetainer {
6568
std::vector<MallocedBuffer<char>> array_buffer_contents_;
6669
std::vector<SharedArrayBufferMetadataReference> shared_array_buffers_;
6770
std::vector<std::unique_ptr<MessagePortData>> message_ports_;
71+
std::vector<v8::WasmCompiledModule::TransferrableModule> wasm_modules_;
6872

6973
friend class MessagePort;
7074
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Flags: --experimental-worker
2+
'use strict';
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const fixtures = require('../common/fixtures');
6+
7+
const { Worker } = require('worker_threads');
8+
const wasmModule = new WebAssembly.Module(fixtures.readSync('test.wasm'));
9+
10+
const worker = new Worker(`
11+
const { parentPort } = require('worker_threads');
12+
parentPort.once('message', ({ wasmModule }) => {
13+
const instance = new WebAssembly.Instance(wasmModule);
14+
parentPort.postMessage(instance.exports.addTwo(10, 20));
15+
});
16+
`, { eval: true });
17+
18+
worker.once('message', common.mustCall((num) => assert.strictEqual(num, 30)));
19+
worker.postMessage({ wasmModule });

test/parallel/test-worker-message-port-wasm-threads.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,23 @@ assert(buffer instanceof SharedArrayBuffer);
3232
// stopped when we exit.
3333
const worker = new Worker(`
3434
const { parentPort } = require('worker_threads');
35+
36+
// Compile the same WASM module from its source bytes.
3537
const wasmSource = new Uint8Array([${wasmSource.join(',')}]);
3638
const wasmModule = new WebAssembly.Module(wasmSource);
3739
const instance = new WebAssembly.Instance(wasmModule);
3840
parentPort.postMessage(instance.exports.memory);
41+
42+
// Do the same thing, except we receive the WASM module via transfer.
43+
parentPort.once('message', ({ wasmModule }) => {
44+
const instance = new WebAssembly.Instance(wasmModule);
45+
parentPort.postMessage(instance.exports.memory);
46+
});
3947
`, { eval: true });
40-
worker.once('message', common.mustCall(({ buffer }) => {
48+
worker.on('message', common.mustCall(({ buffer }) => {
4149
assert(buffer instanceof SharedArrayBuffer);
4250
worker.buf = buffer; // Basically just keep the reference to buffer alive.
43-
}));
51+
}, 2));
4452
worker.once('exit', common.mustCall());
4553
worker.postMessage({ wasmModule });
4654
}

0 commit comments

Comments
 (0)