wasm-engine.h 18.8 KB
Newer Older
1 2 3 4
// 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.

5 6 7 8
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif  // !V8_ENABLE_WEBASSEMBLY

9 10
#ifndef V8_WASM_WASM_ENGINE_H_
#define V8_WASM_WASM_ENGINE_H_
11

12 13
#include <algorithm>
#include <map>
14
#include <memory>
15
#include <unordered_map>
16
#include <unordered_set>
17

18 19
#include "src/base/platform/condition-variable.h"
#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 28 29

namespace v8 {
namespace internal {

30
class AsmWasmData;
31
class CodeTracer;
32
class CompilationStatistics;
33
class HeapNumber;
34
class WasmInstanceObject;
35
class WasmModuleObject;
36
class JSArrayBuffer;
37

38 39
namespace wasm {

40 41 42
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
namespace gdb_server {
class GdbServer;
43
}  // namespace gdb_server
44 45
#endif  // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING

46
class AsyncCompileJob;
47 48
class ErrorThrower;
struct ModuleWireBytes;
49
class StreamingDecoder;
50
class WasmFeatures;
51

52 53 54 55
class V8_EXPORT_PRIVATE CompilationResultResolver {
 public:
  virtual void OnCompilationSucceeded(Handle<WasmModuleObject> result) = 0;
  virtual void OnCompilationFailed(Handle<Object> error_reason) = 0;
56
  virtual ~CompilationResultResolver() = default;
57 58 59 60 61 62
};

class V8_EXPORT_PRIVATE InstantiationResultResolver {
 public:
  virtual void OnInstantiationSucceeded(Handle<WasmInstanceObject> result) = 0;
  virtual void OnInstantiationFailed(Handle<Object> error_reason) = 0;
63
  virtual ~InstantiationResultResolver() = default;
64 65
};

66 67 68
// Native modules cached by their wire bytes.
class NativeModuleCache {
 public:
69 70 71 72
  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;
73
    base::Vector<const uint8_t> bytes;
74 75 76 77 78 79 80 81 82

    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) {
83 84
        DCHECK_IMPLIES(!bytes.empty() && !other.bytes.empty(),
                       bytes != other.bytes);
85 86
        return prefix_hash < other.prefix_hash;
      }
87 88 89 90 91 92
      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()) {
93
        DCHECK_EQ(prefix_hash, other.prefix_hash);
94 95 96 97 98
        return false;
      }
      DCHECK_NOT_NULL(bytes.begin());
      DCHECK_NOT_NULL(other.bytes.begin());
      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 105 106 107
  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);
108 109
  void Erase(NativeModule* native_module);

110 111
  bool empty() { return map_.empty(); }

112
  static size_t WireBytesHash(base::Vector<const uint8_t> bytes);
113 114 115 116 117

  // 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.
118
  static size_t PrefixHash(base::Vector<const uint8_t> wire_bytes);
119

120 121 122 123 124 125 126 127 128 129 130 131
 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.
132
  std::map<Key, base::Optional<std::weak_ptr<NativeModule>>> map_;
133 134 135 136 137 138 139 140 141

  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_;
};

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 148
  WasmEngine(const WasmEngine&) = delete;
  WasmEngine& operator=(const WasmEngine&) = delete;
149
  ~WasmEngine();
150

151
  // Synchronously validates the given bytes that represent an encoded Wasm
152 153
  // module. If validation fails and {error_msg} is present, it is set to the
  // validation error.
154
  bool SyncValidate(Isolate* isolate, const WasmFeatures& enabled,
155 156
                    const ModuleWireBytes& bytes,
                    std::string* error_message = nullptr);
157

158 159
  // Synchronously compiles the given bytes that represent a translated
  // 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 165 166
  Handle<WasmModuleObject> FinalizeTranslatedAsmJs(
      Isolate* isolate, Handle<AsmWasmData> asm_wasm_data,
      Handle<Script> script);
167

168
  // Synchronously compiles the given bytes that represent an encoded Wasm
169 170
  // module.
  MaybeHandle<WasmModuleObject> SyncCompile(Isolate* isolate,
171
                                            const WasmFeatures& enabled,
172 173 174
                                            ErrorThrower* thrower,
                                            const ModuleWireBytes& bytes);

175
  // Synchronously instantiate the given Wasm module with the given imports.
176 177 178 179 180 181 182 183
  // 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
184
  // encoded Wasm module.
185 186
  // The {is_shared} flag indicates if the bytes backing the module could
  // 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 190
                    const ModuleWireBytes& bytes, bool is_shared,
                    const char* api_method_name_for_errors);
191

192
  // Begin an asynchronous instantiation of the given Wasm module.
193 194
  void AsyncInstantiate(Isolate* isolate,
                        std::unique_ptr<InstantiationResultResolver> resolver,
195 196 197
                        Handle<WasmModuleObject> module_object,
                        MaybeHandle<JSReceiver> imports);

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 204 205 206
  // 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,
207
                       uint32_t function_index, ExecutionTier tier);
208

209
  void TierDownAllModulesPerIsolate(Isolate* isolate);
210
  void TierUpAllModulesPerIsolate(Isolate* isolate);
211

212 213 214 215 216 217 218 219
  // 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(
220
      Isolate* isolate, std::shared_ptr<NativeModule> shared_module,
221
      base::Vector<const char> source_url);
222

223 224
  AccountingAllocator* allocator() { return &allocator_; }

225 226 227 228
  // Compilation statistics for TurboFan compilations. Returns a shared_ptr
  // so that background compilation jobs can hold on to it while the main thread
  // shuts down.
  std::shared_ptr<CompilationStatistics> GetOrCreateTurboStatistics();
229 230 231

  // Prints the gathered compilation statistics, then resets them.
  void DumpAndResetTurboStatistics();
232 233
  // Same, but no reset.
  void DumpTurboStatistics();
234

235 236 237
  // Used to redirect tracing output from {stdout} to a file.
  CodeTracer* GetCodeTracer();

238
  // Remove {job} from the list of active compile jobs.
239
  std::unique_ptr<AsyncCompileJob> RemoveCompileJob(AsyncCompileJob* job);
240

241 242 243
  // Returns true if at least one AsyncCompileJob that belongs to the given
  // Isolate is currently running.
  bool HasRunningCompileJob(Isolate* isolate);
244

245 246 247 248 249
  // 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);

250
  // Deletes all AsyncCompileJobs that belong to the given Isolate. All
251 252
  // 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.
253
  void DeleteCompileJobsOnIsolate(Isolate* isolate);
254

255 256 257 258 259 260
  // Get a token for compiling wrappers for an Isolate. The token is used to
  // synchronize background tasks on isolate shutdown. The caller should only
  // hold the token while compiling export wrappers. If the isolate is already
  // shutting down, this method will return an invalid token.
  OperationsBarrier::Token StartWrapperCompilation(Isolate*);

261 262 263 264
  // Manage the set of Isolates that use this WasmEngine.
  void AddIsolate(Isolate* isolate);
  void RemoveIsolate(Isolate* isolate);

265 266 267
  // 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.
268
  void LogCode(base::Vector<WasmCode*>);
269

270 271 272 273 274
  // Enable code logging for the given Isolate. Initially, code logging is
  // enabled if {WasmCode::ShouldBeLogged(Isolate*)} returns true during
  // {AddIsolate}.
  void EnableCodeLogging(Isolate*);

275 276 277 278
  // This is called from the foreground thread of the Isolate to log all
  // outstanding code objects (added via {LogCode}).
  void LogOutstandingCodesForIsolate(Isolate*);

279 280 281
  // 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
282 283 284
  // is determined by {code_size_estimate}. The native module may later request
  // more memory.
  // TODO(wasm): isolate is only required here for CompilationState.
285 286
  std::shared_ptr<NativeModule> NewNativeModule(
      Isolate* isolate, const WasmFeatures& enabled_features,
287
      std::shared_ptr<const WasmModule> module, size_t code_size_estimate);
288

289 290 291 292 293 294 295
  // 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}.
296
  std::shared_ptr<NativeModule> MaybeGetNativeModule(
297 298
      ModuleOrigin origin, base::Vector<const uint8_t> wire_bytes,
      Isolate* isolate);
299

300 301
  // Replace the temporary {nullopt} with the new native module, or
  // erase it if any error occurred. Wake up blocked threads waiting for this
302
  // module.
303 304 305 306 307 308 309
  // 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,
310 311
                               std::shared_ptr<NativeModule>* native_module,
                               Isolate* isolate);
312 313 314 315 316 317 318 319 320 321 322 323 324

  // 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);
325

326 327
  void FreeNativeModule(NativeModule*);

328 329 330 331 332
  // 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>&);

333 334 335
  // 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.
336
  void ReportLiveCodeForGC(Isolate*, base::Vector<WasmCode*>);
337
  void ReportLiveCodeFromStackForGC(Isolate*);
338

339 340 341 342 343 344 345
  // 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*);

346 347 348 349
  // Free dead code.
  using DeadCodeMap = std::unordered_map<NativeModule*, std::vector<WasmCode*>>;
  void FreeDeadCode(const DeadCodeMap&);
  void FreeDeadCodeLocked(const DeadCodeMap&);
350

351 352
  Handle<Script> GetOrCreateScript(Isolate*,
                                   const std::shared_ptr<NativeModule>&,
353
                                   base::Vector<const char> source_url);
354

355 356 357
  // Returns a barrier allowing background compile operations if valid and
  // preventing this object from being destroyed.
  std::shared_ptr<OperationsBarrier> GetBarrierForBackgroundCompile();
358

359 360 361 362
  void SampleThrowEvent(Isolate*);
  void SampleRethrowEvent(Isolate*);
  void SampleCatchEvent(Isolate*);

363 364
  TypeCanonicalizer* type_canonicalizer() { return &type_canonicalizer_; }

365 366 367 368
  // Call on process start and exit.
  static void InitializeOncePerProcess();
  static void GlobalTearDown();

369
 private:
370
  struct CurrentGCInfo;
371
  struct IsolateInfo;
372
  struct NativeModuleInfo;
373

374
  AsyncCompileJob* CreateAsyncCompileJob(
375 376
      Isolate* isolate, const WasmFeatures& enabled,
      std::unique_ptr<byte[]> bytes_copy, size_t length,
377
      Handle<Context> context, const char* api_method_name,
378
      std::shared_ptr<CompilationResultResolver> resolver, int compilation_id);
379

380
  void TriggerGC(int8_t gc_sequence_index);
381

382 383 384 385 386 387 388 389 390
  // 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();

391
  AccountingAllocator allocator_;
392

393 394 395 396 397
#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

398 399
  std::atomic<int> next_compilation_id_{0};

400 401
  TypeCanonicalizer type_canonicalizer_;

402 403 404 405 406 407 408
  // 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_}:

409 410
  // We use an AsyncCompileJob as the key for itself so that we can delete the
  // job from the map when it is finished.
411 412
  std::unordered_map<AsyncCompileJob*, std::unique_ptr<AsyncCompileJob>>
      async_compile_jobs_;
413

414
  std::shared_ptr<CompilationStatistics> compilation_stats_;
415 416
  std::unique_ptr<CodeTracer> code_tracer_;

417 418 419
  // Set of isolates which use this WasmEngine.
  std::unordered_map<Isolate*, std::unique_ptr<IsolateInfo>> isolates_;

420 421 422 423
  // Set of native modules managed by this engine.
  std::unordered_map<NativeModule*, std::unique_ptr<NativeModuleInfo>>
      native_modules_;

424 425
  std::shared_ptr<OperationsBarrier> operations_barrier_{
      std::make_shared<OperationsBarrier>()};
426

427 428 429
  // 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;
430

431 432 433 434
  // If an engine-wide GC is currently running, this pointer stores information
  // about that.
  std::unique_ptr<CurrentGCInfo> current_gc_info_;

435
  NativeModuleCache native_module_cache_;
436

437 438
  // End of fields protected by {mutex_}.
  //////////////////////////////////////////////////////////////////////////////
439 440
};

441 442 443
// Returns a reference to the WasmEngine shared by the entire process.
V8_EXPORT_PRIVATE WasmEngine* GetWasmEngine();

444 445 446
// Returns a reference to the WasmCodeManager shared by the entire process.
V8_EXPORT_PRIVATE WasmCodeManager* GetWasmCodeManager();

447 448 449 450
}  // namespace wasm
}  // namespace internal
}  // namespace v8

451
#endif  // V8_WASM_WASM_ENGINE_H_