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