• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
27 namespace v8 {
28 namespace internal {
29 
30 class AsmWasmData;
31 class CodeTracer;
32 class CompilationStatistics;
33 class HeapNumber;
34 class WasmInstanceObject;
35 class WasmModuleObject;
36 class JSArrayBuffer;
37 
38 namespace wasm {
39 
40 #ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
41 namespace gdb_server {
42 class GdbServer;
43 }  // namespace gdb_server
44 #endif  // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
45 
46 class AsyncCompileJob;
47 class ErrorThrower;
48 struct ModuleWireBytes;
49 class StreamingDecoder;
50 class WasmFeatures;
51 
52 class 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 
59 class 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.
67 class 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 
empty()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.
144 class 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 
allocator()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 
type_canonicalizer()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.
440 V8_EXPORT_PRIVATE WasmEngine* GetWasmEngine();
441 
442 // Returns a reference to the WasmCodeManager shared by the entire process.
443 V8_EXPORT_PRIVATE WasmCodeManager* GetWasmCodeManager();
444 
445 }  // namespace wasm
446 }  // namespace internal
447 }  // namespace v8
448 
449 #endif  // V8_WASM_WASM_ENGINE_H_
450