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#include "src/wasm/module-compiler.h"
6
7#include <algorithm>
8
9#include "src/api.h"
10#include "src/asmjs/asm-js.h"
11#include "src/base/enum-set.h"
12#include "src/base/optional.h"
13#include "src/base/platform/mutex.h"
14#include "src/base/platform/semaphore.h"
15#include "src/base/template-utils.h"
16#include "src/base/utils/random-number-generator.h"
17#include "src/compiler/wasm-compiler.h"
18#include "src/counters.h"
19#include "src/heap/heap-inl.h" // For CodeSpaceMemoryModificationScope.
20#include "src/identity-map.h"
21#include "src/property-descriptor.h"
22#include "src/task-utils.h"
23#include "src/tracing/trace-event.h"
24#include "src/trap-handler/trap-handler.h"
25#include "src/wasm/js-to-wasm-wrapper-cache.h"
26#include "src/wasm/module-decoder.h"
27#include "src/wasm/streaming-decoder.h"
28#include "src/wasm/wasm-code-manager.h"
29#include "src/wasm/wasm-engine.h"
30#include "src/wasm/wasm-import-wrapper-cache.h"
31#include "src/wasm/wasm-js.h"
32#include "src/wasm/wasm-limits.h"
33#include "src/wasm/wasm-memory.h"
34#include "src/wasm/wasm-objects-inl.h"
35#include "src/wasm/wasm-result.h"
36#include "src/wasm/wasm-serialization.h"
37
38#define TRACE_COMPILE(...) \
39 do { \
40 if (FLAG_trace_wasm_compiler) PrintF(__VA_ARGS__); \
41 } while (false)
42
43#define TRACE_STREAMING(...) \
44 do { \
45 if (FLAG_trace_wasm_streaming) PrintF(__VA_ARGS__); \
46 } while (false)
47
48#define TRACE_LAZY(...) \
49 do { \
50 if (FLAG_trace_wasm_lazy_compilation) PrintF(__VA_ARGS__); \
51 } while (false)
52
53namespace v8 {
54namespace internal {
55namespace wasm {
56
57namespace {
58
59enum class CompileMode : uint8_t { kRegular, kTiering };
60
61// Background compile jobs hold a shared pointer to this token. The token is
62// used to notify them that they should stop. As soon as they see this (after
63// finishing their current compilation unit), they will stop.
64// This allows to already remove the NativeModule without having to synchronize
65// on background compile jobs.
66class BackgroundCompileToken {
67 public:
68 explicit BackgroundCompileToken(
69 const std::shared_ptr<NativeModule>& native_module)
70 : native_module_(native_module) {}
71
72 void Cancel() {
73 base::SharedMutexGuard<base::kExclusive> mutex_guard(&mutex_);
74 native_module_.reset();
75 }
76
77 private:
78 friend class BackgroundCompileScope;
79 base::SharedMutex mutex_;
80 std::weak_ptr<NativeModule> native_module_;
81
82 std::shared_ptr<NativeModule> StartScope() {
83 mutex_.LockShared();
84 return native_module_.lock();
85 }
86
87 void ExitScope() { mutex_.UnlockShared(); }
88};
89
90class CompilationStateImpl;
91
92// Keep these scopes short, as they hold the mutex of the token, which
93// sequentializes all these scopes. The mutex is also acquired from foreground
94// tasks, which should not be blocked for a long time.
95class BackgroundCompileScope {
96 public:
97 explicit BackgroundCompileScope(
98 const std::shared_ptr<BackgroundCompileToken>& token)
99 : token_(token.get()), native_module_(token->StartScope()) {}
100
101 ~BackgroundCompileScope() { token_->ExitScope(); }
102
103 bool cancelled() const { return native_module_ == nullptr; }
104
105 NativeModule* native_module() {
106 DCHECK(!cancelled());
107 return native_module_.get();
108 }
109
110 inline CompilationStateImpl* compilation_state();
111
112 private:
113 BackgroundCompileToken* const token_;
114 // Keep the native module alive while in this scope.
115 std::shared_ptr<NativeModule> const native_module_;
116};
117
118enum CompileBaselineOnly : bool {
119 kBaselineOnly = true,
120 kBaselineOrTopTier = false
121};
122
123// A set of work-stealing queues (vectors of units). Each background compile
124// task owns one of the queues and steals from all others once its own queue
125// runs empty.
126class CompilationUnitQueues {
127 public:
128 explicit CompilationUnitQueues(int max_tasks) : queues_(max_tasks) {
129 DCHECK_LT(0, max_tasks);
130 for (int task_id = 0; task_id < max_tasks; ++task_id) {
131 queues_[task_id].next_steal_task_id_ = next_task_id(task_id);
132 }
133 for (auto& atomic_counter : num_units_) {
134 std::atomic_init(&atomic_counter, size_t{0});
135 }
136 }
137
138 std::unique_ptr<WasmCompilationUnit> GetNextUnit(
139 int task_id, CompileBaselineOnly baseline_only) {
140 DCHECK_LE(0, task_id);
141 DCHECK_GT(queues_.size(), task_id);
142
143 // As long as any lower-tier units are outstanding we need to steal them
144 // before executing own higher-tier units.
145 int max_tier = baseline_only ? kBaseline : kTopTier;
146 for (int tier = GetLowestTierWithUnits(); tier <= max_tier; ++tier) {
147 Queue* queue = &queues_[task_id];
148 // First, check whether our own queue has a unit of the wanted tier. If
149 // so, return it, otherwise get the task id to steal from.
150 int steal_task_id;
151 {
152 base::MutexGuard mutex_guard(&queue->mutex_);
153 if (!queue->units_[tier].empty()) {
154 auto unit = std::move(queue->units_[tier].back());
155 queue->units_[tier].pop_back();
156 DecrementUnitCount(tier);
157 return unit;
158 }
159 steal_task_id = queue->next_steal_task_id_;
160 }
161
162 // Try to steal from all other queues. If none of this succeeds, the outer
163 // loop increases the tier and retries.
164 size_t steal_trials = queues_.size();
165 for (; steal_trials > 0;
166 --steal_trials, steal_task_id = next_task_id(steal_task_id)) {
167 if (steal_task_id == task_id) continue;
168 if (auto unit = StealUnitsAndGetFirst(task_id, steal_task_id, tier)) {
169 DecrementUnitCount(tier);
170 return unit;
171 }
172 }
173 }
174 return {};
175 }
176
177 void AddUnits(Vector<std::unique_ptr<WasmCompilationUnit>> baseline_units,
178 Vector<std::unique_ptr<WasmCompilationUnit>> top_tier_units) {
179 DCHECK_LT(0, baseline_units.size() + top_tier_units.size());
180 // Add to the individual queues in a round-robin fashion. No special care is
181 // taken to balance them; they will be balanced by work stealing.
182 int queue_to_add = next_queue_to_add.load(std::memory_order_relaxed);
183 while (!next_queue_to_add.compare_exchange_weak(
184 queue_to_add, next_task_id(queue_to_add), std::memory_order_relaxed)) {
185 // Retry with updated {queue_to_add}.
186 }
187
188 Queue* queue = &queues_[queue_to_add];
189 base::MutexGuard guard(&queue->mutex_);
190 if (!baseline_units.empty()) {
191 queue->units_[kBaseline].insert(
192 queue->units_[kBaseline].end(),
193 std::make_move_iterator(baseline_units.begin()),
194 std::make_move_iterator(baseline_units.end()));
195 num_units_[kBaseline].fetch_add(baseline_units.size(),
196 std::memory_order_relaxed);
197 }
198 if (!top_tier_units.empty()) {
199 queue->units_[kTopTier].insert(
200 queue->units_[kTopTier].end(),
201 std::make_move_iterator(top_tier_units.begin()),
202 std::make_move_iterator(top_tier_units.end()));
203 num_units_[kTopTier].fetch_add(top_tier_units.size(),
204 std::memory_order_relaxed);
205 }
206 }
207
208 // Get the current total number of units in all queues. This is only a
209 // momentary snapshot, it's not guaranteed that {GetNextUnit} returns a unit
210 // if this method returns non-zero.
211 size_t GetTotalSize() const {
212 size_t total = 0;
213 for (auto& atomic_counter : num_units_) {
214 total += atomic_counter.load(std::memory_order_relaxed);
215 }
216 return total;
217 }
218
219 private:
220 // Store tier in int so we can easily loop over it:
221 static constexpr int kBaseline = 0;
222 static constexpr int kTopTier = 1;
223 static constexpr int kNumTiers = kTopTier + 1;
224
225 struct Queue {
226 base::Mutex mutex_;
227
228 // Protected by {mutex_}:
229 std::vector<std::unique_ptr<WasmCompilationUnit>> units_[kNumTiers];
230 int next_steal_task_id_;
231 // End of fields protected by {mutex_}.
232 };
233
234 std::vector<Queue> queues_;
235
236 std::atomic<size_t> num_units_[kNumTiers];
237 std::atomic<int> next_queue_to_add{0};
238
239 int next_task_id(int task_id) const {
240 int next = task_id + 1;
241 return next == static_cast<int>(queues_.size()) ? 0 : next;
242 }
243
244 int GetLowestTierWithUnits() const {
245 for (int tier = 0; tier < kNumTiers; ++tier) {
246 if (num_units_[tier].load(std::memory_order_relaxed) > 0) return tier;
247 }
248 return kNumTiers;
249 }
250
251 void DecrementUnitCount(int tier) {
252 size_t old_units_count = num_units_[tier].fetch_sub(1);
253 DCHECK_LE(1, old_units_count);
254 USE(old_units_count);
255 }
256
257 // Steal units of {wanted_tier} from {steal_from_task_id} to {task_id}. Return
258 // first stolen unit (rest put in queue of {task_id}), or {nullptr} if
259 // {steal_from_task_id} had no units of {wanted_tier}.
260 std::unique_ptr<WasmCompilationUnit> StealUnitsAndGetFirst(
261 int task_id, int steal_from_task_id, int wanted_tier) {
262 DCHECK_NE(task_id, steal_from_task_id);
263 std::vector<std::unique_ptr<WasmCompilationUnit>> stolen;
264 {
265 Queue* steal_queue = &queues_[steal_from_task_id];
266 base::MutexGuard guard(&steal_queue->mutex_);
267 if (steal_queue->units_[wanted_tier].empty()) return {};
268 auto* steal_from_vector = &steal_queue->units_[wanted_tier];
269 size_t remaining = steal_from_vector->size() / 2;
270 stolen.assign(
271 std::make_move_iterator(steal_from_vector->begin()) + remaining,
272 std::make_move_iterator(steal_from_vector->end()));
273 steal_from_vector->resize(remaining);
274 }
275 DCHECK(!stolen.empty());
276 auto returned_unit = std::move(stolen.back());
277 stolen.pop_back();
278 Queue* queue = &queues_[task_id];
279 base::MutexGuard guard(&queue->mutex_);
280 auto* target_queue = &queue->units_[wanted_tier];
281 target_queue->insert(target_queue->end(),
282 std::make_move_iterator(stolen.begin()),
283 std::make_move_iterator(stolen.end()));
284 queue->next_steal_task_id_ = next_task_id(steal_from_task_id);
285 return returned_unit;
286 }
287};
288
289// The {CompilationStateImpl} keeps track of the compilation state of the
290// owning NativeModule, i.e. which functions are left to be compiled.
291// It contains a task manager to allow parallel and asynchronous background
292// compilation of functions.
293// Its public interface {CompilationState} lives in compilation-environment.h.
294class CompilationStateImpl {
295 public:
296 CompilationStateImpl(const std::shared_ptr<NativeModule>& native_module,
297 std::shared_ptr<Counters> async_counters);
298
299 // Cancel all background compilation and wait for all tasks to finish. Call
300 // this before destructing this object.
301 void AbortCompilation();
302
303 // Set the number of compilations unit expected to be executed. Needs to be
304 // set before {AddCompilationUnits} is run, which triggers background
305 // compilation.
306 void SetNumberOfFunctionsToCompile(int num_functions, int num_lazy_functions);
307
308 // Add the callback function to be called on compilation events. Needs to be
309 // set before {AddCompilationUnits} is run to ensure that it receives all
310 // events. The callback object must support being deleted from any thread.
311 void AddCallback(CompilationState::callback_t);
312
313 // Inserts new functions to compile and kicks off compilation.
314 void AddCompilationUnits(
315 Vector<std::unique_ptr<WasmCompilationUnit>> baseline_units,
316 Vector<std::unique_ptr<WasmCompilationUnit>> top_tier_units);
317 void AddTopTierCompilationUnit(std::unique_ptr<WasmCompilationUnit>);
318 std::unique_ptr<WasmCompilationUnit> GetNextCompilationUnit(
319 int task_id, CompileBaselineOnly baseline_only);
320
321 void OnFinishedUnit(WasmCode*);
322 void OnFinishedUnits(Vector<WasmCode*>);
323
324 void OnBackgroundTaskStopped(int task_id, const WasmFeatures& detected);
325 void UpdateDetectedFeatures(const WasmFeatures& detected);
326 void PublishDetectedFeatures(Isolate*);
327 void RestartBackgroundTasks();
328
329 void SetError();
330
331 bool failed() const {
332 return compile_failed_.load(std::memory_order_relaxed);
333 }
334
335 bool baseline_compilation_finished() const {
336 base::MutexGuard guard(&callbacks_mutex_);
337 DCHECK_LE(outstanding_baseline_functions_, outstanding_top_tier_functions_);
338 return outstanding_baseline_functions_ == 0;
339 }
340
341 CompileMode compile_mode() const { return compile_mode_; }
342 Counters* counters() const { return async_counters_.get(); }
343 WasmFeatures* detected_features() { return &detected_features_; }
344
345 void SetWireBytesStorage(
346 std::shared_ptr<WireBytesStorage> wire_bytes_storage) {
347 base::MutexGuard guard(&mutex_);
348 wire_bytes_storage_ = wire_bytes_storage;
349 }
350
351 std::shared_ptr<WireBytesStorage> GetWireBytesStorage() const {
352 base::MutexGuard guard(&mutex_);
353 DCHECK_NOT_NULL(wire_bytes_storage_);
354 return wire_bytes_storage_;
355 }
356
357 const std::shared_ptr<BackgroundCompileToken>& background_compile_token()
358 const {
359 return background_compile_token_;
360 }
361
362 private:
363 NativeModule* const native_module_;
364 const std::shared_ptr<BackgroundCompileToken> background_compile_token_;
365 const CompileMode compile_mode_;
366 const std::shared_ptr<Counters> async_counters_;
367
368 // Compilation error, atomically updated. This flag can be updated and read
369 // using relaxed semantics.
370 std::atomic<bool> compile_failed_{false};
371
372 const int max_background_tasks_ = 0;
373
374 CompilationUnitQueues compilation_unit_queues_;
375
376 // This mutex protects all information of this {CompilationStateImpl} which is
377 // being accessed concurrently.
378 mutable base::Mutex mutex_;
379
380 //////////////////////////////////////////////////////////////////////////////
381 // Protected by {mutex_}:
382
383 // Set of unused task ids; <= {max_background_tasks_} many.
384 std::vector<int> available_task_ids_;
385
386 // Features detected to be used in this module. Features can be detected
387 // as a module is being compiled.
388 WasmFeatures detected_features_ = kNoWasmFeatures;
389
390 // Abstraction over the storage of the wire bytes. Held in a shared_ptr so
391 // that background compilation jobs can keep the storage alive while
392 // compiling.
393 std::shared_ptr<WireBytesStorage> wire_bytes_storage_;
394
395 // End of fields protected by {mutex_}.
396 //////////////////////////////////////////////////////////////////////////////
397
398 // This mutex protects the callbacks vector, and the counters used to
399 // determine which callbacks to call. The counters plus the callbacks
400 // themselves need to be synchronized to ensure correct order of events.
401 mutable base::Mutex callbacks_mutex_;
402
403 //////////////////////////////////////////////////////////////////////////////
404 // Protected by {callbacks_mutex_}:
405
406 // Callback functions to be called on compilation events.
407 std::vector<CompilationState::callback_t> callbacks_;
408
409 int outstanding_baseline_functions_ = 0;
410 int outstanding_top_tier_functions_ = 0;
411 std::vector<ExecutionTier> highest_execution_tier_;
412
413 // End of fields protected by {callbacks_mutex_}.
414 //////////////////////////////////////////////////////////////////////////////
415};
416
417CompilationStateImpl* Impl(CompilationState* compilation_state) {
418 return reinterpret_cast<CompilationStateImpl*>(compilation_state);
419}
420const CompilationStateImpl* Impl(const CompilationState* compilation_state) {
421 return reinterpret_cast<const CompilationStateImpl*>(compilation_state);
422}
423
424CompilationStateImpl* BackgroundCompileScope::compilation_state() {
425 return Impl(native_module()->compilation_state());
426}
427
428void UpdateFeatureUseCounts(Isolate* isolate, const WasmFeatures& detected) {
429 if (detected.threads) {
430 isolate->CountUsage(v8::Isolate::UseCounterFeature::kWasmThreadOpcodes);
431 }
432}
433
434} // namespace
435
436//////////////////////////////////////////////////////
437// PIMPL implementation of {CompilationState}.
438
439CompilationState::~CompilationState() { Impl(this)->~CompilationStateImpl(); }
440
441void CompilationState::AbortCompilation() { Impl(this)->AbortCompilation(); }
442
443void CompilationState::SetError() { Impl(this)->SetError(); }
444
445void CompilationState::SetWireBytesStorage(
446 std::shared_ptr<WireBytesStorage> wire_bytes_storage) {
447 Impl(this)->SetWireBytesStorage(std::move(wire_bytes_storage));
448}
449
450std::shared_ptr<WireBytesStorage> CompilationState::GetWireBytesStorage()
451 const {
452 return Impl(this)->GetWireBytesStorage();
453}
454
455void CompilationState::AddCallback(CompilationState::callback_t callback) {
456 return Impl(this)->AddCallback(std::move(callback));
457}
458
459bool CompilationState::failed() const { return Impl(this)->failed(); }
460
461void CompilationState::OnFinishedUnit(WasmCode* code) {
462 Impl(this)->OnFinishedUnit(code);
463}
464
465void CompilationState::OnFinishedUnits(Vector<WasmCode*> code_vector) {
466 Impl(this)->OnFinishedUnits(code_vector);
467}
468
469// static
470std::unique_ptr<CompilationState> CompilationState::New(
471 const std::shared_ptr<NativeModule>& native_module,
472 std::shared_ptr<Counters> async_counters) {
473 return std::unique_ptr<CompilationState>(reinterpret_cast<CompilationState*>(
474 new CompilationStateImpl(native_module, std::move(async_counters))));
475}
476
477// End of PIMPL implementation of {CompilationState}.
478//////////////////////////////////////////////////////
479
480namespace {
481
482ExecutionTier ApplyHintToExecutionTier(WasmCompilationHintTier hint,
483 ExecutionTier default_tier) {
484 switch (hint) {
485 case WasmCompilationHintTier::kDefault:
486 return default_tier;
487 case WasmCompilationHintTier::kInterpreter:
488 return ExecutionTier::kInterpreter;
489 case WasmCompilationHintTier::kBaseline:
490 return ExecutionTier::kLiftoff;
491 case WasmCompilationHintTier::kOptimized:
492 return ExecutionTier::kTurbofan;
493 }
494 UNREACHABLE();
495}
496
497const WasmCompilationHint* GetCompilationHint(const WasmModule* module,
498 uint32_t func_index) {
499 DCHECK_LE(module->num_imported_functions, func_index);
500 uint32_t hint_index = func_index - module->num_imported_functions;
501 const std::vector<WasmCompilationHint>& compilation_hints =
502 module->compilation_hints;
503 if (hint_index < compilation_hints.size()) {
504 return &compilation_hints[hint_index];
505 }
506 return nullptr;
507}
508
509bool IsLazyCompilation(const WasmModule* module,
510 const NativeModule* native_module,
511 const WasmFeatures& enabled_features,
512 uint32_t func_index) {
513 if (native_module->lazy_compilation()) return true;
514 if (enabled_features.compilation_hints) {
515 const WasmCompilationHint* hint = GetCompilationHint(module, func_index);
516 return hint != nullptr &&
517 hint->strategy == WasmCompilationHintStrategy::kLazy;
518 }
519 return false;
520}
521
522struct ExecutionTierPair {
523 ExecutionTier baseline_tier;
524 ExecutionTier top_tier;
525};
526
527ExecutionTierPair GetRequestedExecutionTiers(
528 const WasmModule* module, CompileMode compile_mode,
529 const WasmFeatures& enabled_features, uint32_t func_index) {
530 ExecutionTierPair result;
531 switch (compile_mode) {
532 case CompileMode::kRegular:
533 result.baseline_tier =
534 WasmCompilationUnit::GetDefaultExecutionTier(module);
535 result.top_tier = result.baseline_tier;
536 return result;
537 case CompileMode::kTiering:
538
539 // Default tiering behaviour.
540 result.baseline_tier = ExecutionTier::kLiftoff;
541 result.top_tier = ExecutionTier::kTurbofan;
542
543 // Check if compilation hints override default tiering behaviour.
544 if (enabled_features.compilation_hints) {
545 const WasmCompilationHint* hint =
546 GetCompilationHint(module, func_index);
547 if (hint != nullptr) {
548 result.baseline_tier = ApplyHintToExecutionTier(hint->baseline_tier,
549 result.baseline_tier);
550 result.top_tier =
551 ApplyHintToExecutionTier(hint->top_tier, result.top_tier);
552 }
553 }
554
555 // Correct top tier if necessary.
556 static_assert(ExecutionTier::kInterpreter < ExecutionTier::kLiftoff &&
557 ExecutionTier::kLiftoff < ExecutionTier::kTurbofan,
558 "Assume an order on execution tiers");
559 if (result.baseline_tier > result.top_tier) {
560 result.top_tier = result.baseline_tier;
561 }
562 return result;
563 }
564 UNREACHABLE();
565}
566
567// The {CompilationUnitBuilder} builds compilation units and stores them in an
568// internal buffer. The buffer is moved into the working queue of the
569// {CompilationStateImpl} when {Commit} is called.
570class CompilationUnitBuilder {
571 public:
572 explicit CompilationUnitBuilder(NativeModule* native_module)
573 : native_module_(native_module),
574 default_tier_(WasmCompilationUnit::GetDefaultExecutionTier(
575 native_module->module())) {}
576
577 void AddUnits(uint32_t func_index) {
578 ExecutionTierPair tiers = GetRequestedExecutionTiers(
579 native_module_->module(), compilation_state()->compile_mode(),
580 native_module_->enabled_features(), func_index);
581 baseline_units_.emplace_back(CreateUnit(func_index, tiers.baseline_tier));
582 if (tiers.baseline_tier != tiers.top_tier) {
583 tiering_units_.emplace_back(CreateUnit(func_index, tiers.top_tier));
584 }
585 }
586
587 bool Commit() {
588 if (baseline_units_.empty() && tiering_units_.empty()) return false;
589 compilation_state()->AddCompilationUnits(VectorOf(baseline_units_),
590 VectorOf(tiering_units_));
591 Clear();
592 return true;
593 }
594
595 void Clear() {
596 baseline_units_.clear();
597 tiering_units_.clear();
598 }
599
600 private:
601 std::unique_ptr<WasmCompilationUnit> CreateUnit(uint32_t func_index,
602 ExecutionTier tier) {
603 return base::make_unique<WasmCompilationUnit>(func_index, tier);
604 }
605
606 CompilationStateImpl* compilation_state() const {
607 return Impl(native_module_->compilation_state());
608 }
609
610 NativeModule* const native_module_;
611 const ExecutionTier default_tier_;
612 std::vector<std::unique_ptr<WasmCompilationUnit>> baseline_units_;
613 std::vector<std::unique_ptr<WasmCompilationUnit>> tiering_units_;
614};
615
616} // namespace
617
618void CompileLazy(Isolate* isolate, NativeModule* native_module,
619 uint32_t func_index) {
620 Counters* counters = isolate->counters();
621 HistogramTimerScope lazy_time_scope(counters->wasm_lazy_compilation_time());
622
623 DCHECK(!native_module->lazy_compile_frozen());
624
625 base::ElapsedTimer compilation_timer;
626
627 NativeModuleModificationScope native_module_modification_scope(native_module);
628
629 DCHECK(!native_module->HasCode(static_cast<uint32_t>(func_index)));
630
631 compilation_timer.Start();
632
633 TRACE_LAZY("Compiling wasm-function#%d.\n", func_index);
634
635 const uint8_t* module_start = native_module->wire_bytes().start();
636
637 const WasmFunction* func = &native_module->module()->functions[func_index];
638 FunctionBody func_body{func->sig, func->code.offset(),
639 module_start + func->code.offset(),
640 module_start + func->code.end_offset()};
641
642 CompilationStateImpl* compilation_state =
643 Impl(native_module->compilation_state());
644 ExecutionTierPair tiers = GetRequestedExecutionTiers(
645 native_module->module(), compilation_state->compile_mode(),
646 native_module->enabled_features(), func_index);
647
648 WasmCompilationUnit baseline_unit(func_index, tiers.baseline_tier);
649 CompilationEnv env = native_module->CreateCompilationEnv();
650 WasmCompilationResult result = baseline_unit.ExecuteCompilation(
651 isolate->wasm_engine(), &env, compilation_state->GetWireBytesStorage(),
652 isolate->counters(), compilation_state->detected_features());
653 WasmCodeRefScope code_ref_scope;
654 WasmCode* code = native_module->AddCompiledCode(std::move(result));
655
656 if (tiers.baseline_tier < tiers.top_tier) {
657 auto tiering_unit =
658 base::make_unique<WasmCompilationUnit>(func_index, tiers.top_tier);
659 compilation_state->AddTopTierCompilationUnit(std::move(tiering_unit));
660 }
661
662 // During lazy compilation, we should never get compilation errors. The module
663 // was verified before starting execution with lazy compilation.
664 // This might be OOM, but then we cannot continue execution anyway.
665 // TODO(clemensh): According to the spec, we can actually skip validation at
666 // module creation time, and return a function that always traps here.
667 CHECK(!compilation_state->failed());
668
669 // The code we just produced should be the one that was requested.
670 DCHECK_EQ(func_index, code->index());
671
672 if (WasmCode::ShouldBeLogged(isolate)) code->LogCode(isolate);
673
674 double func_kb = 1e-3 * func->code.length();
675 double compilation_seconds = compilation_timer.Elapsed().InSecondsF();
676
677 counters->wasm_lazily_compiled_functions()->Increment();
678
679 int throughput_sample = static_cast<int>(func_kb / compilation_seconds);
680 counters->wasm_lazy_compilation_throughput()->AddSample(throughput_sample);
681}
682
683namespace {
684
685void RecordStats(const Code code, Counters* counters) {
686 counters->wasm_generated_code_size()->Increment(code->body_size());
687 counters->wasm_reloc_size()->Increment(code->relocation_info()->length());
688}
689
690constexpr int kMainThreadTaskId = -1;
691
692// Run by the main thread and background tasks to take part in compilation.
693// Returns whether any units were executed.
694bool ExecuteCompilationUnits(
695 const std::shared_ptr<BackgroundCompileToken>& token, Counters* counters,
696 int task_id, CompileBaselineOnly baseline_only) {
697 TRACE_COMPILE("Compiling (task %d)...\n", task_id);
698 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "ExecuteCompilationUnits");
699
700 const bool is_foreground = task_id == kMainThreadTaskId;
701 // The main thread uses task id 0, which might collide with one of the
702 // background tasks. This is fine, as it will only cause some contention on
703 // the one queue, but work otherwise.
704 if (is_foreground) task_id = 0;
705
706 Platform* platform = V8::GetCurrentPlatform();
707 // Deadline is in 50ms from now.
708 static constexpr double kBackgroundCompileTimeLimit =
709 50.0 / base::Time::kMillisecondsPerSecond;
710 const double deadline =
711 platform->MonotonicallyIncreasingTime() + kBackgroundCompileTimeLimit;
712
713 // These fields are initialized in a {BackgroundCompileScope} before
714 // starting compilation.
715 base::Optional<CompilationEnv> env;
716 std::shared_ptr<WireBytesStorage> wire_bytes;
717 std::shared_ptr<const WasmModule> module;
718 WasmEngine* wasm_engine = nullptr;
719 std::unique_ptr<WasmCompilationUnit> unit;
720 WasmFeatures detected_features = kNoWasmFeatures;
721
722 auto stop = [is_foreground, task_id,
723 &detected_features](BackgroundCompileScope& compile_scope) {
724 if (is_foreground) {
725 compile_scope.compilation_state()->UpdateDetectedFeatures(
726 detected_features);
727 } else {
728 compile_scope.compilation_state()->OnBackgroundTaskStopped(
729 task_id, detected_features);
730 }
731 };
732
733 // Preparation (synchronized): Initialize the fields above and get the first
734 // compilation unit.
735 {
736 BackgroundCompileScope compile_scope(token);
737 if (compile_scope.cancelled()) return false;
738 env.emplace(compile_scope.native_module()->CreateCompilationEnv());
739 wire_bytes = compile_scope.compilation_state()->GetWireBytesStorage();
740 module = compile_scope.native_module()->shared_module();
741 wasm_engine = compile_scope.native_module()->engine();
742 unit = compile_scope.compilation_state()->GetNextCompilationUnit(
743 task_id, baseline_only);
744 if (unit == nullptr) {
745 stop(compile_scope);
746 return false;
747 }
748 }
749
750 std::vector<WasmCompilationResult> results_to_publish;
751
752 auto publish_results = [&results_to_publish](
753 BackgroundCompileScope* compile_scope) {
754 if (results_to_publish.empty()) return;
755 WasmCodeRefScope code_ref_scope;
756 std::vector<WasmCode*> code_vector =
757 compile_scope->native_module()->AddCompiledCode(
758 VectorOf(results_to_publish));
759 compile_scope->compilation_state()->OnFinishedUnits(VectorOf(code_vector));
760 results_to_publish.clear();
761 };
762
763 bool compilation_failed = false;
764 while (true) {
765 // (asynchronous): Execute the compilation.
766 WasmCompilationResult result = unit->ExecuteCompilation(
767 wasm_engine, &env.value(), wire_bytes, counters, &detected_features);
768 results_to_publish.emplace_back(std::move(result));
769
770 // (synchronized): Publish the compilation result and get the next unit.
771 {
772 BackgroundCompileScope compile_scope(token);
773 if (compile_scope.cancelled()) return true;
774 if (!results_to_publish.back().succeeded()) {
775 // Compile error.
776 compile_scope.compilation_state()->SetError();
777 stop(compile_scope);
778 compilation_failed = true;
779 break;
780 }
781 // Publish TurboFan units immediately to reduce peak memory consumption.
782 if (result.requested_tier == ExecutionTier::kTurbofan) {
783 publish_results(&compile_scope);
784 }
785
786 // Get next unit.
787 if (deadline < platform->MonotonicallyIncreasingTime()) {
788 unit = nullptr;
789 } else {
790 unit = compile_scope.compilation_state()->GetNextCompilationUnit(
791 task_id, baseline_only);
792 }
793
794 if (unit == nullptr) {
795 publish_results(&compile_scope);
796 stop(compile_scope);
797 return true;
798 }
799 }
800 }
801 // We only get here if compilation failed. Other exits return directly.
802 DCHECK(compilation_failed);
803 USE(compilation_failed);
804 token->Cancel();
805 return true;
806}
807
808void ValidateSequentially(Counters* counters, AccountingAllocator* allocator,
809 NativeModule* native_module, uint32_t func_index,
810 ErrorThrower* thrower) {
811 DCHECK(!thrower->error());
812
813 const WasmModule* module = native_module->module();
814 ModuleWireBytes wire_bytes{native_module->wire_bytes()};
815 const WasmFunction* func = &module->functions[func_index];
816
817 Vector<const uint8_t> code = wire_bytes.GetFunctionBytes(func);
818 FunctionBody body{func->sig, func->code.offset(), code.start(), code.end()};
819 DecodeResult result;
820 {
821 auto time_counter = SELECT_WASM_COUNTER(counters, module->origin,
822 wasm_decode, function_time);
823
824 TimedHistogramScope wasm_decode_function_time_scope(time_counter);
825 WasmFeatures detected;
826 result = VerifyWasmCode(allocator, native_module->enabled_features(),
827 module, &detected, body);
828 }
829 if (result.failed()) {
830 WasmName name = wire_bytes.GetNameOrNull(func, module);
831 if (name.start() == nullptr) {
832 thrower->CompileError("Compiling function #%d failed: %s @+%u",
833 func_index, result.error().message().c_str(),
834 result.error().offset());
835 } else {
836 TruncatedUserString<> name(wire_bytes.GetNameOrNull(func, module));
837 thrower->CompileError("Compiling function #%d:\"%.*s\" failed: %s @+%u",
838 func_index, name.length(), name.start(),
839 result.error().message().c_str(),
840 result.error().offset());
841 }
842 }
843}
844
845void ValidateSequentially(Counters* counters, AccountingAllocator* allocator,
846 NativeModule* native_module, ErrorThrower* thrower) {
847 DCHECK(!thrower->error());
848
849 uint32_t start = native_module->module()->num_imported_functions;
850 uint32_t end = start + native_module->module()->num_declared_functions;
851 for (uint32_t func_index = start; func_index < end; func_index++) {
852 ValidateSequentially(counters, allocator, native_module, func_index,
853 thrower);
854 if (thrower->error()) break;
855 }
856}
857
858// TODO(wasm): This function should not depend on an isolate. Internally, it is
859// used for the ErrorThrower only.
860bool InitializeCompilationUnits(Isolate* isolate, NativeModule* native_module) {
861 // Set number of functions that must be compiled to consider the module fully
862 // compiled.
863 auto wasm_module = native_module->module();
864 int num_functions = wasm_module->num_declared_functions;
865 DCHECK_IMPLIES(!native_module->enabled_features().compilation_hints,
866 wasm_module->num_lazy_compilation_hints == 0);
867 int num_lazy_functions = wasm_module->num_lazy_compilation_hints;
868 CompilationStateImpl* compilation_state =
869 Impl(native_module->compilation_state());
870 compilation_state->SetNumberOfFunctionsToCompile(num_functions,
871 num_lazy_functions);
872
873 ErrorThrower thrower(isolate, "WebAssembly.compile()");
874 ModuleWireBytes wire_bytes(native_module->wire_bytes());
875 const WasmModule* module = native_module->module();
876 CompilationUnitBuilder builder(native_module);
877 uint32_t start = module->num_imported_functions;
878 uint32_t end = start + module->num_declared_functions;
879 for (uint32_t func_index = start; func_index < end; func_index++) {
880 if (IsLazyCompilation(module, native_module,
881 native_module->enabled_features(), func_index)) {
882 ValidateSequentially(isolate->counters(), isolate->allocator(),
883 native_module, func_index, &thrower);
884 native_module->UseLazyStub(func_index);
885 } else {
886 builder.AddUnits(func_index);
887 }
888 }
889 builder.Commit();
890
891 // Handle potential errors internally.
892 if (thrower.error()) {
893 thrower.Reset();
894 return false;
895 }
896 return true;
897}
898
899bool NeedsDeterministicCompile() {
900 return FLAG_trace_wasm_decoder || FLAG_wasm_num_compilation_tasks <= 1;
901}
902
903void CompileNativeModule(Isolate* isolate, ErrorThrower* thrower,
904 const WasmModule* wasm_module,
905 NativeModule* native_module) {
906 ModuleWireBytes wire_bytes(native_module->wire_bytes());
907
908 if (FLAG_wasm_lazy_compilation ||
909 (FLAG_asm_wasm_lazy_compilation && wasm_module->origin == kAsmJsOrigin)) {
910 if (wasm_module->origin == kWasmOrigin) {
911 // Validate wasm modules for lazy compilation. Don't validate asm.js
912 // modules, they are valid by construction (otherwise a CHECK will fail
913 // during lazy compilation).
914 // TODO(clemensh): According to the spec, we can actually skip validation
915 // at module creation time, and return a function that always traps at
916 // (lazy) compilation time.
917 ValidateSequentially(isolate->counters(), isolate->allocator(),
918 native_module, thrower);
919 // On error: Return and leave the module in an unexecutable state.
920 if (thrower->error()) return;
921 }
922 native_module->set_lazy_compilation(true);
923 native_module->UseLazyStubs();
924 return;
925 }
926
927 // Turn on the {CanonicalHandleScope} so that the background threads can
928 // use the node cache.
929 CanonicalHandleScope canonical(isolate);
930
931 auto* compilation_state = Impl(native_module->compilation_state());
932 DCHECK_GE(kMaxInt, native_module->module()->num_declared_functions);
933
934 // Install a callback to notify us once background compilation finished, or
935 // compilation failed.
936 auto baseline_finished_semaphore = std::make_shared<base::Semaphore>(0);
937 // The callback captures a shared ptr to the semaphore.
938 compilation_state->AddCallback(
939 [baseline_finished_semaphore](CompilationEvent event) {
940 if (event == CompilationEvent::kFinishedBaselineCompilation ||
941 event == CompilationEvent::kFailedCompilation) {
942 baseline_finished_semaphore->Signal();
943 }
944 });
945
946 // Initialize the compilation units and kick off background compile tasks.
947 if (!InitializeCompilationUnits(isolate, native_module)) {
948 // TODO(frgossen): Add test coverage for this path.
949 DCHECK(native_module->enabled_features().compilation_hints);
950 compilation_state->SetError();
951 }
952
953 // If tiering is disabled, the main thread can execute any unit (all of them
954 // are part of initial compilation). Otherwise, just execute baseline units.
955 bool is_tiering = compilation_state->compile_mode() == CompileMode::kTiering;
956 auto baseline_only = is_tiering ? kBaselineOnly : kBaselineOrTopTier;
957 // The main threads contributes to the compilation, except if we need
958 // deterministic compilation; in that case, the single background task will
959 // execute all compilation.
960 if (!NeedsDeterministicCompile()) {
961 while (ExecuteCompilationUnits(
962 compilation_state->background_compile_token(), isolate->counters(),
963 kMainThreadTaskId, baseline_only)) {
964 // Continue executing compilation units.
965 }
966 }
967
968 // Now wait until baseline compilation finished.
969 baseline_finished_semaphore->Wait();
970
971 compilation_state->PublishDetectedFeatures(isolate);
972
973 if (compilation_state->failed()) {
974 ValidateSequentially(isolate->counters(), isolate->allocator(),
975 native_module, thrower);
976 CHECK(thrower->error());
977 }
978}
979
980// The runnable task that performs compilations in the background.
981class BackgroundCompileTask : public CancelableTask {
982 public:
983 explicit BackgroundCompileTask(CancelableTaskManager* manager,
984 std::shared_ptr<BackgroundCompileToken> token,
985 std::shared_ptr<Counters> async_counters,
986 int task_id)
987 : CancelableTask(manager),
988 token_(std::move(token)),
989 async_counters_(std::move(async_counters)),
990 task_id_(task_id) {}
991
992 void RunInternal() override {
993 ExecuteCompilationUnits(token_, async_counters_.get(), task_id_,
994 kBaselineOrTopTier);
995 }
996
997 private:
998 const std::shared_ptr<BackgroundCompileToken> token_;
999 const std::shared_ptr<Counters> async_counters_;
1000 const int task_id_;
1001};
1002
1003} // namespace
1004
1005std::shared_ptr<NativeModule> CompileToNativeModule(
1006 Isolate* isolate, const WasmFeatures& enabled, ErrorThrower* thrower,
1007 std::shared_ptr<const WasmModule> module, const ModuleWireBytes& wire_bytes,
1008 Handle<FixedArray>* export_wrappers_out) {
1009 const WasmModule* wasm_module = module.get();
1010 TimedHistogramScope wasm_compile_module_time_scope(SELECT_WASM_COUNTER(
1011 isolate->counters(), wasm_module->origin, wasm_compile, module_time));
1012
1013 // Embedder usage count for declared shared memories.
1014 if (wasm_module->has_shared_memory) {
1015 isolate->CountUsage(v8::Isolate::UseCounterFeature::kWasmSharedMemory);
1016 }
1017 int export_wrapper_size = static_cast<int>(module->num_exported_functions);
1018
1019 // TODO(wasm): only save the sections necessary to deserialize a
1020 // {WasmModule}. E.g. function bodies could be omitted.
1021 OwnedVector<uint8_t> wire_bytes_copy =
1022 OwnedVector<uint8_t>::Of(wire_bytes.module_bytes());
1023
1024 // Create and compile the native module.
1025 size_t code_size_estimate =
1026 wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module.get());
1027
1028 // Create a new {NativeModule} first.
1029 auto native_module = isolate->wasm_engine()->NewNativeModule(
1030 isolate, enabled, code_size_estimate,
1031 wasm::NativeModule::kCanAllocateMoreMemory, std::move(module));
1032 native_module->SetWireBytes(std::move(wire_bytes_copy));
1033 native_module->SetRuntimeStubs(isolate);
1034
1035 CompileNativeModule(isolate, thrower, wasm_module, native_module.get());
1036 if (thrower->error()) return {};
1037
1038 // Compile JS->wasm wrappers for exported functions.
1039 *export_wrappers_out = isolate->factory()->NewFixedArray(
1040 export_wrapper_size, AllocationType::kOld);
1041 CompileJsToWasmWrappers(isolate, native_module->module(),
1042 *export_wrappers_out);
1043
1044 // Log the code within the generated module for profiling.
1045 native_module->LogWasmCodes(isolate);
1046
1047 return native_module;
1048}
1049
1050void CompileNativeModuleWithExplicitBoundsChecks(Isolate* isolate,
1051 ErrorThrower* thrower,
1052 const WasmModule* wasm_module,
1053 NativeModule* native_module) {
1054 native_module->DisableTrapHandler();
1055 CompileNativeModule(isolate, thrower, wasm_module, native_module);
1056}
1057
1058AsyncCompileJob::AsyncCompileJob(
1059 Isolate* isolate, const WasmFeatures& enabled,
1060 std::unique_ptr<byte[]> bytes_copy, size_t length, Handle<Context> context,
1061 std::shared_ptr<CompilationResultResolver> resolver)
1062 : isolate_(isolate),
1063 enabled_features_(enabled),
1064 bytes_copy_(std::move(bytes_copy)),
1065 wire_bytes_(bytes_copy_.get(), bytes_copy_.get() + length),
1066 resolver_(std::move(resolver)) {
1067 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
1068 v8::Platform* platform = V8::GetCurrentPlatform();
1069 foreground_task_runner_ = platform->GetForegroundTaskRunner(v8_isolate);
1070 native_context_ =
1071 isolate->global_handles()->Create(context->native_context());
1072 DCHECK(native_context_->IsNativeContext());
1073}
1074
1075void AsyncCompileJob::Start() {
1076 DoAsync<DecodeModule>(isolate_->counters()); // --
1077}
1078
1079void AsyncCompileJob::Abort() {
1080 // Removing this job will trigger the destructor, which will cancel all
1081 // compilation.
1082 isolate_->wasm_engine()->RemoveCompileJob(this);
1083}
1084
1085class AsyncStreamingProcessor final : public StreamingProcessor {
1086 public:
1087 explicit AsyncStreamingProcessor(AsyncCompileJob* job);
1088
1089 bool ProcessModuleHeader(Vector<const uint8_t> bytes,
1090 uint32_t offset) override;
1091
1092 bool ProcessSection(SectionCode section_code, Vector<const uint8_t> bytes,
1093 uint32_t offset) override;
1094
1095 bool ProcessCodeSectionHeader(int functions_count, uint32_t offset,
1096 std::shared_ptr<WireBytesStorage>) override;
1097
1098 bool ProcessFunctionBody(Vector<const uint8_t> bytes,
1099 uint32_t offset) override;
1100
1101 void OnFinishedChunk() override;
1102
1103 void OnFinishedStream(OwnedVector<uint8_t> bytes) override;
1104
1105 void OnError(const WasmError&) override;
1106
1107 void OnAbort() override;
1108
1109 bool Deserialize(Vector<const uint8_t> wire_bytes,
1110 Vector<const uint8_t> module_bytes) override;
1111
1112 private:
1113 // Finishes the AsyncCompileJob with an error.
1114 void FinishAsyncCompileJobWithError(const WasmError&);
1115
1116 void CommitCompilationUnits();
1117
1118 ModuleDecoder decoder_;
1119 AsyncCompileJob* job_;
1120 std::unique_ptr<CompilationUnitBuilder> compilation_unit_builder_;
1121 int num_functions_ = 0;
1122};
1123
1124std::shared_ptr<StreamingDecoder> AsyncCompileJob::CreateStreamingDecoder() {
1125 DCHECK_NULL(stream_);
1126 stream_.reset(
1127 new StreamingDecoder(base::make_unique<AsyncStreamingProcessor>(this)));
1128 return stream_;
1129}
1130
1131AsyncCompileJob::~AsyncCompileJob() {
1132 // Note: This destructor always runs on the foreground thread of the isolate.
1133 background_task_manager_.CancelAndWait();
1134 // If the runtime objects were not created yet, then initial compilation did
1135 // not finish yet. In this case we can abort compilation.
1136 if (native_module_ && module_object_.is_null()) {
1137 Impl(native_module_->compilation_state())->AbortCompilation();
1138 }
1139 // Tell the streaming decoder that the AsyncCompileJob is not available
1140 // anymore.
1141 // TODO(ahaas): Is this notification really necessary? Check
1142 // https://crbug.com/888170.
1143 if (stream_) stream_->NotifyCompilationEnded();
1144 CancelPendingForegroundTask();
1145 isolate_->global_handles()->Destroy(native_context_.location());
1146 if (!module_object_.is_null()) {
1147 isolate_->global_handles()->Destroy(module_object_.location());
1148 }
1149}
1150
1151void AsyncCompileJob::CreateNativeModule(
1152 std::shared_ptr<const WasmModule> module) {
1153 // Embedder usage count for declared shared memories.
1154 if (module->has_shared_memory) {
1155 isolate_->CountUsage(v8::Isolate::UseCounterFeature::kWasmSharedMemory);
1156 }
1157
1158 // TODO(wasm): Improve efficiency of storing module wire bytes. Only store
1159 // relevant sections, not function bodies
1160
1161 // Create the module object and populate with compiled functions and
1162 // information needed at instantiation time.
1163 // TODO(clemensh): For the same module (same bytes / same hash), we should
1164 // only have one {WasmModuleObject}. Otherwise, we might only set
1165 // breakpoints on a (potentially empty) subset of the instances.
1166 // Create the module object.
1167
1168 size_t code_size_estimate =
1169 wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module.get());
1170 native_module_ = isolate_->wasm_engine()->NewNativeModule(
1171 isolate_, enabled_features_, code_size_estimate,
1172 wasm::NativeModule::kCanAllocateMoreMemory, std::move(module));
1173 native_module_->SetWireBytes({std::move(bytes_copy_), wire_bytes_.length()});
1174 native_module_->SetRuntimeStubs(isolate_);
1175
1176 if (stream_) stream_->NotifyNativeModuleCreated(native_module_);
1177}
1178
1179void AsyncCompileJob::PrepareRuntimeObjects() {
1180 // Create heap objects for script and module bytes to be stored in the
1181 // module object. Asm.js is not compiled asynchronously.
1182 const WasmModule* module = native_module_->module();
1183 Handle<Script> script =
1184 CreateWasmScript(isolate_, wire_bytes_, module->source_map_url);
1185
1186 size_t code_size_estimate =
1187 wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module);
1188 Handle<WasmModuleObject> module_object = WasmModuleObject::New(
1189 isolate_, native_module_, script, code_size_estimate);
1190
1191 module_object_ = isolate_->global_handles()->Create(*module_object);
1192}
1193
1194// This function assumes that it is executed in a HandleScope, and that a
1195// context is set on the isolate.
1196void AsyncCompileJob::FinishCompile() {
1197 bool is_after_deserialization = !module_object_.is_null();
1198 if (!is_after_deserialization) {
1199 PrepareRuntimeObjects();
1200 }
1201 DCHECK(!isolate_->context().is_null());
1202 // Finish the wasm script now and make it public to the debugger.
1203 Handle<Script> script(module_object_->script(), isolate_);
1204 if (script->type() == Script::TYPE_WASM &&
1205 module_object_->module()->source_map_url.size() != 0) {
1206 MaybeHandle<String> src_map_str = isolate_->factory()->NewStringFromUtf8(
1207 CStrVector(module_object_->module()->source_map_url.c_str()),
1208 AllocationType::kOld);
1209 script->set_source_mapping_url(*src_map_str.ToHandleChecked());
1210 }
1211 isolate_->debug()->OnAfterCompile(script);
1212
1213 // We can only update the feature counts once the entire compile is done.
1214 auto compilation_state =
1215 Impl(module_object_->native_module()->compilation_state());
1216 compilation_state->PublishDetectedFeatures(isolate_);
1217
1218 // TODO(bbudge) Allow deserialization without wrapper compilation, so we can
1219 // just compile wrappers here.
1220 if (!is_after_deserialization) {
1221 // TODO(wasm): compiling wrappers should be made async.
1222 CompileWrappers();
1223 }
1224 FinishModule();
1225}
1226
1227void AsyncCompileJob::DecodeFailed(const WasmError& error) {
1228 ErrorThrower thrower(isolate_, "WebAssembly.compile()");
1229 thrower.CompileFailed(error);
1230 // {job} keeps the {this} pointer alive.
1231 std::shared_ptr<AsyncCompileJob> job =
1232 isolate_->wasm_engine()->RemoveCompileJob(this);
1233 resolver_->OnCompilationFailed(thrower.Reify());
1234}
1235
1236void AsyncCompileJob::AsyncCompileFailed() {
1237 ErrorThrower thrower(isolate_, "WebAssembly.compile()");
1238 ValidateSequentially(isolate_->counters(), isolate_->allocator(),
1239 native_module_.get(), &thrower);
1240 DCHECK(thrower.error());
1241 // {job} keeps the {this} pointer alive.
1242 std::shared_ptr<AsyncCompileJob> job =
1243 isolate_->wasm_engine()->RemoveCompileJob(this);
1244 resolver_->OnCompilationFailed(thrower.Reify());
1245}
1246
1247void AsyncCompileJob::AsyncCompileSucceeded(Handle<WasmModuleObject> result) {
1248 resolver_->OnCompilationSucceeded(result);
1249}
1250
1251class AsyncCompileJob::CompilationStateCallback {
1252 public:
1253 explicit CompilationStateCallback(AsyncCompileJob* job) : job_(job) {}
1254
1255 void operator()(CompilationEvent event) {
1256 // This callback is only being called from a foreground task.
1257 switch (event) {
1258 case CompilationEvent::kFinishedBaselineCompilation:
1259 DCHECK(!last_event_.has_value());
1260 if (job_->DecrementAndCheckFinisherCount()) {
1261 job_->DoSync<CompileFinished>();
1262 }
1263 break;
1264 case CompilationEvent::kFinishedTopTierCompilation:
1265 DCHECK_EQ(CompilationEvent::kFinishedBaselineCompilation, last_event_);
1266 // At this point, the job will already be gone, thus do not access it
1267 // here.
1268 break;
1269 case CompilationEvent::kFailedCompilation: {
1270 DCHECK(!last_event_.has_value());
1271 if (job_->DecrementAndCheckFinisherCount()) {
1272 job_->DoSync<CompileFailed>();
1273 }
1274 break;
1275 }
1276 default:
1277 UNREACHABLE();
1278 }
1279#ifdef DEBUG
1280 last_event_ = event;
1281#endif
1282 }
1283
1284 private:
1285 AsyncCompileJob* job_;
1286#ifdef DEBUG
1287 // This will be modified by different threads, but they externally
1288 // synchronize, so no explicit synchronization (currently) needed here.
1289 base::Optional<CompilationEvent> last_event_;
1290#endif
1291};
1292
1293// A closure to run a compilation step (either as foreground or background
1294// task) and schedule the next step(s), if any.
1295class AsyncCompileJob::CompileStep {
1296 public:
1297 virtual ~CompileStep() = default;
1298
1299 void Run(AsyncCompileJob* job, bool on_foreground) {
1300 if (on_foreground) {
1301 HandleScope scope(job->isolate_);
1302 SaveAndSwitchContext saved_context(job->isolate_, *job->native_context_);
1303 RunInForeground(job);
1304 } else {
1305 RunInBackground(job);
1306 }
1307 }
1308
1309 virtual void RunInForeground(AsyncCompileJob*) { UNREACHABLE(); }
1310 virtual void RunInBackground(AsyncCompileJob*) { UNREACHABLE(); }
1311};
1312
1313class AsyncCompileJob::CompileTask : public CancelableTask {
1314 public:
1315 CompileTask(AsyncCompileJob* job, bool on_foreground)
1316 // We only manage the background tasks with the {CancelableTaskManager} of
1317 // the {AsyncCompileJob}. Foreground tasks are managed by the system's
1318 // {CancelableTaskManager}. Background tasks cannot spawn tasks managed by
1319 // their own task manager.
1320 : CancelableTask(on_foreground ? job->isolate_->cancelable_task_manager()
1321 : &job->background_task_manager_),
1322 job_(job),
1323 on_foreground_(on_foreground) {}
1324
1325 ~CompileTask() override {
1326 if (job_ != nullptr && on_foreground_) ResetPendingForegroundTask();
1327 }
1328
1329 void RunInternal() final {
1330 if (!job_) return;
1331 if (on_foreground_) ResetPendingForegroundTask();
1332 job_->step_->Run(job_, on_foreground_);
1333 // After execution, reset {job_} such that we don't try to reset the pending
1334 // foreground task when the task is deleted.
1335 job_ = nullptr;
1336 }
1337
1338 void Cancel() {
1339 DCHECK_NOT_NULL(job_);
1340 job_ = nullptr;
1341 }
1342
1343 private:
1344 // {job_} will be cleared to cancel a pending task.
1345 AsyncCompileJob* job_;
1346 bool on_foreground_;
1347
1348 void ResetPendingForegroundTask() const {
1349 DCHECK_EQ(this, job_->pending_foreground_task_);
1350 job_->pending_foreground_task_ = nullptr;
1351 }
1352};
1353
1354void AsyncCompileJob::StartForegroundTask() {
1355 DCHECK_NULL(pending_foreground_task_);
1356
1357 auto new_task = base::make_unique<CompileTask>(this, true);
1358 pending_foreground_task_ = new_task.get();
1359 foreground_task_runner_->PostTask(std::move(new_task));
1360}
1361
1362void AsyncCompileJob::ExecuteForegroundTaskImmediately() {
1363 DCHECK_NULL(pending_foreground_task_);
1364
1365 auto new_task = base::make_unique<CompileTask>(this, true);
1366 pending_foreground_task_ = new_task.get();
1367 new_task->Run();
1368}
1369
1370void AsyncCompileJob::CancelPendingForegroundTask() {
1371 if (!pending_foreground_task_) return;
1372 pending_foreground_task_->Cancel();
1373 pending_foreground_task_ = nullptr;
1374}
1375
1376void AsyncCompileJob::StartBackgroundTask() {
1377 auto task = base::make_unique<CompileTask>(this, false);
1378
1379 // If --wasm-num-compilation-tasks=0 is passed, do only spawn foreground
1380 // tasks. This is used to make timing deterministic.
1381 if (FLAG_wasm_num_compilation_tasks > 0) {
1382 V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(task));
1383 } else {
1384 foreground_task_runner_->PostTask(std::move(task));
1385 }
1386}
1387
1388template <typename Step,
1389 AsyncCompileJob::UseExistingForegroundTask use_existing_fg_task,
1390 typename... Args>
1391void AsyncCompileJob::DoSync(Args&&... args) {
1392 NextStep<Step>(std::forward<Args>(args)...);
1393 if (use_existing_fg_task && pending_foreground_task_ != nullptr) return;
1394 StartForegroundTask();
1395}
1396
1397template <typename Step, typename... Args>
1398void AsyncCompileJob::DoImmediately(Args&&... args) {
1399 NextStep<Step>(std::forward<Args>(args)...);
1400 ExecuteForegroundTaskImmediately();
1401}
1402
1403template <typename Step, typename... Args>
1404void AsyncCompileJob::DoAsync(Args&&... args) {
1405 NextStep<Step>(std::forward<Args>(args)...);
1406 StartBackgroundTask();
1407}
1408
1409template <typename Step, typename... Args>
1410void AsyncCompileJob::NextStep(Args&&... args) {
1411 step_.reset(new Step(std::forward<Args>(args)...));
1412}
1413
1414//==========================================================================
1415// Step 1: (async) Decode the module.
1416//==========================================================================
1417class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep {
1418 public:
1419 explicit DecodeModule(Counters* counters) : counters_(counters) {}
1420
1421 void RunInBackground(AsyncCompileJob* job) override {
1422 ModuleResult result;
1423 {
1424 DisallowHandleAllocation no_handle;
1425 DisallowHeapAllocation no_allocation;
1426 // Decode the module bytes.
1427 TRACE_COMPILE("(1) Decoding module...\n");
1428 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"),
1429 "AsyncCompileJob::DecodeModule");
1430 result = DecodeWasmModule(
1431 job->enabled_features_, job->wire_bytes_.start(),
1432 job->wire_bytes_.end(), false, kWasmOrigin, counters_,
1433 job->isolate()->wasm_engine()->allocator());
1434 }
1435 if (result.failed()) {
1436 // Decoding failure; reject the promise and clean up.
1437 job->DoSync<DecodeFail>(std::move(result).error());
1438 } else {
1439 // Decode passed.
1440 job->DoSync<PrepareAndStartCompile>(std::move(result).value(), true);
1441 }
1442 }
1443
1444 private:
1445 Counters* const counters_;
1446};
1447
1448//==========================================================================
1449// Step 1b: (sync) Fail decoding the module.
1450//==========================================================================
1451class AsyncCompileJob::DecodeFail : public CompileStep {
1452 public:
1453 explicit DecodeFail(WasmError error) : error_(std::move(error)) {}
1454
1455 private:
1456 WasmError error_;
1457
1458 void RunInForeground(AsyncCompileJob* job) override {
1459 TRACE_COMPILE("(1b) Decoding failed.\n");
1460 // {job_} is deleted in DecodeFailed, therefore the {return}.
1461 return job->DecodeFailed(error_);
1462 }
1463};
1464
1465//==========================================================================
1466// Step 2 (sync): Create heap-allocated data and start compile.
1467//==========================================================================
1468class AsyncCompileJob::PrepareAndStartCompile : public CompileStep {
1469 public:
1470 PrepareAndStartCompile(std::shared_ptr<const WasmModule> module,
1471 bool start_compilation)
1472 : module_(std::move(module)), start_compilation_(start_compilation) {}
1473
1474 private:
1475 std::shared_ptr<const WasmModule> module_;
1476 bool start_compilation_;
1477
1478 void RunInForeground(AsyncCompileJob* job) override {
1479 TRACE_COMPILE("(2) Prepare and start compile...\n");
1480
1481 // Make sure all compilation tasks stopped running. Decoding (async step)
1482 // is done.
1483 job->background_task_manager_.CancelAndWait();
1484
1485 job->CreateNativeModule(module_);
1486
1487 CompilationStateImpl* compilation_state =
1488 Impl(job->native_module_->compilation_state());
1489 compilation_state->AddCallback(CompilationStateCallback{job});
1490 if (start_compilation_) {
1491 // TODO(ahaas): Try to remove the {start_compilation_} check when
1492 // streaming decoding is done in the background. If
1493 // InitializeCompilationUnits always returns 0 for streaming compilation,
1494 // then DoAsync would do the same as NextStep already.
1495
1496 // Add compilation units and kick off compilation.
1497 auto isolate = job->isolate();
1498 bool success =
1499 InitializeCompilationUnits(isolate, job->native_module_.get());
1500 if (!success) {
1501 // TODO(frgossen): Add test coverage for this path.
1502 DCHECK(job->native_module_->enabled_features().compilation_hints);
1503 job->DoSync<CompileFailed>();
1504 }
1505 }
1506 }
1507};
1508
1509//==========================================================================
1510// Step 3a (sync): Compilation failed.
1511//==========================================================================
1512class AsyncCompileJob::CompileFailed : public CompileStep {
1513 private:
1514 void RunInForeground(AsyncCompileJob* job) override {
1515 TRACE_COMPILE("(3a) Compilation failed\n");
1516 DCHECK(job->native_module_->compilation_state()->failed());
1517
1518 // {job_} is deleted in AsyncCompileFailed, therefore the {return}.
1519 return job->AsyncCompileFailed();
1520 }
1521};
1522
1523namespace {
1524class SampleTopTierCodeSizeCallback {
1525 public:
1526 explicit SampleTopTierCodeSizeCallback(
1527 std::weak_ptr<NativeModule> native_module)
1528 : native_module_(std::move(native_module)) {}
1529
1530 void operator()(CompilationEvent event) {
1531 // This callback is registered after baseline compilation finished, so the
1532 // only possible event to follow is {kFinishedTopTierCompilation}.
1533 DCHECK_EQ(CompilationEvent::kFinishedTopTierCompilation, event);
1534 if (std::shared_ptr<NativeModule> native_module = native_module_.lock()) {
1535 native_module->engine()->SampleTopTierCodeSizeInAllIsolates(
1536 native_module);
1537 }
1538 }
1539
1540 private:
1541 std::weak_ptr<NativeModule> native_module_;
1542};
1543} // namespace
1544
1545//==========================================================================
1546// Step 3b (sync): Compilation finished.
1547//==========================================================================
1548class AsyncCompileJob::CompileFinished : public CompileStep {
1549 private:
1550 void RunInForeground(AsyncCompileJob* job) override {
1551 TRACE_COMPILE("(3b) Compilation finished\n");
1552 DCHECK(!job->native_module_->compilation_state()->failed());
1553 // Sample the generated code size when baseline compilation finished.
1554 job->native_module_->SampleCodeSize(job->isolate_->counters(),
1555 NativeModule::kAfterBaseline);
1556 // Also, set a callback to sample the code size after top-tier compilation
1557 // finished. This callback will *not* keep the NativeModule alive.
1558 job->native_module_->compilation_state()->AddCallback(
1559 SampleTopTierCodeSizeCallback{job->native_module_});
1560 // Then finalize and publish the generated module.
1561 job->FinishCompile();
1562 }
1563};
1564
1565void AsyncCompileJob::CompileWrappers() {
1566 // TODO(wasm): Compile all wrappers here, including the start function wrapper
1567 // and the wrappers for the function table elements.
1568 TRACE_COMPILE("(5) Compile wrappers...\n");
1569 // Compile JS->wasm wrappers for exported functions.
1570 CompileJsToWasmWrappers(isolate_, module_object_->native_module()->module(),
1571 handle(module_object_->export_wrappers(), isolate_));
1572}
1573
1574void AsyncCompileJob::FinishModule() {
1575 TRACE_COMPILE("(6) Finish module...\n");
1576 AsyncCompileSucceeded(module_object_);
1577 isolate_->wasm_engine()->RemoveCompileJob(this);
1578}
1579
1580AsyncStreamingProcessor::AsyncStreamingProcessor(AsyncCompileJob* job)
1581 : decoder_(job->enabled_features_),
1582 job_(job),
1583 compilation_unit_builder_(nullptr) {}
1584
1585void AsyncStreamingProcessor::FinishAsyncCompileJobWithError(
1586 const WasmError& error) {
1587 DCHECK(error.has_error());
1588 // Make sure all background tasks stopped executing before we change the state
1589 // of the AsyncCompileJob to DecodeFail.
1590 job_->background_task_manager_.CancelAndWait();
1591
1592 // Check if there is already a CompiledModule, in which case we have to clean
1593 // up the CompilationStateImpl as well.
1594 if (job_->native_module_) {
1595 Impl(job_->native_module_->compilation_state())->AbortCompilation();
1596
1597 job_->DoSync<AsyncCompileJob::DecodeFail,
1598 AsyncCompileJob::kUseExistingForegroundTask>(error);
1599
1600 // Clear the {compilation_unit_builder_} if it exists. This is needed
1601 // because there is a check in the destructor of the
1602 // {CompilationUnitBuilder} that it is empty.
1603 if (compilation_unit_builder_) compilation_unit_builder_->Clear();
1604 } else {
1605 job_->DoSync<AsyncCompileJob::DecodeFail>(error);
1606 }
1607}
1608
1609// Process the module header.
1610bool AsyncStreamingProcessor::ProcessModuleHeader(Vector<const uint8_t> bytes,
1611 uint32_t offset) {
1612 TRACE_STREAMING("Process module header...\n");
1613 decoder_.StartDecoding(job_->isolate()->counters(),
1614 job_->isolate()->wasm_engine()->allocator());
1615 decoder_.DecodeModuleHeader(bytes, offset);
1616 if (!decoder_.ok()) {
1617 FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false).error());
1618 return false;
1619 }
1620 return true;
1621}
1622
1623// Process all sections except for the code section.
1624bool AsyncStreamingProcessor::ProcessSection(SectionCode section_code,
1625 Vector<const uint8_t> bytes,
1626 uint32_t offset) {
1627 TRACE_STREAMING("Process section %d ...\n", section_code);
1628 if (compilation_unit_builder_) {
1629 // We reached a section after the code section, we do not need the
1630 // compilation_unit_builder_ anymore.
1631 CommitCompilationUnits();
1632 compilation_unit_builder_.reset();
1633 }
1634 if (section_code == SectionCode::kUnknownSectionCode) {
1635 Decoder decoder(bytes, offset);
1636 section_code = ModuleDecoder::IdentifyUnknownSection(
1637 decoder, bytes.start() + bytes.length());
1638 if (section_code == SectionCode::kUnknownSectionCode) {
1639 // Skip unknown sections that we do not know how to handle.
1640 return true;
1641 }
1642 // Remove the unknown section tag from the payload bytes.
1643 offset += decoder.position();
1644 bytes = bytes.SubVector(decoder.position(), bytes.size());
1645 }
1646 constexpr bool verify_functions = false;
1647 decoder_.DecodeSection(section_code, bytes, offset, verify_functions);
1648 if (!decoder_.ok()) {
1649 FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false).error());
1650 return false;
1651 }
1652 return true;
1653}
1654
1655// Start the code section.
1656bool AsyncStreamingProcessor::ProcessCodeSectionHeader(
1657 int functions_count, uint32_t offset,
1658 std::shared_ptr<WireBytesStorage> wire_bytes_storage) {
1659 TRACE_STREAMING("Start the code section with %d functions...\n",
1660 functions_count);
1661 if (!decoder_.CheckFunctionsCount(static_cast<uint32_t>(functions_count),
1662 offset)) {
1663 FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false).error());
1664 return false;
1665 }
1666 // Execute the PrepareAndStartCompile step immediately and not in a separate
1667 // task.
1668 job_->DoImmediately<AsyncCompileJob::PrepareAndStartCompile>(
1669 decoder_.shared_module(), false);
1670 auto* compilation_state = Impl(job_->native_module_->compilation_state());
1671 compilation_state->SetWireBytesStorage(std::move(wire_bytes_storage));
1672
1673 // Set number of functions that must be compiled to consider the module fully
1674 // compiled.
1675 auto wasm_module = job_->native_module_->module();
1676 int num_functions = wasm_module->num_declared_functions;
1677 DCHECK_IMPLIES(!job_->native_module_->enabled_features().compilation_hints,
1678 wasm_module->num_lazy_compilation_hints == 0);
1679 int num_lazy_functions = wasm_module->num_lazy_compilation_hints;
1680 compilation_state->SetNumberOfFunctionsToCompile(num_functions,
1681 num_lazy_functions);
1682
1683 // Set outstanding_finishers_ to 2, because both the AsyncCompileJob and the
1684 // AsyncStreamingProcessor have to finish.
1685 job_->outstanding_finishers_.store(2);
1686 compilation_unit_builder_.reset(
1687 new CompilationUnitBuilder(job_->native_module_.get()));
1688 return true;
1689}
1690
1691// Process a function body.
1692bool AsyncStreamingProcessor::ProcessFunctionBody(Vector<const uint8_t> bytes,
1693 uint32_t offset) {
1694 TRACE_STREAMING("Process function body %d ...\n", num_functions_);
1695
1696 decoder_.DecodeFunctionBody(
1697 num_functions_, static_cast<uint32_t>(bytes.length()), offset, false);
1698
1699 NativeModule* native_module = job_->native_module_.get();
1700 const WasmModule* module = native_module->module();
1701 uint32_t func_index =
1702 num_functions_ + decoder_.module()->num_imported_functions;
1703
1704 if (IsLazyCompilation(module, native_module,
1705 native_module->enabled_features(), func_index)) {
1706 // TODO(frgossen): Unify this code with {ValidateSequentially}. Note, that
1707 // the native moudle does not own the wire bytes until {SetWireBytes} is
1708 // called in {OnFinishedStream}. Validation must threfore use the {bytes}
1709 // parameter.
1710 const WasmFunction* func = &module->functions[func_index];
1711 FunctionBody body{func->sig, func->code.offset(), bytes.start(),
1712 bytes.end()};
1713 DecodeResult result;
1714 {
1715 Counters* counters = Impl(native_module->compilation_state())->counters();
1716 auto time_counter = SELECT_WASM_COUNTER(counters, module->origin,
1717 wasm_decode, function_time);
1718
1719 TimedHistogramScope wasm_decode_function_time_scope(time_counter);
1720 WasmFeatures detected;
1721 result = VerifyWasmCode(native_module->engine()->allocator(),
1722 native_module->enabled_features(), module,
1723 &detected, body);
1724 }
1725 if (result.failed()) {
1726 FinishAsyncCompileJobWithError(result.error());
1727 return false;
1728 }
1729 native_module->UseLazyStub(func_index);
1730 } else {
1731 compilation_unit_builder_->AddUnits(func_index);
1732 }
1733
1734 ++num_functions_;
1735
1736 return true;
1737}
1738
1739void AsyncStreamingProcessor::CommitCompilationUnits() {
1740 DCHECK(compilation_unit_builder_);
1741 compilation_unit_builder_->Commit();
1742}
1743
1744void AsyncStreamingProcessor::OnFinishedChunk() {
1745 TRACE_STREAMING("FinishChunk...\n");
1746 if (compilation_unit_builder_) CommitCompilationUnits();
1747}
1748
1749// Finish the processing of the stream.
1750void AsyncStreamingProcessor::OnFinishedStream(OwnedVector<uint8_t> bytes) {
1751 TRACE_STREAMING("Finish stream...\n");
1752 ModuleResult result = decoder_.FinishDecoding(false);
1753 if (result.failed()) {
1754 FinishAsyncCompileJobWithError(result.error());
1755 return;
1756 }
1757 // We have to open a HandleScope and prepare the Context for
1758 // CreateNativeModule, PrepareRuntimeObjects and FinishCompile as this is a
1759 // callback from the embedder.
1760 HandleScope scope(job_->isolate_);
1761 SaveAndSwitchContext saved_context(job_->isolate_, *job_->native_context_);
1762
1763 bool needs_finish = job_->DecrementAndCheckFinisherCount();
1764 if (job_->native_module_ == nullptr) {
1765 // We are processing a WebAssembly module without code section. Create the
1766 // runtime objects now (would otherwise happen in {PrepareAndStartCompile}).
1767 job_->CreateNativeModule(std::move(result).value());
1768 DCHECK(needs_finish);
1769 }
1770 job_->wire_bytes_ = ModuleWireBytes(bytes.as_vector());
1771 job_->native_module_->SetWireBytes(std::move(bytes));
1772 if (needs_finish) {
1773 if (job_->native_module_->compilation_state()->failed()) {
1774 job_->AsyncCompileFailed();
1775 } else {
1776 job_->FinishCompile();
1777 }
1778 }
1779}
1780
1781// Report an error detected in the StreamingDecoder.
1782void AsyncStreamingProcessor::OnError(const WasmError& error) {
1783 TRACE_STREAMING("Stream error...\n");
1784 FinishAsyncCompileJobWithError(error);
1785}
1786
1787void AsyncStreamingProcessor::OnAbort() {
1788 TRACE_STREAMING("Abort stream...\n");
1789 job_->Abort();
1790}
1791
1792bool AsyncStreamingProcessor::Deserialize(Vector<const uint8_t> module_bytes,
1793 Vector<const uint8_t> wire_bytes) {
1794 // DeserializeNativeModule and FinishCompile assume that they are executed in
1795 // a HandleScope, and that a context is set on the isolate.
1796 HandleScope scope(job_->isolate_);
1797 SaveAndSwitchContext saved_context(job_->isolate_, *job_->native_context_);
1798
1799 MaybeHandle<WasmModuleObject> result =
1800 DeserializeNativeModule(job_->isolate_, module_bytes, wire_bytes);
1801 if (result.is_null()) return false;
1802
1803 job_->module_object_ =
1804 job_->isolate_->global_handles()->Create(*result.ToHandleChecked());
1805 job_->native_module_ = job_->module_object_->shared_native_module();
1806 auto owned_wire_bytes = OwnedVector<uint8_t>::Of(wire_bytes);
1807 job_->wire_bytes_ = ModuleWireBytes(owned_wire_bytes.as_vector());
1808 job_->native_module_->SetWireBytes(std::move(owned_wire_bytes));
1809 job_->FinishCompile();
1810 return true;
1811}
1812
1813namespace {
1814int GetMaxBackgroundTasks() {
1815 if (NeedsDeterministicCompile()) return 1;
1816 int num_worker_threads = V8::GetCurrentPlatform()->NumberOfWorkerThreads();
1817 int num_compile_tasks =
1818 std::min(FLAG_wasm_num_compilation_tasks, num_worker_threads);
1819 return std::max(1, num_compile_tasks);
1820}
1821} // namespace
1822
1823CompilationStateImpl::CompilationStateImpl(
1824 const std::shared_ptr<NativeModule>& native_module,
1825 std::shared_ptr<Counters> async_counters)
1826 : native_module_(native_module.get()),
1827 background_compile_token_(
1828 std::make_shared<BackgroundCompileToken>(native_module)),
1829 compile_mode_(FLAG_wasm_tier_up &&
1830 native_module->module()->origin == kWasmOrigin
1831 ? CompileMode::kTiering
1832 : CompileMode::kRegular),
1833 async_counters_(std::move(async_counters)),
1834 max_background_tasks_(GetMaxBackgroundTasks()),
1835 compilation_unit_queues_(max_background_tasks_),
1836 available_task_ids_(max_background_tasks_) {
1837 for (int i = 0; i < max_background_tasks_; ++i) {
1838 // Ids are popped on task creation, so reverse this list. This ensures that
1839 // the first background task gets id 0.
1840 available_task_ids_[i] = max_background_tasks_ - 1 - i;
1841 }
1842}
1843
1844void CompilationStateImpl::AbortCompilation() {
1845 background_compile_token_->Cancel();
1846 // No more callbacks after abort.
1847 base::MutexGuard callbacks_guard(&callbacks_mutex_);
1848 callbacks_.clear();
1849}
1850
1851void CompilationStateImpl::SetNumberOfFunctionsToCompile(
1852 int num_functions, int num_lazy_functions) {
1853 DCHECK(!failed());
1854 base::MutexGuard guard(&callbacks_mutex_);
1855
1856 int num_functions_to_compile = num_functions - num_lazy_functions;
1857 outstanding_baseline_functions_ = num_functions_to_compile;
1858 outstanding_top_tier_functions_ = num_functions_to_compile;
1859 highest_execution_tier_.assign(num_functions, ExecutionTier::kNone);
1860
1861 // Degenerate case of an empty module. Trigger callbacks immediately.
1862 if (num_functions_to_compile == 0) {
1863 for (auto& callback : callbacks_) {
1864 callback(CompilationEvent::kFinishedBaselineCompilation);
1865 }
1866 for (auto& callback : callbacks_) {
1867 callback(CompilationEvent::kFinishedTopTierCompilation);
1868 }
1869 // Clear the callbacks because no more events will be delivered.
1870 callbacks_.clear();
1871 }
1872}
1873
1874void CompilationStateImpl::AddCallback(CompilationState::callback_t callback) {
1875 base::MutexGuard callbacks_guard(&callbacks_mutex_);
1876 callbacks_.emplace_back(std::move(callback));
1877}
1878
1879void CompilationStateImpl::AddCompilationUnits(
1880 Vector<std::unique_ptr<WasmCompilationUnit>> baseline_units,
1881 Vector<std::unique_ptr<WasmCompilationUnit>> top_tier_units) {
1882 compilation_unit_queues_.AddUnits(baseline_units, top_tier_units);
1883
1884 RestartBackgroundTasks();
1885}
1886
1887void CompilationStateImpl::AddTopTierCompilationUnit(
1888 std::unique_ptr<WasmCompilationUnit> unit) {
1889 AddCompilationUnits({}, {&unit, 1});
1890}
1891
1892std::unique_ptr<WasmCompilationUnit>
1893CompilationStateImpl::GetNextCompilationUnit(
1894 int task_id, CompileBaselineOnly baseline_only) {
1895 return compilation_unit_queues_.GetNextUnit(task_id, baseline_only);
1896}
1897
1898void CompilationStateImpl::OnFinishedUnit(WasmCode* code) {
1899 OnFinishedUnits({&code, 1});
1900}
1901
1902void CompilationStateImpl::OnFinishedUnits(Vector<WasmCode*> code_vector) {
1903 base::MutexGuard guard(&callbacks_mutex_);
1904
1905 // Assume an order of execution tiers that represents the quality of their
1906 // generated code.
1907 static_assert(ExecutionTier::kNone < ExecutionTier::kInterpreter &&
1908 ExecutionTier::kInterpreter < ExecutionTier::kLiftoff &&
1909 ExecutionTier::kLiftoff < ExecutionTier::kTurbofan,
1910 "Assume an order on execution tiers");
1911
1912 auto module = native_module_->module();
1913 auto enabled_features = native_module_->enabled_features();
1914 for (WasmCode* code : code_vector) {
1915 DCHECK_NOT_NULL(code);
1916 DCHECK_NE(code->tier(), ExecutionTier::kNone);
1917 native_module_->engine()->LogCode(code);
1918
1919 // Skip lazily compiled code as we do not consider this for the completion
1920 // of baseline respectively top tier compilation.
1921 int func_index = code->index();
1922 if (IsLazyCompilation(module, native_module_, enabled_features,
1923 func_index)) {
1924 continue;
1925 }
1926
1927 // Determine whether we are reaching baseline or top tier with the given
1928 // code.
1929 uint32_t slot_index = code->index() - module->num_imported_functions;
1930 ExecutionTierPair requested_tiers = GetRequestedExecutionTiers(
1931 module, compile_mode(), enabled_features, func_index);
1932 DCHECK_EQ(highest_execution_tier_.size(), module->num_declared_functions);
1933 ExecutionTier prior_tier = highest_execution_tier_[slot_index];
1934 bool had_reached_baseline = prior_tier >= requested_tiers.baseline_tier;
1935 bool had_reached_top_tier = prior_tier >= requested_tiers.top_tier;
1936 DCHECK_IMPLIES(had_reached_baseline, prior_tier > ExecutionTier::kNone);
1937 bool reaches_baseline = !had_reached_baseline;
1938 bool reaches_top_tier =
1939 !had_reached_top_tier && code->tier() >= requested_tiers.top_tier;
1940 DCHECK_IMPLIES(reaches_baseline,
1941 code->tier() >= requested_tiers.baseline_tier);
1942 DCHECK_IMPLIES(reaches_top_tier, had_reached_baseline || reaches_baseline);
1943
1944 // Remember compilation state before update.
1945 bool had_completed_baseline_compilation =
1946 outstanding_baseline_functions_ == 0;
1947 bool had_completed_top_tier_compilation =
1948 outstanding_top_tier_functions_ == 0;
1949
1950 // Update compilation state.
1951 if (code->tier() > prior_tier) {
1952 highest_execution_tier_[slot_index] = code->tier();
1953 }
1954 if (reaches_baseline) outstanding_baseline_functions_--;
1955 if (reaches_top_tier) outstanding_top_tier_functions_--;
1956 DCHECK_LE(0, outstanding_baseline_functions_);
1957 DCHECK_LE(outstanding_baseline_functions_, outstanding_top_tier_functions_);
1958
1959 // Conclude if we are completing baseline or top tier compilation.
1960 bool completes_baseline_compilation = !had_completed_baseline_compilation &&
1961 outstanding_baseline_functions_ == 0;
1962 bool completes_top_tier_compilation = !had_completed_top_tier_compilation &&
1963 outstanding_top_tier_functions_ == 0;
1964 DCHECK_IMPLIES(
1965 completes_top_tier_compilation,
1966 had_completed_baseline_compilation || completes_baseline_compilation);
1967
1968 // Trigger callbacks.
1969 if (completes_baseline_compilation) {
1970 for (auto& callback : callbacks_) {
1971 callback(CompilationEvent::kFinishedBaselineCompilation);
1972 }
1973 }
1974 if (completes_top_tier_compilation) {
1975 for (auto& callback : callbacks_) {
1976 callback(CompilationEvent::kFinishedTopTierCompilation);
1977 }
1978 // Clear the callbacks because no more events will be delivered.
1979 callbacks_.clear();
1980 }
1981 }
1982}
1983
1984void CompilationStateImpl::OnBackgroundTaskStopped(
1985 int task_id, const WasmFeatures& detected) {
1986 {
1987 base::MutexGuard guard(&mutex_);
1988 DCHECK_EQ(0, std::count(available_task_ids_.begin(),
1989 available_task_ids_.end(), task_id));
1990 DCHECK_GT(max_background_tasks_, available_task_ids_.size());
1991 available_task_ids_.push_back(task_id);
1992 UnionFeaturesInto(&detected_features_, detected);
1993 }
1994
1995 // The background task could have stopped while we were adding new units, or
1996 // because it reached its deadline. In both cases we need to restart tasks to
1997 // avoid a potential deadlock.
1998 RestartBackgroundTasks();
1999}
2000
2001void CompilationStateImpl::UpdateDetectedFeatures(
2002 const WasmFeatures& detected) {
2003 base::MutexGuard guard(&mutex_);
2004 UnionFeaturesInto(&detected_features_, detected);
2005}
2006
2007void CompilationStateImpl::PublishDetectedFeatures(Isolate* isolate) {
2008 // Notifying the isolate of the feature counts must take place under
2009 // the mutex, because even if we have finished baseline compilation,
2010 // tiering compilations may still occur in the background.
2011 base::MutexGuard guard(&mutex_);
2012 UpdateFeatureUseCounts(isolate, detected_features_);
2013}
2014
2015void CompilationStateImpl::RestartBackgroundTasks() {
2016 // Create new tasks, but only spawn them after releasing the mutex, because
2017 // some platforms (e.g. the predictable platform) might execute tasks right
2018 // away.
2019 std::vector<std::unique_ptr<Task>> new_tasks;
2020 {
2021 base::MutexGuard guard(&mutex_);
2022 // Explicit fast path (quite common): If no more task ids are available
2023 // (i.e. {max_background_tasks_} tasks are already running), spawn nothing.
2024 if (available_task_ids_.empty()) return;
2025 // No need to restart tasks if compilation already failed.
2026 if (failed()) return;
2027
2028 size_t max_num_restart = compilation_unit_queues_.GetTotalSize();
2029
2030 while (!available_task_ids_.empty() && max_num_restart-- > 0) {
2031 int task_id = available_task_ids_.back();
2032 available_task_ids_.pop_back();
2033 new_tasks.emplace_back(
2034 native_module_->engine()
2035 ->NewBackgroundCompileTask<BackgroundCompileTask>(
2036 background_compile_token_, async_counters_, task_id));
2037 }
2038 }
2039
2040 if (baseline_compilation_finished()) {
2041 for (auto& task : new_tasks) {
2042 V8::GetCurrentPlatform()->CallLowPriorityTaskOnWorkerThread(
2043 std::move(task));
2044 }
2045 } else {
2046 for (auto& task : new_tasks) {
2047 V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(task));
2048 }
2049 }
2050}
2051
2052void CompilationStateImpl::SetError() {
2053 bool expected = false;
2054 if (!compile_failed_.compare_exchange_strong(expected, true,
2055 std::memory_order_relaxed)) {
2056 return; // Already failed before.
2057 }
2058
2059 base::MutexGuard callbacks_guard(&callbacks_mutex_);
2060 for (auto& callback : callbacks_) {
2061 callback(CompilationEvent::kFailedCompilation);
2062 }
2063 // No more callbacks after an error.
2064 callbacks_.clear();
2065}
2066
2067void CompileJsToWasmWrappers(Isolate* isolate, const WasmModule* module,
2068 Handle<FixedArray> export_wrappers) {
2069 JSToWasmWrapperCache js_to_wasm_cache;
2070 int wrapper_index = 0;
2071
2072 // TODO(6792): Wrappers below are allocated with {Factory::NewCode}. As an
2073 // optimization we keep the code space unlocked to avoid repeated unlocking
2074 // because many such wrapper are allocated in sequence below.
2075 CodeSpaceMemoryModificationScope modification_scope(isolate->heap());
2076 for (auto exp : module->export_table) {
2077 if (exp.kind != kExternalFunction) continue;
2078 auto& function = module->functions[exp.index];
2079 Handle<Code> wrapper_code = js_to_wasm_cache.GetOrCompileJSToWasmWrapper(
2080 isolate, function.sig, function.imported);
2081 export_wrappers->set(wrapper_index, *wrapper_code);
2082 RecordStats(*wrapper_code, isolate->counters());
2083 ++wrapper_index;
2084 }
2085}
2086
2087Handle<Script> CreateWasmScript(Isolate* isolate,
2088 const ModuleWireBytes& wire_bytes,
2089 const std::string& source_map_url) {
2090 Handle<Script> script =
2091 isolate->factory()->NewScript(isolate->factory()->empty_string());
2092 script->set_context_data(isolate->native_context()->debug_context_id());
2093 script->set_type(Script::TYPE_WASM);
2094
2095 int hash = StringHasher::HashSequentialString(
2096 reinterpret_cast<const char*>(wire_bytes.start()),
2097 static_cast<int>(wire_bytes.length()), kZeroHashSeed);
2098
2099 const int kBufferSize = 32;
2100 char buffer[kBufferSize];
2101
2102 int name_chars = SNPrintF(ArrayVector(buffer), "wasm-%08x", hash);
2103 DCHECK(name_chars >= 0 && name_chars < kBufferSize);
2104 MaybeHandle<String> name_str = isolate->factory()->NewStringFromOneByte(
2105 VectorOf(reinterpret_cast<uint8_t*>(buffer), name_chars),
2106 AllocationType::kOld);
2107 script->set_name(*name_str.ToHandleChecked());
2108
2109 if (source_map_url.size() != 0) {
2110 MaybeHandle<String> src_map_str = isolate->factory()->NewStringFromUtf8(
2111 CStrVector(source_map_url.c_str()), AllocationType::kOld);
2112 script->set_source_mapping_url(*src_map_str.ToHandleChecked());
2113 }
2114 return script;
2115}
2116
2117} // namespace wasm
2118} // namespace internal
2119} // namespace v8
2120
2121#undef TRACE_COMPILE
2122#undef TRACE_STREAMING
2123#undef TRACE_LAZY
2124