1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
|
// Copyright 2017 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_WASM_WASM_ENGINE_H_
#define V8_WASM_WASM_ENGINE_H_
#include <algorithm>
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include "src/base/platform/condition-variable.h"
#include "src/base/platform/mutex.h"
#include "src/tasks/cancelable-task.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-tier.h"
#include "src/zone/accounting-allocator.h"
namespace v8 {
namespace internal {
class AsmWasmData;
class CodeTracer;
class CompilationStatistics;
class HeapNumber;
class WasmInstanceObject;
class WasmModuleObject;
class JSArrayBuffer;
namespace wasm {
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
namespace gdb_server {
class GdbServer;
}
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
class AsyncCompileJob;
class ErrorThrower;
struct ModuleWireBytes;
class WasmFeatures;
class V8_EXPORT_PRIVATE CompilationResultResolver {
public:
virtual void OnCompilationSucceeded(Handle<WasmModuleObject> result) = 0;
virtual void OnCompilationFailed(Handle<Object> error_reason) = 0;
virtual ~CompilationResultResolver() = default;
};
class V8_EXPORT_PRIVATE InstantiationResultResolver {
public:
virtual void OnInstantiationSucceeded(Handle<WasmInstanceObject> result) = 0;
virtual void OnInstantiationFailed(Handle<Object> error_reason) = 0;
virtual ~InstantiationResultResolver() = default;
};
// Native modules cached by their wire bytes.
class NativeModuleCache {
public:
struct Key {
// Store the prefix hash as part of the key for faster lookup, and to
// quickly check existing prefixes for streaming compilation.
size_t prefix_hash;
Vector<const uint8_t> bytes;
bool operator==(const Key& other) const {
bool eq = bytes == other.bytes;
DCHECK_IMPLIES(eq, prefix_hash == other.prefix_hash);
return eq;
}
bool operator<(const Key& other) const {
if (prefix_hash != other.prefix_hash) {
DCHECK_IMPLIES(!bytes.empty() && !other.bytes.empty(),
bytes != other.bytes);
return prefix_hash < other.prefix_hash;
}
if (bytes.size() != other.bytes.size()) {
return bytes.size() < other.bytes.size();
}
// Fast path when the base pointers are the same.
// Also handles the {nullptr} case which would be UB for memcmp.
if (bytes.begin() == other.bytes.begin()) {
DCHECK_EQ(prefix_hash, other.prefix_hash);
return false;
}
DCHECK_NOT_NULL(bytes.begin());
DCHECK_NOT_NULL(other.bytes.begin());
return memcmp(bytes.begin(), other.bytes.begin(), bytes.size()) < 0;
}
};
std::shared_ptr<NativeModule> MaybeGetNativeModule(
ModuleOrigin origin, Vector<const uint8_t> wire_bytes);
bool GetStreamingCompilationOwnership(size_t prefix_hash);
void StreamingCompilationFailed(size_t prefix_hash);
std::shared_ptr<NativeModule> Update(
std::shared_ptr<NativeModule> native_module, bool error);
void Erase(NativeModule* native_module);
bool empty() { return map_.empty(); }
static size_t WireBytesHash(Vector<const uint8_t> bytes);
// Hash the wire bytes up to the code section header. Used as a heuristic to
// avoid streaming compilation of modules that are likely already in the
// cache. See {GetStreamingCompilationOwnership}. Assumes that the bytes have
// already been validated.
static size_t PrefixHash(Vector<const uint8_t> wire_bytes);
private:
// Each key points to the corresponding native module's wire bytes, so they
// should always be valid as long as the native module is alive. When
// the native module dies, {FreeNativeModule} deletes the entry from the
// map, so that we do not leave any dangling key pointing to an expired
// weak_ptr. This also serves as a way to regularly clean up the map, which
// would otherwise accumulate expired entries.
// A {nullopt} value is inserted to indicate that this native module is
// currently being created in some thread, and that other threads should wait
// before trying to get it from the cache.
// By contrast, an expired {weak_ptr} indicates that the native module died
// and will soon be cleaned up from the cache.
std::map<Key, base::Optional<std::weak_ptr<NativeModule>>> map_;
base::Mutex mutex_;
// This condition variable is used to synchronize threads compiling the same
// module. Only one thread will create the {NativeModule}. Other threads
// will wait on this variable until the first thread wakes them up.
base::ConditionVariable cache_cv_;
};
// The central data structure that represents an engine instance capable of
// loading, instantiating, and executing Wasm code.
class V8_EXPORT_PRIVATE WasmEngine {
public:
WasmEngine();
~WasmEngine();
// Synchronously validates the given bytes that represent an encoded Wasm
// module.
bool SyncValidate(Isolate* isolate, const WasmFeatures& enabled,
const ModuleWireBytes& bytes);
// Synchronously compiles the given bytes that represent a translated
// asm.js module.
MaybeHandle<AsmWasmData> SyncCompileTranslatedAsmJs(
Isolate* isolate, ErrorThrower* thrower, const ModuleWireBytes& bytes,
Vector<const byte> asm_js_offset_table_bytes,
Handle<HeapNumber> uses_bitset, LanguageMode language_mode);
Handle<WasmModuleObject> FinalizeTranslatedAsmJs(
Isolate* isolate, Handle<AsmWasmData> asm_wasm_data,
Handle<Script> script);
// Synchronously compiles the given bytes that represent an encoded Wasm
// module.
MaybeHandle<WasmModuleObject> SyncCompile(Isolate* isolate,
const WasmFeatures& enabled,
ErrorThrower* thrower,
const ModuleWireBytes& bytes);
// Synchronously instantiate the given Wasm module with the given imports.
// If the module represents an asm.js module, then the supplied {memory}
// should be used as the memory of the instance.
MaybeHandle<WasmInstanceObject> SyncInstantiate(
Isolate* isolate, ErrorThrower* thrower,
Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports,
MaybeHandle<JSArrayBuffer> memory);
// Begin an asynchronous compilation of the given bytes that represent an
// encoded Wasm module.
// The {is_shared} flag indicates if the bytes backing the module could
// be shared across threads, i.e. could be concurrently modified.
void AsyncCompile(Isolate* isolate, const WasmFeatures& enabled,
std::shared_ptr<CompilationResultResolver> resolver,
const ModuleWireBytes& bytes, bool is_shared,
const char* api_method_name_for_errors);
// Begin an asynchronous instantiation of the given Wasm module.
void AsyncInstantiate(Isolate* isolate,
std::unique_ptr<InstantiationResultResolver> resolver,
Handle<WasmModuleObject> module_object,
MaybeHandle<JSReceiver> imports);
std::shared_ptr<StreamingDecoder> StartStreamingCompilation(
Isolate* isolate, const WasmFeatures& enabled, Handle<Context> context,
const char* api_method_name,
std::shared_ptr<CompilationResultResolver> resolver);
// Compiles the function with the given index at a specific compilation tier.
// Errors are stored internally in the CompilationState.
// This is mostly used for testing to force a function into a specific tier.
void CompileFunction(Isolate* isolate, NativeModule* native_module,
uint32_t function_index, ExecutionTier tier);
void TierDownAllModulesPerIsolate(Isolate* isolate);
void TierUpAllModulesPerIsolate(Isolate* isolate);
// Exports the sharable parts of the given module object so that they can be
// transferred to a different Context/Isolate using the same engine.
std::shared_ptr<NativeModule> ExportNativeModule(
Handle<WasmModuleObject> module_object);
// Imports the shared part of a module from a different Context/Isolate using
// the the same engine, recreating a full module object in the given Isolate.
Handle<WasmModuleObject> ImportNativeModule(
Isolate* isolate, std::shared_ptr<NativeModule> shared_module,
Vector<const char> source_url);
WasmCodeManager* code_manager() { return &code_manager_; }
AccountingAllocator* allocator() { return &allocator_; }
// Compilation statistics for TurboFan compilations.
CompilationStatistics* GetOrCreateTurboStatistics();
// Prints the gathered compilation statistics, then resets them.
void DumpAndResetTurboStatistics();
// Used to redirect tracing output from {stdout} to a file.
CodeTracer* GetCodeTracer();
// Remove {job} from the list of active compile jobs.
std::unique_ptr<AsyncCompileJob> RemoveCompileJob(AsyncCompileJob* job);
// Returns true if at least one AsyncCompileJob that belongs to the given
// Isolate is currently running.
bool HasRunningCompileJob(Isolate* isolate);
// Deletes all AsyncCompileJobs that belong to the given context. All
// compilation is aborted, no more callbacks will be triggered. This is used
// when a context is disposed, e.g. because of browser navigation.
void DeleteCompileJobsOnContext(Handle<Context> context);
// Deletes all AsyncCompileJobs that belong to the given Isolate. All
// compilation is aborted, no more callbacks will be triggered. This is used
// for tearing down an isolate, or to clean it up to be reused.
void DeleteCompileJobsOnIsolate(Isolate* isolate);
// Manage the set of Isolates that use this WasmEngine.
void AddIsolate(Isolate* isolate);
void RemoveIsolate(Isolate* isolate);
template <typename T, typename... Args>
std::unique_ptr<T> NewBackgroundCompileTask(Args&&... args) {
return std::make_unique<T>(&background_compile_task_manager_,
std::forward<Args>(args)...);
}
// Trigger code logging for the given code objects in all Isolates which have
// access to the NativeModule containing this code. This method can be called
// from background threads.
void LogCode(Vector<WasmCode*>);
// Enable code logging for the given Isolate. Initially, code logging is
// enabled if {WasmCode::ShouldBeLogged(Isolate*)} returns true during
// {AddIsolate}.
void EnableCodeLogging(Isolate*);
// This is called from the foreground thread of the Isolate to log all
// outstanding code objects (added via {LogCode}).
void LogOutstandingCodesForIsolate(Isolate*);
// Create a new NativeModule. The caller is responsible for its
// lifetime. The native module will be given some memory for code,
// which will be page size aligned. The size of the initial memory
// is determined by {code_size_estimate}. The native module may later request
// more memory.
// TODO(wasm): isolate is only required here for CompilationState.
std::shared_ptr<NativeModule> NewNativeModule(
Isolate* isolate, const WasmFeatures& enabled_features,
std::shared_ptr<const WasmModule> module, size_t code_size_estimate);
// Try getting a cached {NativeModule}, or get ownership for its creation.
// Return {nullptr} if no {NativeModule} exists for these bytes. In this case,
// a {nullopt} entry is added to let other threads know that a {NativeModule}
// for these bytes is currently being created. The caller should eventually
// call {UpdateNativeModuleCache} to update the entry and wake up other
// threads. The {wire_bytes}' underlying array should be valid at least until
// the call to {UpdateNativeModuleCache}.
std::shared_ptr<NativeModule> MaybeGetNativeModule(
ModuleOrigin origin, Vector<const uint8_t> wire_bytes, Isolate* isolate);
// Replace the temporary {nullopt} with the new native module, or
// erase it if any error occurred. Wake up blocked threads waiting for this
// module.
// To avoid a deadlock on the main thread between synchronous and streaming
// compilation, two compilation jobs might compile the same native module at
// the same time. In this case the first call to {UpdateNativeModuleCache}
// will insert the native module in the cache, and the last call will discard
// its {native_module} argument and replace it with the existing entry.
// Return true in the former case, and false in the latter.
bool UpdateNativeModuleCache(bool error,
std::shared_ptr<NativeModule>* native_module,
Isolate* isolate);
// Register this prefix hash for a streaming compilation job.
// If the hash is not in the cache yet, the function returns true and the
// caller owns the compilation of this module.
// Otherwise another compilation job is currently preparing or has already
// prepared a module with the same prefix hash. The caller should wait until
// the stream is finished and call {MaybeGetNativeModule} to either get the
// module from the cache or get ownership for the compilation of these bytes.
bool GetStreamingCompilationOwnership(size_t prefix_hash);
// Remove the prefix hash from the cache when compilation failed. If
// compilation succeeded, {UpdateNativeModuleCache} should be called instead.
void StreamingCompilationFailed(size_t prefix_hash);
void FreeNativeModule(NativeModule*);
// Sample the code size of the given {NativeModule} in all isolates that have
// access to it. Call this after top-tier compilation finished.
// This will spawn foreground tasks that do *not* keep the NativeModule alive.
void SampleTopTierCodeSizeInAllIsolates(const std::shared_ptr<NativeModule>&);
// Called by each Isolate to report its live code for a GC cycle. First
// version reports an externally determined set of live code (might be empty),
// second version gets live code from the execution stack of that isolate.
void ReportLiveCodeForGC(Isolate*, Vector<WasmCode*>);
void ReportLiveCodeFromStackForGC(Isolate*);
// Add potentially dead code. The occurrence in the set of potentially dead
// code counts as a reference, and is decremented on the next GC.
// Returns {true} if the code was added to the set of potentially dead code,
// {false} if an entry already exists. The ref count is *unchanged* in any
// case.
V8_WARN_UNUSED_RESULT bool AddPotentiallyDeadCode(WasmCode*);
// Free dead code.
using DeadCodeMap = std::unordered_map<NativeModule*, std::vector<WasmCode*>>;
void FreeDeadCode(const DeadCodeMap&);
void FreeDeadCodeLocked(const DeadCodeMap&);
Handle<Script> GetOrCreateScript(Isolate*,
const std::shared_ptr<NativeModule>&,
Vector<const char> source_url = {});
// Call on process start and exit.
static void InitializeOncePerProcess();
static void GlobalTearDown();
// Returns a reference to the WasmEngine shared by the entire process. Try to
// use {Isolate::wasm_engine} instead if it is available, which encapsulates
// engine lifetime decisions during Isolate bootstrapping.
static std::shared_ptr<WasmEngine> GetWasmEngine();
private:
struct CurrentGCInfo;
struct IsolateInfo;
struct NativeModuleInfo;
AsyncCompileJob* CreateAsyncCompileJob(
Isolate* isolate, const WasmFeatures& enabled,
std::unique_ptr<byte[]> bytes_copy, size_t length,
Handle<Context> context, const char* api_method_name,
std::shared_ptr<CompilationResultResolver> resolver);
void TriggerGC(int8_t gc_sequence_index);
// Remove an isolate from the outstanding isolates of the current GC. Returns
// true if the isolate was still outstanding, false otherwise. Hold {mutex_}
// when calling this method.
bool RemoveIsolateFromCurrentGC(Isolate*);
// Finish a GC if there are no more outstanding isolates. Hold {mutex_} when
// calling this method.
void PotentiallyFinishCurrentGC();
WasmCodeManager code_manager_;
AccountingAllocator allocator_;
// Task manager managing all background compile jobs. Before shut down of the
// engine, they must all be finished because they access the allocator.
CancelableTaskManager background_compile_task_manager_;
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
// Implements a GDB-remote stub for WebAssembly debugging.
std::unique_ptr<gdb_server::GdbServer> gdb_server_;
#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
// This mutex protects all information which is mutated concurrently or
// fields that are initialized lazily on the first access.
base::Mutex mutex_;
//////////////////////////////////////////////////////////////////////////////
// Protected by {mutex_}:
// We use an AsyncCompileJob as the key for itself so that we can delete the
// job from the map when it is finished.
std::unordered_map<AsyncCompileJob*, std::unique_ptr<AsyncCompileJob>>
async_compile_jobs_;
std::unique_ptr<CompilationStatistics> compilation_stats_;
std::unique_ptr<CodeTracer> code_tracer_;
// Set of isolates which use this WasmEngine.
std::unordered_map<Isolate*, std::unique_ptr<IsolateInfo>> isolates_;
// Set of native modules managed by this engine.
std::unordered_map<NativeModule*, std::unique_ptr<NativeModuleInfo>>
native_modules_;
// Size of code that became dead since the last GC. If this exceeds a certain
// threshold, a new GC is triggered.
size_t new_potentially_dead_code_size_ = 0;
// If an engine-wide GC is currently running, this pointer stores information
// about that.
std::unique_ptr<CurrentGCInfo> current_gc_info_;
NativeModuleCache native_module_cache_;
// End of fields protected by {mutex_}.
//////////////////////////////////////////////////////////////////////////////
DISALLOW_COPY_AND_ASSIGN(WasmEngine);
};
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_WASM_ENGINE_H_
|