wasm-engine.cc 37.7 KB
Newer Older
1 2 3 4 5
// Copyright 2018 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.

#include "src/wasm/wasm-engine.h"
6

7
#include "src/base/platform/time.h"
8
#include "src/diagnostics/code-tracer.h"
9
#include "src/diagnostics/compilation-statistics.h"
10
#include "src/execution/frames.h"
11
#include "src/execution/v8threads.h"
12
#include "src/logging/counters.h"
13
#include "src/objects/heap-number.h"
14
#include "src/objects/js-promise.h"
15
#include "src/objects/objects-inl.h"
16
#include "src/utils/ostreams.h"
17
#include "src/wasm/function-compiler.h"
18
#include "src/wasm/module-compiler.h"
Marja Hölttä's avatar
Marja Hölttä committed
19
#include "src/wasm/module-decoder.h"
20
#include "src/wasm/module-instantiate.h"
Marja Hölttä's avatar
Marja Hölttä committed
21
#include "src/wasm/streaming-decoder.h"
22
#include "src/wasm/wasm-objects-inl.h"
23 24 25 26 27

namespace v8 {
namespace internal {
namespace wasm {

28 29 30 31 32
#define TRACE_CODE_GC(...)                                         \
  do {                                                             \
    if (FLAG_trace_wasm_code_gc) PrintF("[wasm-gc] " __VA_ARGS__); \
  } while (false)

33
namespace {
34 35 36
// A task to log a set of {WasmCode} objects in an isolate. It does not own any
// data itself, since it is owned by the platform, so lifetime is not really
// bound to the wasm engine.
37 38
class LogCodesTask : public Task {
 public:
39 40 41 42 43 44
  LogCodesTask(base::Mutex* mutex, LogCodesTask** task_slot, Isolate* isolate,
               WasmEngine* engine)
      : mutex_(mutex),
        task_slot_(task_slot),
        isolate_(isolate),
        engine_(engine) {
45 46 47 48 49 50 51 52
    DCHECK_NOT_NULL(task_slot);
    DCHECK_NOT_NULL(isolate);
  }

  ~LogCodesTask() {
    // If the platform deletes this task before executing it, we also deregister
    // it to avoid use-after-free from still-running background threads.
    if (!cancelled()) DeregisterTask();
53
  }
54 55

  void Run() override {
56 57
    if (cancelled()) return;
    DeregisterTask();
58
    engine_->LogOutstandingCodesForIsolate(isolate_);
59 60 61 62 63 64 65 66
  }

  void Cancel() {
    // Cancel will only be called on Isolate shutdown, which happens on the
    // Isolate's foreground thread. Thus no synchronization needed.
    isolate_ = nullptr;
  }

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
  bool cancelled() const { return isolate_ == nullptr; }

  void DeregisterTask() {
    // The task will only be deregistered from the foreground thread (executing
    // this task or calling its destructor), thus we do not need synchronization
    // on this field access.
    if (task_slot_ == nullptr) return;  // already deregistered.
    // Remove this task from the {IsolateInfo} in the engine. The next
    // logging request will allocate and schedule a new task.
    base::MutexGuard guard(mutex_);
    DCHECK_EQ(this, *task_slot_);
    *task_slot_ = nullptr;
    task_slot_ = nullptr;
  }

82 83 84 85
 private:
  // The mutex of the WasmEngine.
  base::Mutex* const mutex_;
  // The slot in the WasmEngine where this LogCodesTask is stored. This is
86 87
  // cleared by this task before execution or on task destruction.
  LogCodesTask** task_slot_;
88
  Isolate* isolate_;
89
  WasmEngine* const engine_;
90
};
91

92 93 94 95 96 97 98 99 100 101 102 103
void CheckNoArchivedThreads(Isolate* isolate) {
  class ArchivedThreadsVisitor : public ThreadVisitor {
    void VisitThread(Isolate* isolate, ThreadLocalTop* top) override {
      // Archived threads are rarely used, and not combined with Wasm at the
      // moment. Implement this and test it properly once we have a use case for
      // that.
      FATAL("archived threads in combination with wasm not supported");
    }
  } archived_threads_visitor;
  isolate->thread_manager()->IterateArchivedThreads(&archived_threads_visitor);
}

104
class WasmGCForegroundTask : public CancelableTask {
105
 public:
106 107
  explicit WasmGCForegroundTask(Isolate* isolate)
      : CancelableTask(isolate->cancelable_task_manager()), isolate_(isolate) {}
108

109
  void RunInternal() final {
110 111 112
    WasmEngine* engine = isolate_->wasm_engine();
    // If the foreground task is executing, there is no wasm code active. Just
    // report an empty set of live wasm code.
113 114 115 116 117
#ifdef ENABLE_SLOW_DCHECKS
    for (StackFrameIterator it(isolate_); !it.done(); it.Advance()) {
      DCHECK_NE(StackFrame::WASM_COMPILED, it.frame()->type());
    }
#endif
118
    CheckNoArchivedThreads(isolate_);
119 120 121 122 123 124 125
    engine->ReportLiveCodeForGC(isolate_, Vector<WasmCode*>{});
  }

 private:
  Isolate* isolate_;
};

126 127
}  // namespace

128
struct WasmEngine::CurrentGCInfo {
129 130 131 132 133
  explicit CurrentGCInfo(int8_t gc_sequence_index)
      : gc_sequence_index(gc_sequence_index) {
    DCHECK_NE(0, gc_sequence_index);
  }

134 135 136 137 138 139 140
  // Set of isolates that did not scan their stack yet for used WasmCode, and
  // their scheduled foreground task.
  std::unordered_map<Isolate*, WasmGCForegroundTask*> outstanding_isolates;

  // Set of dead code. Filled with all potentially dead code on initialization.
  // Code that is still in-use is removed by the individual isolates.
  std::unordered_set<WasmCode*> dead_code;
141

142 143 144 145 146
  // The number of GCs triggered in the native module that triggered this GC.
  // This is stored in the histogram for each participating isolate during
  // execution of that isolate's foreground task.
  const int8_t gc_sequence_index;

147 148
  // If during this GC, another GC was requested, we skipped that other GC (we
  // only run one GC at a time). Remember though to trigger another one once
149 150 151 152
  // this one finishes. {next_gc_sequence_index} is 0 if no next GC is needed,
  // and >0 otherwise. It stores the {num_code_gcs_triggered} of the native
  // module which triggered the next GC.
  int8_t next_gc_sequence_index = 0;
153 154 155 156

  // The start time of this GC; used for tracing and sampled via {Counters}.
  // Can be null ({TimeTicks::IsNull()}) if timer is not high resolution.
  base::TimeTicks start_time;
157 158
};

159
struct WasmEngine::IsolateInfo {
160
  explicit IsolateInfo(Isolate* isolate)
161 162
      : log_codes(WasmCode::ShouldBeLogged(isolate)),
        async_counters(isolate->async_counters()) {
163 164 165 166 167
    v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
    v8::Platform* platform = V8::GetCurrentPlatform();
    foreground_task_runner = platform->GetForegroundTaskRunner(v8_isolate);
  }

168 169 170 171 172 173 174 175
#ifdef DEBUG
  ~IsolateInfo() {
    // Before destructing, the {WasmEngine} must have cleared outstanding code
    // to log.
    DCHECK_EQ(0, code_to_log.size());
  }
#endif

176 177 178
  // All native modules that are being used by this Isolate (currently only
  // grows, never shrinks).
  std::set<NativeModule*> native_modules;
179

180 181 182
  // Caches whether code needs to be logged on this isolate.
  bool log_codes;

183 184 185
  // The currently scheduled LogCodesTask.
  LogCodesTask* log_codes_task = nullptr;

186 187 188
  // The vector of code objects that still need to be logged in this isolate.
  std::vector<WasmCode*> code_to_log;

189 190
  // The foreground task runner of the isolate (can be called from background).
  std::shared_ptr<v8::TaskRunner> foreground_task_runner;
191 192

  const std::shared_ptr<Counters> async_counters;
193 194
};

195 196 197 198
struct WasmEngine::NativeModuleInfo {
  // Set of isolates using this NativeModule.
  std::unordered_set<Isolate*> isolates;

199 200 201 202
  // Set of potentially dead code. This set holds one ref for each code object,
  // until code is detected to be really dead. At that point, the ref count is
  // decremented and code is move to the {dead_code} set. If the code is finally
  // deleted, it is also removed from {dead_code}.
203
  std::unordered_set<WasmCode*> potentially_dead_code;
204 205 206 207

  // Code that is not being executed in any isolate any more, but the ref count
  // did not drop to zero yet.
  std::unordered_set<WasmCode*> dead_code;
208 209 210 211

  // Number of code GCs triggered because code in this native module became
  // potentially dead.
  int8_t num_code_gcs_triggered = 0;
212 213
};

214
WasmEngine::WasmEngine() : code_manager_(FLAG_wasm_max_code_space * MB) {}
215

216
WasmEngine::~WasmEngine() {
217 218
  // Synchronize on all background compile tasks.
  background_compile_task_manager_.CancelAndWait();
219
  // All AsyncCompileJobs have been canceled.
220
  DCHECK(async_compile_jobs_.empty());
221 222
  // All Isolates have been deregistered.
  DCHECK(isolates_.empty());
223
  // All NativeModules did die.
224
  DCHECK(native_modules_.empty());
225
}
226

227 228
bool WasmEngine::SyncValidate(Isolate* isolate, const WasmFeatures& enabled,
                              const ModuleWireBytes& bytes) {
229 230
  // TODO(titzer): remove dependency on the isolate.
  if (bytes.start() == nullptr || bytes.length() == 0) return false;
231
  ModuleResult result =
232
      DecodeWasmModule(enabled, bytes.start(), bytes.end(), true, kWasmOrigin,
233
                       isolate->counters(), allocator());
234 235 236
  return result.ok();
}

237
MaybeHandle<AsmWasmData> WasmEngine::SyncCompileTranslatedAsmJs(
238
    Isolate* isolate, ErrorThrower* thrower, const ModuleWireBytes& bytes,
239
    Vector<const byte> asm_js_offset_table_bytes,
240 241 242 243
    Handle<HeapNumber> uses_bitset, LanguageMode language_mode) {
  ModuleOrigin origin = language_mode == LanguageMode::kSloppy
                            ? kAsmJsSloppyOrigin
                            : kAsmJsStrictOrigin;
244
  ModuleResult result =
245
      DecodeWasmModule(kAsmjsWasmFeatures, bytes.start(), bytes.end(), false,
246
                       origin, isolate->counters(), allocator());
247 248 249 250 251 252
  if (result.failed()) {
    // This happens once in a while when we have missed some limit check
    // in the asm parser. Output an error message to help diagnose, but crash.
    std::cout << result.error().message();
    UNREACHABLE();
  }
253

254
  // Transfer ownership of the WasmModule to the {Managed<WasmModule>} generated
255 256
  // in {CompileToNativeModule}.
  Handle<FixedArray> export_wrappers;
257
  std::shared_ptr<NativeModule> native_module =
258 259 260 261 262 263 264 265
      CompileToNativeModule(isolate, kAsmjsWasmFeatures, thrower,
                            std::move(result).value(), bytes, &export_wrappers);
  if (!native_module) return {};

  // Create heap objects for asm.js offset table to be stored in the module
  // object.
  Handle<ByteArray> asm_js_offset_table =
      isolate->factory()->NewByteArray(asm_js_offset_table_bytes.length());
266
  asm_js_offset_table->copy_in(0, asm_js_offset_table_bytes.begin(),
267 268 269 270 271 272 273 274 275 276
                               asm_js_offset_table_bytes.length());

  return AsmWasmData::New(isolate, std::move(native_module), export_wrappers,
                          asm_js_offset_table, uses_bitset);
}

Handle<WasmModuleObject> WasmEngine::FinalizeTranslatedAsmJs(
    Isolate* isolate, Handle<AsmWasmData> asm_wasm_data,
    Handle<Script> script) {
  std::shared_ptr<NativeModule> native_module =
277
      asm_wasm_data->managed_native_module().get();
278 279
  Handle<FixedArray> export_wrappers =
      handle(asm_wasm_data->export_wrappers(), isolate);
280 281
  Handle<WasmModuleObject> module_object = WasmModuleObject::New(
      isolate, std::move(native_module), script, export_wrappers);
282 283
  module_object->set_asm_js_offset_table(asm_wasm_data->asm_js_offset_table());
  return module_object;
284 285 286
}

MaybeHandle<WasmModuleObject> WasmEngine::SyncCompile(
287 288
    Isolate* isolate, const WasmFeatures& enabled, ErrorThrower* thrower,
    const ModuleWireBytes& bytes) {
289
  ModuleResult result =
290
      DecodeWasmModule(enabled, bytes.start(), bytes.end(), false, kWasmOrigin,
291
                       isolate->counters(), allocator());
292
  if (result.failed()) {
293
    thrower->CompileFailed(result.error());
294 295 296
    return {};
  }

297
  // Transfer ownership of the WasmModule to the {Managed<WasmModule>} generated
298
  // in {CompileToModuleObject}.
299
  Handle<FixedArray> export_wrappers;
300
  std::shared_ptr<NativeModule> native_module =
301 302 303 304 305 306 307 308
      CompileToNativeModule(isolate, enabled, thrower,
                            std::move(result).value(), bytes, &export_wrappers);
  if (!native_module) return {};

  Handle<Script> script =
      CreateWasmScript(isolate, bytes, native_module->module()->source_map_url);

  // Create the module object.
309
  // TODO(clemensb): For the same module (same bytes / same hash), we should
310 311 312 313 314 315 316
  // only have one WasmModuleObject. Otherwise, we might only set
  // breakpoints on a (potentially empty) subset of the instances.

  // Create the compiled module object and populate with compiled functions
  // and information needed at instantiation time. This object needs to be
  // serializable. Instantiation may occur off a deserialized version of this
  // object.
317 318
  Handle<WasmModuleObject> module_object = WasmModuleObject::New(
      isolate, std::move(native_module), script, export_wrappers);
319 320 321 322

  // Finish the Wasm script now and make it public to the debugger.
  isolate->debug()->OnAfterCompile(script);
  return module_object;
323 324 325 326 327 328 329 330 331 332
}

MaybeHandle<WasmInstanceObject> WasmEngine::SyncInstantiate(
    Isolate* isolate, ErrorThrower* thrower,
    Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports,
    MaybeHandle<JSArrayBuffer> memory) {
  return InstantiateToInstanceObject(isolate, thrower, module_object, imports,
                                     memory);
}

333 334 335
void WasmEngine::AsyncInstantiate(
    Isolate* isolate, std::unique_ptr<InstantiationResultResolver> resolver,
    Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports) {
336
  ErrorThrower thrower(isolate, "WebAssembly.instantiate()");
337 338
  // Instantiate a TryCatch so that caught exceptions won't progagate out.
  // They will still be set as pending exceptions on the isolate.
339
  // TODO(clemensb): Avoid TryCatch, use Execution::TryCall internally to invoke
340 341 342 343 344
  // start function and report thrown exception explicitly via out argument.
  v8::TryCatch catcher(reinterpret_cast<v8::Isolate*>(isolate));
  catcher.SetVerbose(false);
  catcher.SetCaptureMessage(false);

345 346
  MaybeHandle<WasmInstanceObject> instance_object = SyncInstantiate(
      isolate, &thrower, module_object, imports, Handle<JSArrayBuffer>::null());
347 348 349 350 351 352

  if (!instance_object.is_null()) {
    resolver->OnInstantiationSucceeded(instance_object.ToHandleChecked());
    return;
  }

353 354 355
  if (isolate->has_pending_exception()) {
    // The JS code executed during instantiation has thrown an exception.
    // We have to move the exception to the promise chain.
356 357 358 359
    Handle<Object> exception(isolate->pending_exception(), isolate);
    isolate->clear_pending_exception();
    *isolate->external_caught_exception_address() = false;
    resolver->OnInstantiationFailed(exception);
360 361 362 363
    thrower.Reset();
  } else {
    DCHECK(thrower.error());
    resolver->OnInstantiationFailed(thrower.Reify());
364 365 366
  }
}

367
void WasmEngine::AsyncCompile(
368
    Isolate* isolate, const WasmFeatures& enabled,
369
    std::shared_ptr<CompilationResultResolver> resolver,
370 371
    const ModuleWireBytes& bytes, bool is_shared,
    const char* api_method_name_for_errors) {
372 373
  if (!FLAG_wasm_async_compilation) {
    // Asynchronous compilation disabled; fall back on synchronous compilation.
374
    ErrorThrower thrower(isolate, api_method_name_for_errors);
375 376 377 378 379
    MaybeHandle<WasmModuleObject> module_object;
    if (is_shared) {
      // Make a copy of the wire bytes to avoid concurrent modification.
      std::unique_ptr<uint8_t[]> copy(new uint8_t[bytes.length()]);
      memcpy(copy.get(), bytes.start(), bytes.length());
380
      ModuleWireBytes bytes_copy(copy.get(), copy.get() + bytes.length());
381
      module_object = SyncCompile(isolate, enabled, &thrower, bytes_copy);
382 383
    } else {
      // The wire bytes are not shared, OK to use them directly.
384
      module_object = SyncCompile(isolate, enabled, &thrower, bytes);
385 386
    }
    if (thrower.error()) {
387
      resolver->OnCompilationFailed(thrower.Reify());
388 389 390
      return;
    }
    Handle<WasmModuleObject> module = module_object.ToHandleChecked();
391
    resolver->OnCompilationSucceeded(module);
392 393 394 395 396
    return;
  }

  if (FLAG_wasm_test_streaming) {
    std::shared_ptr<StreamingDecoder> streaming_decoder =
397 398 399
        StartStreamingCompilation(
            isolate, enabled, handle(isolate->context(), isolate),
            api_method_name_for_errors, std::move(resolver));
400 401 402 403 404 405 406 407
    streaming_decoder->OnBytesReceived(bytes.module_bytes());
    streaming_decoder->Finish();
    return;
  }
  // Make a copy of the wire bytes in case the user program changes them
  // during asynchronous compilation.
  std::unique_ptr<byte[]> copy(new byte[bytes.length()]);
  memcpy(copy.get(), bytes.start(), bytes.length());
408

409 410 411 412
  AsyncCompileJob* job =
      CreateAsyncCompileJob(isolate, enabled, std::move(copy), bytes.length(),
                            handle(isolate->context(), isolate),
                            api_method_name_for_errors, std::move(resolver));
413 414 415 416
  job->Start();
}

std::shared_ptr<StreamingDecoder> WasmEngine::StartStreamingCompilation(
417
    Isolate* isolate, const WasmFeatures& enabled, Handle<Context> context,
418
    const char* api_method_name,
419
    std::shared_ptr<CompilationResultResolver> resolver) {
420
  AsyncCompileJob* job =
421
      CreateAsyncCompileJob(isolate, enabled, std::unique_ptr<byte[]>(nullptr),
422
                            0, context, api_method_name, std::move(resolver));
423
  return job->CreateStreamingDecoder();
424 425
}

426
void WasmEngine::CompileFunction(Isolate* isolate, NativeModule* native_module,
427
                                 uint32_t function_index, ExecutionTier tier) {
428 429
  // Note we assume that "one-off" compilations can discard detected features.
  WasmFeatures detected = kNoWasmFeatures;
430
  WasmCompilationUnit::CompileWasmFunction(
431
      isolate, native_module, &detected,
432
      &native_module->module()->functions[function_index], tier);
433 434
}

435 436
std::shared_ptr<NativeModule> WasmEngine::ExportNativeModule(
    Handle<WasmModuleObject> module_object) {
437
  return module_object->shared_native_module();
438 439 440
}

Handle<WasmModuleObject> WasmEngine::ImportNativeModule(
441 442 443
    Isolate* isolate, std::shared_ptr<NativeModule> shared_native_module) {
  NativeModule* native_module = shared_native_module.get();
  ModuleWireBytes wire_bytes(native_module->wire_bytes());
444 445 446 447
  Handle<Script> script = CreateWasmScript(
      isolate, wire_bytes, native_module->module()->source_map_url);
  Handle<FixedArray> export_wrappers;
  CompileJsToWasmWrappers(isolate, native_module->module(), &export_wrappers);
448
  Handle<WasmModuleObject> module_object = WasmModuleObject::New(
449 450
      isolate, std::move(shared_native_module), script, export_wrappers,
      native_module->committed_code_space());
451 452 453 454
  {
    base::MutexGuard lock(&mutex_);
    DCHECK_EQ(1, isolates_.count(isolate));
    isolates_[isolate]->native_modules.insert(native_module);
455 456
    DCHECK_EQ(1, native_modules_.count(native_module));
    native_modules_[native_module]->isolates.insert(isolate);
457
  }
458 459 460

  // Finish the Wasm script now and make it public to the debugger.
  isolate->debug()->OnAfterCompile(script);
461 462 463
  return module_object;
}

464
CompilationStatistics* WasmEngine::GetOrCreateTurboStatistics() {
465
  base::MutexGuard guard(&mutex_);
466 467 468 469 470 471 472
  if (compilation_stats_ == nullptr) {
    compilation_stats_.reset(new CompilationStatistics());
  }
  return compilation_stats_.get();
}

void WasmEngine::DumpAndResetTurboStatistics() {
473
  base::MutexGuard guard(&mutex_);
474 475 476 477 478 479 480
  if (compilation_stats_ != nullptr) {
    StdoutStream os;
    os << AsPrintableStatistics{*compilation_stats_.get(), false} << std::endl;
  }
  compilation_stats_.reset();
}

481
CodeTracer* WasmEngine::GetCodeTracer() {
482
  base::MutexGuard guard(&mutex_);
483 484 485 486
  if (code_tracer_ == nullptr) code_tracer_.reset(new CodeTracer(-1));
  return code_tracer_.get();
}

487
AsyncCompileJob* WasmEngine::CreateAsyncCompileJob(
488 489
    Isolate* isolate, const WasmFeatures& enabled,
    std::unique_ptr<byte[]> bytes_copy, size_t length, Handle<Context> context,
490
    const char* api_method_name,
491
    std::shared_ptr<CompilationResultResolver> resolver) {
492 493
  AsyncCompileJob* job =
      new AsyncCompileJob(isolate, enabled, std::move(bytes_copy), length,
494
                          context, api_method_name, std::move(resolver));
495
  // Pass ownership to the unique_ptr in {async_compile_jobs_}.
496
  base::MutexGuard guard(&mutex_);
497
  async_compile_jobs_[job] = std::unique_ptr<AsyncCompileJob>(job);
498 499 500
  return job;
}

501 502
std::unique_ptr<AsyncCompileJob> WasmEngine::RemoveCompileJob(
    AsyncCompileJob* job) {
503
  base::MutexGuard guard(&mutex_);
504 505
  auto item = async_compile_jobs_.find(job);
  DCHECK(item != async_compile_jobs_.end());
506
  std::unique_ptr<AsyncCompileJob> result = std::move(item->second);
507
  async_compile_jobs_.erase(item);
508
  return result;
509 510
}

511
bool WasmEngine::HasRunningCompileJob(Isolate* isolate) {
512
  base::MutexGuard guard(&mutex_);
513
  DCHECK_EQ(1, isolates_.count(isolate));
514
  for (auto& entry : async_compile_jobs_) {
515 516 517 518 519
    if (entry.first->isolate() == isolate) return true;
  }
  return false;
}

520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537
void WasmEngine::DeleteCompileJobsOnContext(Handle<Context> context) {
  // Under the mutex get all jobs to delete. Then delete them without holding
  // the mutex, such that deletion can reenter the WasmEngine.
  std::vector<std::unique_ptr<AsyncCompileJob>> jobs_to_delete;
  {
    base::MutexGuard guard(&mutex_);
    for (auto it = async_compile_jobs_.begin();
         it != async_compile_jobs_.end();) {
      if (!it->first->context().is_identical_to(context)) {
        ++it;
        continue;
      }
      jobs_to_delete.push_back(std::move(it->second));
      it = async_compile_jobs_.erase(it);
    }
  }
}

538
void WasmEngine::DeleteCompileJobsOnIsolate(Isolate* isolate) {
539 540 541 542 543 544
  // Under the mutex get all jobs to delete. Then delete them without holding
  // the mutex, such that deletion can reenter the WasmEngine.
  std::vector<std::unique_ptr<AsyncCompileJob>> jobs_to_delete;
  {
    base::MutexGuard guard(&mutex_);
    DCHECK_EQ(1, isolates_.count(isolate));
545 546
    for (auto it = async_compile_jobs_.begin();
         it != async_compile_jobs_.end();) {
547 548 549 550 551
      if (it->first->isolate() != isolate) {
        ++it;
        continue;
      }
      jobs_to_delete.push_back(std::move(it->second));
552
      it = async_compile_jobs_.erase(it);
553 554 555 556
    }
  }
}

557 558 559 560 561 562 563 564 565
namespace {
int GetGCTimeMicros(base::TimeTicks start) {
  DCHECK(!start.IsNull());
  int64_t duration_us = (base::TimeTicks::Now() - start).InMicroseconds();
  return static_cast<int>(
      std::min(std::max(int64_t{0}, duration_us), int64_t{kMaxInt}));
}
}  // namespace

566
void WasmEngine::AddIsolate(Isolate* isolate) {
567
  base::MutexGuard guard(&mutex_);
568
  DCHECK_EQ(0, isolates_.count(isolate));
569
  isolates_.emplace(isolate, std::make_unique<IsolateInfo>(isolate));
570 571 572 573 574 575 576 577

  // Install sampling GC callback.
  // TODO(v8:7424): For now we sample module sizes in a GC callback. This will
  // bias samples towards apps with high memory pressure. We should switch to
  // using sampling based on regular intervals independent of the GC.
  auto callback = [](v8::Isolate* v8_isolate, v8::GCType type,
                     v8::GCCallbackFlags flags, void* data) {
    Isolate* isolate = reinterpret_cast<Isolate*>(v8_isolate);
578
    Counters* counters = isolate->counters();
579 580 581
    WasmEngine* engine = isolate->wasm_engine();
    base::MutexGuard lock(&engine->mutex_);
    DCHECK_EQ(1, engine->isolates_.count(isolate));
582 583
    for (auto* native_module : engine->isolates_[isolate]->native_modules) {
      native_module->SampleCodeSize(counters, NativeModule::kSampling);
584
    }
585 586 587 588 589 590 591
    // If there is an ongoing code GC, sample its time here. This will record
    // samples for very long-running or never ending GCs.
    if (engine->current_gc_info_ &&
        !engine->current_gc_info_->start_time.IsNull()) {
      isolate->counters()->wasm_code_gc_time()->AddSample(
          GetGCTimeMicros(engine->current_gc_info_->start_time));
    }
592 593 594
  };
  isolate->heap()->AddGCEpilogueCallback(callback, v8::kGCTypeMarkSweepCompact,
                                         nullptr);
595 596 597
}

void WasmEngine::RemoveIsolate(Isolate* isolate) {
598
  base::MutexGuard guard(&mutex_);
599 600
  auto it = isolates_.find(isolate);
  DCHECK_NE(isolates_.end(), it);
601 602 603
  std::unique_ptr<IsolateInfo> info = std::move(it->second);
  isolates_.erase(it);
  for (NativeModule* native_module : info->native_modules) {
604 605 606 607
    DCHECK_EQ(1, native_modules_.count(native_module));
    DCHECK_EQ(1, native_modules_[native_module]->isolates.count(isolate));
    auto* info = native_modules_[native_module].get();
    info->isolates.erase(isolate);
608 609 610 611 612
    if (current_gc_info_) {
      for (WasmCode* code : info->potentially_dead_code) {
        current_gc_info_->dead_code.erase(code);
      }
    }
613
  }
614 615 616
  if (current_gc_info_) {
    if (RemoveIsolateFromCurrentGC(isolate)) PotentiallyFinishCurrentGC();
  }
617 618 619 620 621
  if (auto* task = info->log_codes_task) task->Cancel();
  if (!info->code_to_log.empty()) {
    WasmCode::DecrementRefCount(VectorOf(info->code_to_log));
    info->code_to_log.clear();
  }
622 623
}

624 625 626
void WasmEngine::LogCode(WasmCode* code) {
  base::MutexGuard guard(&mutex_);
  NativeModule* native_module = code->native_module();
627 628
  DCHECK_EQ(1, native_modules_.count(native_module));
  for (Isolate* isolate : native_modules_[native_module]->isolates) {
629 630
    DCHECK_EQ(1, isolates_.count(isolate));
    IsolateInfo* info = isolates_[isolate].get();
631
    if (info->log_codes == false) continue;
632
    if (info->log_codes_task == nullptr) {
633
      auto new_task = std::make_unique<LogCodesTask>(
634
          &mutex_, &info->log_codes_task, isolate, this);
635 636
      info->log_codes_task = new_task.get();
      info->foreground_task_runner->PostTask(std::move(new_task));
637 638
    }
    if (info->code_to_log.empty()) {
639
      isolate->stack_guard()->RequestLogWasmCode();
640
    }
641 642
    info->code_to_log.push_back(code);
    code->IncRef();
643 644 645
  }
}

646 647 648 649 650 651 652
void WasmEngine::EnableCodeLogging(Isolate* isolate) {
  base::MutexGuard guard(&mutex_);
  auto it = isolates_.find(isolate);
  DCHECK_NE(isolates_.end(), it);
  it->second->log_codes = true;
}

653 654 655 656
void WasmEngine::LogOutstandingCodesForIsolate(Isolate* isolate) {
  // If by now we should not log code any more, do not log it.
  if (!WasmCode::ShouldBeLogged(isolate)) return;

657 658 659 660 661 662 663 664 665 666
  // Under the mutex, get the vector of wasm code to log. Then log and decrement
  // the ref count without holding the mutex.
  std::vector<WasmCode*> code_to_log;
  {
    base::MutexGuard guard(&mutex_);
    DCHECK_EQ(1, isolates_.count(isolate));
    code_to_log.swap(isolates_[isolate]->code_to_log);
  }
  if (code_to_log.empty()) return;
  for (WasmCode* code : code_to_log) {
667 668
    code->LogCode(isolate);
  }
669
  WasmCode::DecrementRefCount(VectorOf(code_to_log));
670 671
}

672 673 674 675 676 677
std::shared_ptr<NativeModule> WasmEngine::NewNativeModule(
    Isolate* isolate, const WasmFeatures& enabled,
    std::shared_ptr<const WasmModule> module) {
  size_t code_size_estimate =
      wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module.get());
  return NewNativeModule(isolate, enabled, code_size_estimate,
678 679
                         !wasm::NativeModule::kNeedsFarJumpsBetweenCodeSpaces ||
                             FLAG_wasm_far_jump_table,
680 681 682
                         std::move(module));
}

683
std::shared_ptr<NativeModule> WasmEngine::NewNativeModule(
684 685
    Isolate* isolate, const WasmFeatures& enabled, size_t code_size_estimate,
    bool can_request_more, std::shared_ptr<const WasmModule> module) {
686
  std::shared_ptr<NativeModule> native_module =
687 688 689
      code_manager_.NewNativeModule(this, isolate, enabled, code_size_estimate,
                                    can_request_more, std::move(module));
  base::MutexGuard lock(&mutex_);
690
  auto pair = native_modules_.insert(std::make_pair(
691
      native_module.get(), std::make_unique<NativeModuleInfo>()));
692 693
  DCHECK(pair.second);  // inserted new entry.
  pair.first->second.get()->isolates.insert(isolate);
694 695 696 697 698
  isolates_[isolate]->native_modules.insert(native_module.get());
  return native_module;
}

void WasmEngine::FreeNativeModule(NativeModule* native_module) {
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
  base::MutexGuard guard(&mutex_);
  auto it = native_modules_.find(native_module);
  DCHECK_NE(native_modules_.end(), it);
  for (Isolate* isolate : it->second->isolates) {
    DCHECK_EQ(1, isolates_.count(isolate));
    IsolateInfo* info = isolates_[isolate].get();
    DCHECK_EQ(1, info->native_modules.count(native_module));
    info->native_modules.erase(native_module);
    // If there are {WasmCode} objects of the deleted {NativeModule}
    // outstanding to be logged in this isolate, remove them. Decrementing the
    // ref count is not needed, since the {NativeModule} dies anyway.
    size_t remaining = info->code_to_log.size();
    if (remaining > 0) {
      for (size_t i = 0; i < remaining; ++i) {
        while (i < remaining &&
               info->code_to_log[i]->native_module() == native_module) {
          // Move the last remaining item to this slot (this can be the same
          // as {i}, which is OK).
          info->code_to_log[i] = info->code_to_log[--remaining];
718 719
        }
      }
720
      info->code_to_log.resize(remaining);
721
    }
722 723 724 725 726 727 728 729 730 731 732
  }
  // If there is a GC running which has references to code contained in the
  // deleted {NativeModule}, remove those references.
  if (current_gc_info_) {
    for (auto it = current_gc_info_->dead_code.begin(),
              end = current_gc_info_->dead_code.end();
         it != end;) {
      if ((*it)->native_module() == native_module) {
        it = current_gc_info_->dead_code.erase(it);
      } else {
        ++it;
733 734
      }
    }
735 736
    TRACE_CODE_GC("Native module %p died, reducing dead code objects to %zu.\n",
                  native_module, current_gc_info_->dead_code.size());
737
  }
738
  native_modules_.erase(it);
739 740
}

741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
namespace {
class SampleTopTierCodeSizeTask : public CancelableTask {
 public:
  SampleTopTierCodeSizeTask(Isolate* isolate,
                            std::weak_ptr<NativeModule> native_module)
      : CancelableTask(isolate),
        isolate_(isolate),
        native_module_(std::move(native_module)) {}

  void RunInternal() override {
    if (std::shared_ptr<NativeModule> native_module = native_module_.lock()) {
      native_module->SampleCodeSize(isolate_->counters(),
                                    NativeModule::kAfterTopTier);
    }
  }

 private:
  Isolate* const isolate_;
  const std::weak_ptr<NativeModule> native_module_;
};
}  // namespace

void WasmEngine::SampleTopTierCodeSizeInAllIsolates(
    const std::shared_ptr<NativeModule>& native_module) {
  base::MutexGuard lock(&mutex_);
766 767
  DCHECK_EQ(1, native_modules_.count(native_module.get()));
  for (Isolate* isolate : native_modules_[native_module.get()]->isolates) {
768 769 770
    DCHECK_EQ(1, isolates_.count(isolate));
    IsolateInfo* info = isolates_[isolate].get();
    info->foreground_task_runner->PostTask(
771
        std::make_unique<SampleTopTierCodeSizeTask>(isolate, native_module));
772 773 774
  }
}

775 776
void WasmEngine::ReportLiveCodeForGC(Isolate* isolate,
                                     Vector<WasmCode*> live_code) {
777
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "ReportLiveCodeForGC");
778 779
  TRACE_CODE_GC("Isolate %d reporting %zu live code objects.\n", isolate->id(),
                live_code.size());
780
  base::MutexGuard guard(&mutex_);
781 782 783
  // This report might come in late (note that we trigger both a stack guard and
  // a foreground task). In that case, ignore it.
  if (current_gc_info_ == nullptr) return;
784
  if (!RemoveIsolateFromCurrentGC(isolate)) return;
785 786
  isolate->counters()->wasm_module_num_triggered_code_gcs()->AddSample(
      current_gc_info_->gc_sequence_index);
787
  for (WasmCode* code : live_code) current_gc_info_->dead_code.erase(code);
788
  PotentiallyFinishCurrentGC();
789 790
}

791 792 793 794 795 796 797 798 799
void WasmEngine::ReportLiveCodeFromStackForGC(Isolate* isolate) {
  wasm::WasmCodeRefScope code_ref_scope;
  std::unordered_set<wasm::WasmCode*> live_wasm_code;
  for (StackFrameIterator it(isolate); !it.done(); it.Advance()) {
    StackFrame* const frame = it.frame();
    if (frame->type() != StackFrame::WASM_COMPILED) continue;
    live_wasm_code.insert(WasmCompiledFrame::cast(frame)->wasm_code());
  }

800 801
  CheckNoArchivedThreads(isolate);

802 803 804 805
  ReportLiveCodeForGC(isolate,
                      OwnedVector<WasmCode*>::Of(live_wasm_code).as_vector());
}

806 807 808 809
bool WasmEngine::AddPotentiallyDeadCode(WasmCode* code) {
  base::MutexGuard guard(&mutex_);
  auto it = native_modules_.find(code->native_module());
  DCHECK_NE(native_modules_.end(), it);
810 811 812
  NativeModuleInfo* info = it->second.get();
  if (info->dead_code.count(code)) return false;  // Code is already dead.
  auto added = info->potentially_dead_code.insert(code);
813 814
  if (!added.second) return false;  // An entry already existed.
  new_potentially_dead_code_size_ += code->instructions().size();
815
  if (FLAG_wasm_code_gc) {
816
    // Trigger a GC if 64kB plus 10% of committed code are potentially dead.
817 818 819
    size_t dead_code_limit =
        FLAG_stress_wasm_code_gc
            ? 0
820
            : 64 * KB + code_manager_.committed_code_space() / 10;
821
    if (new_potentially_dead_code_size_ > dead_code_limit) {
822 823
      bool inc_gc_count =
          info->num_code_gcs_triggered < std::numeric_limits<int8_t>::max();
824
      if (current_gc_info_ == nullptr) {
825
        if (inc_gc_count) ++info->num_code_gcs_triggered;
826 827 828
        TRACE_CODE_GC(
            "Triggering GC (potentially dead: %zu bytes; limit: %zu bytes).\n",
            new_potentially_dead_code_size_, dead_code_limit);
829 830 831
        TriggerGC(info->num_code_gcs_triggered);
      } else if (current_gc_info_->next_gc_sequence_index == 0) {
        if (inc_gc_count) ++info->num_code_gcs_triggered;
832 833 834 835
        TRACE_CODE_GC(
            "Scheduling another GC after the current one (potentially dead: "
            "%zu bytes; limit: %zu bytes).\n",
            new_potentially_dead_code_size_, dead_code_limit);
836 837
        current_gc_info_->next_gc_sequence_index = info->num_code_gcs_triggered;
        DCHECK_NE(0, current_gc_info_->next_gc_sequence_index);
838
      }
839
    }
840
  }
841 842 843
  return true;
}

844 845 846 847 848 849
void WasmEngine::FreeDeadCode(const DeadCodeMap& dead_code) {
  base::MutexGuard guard(&mutex_);
  FreeDeadCodeLocked(dead_code);
}

void WasmEngine::FreeDeadCodeLocked(const DeadCodeMap& dead_code) {
850
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "FreeDeadCode");
851 852 853 854
  DCHECK(!mutex_.TryLock());
  for (auto& dead_code_entry : dead_code) {
    NativeModule* native_module = dead_code_entry.first;
    const std::vector<WasmCode*>& code_vec = dead_code_entry.second;
855 856
    DCHECK_EQ(1, native_modules_.count(native_module));
    auto* info = native_modules_[native_module].get();
857 858 859
    TRACE_CODE_GC("Freeing %zu code object%s of module %p.\n", code_vec.size(),
                  code_vec.size() == 1 ? "" : "s", native_module);
    for (WasmCode* code : code_vec) {
860 861 862
      DCHECK_EQ(1, info->dead_code.count(code));
      info->dead_code.erase(code);
    }
863
    native_module->FreeCode(VectorOf(code_vec));
864 865 866
  }
}

867
void WasmEngine::TriggerGC(int8_t gc_sequence_index) {
868 869
  DCHECK_NULL(current_gc_info_);
  DCHECK(FLAG_wasm_code_gc);
870
  new_potentially_dead_code_size_ = 0;
871
  current_gc_info_.reset(new CurrentGCInfo(gc_sequence_index));
872 873 874
  if (base::TimeTicks::IsHighResolution()) {
    current_gc_info_->start_time = base::TimeTicks::Now();
  }
875 876 877 878 879 880 881 882
  // Add all potentially dead code to this GC, and trigger a GC task in each
  // isolate.
  for (auto& entry : native_modules_) {
    NativeModuleInfo* info = entry.second.get();
    if (info->potentially_dead_code.empty()) continue;
    for (auto* isolate : native_modules_[entry.first]->isolates) {
      auto& gc_task = current_gc_info_->outstanding_isolates[isolate];
      if (!gc_task) {
883
        auto new_task = std::make_unique<WasmGCForegroundTask>(isolate);
884 885 886 887 888
        gc_task = new_task.get();
        DCHECK_EQ(1, isolates_.count(isolate));
        isolates_[isolate]->foreground_task_runner->PostTask(
            std::move(new_task));
      }
889
      isolate->stack_guard()->RequestWasmCodeGC();
890 891 892 893 894
    }
    for (WasmCode* code : info->potentially_dead_code) {
      current_gc_info_->dead_code.insert(code);
    }
  }
895 896 897
  TRACE_CODE_GC(
      "Starting GC. Total number of potentially dead code objects: %zu\n",
      current_gc_info_->dead_code.size());
898 899
}

900 901 902
bool WasmEngine::RemoveIsolateFromCurrentGC(Isolate* isolate) {
  DCHECK(!mutex_.TryLock());
  DCHECK_NOT_NULL(current_gc_info_);
903
  return current_gc_info_->outstanding_isolates.erase(isolate) != 0;
904 905 906 907 908 909 910 911 912 913 914 915 916 917 918
}

void WasmEngine::PotentiallyFinishCurrentGC() {
  DCHECK(!mutex_.TryLock());
  TRACE_CODE_GC(
      "Remaining dead code objects: %zu; outstanding isolates: %zu.\n",
      current_gc_info_->dead_code.size(),
      current_gc_info_->outstanding_isolates.size());

  // If there are more outstanding isolates, return immediately.
  if (!current_gc_info_->outstanding_isolates.empty()) return;

  // All remaining code in {current_gc_info->dead_code} is really dead.
  // Move it from the set of potentially dead code to the set of dead code,
  // and decrement its ref count.
919
  size_t num_freed = 0;
920 921 922 923 924 925 926 927 928 929
  DeadCodeMap dead_code;
  for (WasmCode* code : current_gc_info_->dead_code) {
    DCHECK_EQ(1, native_modules_.count(code->native_module()));
    auto* native_module_info = native_modules_[code->native_module()].get();
    DCHECK_EQ(1, native_module_info->potentially_dead_code.count(code));
    native_module_info->potentially_dead_code.erase(code);
    DCHECK_EQ(0, native_module_info->dead_code.count(code));
    native_module_info->dead_code.insert(code);
    if (code->DecRefOnDeadCode()) {
      dead_code[code->native_module()].push_back(code);
930
      ++num_freed;
931 932
    }
  }
933 934 935 936 937 938 939 940 941 942 943 944 945 946 947

  FreeDeadCodeLocked(dead_code);

  int duration_us = 0;
  if (!current_gc_info_->start_time.IsNull()) {
    duration_us = GetGCTimeMicros(current_gc_info_->start_time);
    for (auto& entry : isolates_) {
      entry.second->async_counters->wasm_code_gc_time()->AddSample(duration_us);
    }
  }

  TRACE_CODE_GC("Took %d us; found %zu dead code objects, freed %zu.\n",
                duration_us, current_gc_info_->dead_code.size(), num_freed);
  USE(num_freed);

948
  int8_t next_gc_sequence_index = current_gc_info_->next_gc_sequence_index;
949
  current_gc_info_.reset();
950
  if (next_gc_sequence_index != 0) TriggerGC(next_gc_sequence_index);
951 952
}

953 954
namespace {

955
DEFINE_LAZY_LEAKY_OBJECT_GETTER(std::shared_ptr<WasmEngine>,
956
                                GetSharedWasmEngine)
957 958 959

}  // namespace

960
// static
961
void WasmEngine::InitializeOncePerProcess() {
962
  *GetSharedWasmEngine() = std::make_shared<WasmEngine>();
963 964
}

965
// static
966
void WasmEngine::GlobalTearDown() {
967
  GetSharedWasmEngine()->reset();
968 969
}

970
// static
971
std::shared_ptr<WasmEngine> WasmEngine::GetWasmEngine() {
972
  return *GetSharedWasmEngine();
973 974
}

975 976 977 978 979 980
// {max_mem_pages} is declared in wasm-limits.h.
uint32_t max_mem_pages() {
  STATIC_ASSERT(kV8MaxWasmMemoryPages <= kMaxUInt32);
  return std::min(uint32_t{kV8MaxWasmMemoryPages}, FLAG_wasm_max_mem_pages);
}

981 982 983 984 985 986
// {max_table_init_entries} is declared in wasm-limits.h.
uint32_t max_table_init_entries() {
  return std::min(uint32_t{kV8MaxWasmTableInitEntries},
                  FLAG_wasm_max_table_size);
}

987 988
#undef TRACE_CODE_GC

989 990 991
}  // namespace wasm
}  // namespace internal
}  // namespace v8