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