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 #include <queue>
9 
10 #include "src/api/api-inl.h"
11 #include "src/asmjs/asm-js.h"
12 #include "src/base/enum-set.h"
13 #include "src/base/optional.h"
14 #include "src/base/platform/mutex.h"
15 #include "src/base/platform/semaphore.h"
16 #include "src/base/platform/time.h"
17 #include "src/base/utils/random-number-generator.h"
18 #include "src/compiler/wasm-compiler.h"
19 #include "src/heap/heap-inl.h"  // For CodeSpaceMemoryModificationScope.
20 #include "src/logging/counters.h"
21 #include "src/logging/metrics.h"
22 #include "src/objects/property-descriptor.h"
23 #include "src/tasks/task-utils.h"
24 #include "src/tracing/trace-event.h"
25 #include "src/trap-handler/trap-handler.h"
26 #include "src/utils/identity-map.h"
27 #include "src/wasm/module-decoder.h"
28 #include "src/wasm/streaming-decoder.h"
29 #include "src/wasm/wasm-code-manager.h"
30 #include "src/wasm/wasm-engine.h"
31 #include "src/wasm/wasm-import-wrapper-cache.h"
32 #include "src/wasm/wasm-js.h"
33 #include "src/wasm/wasm-limits.h"
34 #include "src/wasm/wasm-objects-inl.h"
35 #include "src/wasm/wasm-opcodes.h"
36 #include "src/wasm/wasm-result.h"
37 #include "src/wasm/wasm-serialization.h"
38 
39 #define TRACE_COMPILE(...)                             \
40   do {                                                 \
41     if (FLAG_trace_wasm_compiler) PrintF(__VA_ARGS__); \
42   } while (false)
43 
44 #define TRACE_STREAMING(...)                            \
45   do {                                                  \
46     if (FLAG_trace_wasm_streaming) PrintF(__VA_ARGS__); \
47   } while (false)
48 
49 #define TRACE_LAZY(...)                                        \
50   do {                                                         \
51     if (FLAG_trace_wasm_lazy_compilation) PrintF(__VA_ARGS__); \
52   } while (false)
53 
54 namespace v8 {
55 namespace internal {
56 namespace wasm {
57 
58 namespace {
59 
60 enum class CompileMode : uint8_t { kRegular, kTiering };
61 
62 enum class CompileStrategy : uint8_t {
63   // Compiles functions on first use. In this case, execution will block until
64   // the function's baseline is reached and top tier compilation starts in
65   // background (if applicable).
66   // Lazy compilation can help to reduce startup time and code size at the risk
67   // of blocking execution.
68   kLazy,
69   // Compiles baseline ahead of execution and starts top tier compilation in
70   // background (if applicable).
71   kEager,
72   // Triggers baseline compilation on first use (just like {kLazy}) with the
73   // difference that top tier compilation is started eagerly.
74   // This strategy can help to reduce startup time at the risk of blocking
75   // execution, but only in its early phase (until top tier compilation
76   // finishes).
77   kLazyBaselineEagerTopTier,
78   // Marker for default strategy.
79   kDefault = kEager,
80 };
81 
82 class CompilationStateImpl;
83 
84 class BackgroundCompileScope {
85  public:
BackgroundCompileScope(std::weak_ptr<NativeModule> native_module)86   explicit BackgroundCompileScope(std::weak_ptr<NativeModule> native_module)
87       : native_module_(native_module.lock()) {}
88 
native_module() const89   NativeModule* native_module() const {
90     DCHECK(native_module_);
91     return native_module_.get();
92   }
93   inline CompilationStateImpl* compilation_state() const;
94 
95   bool cancelled() const;
96 
97  private:
98   // Keep the native module alive while in this scope.
99   std::shared_ptr<NativeModule> native_module_;
100 };
101 
102 enum CompileBaselineOnly : bool {
103   kBaselineOnly = true,
104   kBaselineOrTopTier = false
105 };
106 
107 // A set of work-stealing queues (vectors of units). Each background compile
108 // task owns one of the queues and steals from all others once its own queue
109 // runs empty.
110 class CompilationUnitQueues {
111  public:
112   // Public API for QueueImpl.
113   struct Queue {
114     bool ShouldPublish(int num_processed_units) const;
115   };
116 
CompilationUnitQueues(int num_declared_functions)117   explicit CompilationUnitQueues(int num_declared_functions)
118       : num_declared_functions_(num_declared_functions) {
119     // Add one first queue, to add units to.
120     queues_.emplace_back(std::make_unique<QueueImpl>(0));
121 
122     for (auto& atomic_counter : num_units_) {
123       std::atomic_init(&atomic_counter, size_t{0});
124     }
125 
126     top_tier_compiled_ =
127         std::make_unique<std::atomic<bool>[]>(num_declared_functions);
128 
129     for (int i = 0; i < num_declared_functions; i++) {
130       std::atomic_init(&top_tier_compiled_.get()[i], false);
131     }
132   }
133 
GetQueueForTask(int task_id)134   Queue* GetQueueForTask(int task_id) {
135     int required_queues = task_id + 1;
136     {
137       base::SharedMutexGuard<base::kShared> queues_guard(&queues_mutex_);
138       if (V8_LIKELY(static_cast<int>(queues_.size()) >= required_queues)) {
139         return queues_[task_id].get();
140       }
141     }
142 
143     // Otherwise increase the number of queues.
144     base::SharedMutexGuard<base::kExclusive> queues_guard(&queues_mutex_);
145     int num_queues = static_cast<int>(queues_.size());
146     while (num_queues < required_queues) {
147       int steal_from = num_queues + 1;
148       queues_.emplace_back(std::make_unique<QueueImpl>(steal_from));
149       ++num_queues;
150     }
151 
152     // Update the {publish_limit}s of all queues.
153 
154     // We want background threads to publish regularly (to avoid contention when
155     // they are all publishing at the end). On the other side, each publishing
156     // has some overhead (part of it for synchronizing between threads), so it
157     // should not happen *too* often. Thus aim for 4-8 publishes per thread, but
158     // distribute it such that publishing is likely to happen at different
159     // times.
160     int units_per_thread = num_declared_functions_ / num_queues;
161     int min = std::max(10, units_per_thread / 8);
162     int queue_id = 0;
163     for (auto& queue : queues_) {
164       // Set a limit between {min} and {2*min}, but not smaller than {10}.
165       int limit = min + (min * queue_id / num_queues);
166       queue->publish_limit.store(limit, std::memory_order_relaxed);
167       ++queue_id;
168     }
169 
170     return queues_[task_id].get();
171   }
172 
GetNextUnit(Queue * queue,CompileBaselineOnly baseline_only)173   base::Optional<WasmCompilationUnit> GetNextUnit(
174       Queue* queue, CompileBaselineOnly baseline_only) {
175     // As long as any lower-tier units are outstanding we need to steal them
176     // before executing own higher-tier units.
177     int max_tier = baseline_only ? kBaseline : kTopTier;
178     for (int tier = GetLowestTierWithUnits(); tier <= max_tier; ++tier) {
179       if (auto unit = GetNextUnitOfTier(queue, tier)) {
180         size_t old_units_count =
181             num_units_[tier].fetch_sub(1, std::memory_order_relaxed);
182         DCHECK_LE(1, old_units_count);
183         USE(old_units_count);
184         return unit;
185       }
186     }
187     return {};
188   }
189 
AddUnits(Vector<WasmCompilationUnit> baseline_units,Vector<WasmCompilationUnit> top_tier_units,const WasmModule * module)190   void AddUnits(Vector<WasmCompilationUnit> baseline_units,
191                 Vector<WasmCompilationUnit> top_tier_units,
192                 const WasmModule* module) {
193     DCHECK_LT(0, baseline_units.size() + top_tier_units.size());
194     // Add to the individual queues in a round-robin fashion. No special care is
195     // taken to balance them; they will be balanced by work stealing.
196     QueueImpl* queue;
197     {
198       int queue_to_add = next_queue_to_add.load(std::memory_order_relaxed);
199       base::SharedMutexGuard<base::kShared> queues_guard(&queues_mutex_);
200       while (!next_queue_to_add.compare_exchange_weak(
201           queue_to_add, next_task_id(queue_to_add, queues_.size()),
202           std::memory_order_relaxed)) {
203         // Retry with updated {queue_to_add}.
204       }
205       queue = queues_[queue_to_add].get();
206     }
207 
208     base::MutexGuard guard(&queue->mutex);
209     base::Optional<base::MutexGuard> big_units_guard;
210     for (auto pair : {std::make_pair(int{kBaseline}, baseline_units),
211                       std::make_pair(int{kTopTier}, top_tier_units)}) {
212       int tier = pair.first;
213       Vector<WasmCompilationUnit> units = pair.second;
214       if (units.empty()) continue;
215       num_units_[tier].fetch_add(units.size(), std::memory_order_relaxed);
216       for (WasmCompilationUnit unit : units) {
217         size_t func_size = module->functions[unit.func_index()].code.length();
218         if (func_size <= kBigUnitsLimit) {
219           queue->units[tier].push_back(unit);
220         } else {
221           if (!big_units_guard) {
222             big_units_guard.emplace(&big_units_queue_.mutex);
223           }
224           big_units_queue_.has_units[tier].store(true,
225                                                  std::memory_order_relaxed);
226           big_units_queue_.units[tier].emplace(func_size, unit);
227         }
228       }
229     }
230   }
231 
AddTopTierPriorityUnit(WasmCompilationUnit unit,size_t priority)232   void AddTopTierPriorityUnit(WasmCompilationUnit unit, size_t priority) {
233     base::SharedMutexGuard<base::kShared> queues_guard(&queues_mutex_);
234     // Add to the individual queues in a round-robin fashion. No special care is
235     // taken to balance them; they will be balanced by work stealing. We use
236     // the same counter for this reason.
237     int queue_to_add = next_queue_to_add.load(std::memory_order_relaxed);
238     while (!next_queue_to_add.compare_exchange_weak(
239         queue_to_add, next_task_id(queue_to_add, queues_.size()),
240         std::memory_order_relaxed)) {
241       // Retry with updated {queue_to_add}.
242     }
243 
244     {
245       auto* queue = queues_[queue_to_add].get();
246       base::MutexGuard guard(&queue->mutex);
247       queue->top_tier_priority_units.emplace(priority, unit);
248     }
249     num_priority_units_.fetch_add(1, std::memory_order_relaxed);
250     num_units_[kTopTier].fetch_add(1, std::memory_order_relaxed);
251   }
252 
253   // Get the current total number of units in all queues. This is only a
254   // momentary snapshot, it's not guaranteed that {GetNextUnit} returns a unit
255   // if this method returns non-zero.
GetTotalSize() const256   size_t GetTotalSize() const {
257     size_t total = 0;
258     for (auto& atomic_counter : num_units_) {
259       total += atomic_counter.load(std::memory_order_relaxed);
260     }
261     return total;
262   }
263 
264  private:
265   // Store tier in int so we can easily loop over it:
266   static constexpr int kBaseline = 0;
267   static constexpr int kTopTier = 1;
268   static constexpr int kNumTiers = kTopTier + 1;
269 
270   // Functions bigger than {kBigUnitsLimit} will be compiled first, in ascending
271   // order of their function body size.
272   static constexpr size_t kBigUnitsLimit = 4096;
273 
274   struct BigUnit {
BigUnitv8::internal::wasm::__anon1ac07e020111::CompilationUnitQueues::BigUnit275     BigUnit(size_t func_size, WasmCompilationUnit unit)
276         : func_size{func_size}, unit(unit) {}
277 
278     size_t func_size;
279     WasmCompilationUnit unit;
280 
operator <v8::internal::wasm::__anon1ac07e020111::CompilationUnitQueues::BigUnit281     bool operator<(const BigUnit& other) const {
282       return func_size < other.func_size;
283     }
284   };
285 
286   struct TopTierPriorityUnit {
TopTierPriorityUnitv8::internal::wasm::__anon1ac07e020111::CompilationUnitQueues::TopTierPriorityUnit287     TopTierPriorityUnit(int priority, WasmCompilationUnit unit)
288         : priority(priority), unit(unit) {}
289 
290     size_t priority;
291     WasmCompilationUnit unit;
292 
operator <v8::internal::wasm::__anon1ac07e020111::CompilationUnitQueues::TopTierPriorityUnit293     bool operator<(const TopTierPriorityUnit& other) const {
294       return priority < other.priority;
295     }
296   };
297 
298   struct BigUnitsQueue {
BigUnitsQueuev8::internal::wasm::__anon1ac07e020111::CompilationUnitQueues::BigUnitsQueue299     BigUnitsQueue() {
300       for (auto& atomic : has_units) std::atomic_init(&atomic, false);
301     }
302 
303     base::Mutex mutex;
304 
305     // Can be read concurrently to check whether any elements are in the queue.
306     std::atomic<bool> has_units[kNumTiers];
307 
308     // Protected by {mutex}:
309     std::priority_queue<BigUnit> units[kNumTiers];
310   };
311 
312   struct QueueImpl : public Queue {
QueueImplv8::internal::wasm::__anon1ac07e020111::CompilationUnitQueues::QueueImpl313     explicit QueueImpl(int next_steal_task_id)
314         : next_steal_task_id(next_steal_task_id) {}
315 
316     // Number of units after which the task processing this queue should publish
317     // compilation results. Updated (reduced, using relaxed ordering) when new
318     // queues are allocated. If there is only one thread running, we can delay
319     // publishing arbitrarily.
320     std::atomic<int> publish_limit{kMaxInt};
321 
322     base::Mutex mutex;
323 
324     // All fields below are protected by {mutex}.
325     std::vector<WasmCompilationUnit> units[kNumTiers];
326     std::priority_queue<TopTierPriorityUnit> top_tier_priority_units;
327     int next_steal_task_id;
328   };
329 
next_task_id(int task_id,size_t num_queues) const330   int next_task_id(int task_id, size_t num_queues) const {
331     int next = task_id + 1;
332     return next == static_cast<int>(num_queues) ? 0 : next;
333   }
334 
GetLowestTierWithUnits() const335   int GetLowestTierWithUnits() const {
336     for (int tier = 0; tier < kNumTiers; ++tier) {
337       if (num_units_[tier].load(std::memory_order_relaxed) > 0) return tier;
338     }
339     return kNumTiers;
340   }
341 
GetNextUnitOfTier(Queue * public_queue,int tier)342   base::Optional<WasmCompilationUnit> GetNextUnitOfTier(Queue* public_queue,
343                                                         int tier) {
344     QueueImpl* queue = static_cast<QueueImpl*>(public_queue);
345 
346     // First check whether there is a priority unit. Execute that first.
347     if (tier == kTopTier) {
348       if (auto unit = GetTopTierPriorityUnit(queue)) {
349         return unit;
350       }
351     }
352 
353     // Then check whether there is a big unit of that tier.
354     if (auto unit = GetBigUnitOfTier(tier)) return unit;
355 
356     // Finally check whether our own queue has a unit of the wanted tier. If
357     // so, return it, otherwise get the task id to steal from.
358     int steal_task_id;
359     {
360       base::MutexGuard mutex_guard(&queue->mutex);
361       if (!queue->units[tier].empty()) {
362         auto unit = queue->units[tier].back();
363         queue->units[tier].pop_back();
364         return unit;
365       }
366       steal_task_id = queue->next_steal_task_id;
367     }
368 
369     // Try to steal from all other queues. If this succeeds, return one of the
370     // stolen units.
371     {
372       base::SharedMutexGuard<base::kShared> guard(&queues_mutex_);
373       for (size_t steal_trials = 0; steal_trials < queues_.size();
374            ++steal_trials, ++steal_task_id) {
375         if (steal_task_id >= static_cast<int>(queues_.size())) {
376           steal_task_id = 0;
377         }
378         if (auto unit = StealUnitsAndGetFirst(queue, steal_task_id, tier)) {
379           return unit;
380         }
381       }
382     }
383 
384     // If we reach here, we didn't find any unit of the requested tier.
385     return {};
386   }
387 
GetBigUnitOfTier(int tier)388   base::Optional<WasmCompilationUnit> GetBigUnitOfTier(int tier) {
389     // Fast path without locking.
390     if (!big_units_queue_.has_units[tier].load(std::memory_order_relaxed)) {
391       return {};
392     }
393     base::MutexGuard guard(&big_units_queue_.mutex);
394     if (big_units_queue_.units[tier].empty()) return {};
395     WasmCompilationUnit unit = big_units_queue_.units[tier].top().unit;
396     big_units_queue_.units[tier].pop();
397     if (big_units_queue_.units[tier].empty()) {
398       big_units_queue_.has_units[tier].store(false, std::memory_order_relaxed);
399     }
400     return unit;
401   }
402 
GetTopTierPriorityUnit(QueueImpl * queue)403   base::Optional<WasmCompilationUnit> GetTopTierPriorityUnit(QueueImpl* queue) {
404     // Fast path without locking.
405     if (num_priority_units_.load(std::memory_order_relaxed) == 0) {
406       return {};
407     }
408 
409     int steal_task_id;
410     {
411       base::MutexGuard mutex_guard(&queue->mutex);
412       while (!queue->top_tier_priority_units.empty()) {
413         auto unit = queue->top_tier_priority_units.top().unit;
414         queue->top_tier_priority_units.pop();
415         num_priority_units_.fetch_sub(1, std::memory_order_relaxed);
416 
417         if (!top_tier_compiled_[unit.func_index()].exchange(
418                 true, std::memory_order_relaxed)) {
419           return unit;
420         }
421         num_units_[kTopTier].fetch_sub(1, std::memory_order_relaxed);
422       }
423       steal_task_id = queue->next_steal_task_id;
424     }
425 
426     // Try to steal from all other queues. If this succeeds, return one of the
427     // stolen units.
428     {
429       base::SharedMutexGuard<base::kShared> guard(&queues_mutex_);
430       for (size_t steal_trials = 0; steal_trials < queues_.size();
431            ++steal_trials, ++steal_task_id) {
432         if (steal_task_id >= static_cast<int>(queues_.size())) {
433           steal_task_id = 0;
434         }
435         if (auto unit = StealTopTierPriorityUnit(queue, steal_task_id)) {
436           return unit;
437         }
438       }
439     }
440 
441     return {};
442   }
443 
444   // Steal units of {wanted_tier} from {steal_from_task_id} to {queue}. Return
445   // first stolen unit (rest put in queue of {task_id}), or {nullopt} if
446   // {steal_from_task_id} had no units of {wanted_tier}.
447   // Hold a shared lock on {queues_mutex_} when calling this method.
StealUnitsAndGetFirst(QueueImpl * queue,int steal_from_task_id,int wanted_tier)448   base::Optional<WasmCompilationUnit> StealUnitsAndGetFirst(
449       QueueImpl* queue, int steal_from_task_id, int wanted_tier) {
450     auto* steal_queue = queues_[steal_from_task_id].get();
451     // Cannot steal from own queue.
452     if (steal_queue == queue) return {};
453     std::vector<WasmCompilationUnit> stolen;
454     base::Optional<WasmCompilationUnit> returned_unit;
455     {
456       base::MutexGuard guard(&steal_queue->mutex);
457       auto* steal_from_vector = &steal_queue->units[wanted_tier];
458       if (steal_from_vector->empty()) return {};
459       size_t remaining = steal_from_vector->size() / 2;
460       auto steal_begin = steal_from_vector->begin() + remaining;
461       returned_unit = *steal_begin;
462       stolen.assign(steal_begin + 1, steal_from_vector->end());
463       steal_from_vector->erase(steal_begin, steal_from_vector->end());
464     }
465     base::MutexGuard guard(&queue->mutex);
466     auto* target_queue = &queue->units[wanted_tier];
467     target_queue->insert(target_queue->end(), stolen.begin(), stolen.end());
468     queue->next_steal_task_id = steal_from_task_id + 1;
469     return returned_unit;
470   }
471 
472   // Steal one priority unit from {steal_from_task_id} to {task_id}. Return
473   // stolen unit, or {nullopt} if {steal_from_task_id} had no priority units.
474   // Hold a shared lock on {queues_mutex_} when calling this method.
StealTopTierPriorityUnit(QueueImpl * queue,int steal_from_task_id)475   base::Optional<WasmCompilationUnit> StealTopTierPriorityUnit(
476       QueueImpl* queue, int steal_from_task_id) {
477     auto* steal_queue = queues_[steal_from_task_id].get();
478     // Cannot steal from own queue.
479     if (steal_queue == queue) return {};
480     base::Optional<WasmCompilationUnit> returned_unit;
481     {
482       base::MutexGuard guard(&steal_queue->mutex);
483       while (true) {
484         if (steal_queue->top_tier_priority_units.empty()) return {};
485 
486         auto unit = steal_queue->top_tier_priority_units.top().unit;
487         steal_queue->top_tier_priority_units.pop();
488         num_priority_units_.fetch_sub(1, std::memory_order_relaxed);
489 
490         if (!top_tier_compiled_[unit.func_index()].exchange(
491                 true, std::memory_order_relaxed)) {
492           returned_unit = unit;
493           break;
494         }
495         num_units_[kTopTier].fetch_sub(1, std::memory_order_relaxed);
496       }
497     }
498     base::MutexGuard guard(&queue->mutex);
499     queue->next_steal_task_id = steal_from_task_id + 1;
500     return returned_unit;
501   }
502 
503   // {queues_mutex_} protectes {queues_};
504   base::SharedMutex queues_mutex_;
505   std::vector<std::unique_ptr<QueueImpl>> queues_;
506 
507   const int num_declared_functions_;
508 
509   BigUnitsQueue big_units_queue_;
510 
511   std::atomic<size_t> num_units_[kNumTiers];
512   std::atomic<size_t> num_priority_units_{0};
513   std::unique_ptr<std::atomic<bool>[]> top_tier_compiled_;
514   std::atomic<int> next_queue_to_add{0};
515 };
516 
ShouldPublish(int num_processed_units) const517 bool CompilationUnitQueues::Queue::ShouldPublish(
518     int num_processed_units) const {
519   auto* queue = static_cast<const QueueImpl*>(this);
520   return num_processed_units >=
521          queue->publish_limit.load(std::memory_order_relaxed);
522 }
523 
524 // The {CompilationStateImpl} keeps track of the compilation state of the
525 // owning NativeModule, i.e. which functions are left to be compiled.
526 // It contains a task manager to allow parallel and asynchronous background
527 // compilation of functions.
528 // Its public interface {CompilationState} lives in compilation-environment.h.
529 class CompilationStateImpl {
530  public:
531   CompilationStateImpl(const std::shared_ptr<NativeModule>& native_module,
532                        std::shared_ptr<Counters> async_counters);
533 
534   // Cancel all background compilation, without waiting for compile tasks to
535   // finish.
536   void CancelCompilation();
537   bool cancelled() const;
538 
539   // Initialize compilation progress. Set compilation tiers to expect for
540   // baseline and top tier compilation. Must be set before {AddCompilationUnits}
541   // is invoked which triggers background compilation.
542   void InitializeCompilationProgress(bool lazy_module, int num_import_wrappers,
543                                      int num_export_wrappers);
544 
545   // Initialize the compilation progress after deserialization. This is needed
546   // for recompilation (e.g. for tier down) to work later.
547   void InitializeCompilationProgressAfterDeserialization();
548 
549   // Initialize recompilation of the whole module: Setup compilation progress
550   // for recompilation and add the respective compilation units. The callback is
551   // called immediately if no recompilation is needed, or called later
552   // otherwise.
553   void InitializeRecompilation(
554       TieringState new_tiering_state,
555       CompilationState::callback_t recompilation_finished_callback);
556 
557   // Add the callback function to be called on compilation events. Needs to be
558   // set before {AddCompilationUnits} is run to ensure that it receives all
559   // events. The callback object must support being deleted from any thread.
560   void AddCallback(CompilationState::callback_t);
561 
562   // Inserts new functions to compile and kicks off compilation.
563   void AddCompilationUnits(
564       Vector<WasmCompilationUnit> baseline_units,
565       Vector<WasmCompilationUnit> top_tier_units,
566       Vector<std::shared_ptr<JSToWasmWrapperCompilationUnit>>
567           js_to_wasm_wrapper_units);
568   void AddTopTierCompilationUnit(WasmCompilationUnit);
569   void AddTopTierPriorityCompilationUnit(WasmCompilationUnit, size_t);
570 
571   CompilationUnitQueues::Queue* GetQueueForCompileTask(int task_id);
572 
573   base::Optional<WasmCompilationUnit> GetNextCompilationUnit(
574       CompilationUnitQueues::Queue*, CompileBaselineOnly);
575 
576   std::shared_ptr<JSToWasmWrapperCompilationUnit>
577   GetNextJSToWasmWrapperCompilationUnit();
578   void FinalizeJSToWasmWrappers(Isolate* isolate, const WasmModule* module,
579                                 Handle<FixedArray>* export_wrappers_out);
580 
581   void OnFinishedUnits(Vector<WasmCode*>);
582   void OnFinishedJSToWasmWrapperUnits(int num);
583 
584   void OnCompilationStopped(const WasmFeatures& detected);
585   void PublishDetectedFeatures(Isolate*);
586   void SchedulePublishCompilationResults(
587       std::vector<std::unique_ptr<WasmCode>> unpublished_code);
588   // Ensure that a compilation job is running, and increase its concurrency if
589   // needed.
590   void ScheduleCompileJobForNewUnits();
591 
592   size_t NumOutstandingCompilations() const;
593 
594   void SetError();
595 
596   void WaitForCompilationEvent(CompilationEvent event);
597 
SetHighPriority()598   void SetHighPriority() { has_priority_ = true; }
599 
failed() const600   bool failed() const {
601     return compile_failed_.load(std::memory_order_relaxed);
602   }
603 
baseline_compilation_finished() const604   bool baseline_compilation_finished() const {
605     base::MutexGuard guard(&callbacks_mutex_);
606     return outstanding_baseline_units_ == 0 &&
607            outstanding_export_wrappers_ == 0;
608   }
609 
top_tier_compilation_finished() const610   bool top_tier_compilation_finished() const {
611     base::MutexGuard guard(&callbacks_mutex_);
612     return outstanding_top_tier_functions_ == 0;
613   }
614 
recompilation_finished() const615   bool recompilation_finished() const {
616     base::MutexGuard guard(&callbacks_mutex_);
617     return outstanding_recompilation_functions_ == 0;
618   }
619 
compile_mode() const620   CompileMode compile_mode() const { return compile_mode_; }
counters() const621   Counters* counters() const { return async_counters_.get(); }
detected_features()622   WasmFeatures* detected_features() { return &detected_features_; }
623 
SetWireBytesStorage(std::shared_ptr<WireBytesStorage> wire_bytes_storage)624   void SetWireBytesStorage(
625       std::shared_ptr<WireBytesStorage> wire_bytes_storage) {
626     base::MutexGuard guard(&mutex_);
627     wire_bytes_storage_ = wire_bytes_storage;
628   }
629 
GetWireBytesStorage() const630   std::shared_ptr<WireBytesStorage> GetWireBytesStorage() const {
631     base::MutexGuard guard(&mutex_);
632     DCHECK_NOT_NULL(wire_bytes_storage_);
633     return wire_bytes_storage_;
634   }
635 
636  private:
637   // Trigger callbacks according to the internal counters below
638   // (outstanding_...), plus the given events.
639   // Hold the {callbacks_mutex_} when calling this method.
640   void TriggerCallbacks(base::EnumSet<CompilationEvent> additional_events = {});
641 
642   void PublishCompilationResults(
643       std::vector<std::unique_ptr<WasmCode>> unpublished_code);
644   void PublishCode(Vector<std::unique_ptr<WasmCode>> codes);
645 
646   NativeModule* const native_module_;
647   std::weak_ptr<NativeModule> const native_module_weak_;
648   const CompileMode compile_mode_;
649   const std::shared_ptr<Counters> async_counters_;
650 
651   // Compilation error, atomically updated. This flag can be updated and read
652   // using relaxed semantics.
653   std::atomic<bool> compile_failed_{false};
654 
655   // True if compilation was cancelled and worker threads should return. This
656   // flag can be updated and read using relaxed semantics.
657   std::atomic<bool> compile_cancelled_{false};
658 
659   CompilationUnitQueues compilation_unit_queues_;
660 
661   // Index of the next wrapper to compile in {js_to_wasm_wrapper_units_}.
662   std::atomic<int> js_to_wasm_wrapper_id_{0};
663   // Wrapper compilation units are stored in shared_ptrs so that they are kept
664   // alive by the tasks even if the NativeModule dies.
665   std::vector<std::shared_ptr<JSToWasmWrapperCompilationUnit>>
666       js_to_wasm_wrapper_units_;
667 
668   bool has_priority_ = false;
669 
670   // This mutex protects all information of this {CompilationStateImpl} which is
671   // being accessed concurrently.
672   mutable base::Mutex mutex_;
673 
674   //////////////////////////////////////////////////////////////////////////////
675   // Protected by {mutex_}:
676 
677   std::shared_ptr<JobHandle> current_compile_job_;
678 
679   // Features detected to be used in this module. Features can be detected
680   // as a module is being compiled.
681   WasmFeatures detected_features_ = WasmFeatures::None();
682 
683   // Abstraction over the storage of the wire bytes. Held in a shared_ptr so
684   // that background compilation jobs can keep the storage alive while
685   // compiling.
686   std::shared_ptr<WireBytesStorage> wire_bytes_storage_;
687 
688   // End of fields protected by {mutex_}.
689   //////////////////////////////////////////////////////////////////////////////
690 
691   // This mutex protects the callbacks vector, and the counters used to
692   // determine which callbacks to call. The counters plus the callbacks
693   // themselves need to be synchronized to ensure correct order of events.
694   mutable base::Mutex callbacks_mutex_;
695 
696   //////////////////////////////////////////////////////////////////////////////
697   // Protected by {callbacks_mutex_}:
698 
699   // Callback functions to be called on compilation events.
700   std::vector<CompilationState::callback_t> callbacks_;
701 
702   // Events that already happened.
703   base::EnumSet<CompilationEvent> finished_events_;
704 
705   int outstanding_baseline_units_ = 0;
706   int outstanding_export_wrappers_ = 0;
707   int outstanding_top_tier_functions_ = 0;
708   std::vector<uint8_t> compilation_progress_;
709 
710   int outstanding_recompilation_functions_ = 0;
711   TieringState tiering_state_ = kTieredUp;
712 
713   // End of fields protected by {callbacks_mutex_}.
714   //////////////////////////////////////////////////////////////////////////////
715 
716   // {publish_mutex_} protects {publish_queue_} and {publisher_running_}.
717   base::Mutex publish_mutex_;
718   std::vector<std::unique_ptr<WasmCode>> publish_queue_;
719   bool publisher_running_ = false;
720 
721   // Encoding of fields in the {compilation_progress_} vector.
722   using RequiredBaselineTierField = base::BitField8<ExecutionTier, 0, 2>;
723   using RequiredTopTierField = base::BitField8<ExecutionTier, 2, 2>;
724   using ReachedTierField = base::BitField8<ExecutionTier, 4, 2>;
725   using MissingRecompilationField = base::BitField8<bool, 6, 1>;
726 };
727 
Impl(CompilationState * compilation_state)728 CompilationStateImpl* Impl(CompilationState* compilation_state) {
729   return reinterpret_cast<CompilationStateImpl*>(compilation_state);
730 }
Impl(const CompilationState * compilation_state)731 const CompilationStateImpl* Impl(const CompilationState* compilation_state) {
732   return reinterpret_cast<const CompilationStateImpl*>(compilation_state);
733 }
734 
compilation_state() const735 CompilationStateImpl* BackgroundCompileScope::compilation_state() const {
736   DCHECK(native_module_);
737   return Impl(native_module_->compilation_state());
738 }
739 
cancelled() const740 bool BackgroundCompileScope::cancelled() const {
741   return native_module_ == nullptr ||
742          Impl(native_module_->compilation_state())->cancelled();
743 }
744 
UpdateFeatureUseCounts(Isolate * isolate,const WasmFeatures & detected)745 void UpdateFeatureUseCounts(Isolate* isolate, const WasmFeatures& detected) {
746   using Feature = v8::Isolate::UseCounterFeature;
747   constexpr static std::pair<WasmFeature, Feature> kUseCounters[] = {
748       {kFeature_reftypes, Feature::kWasmRefTypes},
749       {kFeature_bulk_memory, Feature::kWasmBulkMemory},
750       {kFeature_mv, Feature::kWasmMultiValue},
751       {kFeature_simd, Feature::kWasmSimdOpcodes},
752       {kFeature_threads, Feature::kWasmThreadOpcodes}};
753 
754   for (auto& feature : kUseCounters) {
755     if (detected.contains(feature.first)) isolate->CountUsage(feature.second);
756   }
757 }
758 
759 }  // namespace
760 
761 // static
762 constexpr uint32_t CompilationEnv::kMaxMemoryPagesAtRuntime;
763 
764 //////////////////////////////////////////////////////
765 // PIMPL implementation of {CompilationState}.
766 
~CompilationState()767 CompilationState::~CompilationState() { Impl(this)->~CompilationStateImpl(); }
768 
CancelCompilation()769 void CompilationState::CancelCompilation() { Impl(this)->CancelCompilation(); }
770 
SetError()771 void CompilationState::SetError() { Impl(this)->SetError(); }
772 
SetWireBytesStorage(std::shared_ptr<WireBytesStorage> wire_bytes_storage)773 void CompilationState::SetWireBytesStorage(
774     std::shared_ptr<WireBytesStorage> wire_bytes_storage) {
775   Impl(this)->SetWireBytesStorage(std::move(wire_bytes_storage));
776 }
777 
GetWireBytesStorage() const778 std::shared_ptr<WireBytesStorage> CompilationState::GetWireBytesStorage()
779     const {
780   return Impl(this)->GetWireBytesStorage();
781 }
782 
AddCallback(CompilationState::callback_t callback)783 void CompilationState::AddCallback(CompilationState::callback_t callback) {
784   return Impl(this)->AddCallback(std::move(callback));
785 }
786 
WaitForTopTierFinished()787 void CompilationState::WaitForTopTierFinished() {
788   // TODO(clemensb): Contribute to compilation while waiting.
789   auto top_tier_finished_semaphore = std::make_shared<base::Semaphore>(0);
790   AddCallback([top_tier_finished_semaphore](CompilationEvent event) {
791     if (event == CompilationEvent::kFailedCompilation ||
792         event == CompilationEvent::kFinishedTopTierCompilation) {
793       top_tier_finished_semaphore->Signal();
794     }
795   });
796   top_tier_finished_semaphore->Wait();
797 }
798 
SetHighPriority()799 void CompilationState::SetHighPriority() { Impl(this)->SetHighPriority(); }
800 
InitializeAfterDeserialization()801 void CompilationState::InitializeAfterDeserialization() {
802   Impl(this)->InitializeCompilationProgressAfterDeserialization();
803 }
804 
failed() const805 bool CompilationState::failed() const { return Impl(this)->failed(); }
806 
baseline_compilation_finished() const807 bool CompilationState::baseline_compilation_finished() const {
808   return Impl(this)->baseline_compilation_finished();
809 }
810 
top_tier_compilation_finished() const811 bool CompilationState::top_tier_compilation_finished() const {
812   return Impl(this)->top_tier_compilation_finished();
813 }
814 
recompilation_finished() const815 bool CompilationState::recompilation_finished() const {
816   return Impl(this)->recompilation_finished();
817 }
818 
819 // static
New(const std::shared_ptr<NativeModule> & native_module,std::shared_ptr<Counters> async_counters)820 std::unique_ptr<CompilationState> CompilationState::New(
821     const std::shared_ptr<NativeModule>& native_module,
822     std::shared_ptr<Counters> async_counters) {
823   return std::unique_ptr<CompilationState>(
824       reinterpret_cast<CompilationState*>(new CompilationStateImpl(
825           std::move(native_module), std::move(async_counters))));
826 }
827 
828 // End of PIMPL implementation of {CompilationState}.
829 //////////////////////////////////////////////////////
830 
831 namespace {
832 
ApplyHintToExecutionTier(WasmCompilationHintTier hint,ExecutionTier default_tier)833 ExecutionTier ApplyHintToExecutionTier(WasmCompilationHintTier hint,
834                                        ExecutionTier default_tier) {
835   switch (hint) {
836     case WasmCompilationHintTier::kDefault:
837       return default_tier;
838     case WasmCompilationHintTier::kBaseline:
839       return ExecutionTier::kLiftoff;
840     case WasmCompilationHintTier::kOptimized:
841       return ExecutionTier::kTurbofan;
842   }
843   UNREACHABLE();
844 }
845 
GetCompilationHint(const WasmModule * module,uint32_t func_index)846 const WasmCompilationHint* GetCompilationHint(const WasmModule* module,
847                                               uint32_t func_index) {
848   DCHECK_LE(module->num_imported_functions, func_index);
849   uint32_t hint_index = declared_function_index(module, func_index);
850   const std::vector<WasmCompilationHint>& compilation_hints =
851       module->compilation_hints;
852   if (hint_index < compilation_hints.size()) {
853     return &compilation_hints[hint_index];
854   }
855   return nullptr;
856 }
857 
GetCompileStrategy(const WasmModule * module,const WasmFeatures & enabled_features,uint32_t func_index,bool lazy_module)858 CompileStrategy GetCompileStrategy(const WasmModule* module,
859                                    const WasmFeatures& enabled_features,
860                                    uint32_t func_index, bool lazy_module) {
861   if (lazy_module) return CompileStrategy::kLazy;
862   if (!enabled_features.has_compilation_hints()) {
863     return CompileStrategy::kDefault;
864   }
865   auto* hint = GetCompilationHint(module, func_index);
866   if (hint == nullptr) return CompileStrategy::kDefault;
867   switch (hint->strategy) {
868     case WasmCompilationHintStrategy::kLazy:
869       return CompileStrategy::kLazy;
870     case WasmCompilationHintStrategy::kEager:
871       return CompileStrategy::kEager;
872     case WasmCompilationHintStrategy::kLazyBaselineEagerTopTier:
873       return CompileStrategy::kLazyBaselineEagerTopTier;
874     case WasmCompilationHintStrategy::kDefault:
875       return CompileStrategy::kDefault;
876   }
877 }
878 
879 struct ExecutionTierPair {
880   ExecutionTier baseline_tier;
881   ExecutionTier top_tier;
882 };
883 
GetRequestedExecutionTiers(const WasmModule * module,CompileMode compile_mode,const WasmFeatures & enabled_features,uint32_t func_index)884 ExecutionTierPair GetRequestedExecutionTiers(
885     const WasmModule* module, CompileMode compile_mode,
886     const WasmFeatures& enabled_features, uint32_t func_index) {
887   ExecutionTierPair result;
888 
889   result.baseline_tier = WasmCompilationUnit::GetBaselineExecutionTier(module);
890   switch (compile_mode) {
891     case CompileMode::kRegular:
892       result.top_tier = result.baseline_tier;
893       return result;
894 
895     case CompileMode::kTiering:
896 
897       // Default tiering behaviour.
898       result.top_tier = ExecutionTier::kTurbofan;
899 
900       // Check if compilation hints override default tiering behaviour.
901       if (enabled_features.has_compilation_hints()) {
902         const WasmCompilationHint* hint =
903             GetCompilationHint(module, func_index);
904         if (hint != nullptr) {
905           result.baseline_tier = ApplyHintToExecutionTier(hint->baseline_tier,
906                                                           result.baseline_tier);
907           result.top_tier =
908               ApplyHintToExecutionTier(hint->top_tier, result.top_tier);
909         }
910       }
911 
912       // Correct top tier if necessary.
913       static_assert(ExecutionTier::kLiftoff < ExecutionTier::kTurbofan,
914                     "Assume an order on execution tiers");
915       if (result.baseline_tier > result.top_tier) {
916         result.top_tier = result.baseline_tier;
917       }
918       return result;
919   }
920   UNREACHABLE();
921 }
922 
923 // The {CompilationUnitBuilder} builds compilation units and stores them in an
924 // internal buffer. The buffer is moved into the working queue of the
925 // {CompilationStateImpl} when {Commit} is called.
926 class CompilationUnitBuilder {
927  public:
CompilationUnitBuilder(NativeModule * native_module)928   explicit CompilationUnitBuilder(NativeModule* native_module)
929       : native_module_(native_module) {}
930 
AddUnits(uint32_t func_index)931   void AddUnits(uint32_t func_index) {
932     if (func_index < native_module_->module()->num_imported_functions) {
933       baseline_units_.emplace_back(func_index, ExecutionTier::kNone,
934                                    kNoDebugging);
935       return;
936     }
937     ExecutionTierPair tiers = GetRequestedExecutionTiers(
938         native_module_->module(), compilation_state()->compile_mode(),
939         native_module_->enabled_features(), func_index);
940     // Compile everything for non-debugging initially. If needed, we will tier
941     // down when the module is fully compiled. Synchronization would be pretty
942     // difficult otherwise.
943     baseline_units_.emplace_back(func_index, tiers.baseline_tier, kNoDebugging);
944     if (tiers.baseline_tier != tiers.top_tier) {
945       tiering_units_.emplace_back(func_index, tiers.top_tier, kNoDebugging);
946     }
947   }
948 
AddJSToWasmWrapperUnit(std::shared_ptr<JSToWasmWrapperCompilationUnit> unit)949   void AddJSToWasmWrapperUnit(
950       std::shared_ptr<JSToWasmWrapperCompilationUnit> unit) {
951     js_to_wasm_wrapper_units_.emplace_back(std::move(unit));
952   }
953 
AddTopTierUnit(int func_index)954   void AddTopTierUnit(int func_index) {
955     ExecutionTierPair tiers = GetRequestedExecutionTiers(
956         native_module_->module(), compilation_state()->compile_mode(),
957         native_module_->enabled_features(), func_index);
958     // In this case, the baseline is lazily compiled, if at all. The compilation
959     // unit is added even if the baseline tier is the same.
960 #ifdef DEBUG
961     auto* module = native_module_->module();
962     DCHECK_EQ(kWasmOrigin, module->origin);
963     const bool lazy_module = false;
964     DCHECK_EQ(CompileStrategy::kLazyBaselineEagerTopTier,
965               GetCompileStrategy(module, native_module_->enabled_features(),
966                                  func_index, lazy_module));
967 #endif
968     tiering_units_.emplace_back(func_index, tiers.top_tier, kNoDebugging);
969   }
970 
AddRecompilationUnit(int func_index,ExecutionTier tier)971   void AddRecompilationUnit(int func_index, ExecutionTier tier) {
972     // For recompilation, just treat all units like baseline units.
973     baseline_units_.emplace_back(
974         func_index, tier,
975         tier == ExecutionTier::kLiftoff ? kForDebugging : kNoDebugging);
976   }
977 
Commit()978   bool Commit() {
979     if (baseline_units_.empty() && tiering_units_.empty() &&
980         js_to_wasm_wrapper_units_.empty()) {
981       return false;
982     }
983     compilation_state()->AddCompilationUnits(
984         VectorOf(baseline_units_), VectorOf(tiering_units_),
985         VectorOf(js_to_wasm_wrapper_units_));
986     Clear();
987     return true;
988   }
989 
Clear()990   void Clear() {
991     baseline_units_.clear();
992     tiering_units_.clear();
993     js_to_wasm_wrapper_units_.clear();
994   }
995 
996  private:
compilation_state() const997   CompilationStateImpl* compilation_state() const {
998     return Impl(native_module_->compilation_state());
999   }
1000 
1001   NativeModule* const native_module_;
1002   std::vector<WasmCompilationUnit> baseline_units_;
1003   std::vector<WasmCompilationUnit> tiering_units_;
1004   std::vector<std::shared_ptr<JSToWasmWrapperCompilationUnit>>
1005       js_to_wasm_wrapper_units_;
1006 };
1007 
SetCompileError(ErrorThrower * thrower,ModuleWireBytes wire_bytes,const WasmFunction * func,const WasmModule * module,WasmError error)1008 void SetCompileError(ErrorThrower* thrower, ModuleWireBytes wire_bytes,
1009                      const WasmFunction* func, const WasmModule* module,
1010                      WasmError error) {
1011   WasmName name = wire_bytes.GetNameOrNull(func, module);
1012   if (name.begin() == nullptr) {
1013     thrower->CompileError("Compiling function #%d failed: %s @+%u",
1014                           func->func_index, error.message().c_str(),
1015                           error.offset());
1016   } else {
1017     TruncatedUserString<> truncated_name(name);
1018     thrower->CompileError("Compiling function #%d:\"%.*s\" failed: %s @+%u",
1019                           func->func_index, truncated_name.length(),
1020                           truncated_name.start(), error.message().c_str(),
1021                           error.offset());
1022   }
1023 }
1024 
ValidateSingleFunction(const WasmModule * module,int func_index,Vector<const uint8_t> code,Counters * counters,AccountingAllocator * allocator,WasmFeatures enabled_features)1025 DecodeResult ValidateSingleFunction(const WasmModule* module, int func_index,
1026                                     Vector<const uint8_t> code,
1027                                     Counters* counters,
1028                                     AccountingAllocator* allocator,
1029                                     WasmFeatures enabled_features) {
1030   const WasmFunction* func = &module->functions[func_index];
1031   FunctionBody body{func->sig, func->code.offset(), code.begin(), code.end()};
1032   DecodeResult result;
1033 
1034   WasmFeatures detected;
1035   return VerifyWasmCode(allocator, enabled_features, module, &detected, body);
1036 }
1037 
1038 enum OnlyLazyFunctions : bool {
1039   kAllFunctions = false,
1040   kOnlyLazyFunctions = true,
1041 };
1042 
ValidateSequentially(const WasmModule * module,NativeModule * native_module,Counters * counters,AccountingAllocator * allocator,ErrorThrower * thrower,bool lazy_module,OnlyLazyFunctions only_lazy_functions=kAllFunctions)1043 void ValidateSequentially(
1044     const WasmModule* module, NativeModule* native_module, Counters* counters,
1045     AccountingAllocator* allocator, ErrorThrower* thrower, bool lazy_module,
1046     OnlyLazyFunctions only_lazy_functions = kAllFunctions) {
1047   DCHECK(!thrower->error());
1048   uint32_t start = module->num_imported_functions;
1049   uint32_t end = start + module->num_declared_functions;
1050   auto enabled_features = native_module->enabled_features();
1051   for (uint32_t func_index = start; func_index < end; func_index++) {
1052     // Skip non-lazy functions if requested.
1053     if (only_lazy_functions) {
1054       CompileStrategy strategy =
1055           GetCompileStrategy(module, enabled_features, func_index, lazy_module);
1056       if (strategy != CompileStrategy::kLazy &&
1057           strategy != CompileStrategy::kLazyBaselineEagerTopTier) {
1058         continue;
1059       }
1060     }
1061 
1062     ModuleWireBytes wire_bytes{native_module->wire_bytes()};
1063     const WasmFunction* func = &module->functions[func_index];
1064     Vector<const uint8_t> code = wire_bytes.GetFunctionBytes(func);
1065     DecodeResult result = ValidateSingleFunction(
1066         module, func_index, code, counters, allocator, enabled_features);
1067     if (result.failed()) {
1068       SetCompileError(thrower, wire_bytes, func, module, result.error());
1069     }
1070   }
1071 }
1072 
IsLazyModule(const WasmModule * module)1073 bool IsLazyModule(const WasmModule* module) {
1074   return FLAG_wasm_lazy_compilation ||
1075          (FLAG_asm_wasm_lazy_compilation && is_asmjs_module(module));
1076 }
1077 
1078 }  // namespace
1079 
CompileLazy(Isolate * isolate,NativeModule * native_module,int func_index)1080 bool CompileLazy(Isolate* isolate, NativeModule* native_module,
1081                  int func_index) {
1082   const WasmModule* module = native_module->module();
1083   auto enabled_features = native_module->enabled_features();
1084   Counters* counters = isolate->counters();
1085 
1086   DCHECK(!native_module->lazy_compile_frozen());
1087   NativeModuleModificationScope native_module_modification_scope(native_module);
1088 
1089   TRACE_LAZY("Compiling wasm-function#%d.\n", func_index);
1090 
1091   CompilationStateImpl* compilation_state =
1092       Impl(native_module->compilation_state());
1093   ExecutionTierPair tiers = GetRequestedExecutionTiers(
1094       module, compilation_state->compile_mode(), enabled_features, func_index);
1095 
1096   DCHECK_LE(native_module->num_imported_functions(), func_index);
1097   DCHECK_LT(func_index, native_module->num_functions());
1098   WasmCompilationUnit baseline_unit{func_index, tiers.baseline_tier,
1099                                     kNoDebugging};
1100   CompilationEnv env = native_module->CreateCompilationEnv();
1101   WasmCompilationResult result = baseline_unit.ExecuteCompilation(
1102       isolate->wasm_engine(), &env, compilation_state->GetWireBytesStorage(),
1103       counters, compilation_state->detected_features());
1104 
1105   // During lazy compilation, we can only get compilation errors when
1106   // {--wasm-lazy-validation} is enabled. Otherwise, the module was fully
1107   // verified before starting its execution.
1108   CHECK_IMPLIES(result.failed(), FLAG_wasm_lazy_validation);
1109   const WasmFunction* func = &module->functions[func_index];
1110   if (result.failed()) {
1111     ErrorThrower thrower(isolate, nullptr);
1112     Vector<const uint8_t> code =
1113         compilation_state->GetWireBytesStorage()->GetCode(func->code);
1114     DecodeResult decode_result = ValidateSingleFunction(
1115         module, func_index, code, counters, isolate->wasm_engine()->allocator(),
1116         enabled_features);
1117     CHECK(decode_result.failed());
1118     SetCompileError(&thrower, ModuleWireBytes(native_module->wire_bytes()),
1119                     func, module, decode_result.error());
1120     return false;
1121   }
1122 
1123   WasmCodeRefScope code_ref_scope;
1124   WasmCode* code = native_module->PublishCode(
1125       native_module->AddCompiledCode(std::move(result)));
1126   DCHECK_EQ(func_index, code->index());
1127 
1128   if (WasmCode::ShouldBeLogged(isolate)) code->LogCode(isolate);
1129 
1130   counters->wasm_lazily_compiled_functions()->Increment();
1131 
1132   const bool lazy_module = IsLazyModule(module);
1133   if (GetCompileStrategy(module, enabled_features, func_index, lazy_module) ==
1134           CompileStrategy::kLazy &&
1135       tiers.baseline_tier < tiers.top_tier) {
1136     WasmCompilationUnit tiering_unit{func_index, tiers.top_tier, kNoDebugging};
1137     compilation_state->AddTopTierCompilationUnit(tiering_unit);
1138   }
1139 
1140   return true;
1141 }
1142 
TriggerTierUp(Isolate * isolate,NativeModule * native_module,int func_index)1143 void TriggerTierUp(Isolate* isolate, NativeModule* native_module,
1144                    int func_index) {
1145   CompilationStateImpl* compilation_state =
1146       Impl(native_module->compilation_state());
1147   WasmCompilationUnit tiering_unit{func_index, ExecutionTier::kTurbofan,
1148                                    kNoDebugging};
1149 
1150   uint32_t* call_array = native_module->num_liftoff_function_calls_array();
1151   int offset =
1152       wasm::declared_function_index(native_module->module(), func_index);
1153 
1154   size_t priority =
1155       base::Relaxed_Load(reinterpret_cast<int*>(&call_array[offset]));
1156   compilation_state->AddTopTierPriorityCompilationUnit(tiering_unit, priority);
1157 }
1158 
1159 namespace {
1160 
RecordStats(const Code code,Counters * counters)1161 void RecordStats(const Code code, Counters* counters) {
1162   counters->wasm_generated_code_size()->Increment(code.raw_body_size());
1163   counters->wasm_reloc_size()->Increment(code.relocation_info().length());
1164 }
1165 
1166 enum CompilationExecutionResult : int8_t { kNoMoreUnits, kYield };
1167 
ExecuteJSToWasmWrapperCompilationUnits(std::weak_ptr<NativeModule> native_module,JobDelegate * delegate)1168 CompilationExecutionResult ExecuteJSToWasmWrapperCompilationUnits(
1169     std::weak_ptr<NativeModule> native_module, JobDelegate* delegate) {
1170   std::shared_ptr<JSToWasmWrapperCompilationUnit> wrapper_unit = nullptr;
1171   int num_processed_wrappers = 0;
1172 
1173   {
1174     BackgroundCompileScope compile_scope(native_module);
1175     if (compile_scope.cancelled()) return kNoMoreUnits;
1176     wrapper_unit = compile_scope.compilation_state()
1177                        ->GetNextJSToWasmWrapperCompilationUnit();
1178     if (!wrapper_unit) return kNoMoreUnits;
1179   }
1180 
1181   TRACE_EVENT0("v8.wasm", "wasm.JSToWasmWrapperCompilation");
1182   while (true) {
1183     wrapper_unit->Execute();
1184     ++num_processed_wrappers;
1185     bool yield = delegate && delegate->ShouldYield();
1186     BackgroundCompileScope compile_scope(native_module);
1187     if (compile_scope.cancelled()) return kNoMoreUnits;
1188     if (yield ||
1189         !(wrapper_unit = compile_scope.compilation_state()
1190                              ->GetNextJSToWasmWrapperCompilationUnit())) {
1191       compile_scope.compilation_state()->OnFinishedJSToWasmWrapperUnits(
1192           num_processed_wrappers);
1193       return yield ? kYield : kNoMoreUnits;
1194     }
1195   }
1196 }
1197 
1198 namespace {
GetCompilationEventName(const WasmCompilationUnit & unit,const CompilationEnv & env)1199 const char* GetCompilationEventName(const WasmCompilationUnit& unit,
1200                                     const CompilationEnv& env) {
1201   ExecutionTier tier = unit.tier();
1202   if (tier == ExecutionTier::kLiftoff) {
1203     return "wasm.BaselineCompilation";
1204   }
1205   if (tier == ExecutionTier::kTurbofan) {
1206     return "wasm.TopTierCompilation";
1207   }
1208   if (unit.func_index() <
1209       static_cast<int>(env.module->num_imported_functions)) {
1210     return "wasm.WasmToJSWrapperCompilation";
1211   }
1212   return "wasm.OtherCompilation";
1213 }
1214 }  // namespace
1215 
1216 // Run by the {BackgroundCompileJob} (on any thread).
ExecuteCompilationUnits(std::weak_ptr<NativeModule> native_module,Counters * counters,JobDelegate * delegate,CompileBaselineOnly baseline_only)1217 CompilationExecutionResult ExecuteCompilationUnits(
1218     std::weak_ptr<NativeModule> native_module, Counters* counters,
1219     JobDelegate* delegate, CompileBaselineOnly baseline_only) {
1220   TRACE_EVENT0("v8.wasm", "wasm.ExecuteCompilationUnits");
1221 
1222   // Execute JS to Wasm wrapper units first, so that they are ready to be
1223   // finalized by the main thread when the kFinishedBaselineCompilation event is
1224   // triggered.
1225   if (ExecuteJSToWasmWrapperCompilationUnits(native_module, delegate) ==
1226       kYield) {
1227     return kYield;
1228   }
1229 
1230   // These fields are initialized in a {BackgroundCompileScope} before
1231   // starting compilation.
1232   base::Optional<CompilationEnv> env;
1233   std::shared_ptr<WireBytesStorage> wire_bytes;
1234   std::shared_ptr<const WasmModule> module;
1235   WasmEngine* wasm_engine;
1236   // Task 0 is any main thread (there might be multiple from multiple isolates),
1237   // worker threads start at 1 (thus the "+ 1").
1238   int task_id = delegate ? (int{delegate->GetTaskId()} + 1) : 0;
1239   DCHECK_LE(0, task_id);
1240   CompilationUnitQueues::Queue* queue;
1241   base::Optional<WasmCompilationUnit> unit;
1242 
1243   WasmFeatures detected_features = WasmFeatures::None();
1244 
1245   // Preparation (synchronized): Initialize the fields above and get the first
1246   // compilation unit.
1247   {
1248     BackgroundCompileScope compile_scope(native_module);
1249     if (compile_scope.cancelled()) return kNoMoreUnits;
1250     auto* compilation_state = compile_scope.compilation_state();
1251     env.emplace(compile_scope.native_module()->CreateCompilationEnv());
1252     wire_bytes = compilation_state->GetWireBytesStorage();
1253     module = compile_scope.native_module()->shared_module();
1254     wasm_engine = compile_scope.native_module()->engine();
1255     queue = compilation_state->GetQueueForCompileTask(task_id);
1256     unit = compilation_state->GetNextCompilationUnit(queue, baseline_only);
1257     if (!unit) return kNoMoreUnits;
1258   }
1259   TRACE_COMPILE("ExecuteCompilationUnits (task id %d)\n", task_id);
1260 
1261   std::vector<WasmCompilationResult> results_to_publish;
1262   while (true) {
1263     ExecutionTier current_tier = unit->tier();
1264     const char* event_name = GetCompilationEventName(unit.value(), env.value());
1265     TRACE_EVENT0("v8.wasm", event_name);
1266     while (unit->tier() == current_tier) {
1267       // (asynchronous): Execute the compilation.
1268       WasmCompilationResult result = unit->ExecuteCompilation(
1269           wasm_engine, &env.value(), wire_bytes, counters, &detected_features);
1270       results_to_publish.emplace_back(std::move(result));
1271 
1272       bool yield = delegate && delegate->ShouldYield();
1273 
1274       // (synchronized): Publish the compilation result and get the next unit.
1275       BackgroundCompileScope compile_scope(native_module);
1276       if (compile_scope.cancelled()) return kNoMoreUnits;
1277 
1278       if (!results_to_publish.back().succeeded()) {
1279         compile_scope.compilation_state()->SetError();
1280         return kNoMoreUnits;
1281       }
1282 
1283       // Yield or get next unit.
1284       if (yield ||
1285           !(unit = compile_scope.compilation_state()->GetNextCompilationUnit(
1286                 queue, baseline_only))) {
1287         std::vector<std::unique_ptr<WasmCode>> unpublished_code =
1288             compile_scope.native_module()->AddCompiledCode(
1289                 VectorOf(std::move(results_to_publish)));
1290         results_to_publish.clear();
1291         compile_scope.compilation_state()->SchedulePublishCompilationResults(
1292             std::move(unpublished_code));
1293         compile_scope.compilation_state()->OnCompilationStopped(
1294             detected_features);
1295         return yield ? kYield : kNoMoreUnits;
1296       }
1297 
1298       // Before executing a TurboFan unit, ensure to publish all previous
1299       // units. If we compiled Liftoff before, we need to publish them anyway
1300       // to ensure fast completion of baseline compilation, if we compiled
1301       // TurboFan before, we publish to reduce peak memory consumption.
1302       // Also publish after finishing a certain amount of units, to avoid
1303       // contention when all threads publish at the end.
1304       if (unit->tier() == ExecutionTier::kTurbofan ||
1305           queue->ShouldPublish(static_cast<int>(results_to_publish.size()))) {
1306         std::vector<std::unique_ptr<WasmCode>> unpublished_code =
1307             compile_scope.native_module()->AddCompiledCode(
1308                 VectorOf(std::move(results_to_publish)));
1309         results_to_publish.clear();
1310         compile_scope.compilation_state()->SchedulePublishCompilationResults(
1311             std::move(unpublished_code));
1312       }
1313     }
1314   }
1315   UNREACHABLE();
1316 }
1317 
1318 using JSToWasmWrapperKey = std::pair<bool, FunctionSig>;
1319 
1320 // Returns the number of units added.
AddExportWrapperUnits(Isolate * isolate,WasmEngine * wasm_engine,NativeModule * native_module,CompilationUnitBuilder * builder,const WasmFeatures & enabled_features)1321 int AddExportWrapperUnits(Isolate* isolate, WasmEngine* wasm_engine,
1322                           NativeModule* native_module,
1323                           CompilationUnitBuilder* builder,
1324                           const WasmFeatures& enabled_features) {
1325   std::unordered_set<JSToWasmWrapperKey, base::hash<JSToWasmWrapperKey>> keys;
1326   for (auto exp : native_module->module()->export_table) {
1327     if (exp.kind != kExternalFunction) continue;
1328     auto& function = native_module->module()->functions[exp.index];
1329     JSToWasmWrapperKey key(function.imported, *function.sig);
1330     if (keys.insert(key).second) {
1331       auto unit = std::make_shared<JSToWasmWrapperCompilationUnit>(
1332           isolate, wasm_engine, function.sig, native_module->module(),
1333           function.imported, enabled_features,
1334           JSToWasmWrapperCompilationUnit::kAllowGeneric);
1335       builder->AddJSToWasmWrapperUnit(std::move(unit));
1336     }
1337   }
1338 
1339   return static_cast<int>(keys.size());
1340 }
1341 
1342 // Returns the number of units added.
AddImportWrapperUnits(NativeModule * native_module,CompilationUnitBuilder * builder)1343 int AddImportWrapperUnits(NativeModule* native_module,
1344                           CompilationUnitBuilder* builder) {
1345   std::unordered_set<WasmImportWrapperCache::CacheKey,
1346                      WasmImportWrapperCache::CacheKeyHash>
1347       keys;
1348   int num_imported_functions = native_module->num_imported_functions();
1349   for (int func_index = 0; func_index < num_imported_functions; func_index++) {
1350     const FunctionSig* sig = native_module->module()->functions[func_index].sig;
1351     if (!IsJSCompatibleSignature(sig, native_module->module(),
1352                                  native_module->enabled_features())) {
1353       continue;
1354     }
1355     WasmImportWrapperCache::CacheKey key(
1356         compiler::kDefaultImportCallKind, sig,
1357         static_cast<int>(sig->parameter_count()));
1358     auto it = keys.insert(key);
1359     if (it.second) {
1360       // Ensure that all keys exist in the cache, so that we can populate the
1361       // cache later without locking.
1362       (*native_module->import_wrapper_cache())[key] = nullptr;
1363       builder->AddUnits(func_index);
1364     }
1365   }
1366   return static_cast<int>(keys.size());
1367 }
1368 
InitializeCompilationUnits(Isolate * isolate,NativeModule * native_module)1369 void InitializeCompilationUnits(Isolate* isolate, NativeModule* native_module) {
1370   CompilationStateImpl* compilation_state =
1371       Impl(native_module->compilation_state());
1372   const bool lazy_module = IsLazyModule(native_module->module());
1373   ModuleWireBytes wire_bytes(native_module->wire_bytes());
1374   CompilationUnitBuilder builder(native_module);
1375   auto* module = native_module->module();
1376   const bool prefer_liftoff = native_module->IsTieredDown();
1377 
1378   uint32_t start = module->num_imported_functions;
1379   uint32_t end = start + module->num_declared_functions;
1380   for (uint32_t func_index = start; func_index < end; func_index++) {
1381     if (prefer_liftoff) {
1382       builder.AddRecompilationUnit(func_index, ExecutionTier::kLiftoff);
1383       continue;
1384     }
1385     CompileStrategy strategy = GetCompileStrategy(
1386         module, native_module->enabled_features(), func_index, lazy_module);
1387     if (strategy == CompileStrategy::kLazy) {
1388       native_module->UseLazyStub(func_index);
1389     } else if (strategy == CompileStrategy::kLazyBaselineEagerTopTier) {
1390       builder.AddTopTierUnit(func_index);
1391       native_module->UseLazyStub(func_index);
1392     } else {
1393       DCHECK_EQ(strategy, CompileStrategy::kEager);
1394       builder.AddUnits(func_index);
1395     }
1396   }
1397   int num_import_wrappers = AddImportWrapperUnits(native_module, &builder);
1398   int num_export_wrappers =
1399       AddExportWrapperUnits(isolate, isolate->wasm_engine(), native_module,
1400                             &builder, WasmFeatures::FromIsolate(isolate));
1401   compilation_state->InitializeCompilationProgress(
1402       lazy_module, num_import_wrappers, num_export_wrappers);
1403   builder.Commit();
1404 }
1405 
MayCompriseLazyFunctions(const WasmModule * module,const WasmFeatures & enabled_features,bool lazy_module)1406 bool MayCompriseLazyFunctions(const WasmModule* module,
1407                               const WasmFeatures& enabled_features,
1408                               bool lazy_module) {
1409   if (lazy_module || enabled_features.has_compilation_hints()) return true;
1410 #ifdef ENABLE_SLOW_DCHECKS
1411   int start = module->num_imported_functions;
1412   int end = start + module->num_declared_functions;
1413   for (int func_index = start; func_index < end; func_index++) {
1414     SLOW_DCHECK(GetCompileStrategy(module, enabled_features, func_index,
1415                                    lazy_module) != CompileStrategy::kLazy);
1416   }
1417 #endif
1418   return false;
1419 }
1420 
1421 class CompilationTimeCallback {
1422  public:
1423   enum CompileMode { kSynchronous, kAsync, kStreaming };
CompilationTimeCallback(std::shared_ptr<Counters> async_counters,std::shared_ptr<metrics::Recorder> metrics_recorder,v8::metrics::Recorder::ContextId context_id,std::weak_ptr<NativeModule> native_module,CompileMode compile_mode)1424   explicit CompilationTimeCallback(
1425       std::shared_ptr<Counters> async_counters,
1426       std::shared_ptr<metrics::Recorder> metrics_recorder,
1427       v8::metrics::Recorder::ContextId context_id,
1428       std::weak_ptr<NativeModule> native_module, CompileMode compile_mode)
1429       : start_time_(base::TimeTicks::Now()),
1430         async_counters_(std::move(async_counters)),
1431         metrics_recorder_(std::move(metrics_recorder)),
1432         context_id_(context_id),
1433         native_module_(std::move(native_module)),
1434         compile_mode_(compile_mode) {}
1435 
operator ()(CompilationEvent event)1436   void operator()(CompilationEvent event) {
1437     DCHECK(base::TimeTicks::IsHighResolution());
1438     std::shared_ptr<NativeModule> native_module = native_module_.lock();
1439     if (!native_module) return;
1440     auto now = base::TimeTicks::Now();
1441     auto duration = now - start_time_;
1442     if (event == CompilationEvent::kFinishedBaselineCompilation) {
1443       // Reset {start_time_} to measure tier-up time.
1444       start_time_ = now;
1445       if (compile_mode_ != kSynchronous) {
1446         TimedHistogram* histogram =
1447             compile_mode_ == kAsync
1448                 ? async_counters_->wasm_async_compile_wasm_module_time()
1449                 : async_counters_->wasm_streaming_compile_wasm_module_time();
1450         histogram->AddSample(static_cast<int>(duration.InMicroseconds()));
1451       }
1452 
1453       // TODO(sartang@microsoft.com): Remove wall_clock_time_in_us field
1454       v8::metrics::WasmModuleCompiled event{
1455           (compile_mode_ != kSynchronous),         // async
1456           (compile_mode_ == kStreaming),           // streamed
1457           false,                                   // cached
1458           false,                                   // deserialized
1459           FLAG_wasm_lazy_compilation,              // lazy
1460           true,                                    // success
1461           native_module->liftoff_code_size(),      // code_size_in_bytes
1462           native_module->liftoff_bailout_count(),  // liftoff_bailout_count
1463           duration.InMicroseconds(),               // wall_clock_time_in_us
1464           duration.InMicroseconds()                // wall_clock_duration_in_us
1465       };
1466       metrics_recorder_->DelayMainThreadEvent(event, context_id_);
1467     }
1468     if (event == CompilationEvent::kFinishedTopTierCompilation) {
1469       TimedHistogram* histogram = async_counters_->wasm_tier_up_module_time();
1470       histogram->AddSample(static_cast<int>(duration.InMicroseconds()));
1471 
1472       v8::metrics::WasmModuleTieredUp event{
1473           FLAG_wasm_lazy_compilation,           // lazy
1474           native_module->turbofan_code_size(),  // code_size_in_bytes
1475           duration.InMicroseconds(),            // wall_clock_time_in_us
1476           duration.InMicroseconds()             // wall_clock_duration_in_us
1477       };
1478       metrics_recorder_->DelayMainThreadEvent(event, context_id_);
1479     }
1480     if (event == CompilationEvent::kFailedCompilation) {
1481       v8::metrics::WasmModuleCompiled event{
1482           (compile_mode_ != kSynchronous),         // async
1483           (compile_mode_ == kStreaming),           // streamed
1484           false,                                   // cached
1485           false,                                   // deserialized
1486           FLAG_wasm_lazy_compilation,              // lazy
1487           false,                                   // success
1488           native_module->liftoff_code_size(),      // code_size_in_bytes
1489           native_module->liftoff_bailout_count(),  // liftoff_bailout_count
1490           duration.InMicroseconds(),               // wall_clock_time_in_us
1491           duration.InMicroseconds()                // wall_clock_duration_in_us
1492       };
1493       metrics_recorder_->DelayMainThreadEvent(event, context_id_);
1494     }
1495   }
1496 
1497  private:
1498   base::TimeTicks start_time_;
1499   const std::shared_ptr<Counters> async_counters_;
1500   std::shared_ptr<metrics::Recorder> metrics_recorder_;
1501   v8::metrics::Recorder::ContextId context_id_;
1502   std::weak_ptr<NativeModule> native_module_;
1503   const CompileMode compile_mode_;
1504 };
1505 
CompileNativeModule(Isolate * isolate,v8::metrics::Recorder::ContextId context_id,ErrorThrower * thrower,const WasmModule * wasm_module,std::shared_ptr<NativeModule> native_module,Handle<FixedArray> * export_wrappers_out)1506 void CompileNativeModule(Isolate* isolate,
1507                          v8::metrics::Recorder::ContextId context_id,
1508                          ErrorThrower* thrower, const WasmModule* wasm_module,
1509                          std::shared_ptr<NativeModule> native_module,
1510                          Handle<FixedArray>* export_wrappers_out) {
1511   CHECK(!FLAG_jitless);
1512   ModuleWireBytes wire_bytes(native_module->wire_bytes());
1513   const bool lazy_module = IsLazyModule(wasm_module);
1514   if (!FLAG_wasm_lazy_validation && wasm_module->origin == kWasmOrigin &&
1515       MayCompriseLazyFunctions(wasm_module, native_module->enabled_features(),
1516                                lazy_module)) {
1517     // Validate wasm modules for lazy compilation if requested. Never validate
1518     // asm.js modules as these are valid by construction (additionally a CHECK
1519     // will catch this during lazy compilation).
1520     ValidateSequentially(wasm_module, native_module.get(), isolate->counters(),
1521                          isolate->allocator(), thrower, lazy_module,
1522                          kOnlyLazyFunctions);
1523     // On error: Return and leave the module in an unexecutable state.
1524     if (thrower->error()) return;
1525   }
1526 
1527   DCHECK_GE(kMaxInt, native_module->module()->num_declared_functions);
1528 
1529   // The callback captures a shared ptr to the semaphore.
1530   auto* compilation_state = Impl(native_module->compilation_state());
1531   if (base::TimeTicks::IsHighResolution()) {
1532     compilation_state->AddCallback(CompilationTimeCallback{
1533         isolate->async_counters(), isolate->metrics_recorder(), context_id,
1534         native_module, CompilationTimeCallback::kSynchronous});
1535   }
1536 
1537   // Initialize the compilation units and kick off background compile tasks.
1538   InitializeCompilationUnits(isolate, native_module.get());
1539 
1540   compilation_state->WaitForCompilationEvent(
1541       CompilationEvent::kFinishedExportWrappers);
1542 
1543   if (compilation_state->failed()) {
1544     DCHECK_IMPLIES(lazy_module, !FLAG_wasm_lazy_validation);
1545     ValidateSequentially(wasm_module, native_module.get(), isolate->counters(),
1546                          isolate->allocator(), thrower, lazy_module);
1547     CHECK(thrower->error());
1548     return;
1549   }
1550 
1551   if (!FLAG_predictable) {
1552     // For predictable mode, do not finalize wrappers yet to make sure we catch
1553     // validation errors first.
1554     compilation_state->FinalizeJSToWasmWrappers(
1555         isolate, native_module->module(), export_wrappers_out);
1556   }
1557 
1558   compilation_state->WaitForCompilationEvent(
1559       CompilationEvent::kFinishedBaselineCompilation);
1560 
1561   compilation_state->PublishDetectedFeatures(isolate);
1562 
1563   if (compilation_state->failed()) {
1564     DCHECK_IMPLIES(lazy_module, !FLAG_wasm_lazy_validation);
1565     ValidateSequentially(wasm_module, native_module.get(), isolate->counters(),
1566                          isolate->allocator(), thrower, lazy_module);
1567     CHECK(thrower->error());
1568   } else if (FLAG_predictable) {
1569     compilation_state->FinalizeJSToWasmWrappers(
1570         isolate, native_module->module(), export_wrappers_out);
1571   }
1572 }
1573 
1574 class BackgroundCompileJob final : public JobTask {
1575  public:
BackgroundCompileJob(std::weak_ptr<NativeModule> native_module,std::shared_ptr<Counters> async_counters)1576   explicit BackgroundCompileJob(std::weak_ptr<NativeModule> native_module,
1577                                 std::shared_ptr<Counters> async_counters)
1578       : native_module_(std::move(native_module)),
1579         async_counters_(std::move(async_counters)) {}
1580 
Run(JobDelegate * delegate)1581   void Run(JobDelegate* delegate) override {
1582     ExecuteCompilationUnits(native_module_, async_counters_.get(), delegate,
1583                             kBaselineOrTopTier);
1584   }
1585 
GetMaxConcurrency(size_t worker_count) const1586   size_t GetMaxConcurrency(size_t worker_count) const override {
1587     BackgroundCompileScope scope(native_module_);
1588     if (scope.cancelled()) return 0;
1589     // NumOutstandingCompilations() does not reflect the units that running
1590     // workers are processing, thus add the current worker count to that number.
1591     size_t flag_limit =
1592         static_cast<size_t>(std::max(1, FLAG_wasm_num_compilation_tasks));
1593     return std::min(
1594         flag_limit,
1595         worker_count + scope.compilation_state()->NumOutstandingCompilations());
1596   }
1597 
1598  private:
1599   const std::weak_ptr<NativeModule> native_module_;
1600   const std::shared_ptr<Counters> async_counters_;
1601 };
1602 
1603 }  // namespace
1604 
CompileToNativeModule(Isolate * isolate,const WasmFeatures & enabled,ErrorThrower * thrower,std::shared_ptr<const WasmModule> module,const ModuleWireBytes & wire_bytes,Handle<FixedArray> * export_wrappers_out)1605 std::shared_ptr<NativeModule> CompileToNativeModule(
1606     Isolate* isolate, const WasmFeatures& enabled, ErrorThrower* thrower,
1607     std::shared_ptr<const WasmModule> module, const ModuleWireBytes& wire_bytes,
1608     Handle<FixedArray>* export_wrappers_out) {
1609   const WasmModule* wasm_module = module.get();
1610   OwnedVector<uint8_t> wire_bytes_copy =
1611       OwnedVector<uint8_t>::Of(wire_bytes.module_bytes());
1612   // Prefer {wire_bytes_copy} to {wire_bytes.module_bytes()} for the temporary
1613   // cache key. When we eventually install the module in the cache, the wire
1614   // bytes of the temporary key and the new key have the same base pointer and
1615   // we can skip the full bytes comparison.
1616   std::shared_ptr<NativeModule> native_module =
1617       isolate->wasm_engine()->MaybeGetNativeModule(
1618           wasm_module->origin, wire_bytes_copy.as_vector(), isolate);
1619   if (native_module) {
1620     // TODO(thibaudm): Look into sharing export wrappers.
1621     CompileJsToWasmWrappers(isolate, wasm_module, export_wrappers_out);
1622     return native_module;
1623   }
1624 
1625   TimedHistogramScope wasm_compile_module_time_scope(SELECT_WASM_COUNTER(
1626       isolate->counters(), wasm_module->origin, wasm_compile, module_time));
1627 
1628   // Embedder usage count for declared shared memories.
1629   if (wasm_module->has_shared_memory) {
1630     isolate->CountUsage(v8::Isolate::UseCounterFeature::kWasmSharedMemory);
1631   }
1632 
1633   // Create a new {NativeModule} first.
1634   const bool uses_liftoff = module->origin == kWasmOrigin && FLAG_liftoff;
1635   size_t code_size_estimate =
1636       wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module.get(),
1637                                                           uses_liftoff);
1638   native_module = isolate->wasm_engine()->NewNativeModule(
1639       isolate, enabled, module, code_size_estimate);
1640   native_module->SetWireBytes(std::move(wire_bytes_copy));
1641   // Sync compilation is user blocking, so we increase the priority.
1642   native_module->compilation_state()->SetHighPriority();
1643 
1644   v8::metrics::Recorder::ContextId context_id =
1645       isolate->GetOrRegisterRecorderContextId(isolate->native_context());
1646   CompileNativeModule(isolate, context_id, thrower, wasm_module, native_module,
1647                       export_wrappers_out);
1648   bool cache_hit = !isolate->wasm_engine()->UpdateNativeModuleCache(
1649       thrower->error(), &native_module, isolate);
1650   if (thrower->error()) return {};
1651 
1652   if (cache_hit) {
1653     CompileJsToWasmWrappers(isolate, wasm_module, export_wrappers_out);
1654     return native_module;
1655   }
1656 
1657   // Ensure that the code objects are logged before returning.
1658   isolate->wasm_engine()->LogOutstandingCodesForIsolate(isolate);
1659 
1660   return native_module;
1661 }
1662 
RecompileNativeModule(NativeModule * native_module,TieringState tiering_state)1663 void RecompileNativeModule(NativeModule* native_module,
1664                            TieringState tiering_state) {
1665   // Install a callback to notify us once background recompilation finished.
1666   auto recompilation_finished_semaphore = std::make_shared<base::Semaphore>(0);
1667   auto* compilation_state = Impl(native_module->compilation_state());
1668 
1669   // The callback captures a shared ptr to the semaphore.
1670   // Initialize the compilation units and kick off background compile tasks.
1671   compilation_state->InitializeRecompilation(
1672       tiering_state,
1673       [recompilation_finished_semaphore](CompilationEvent event) {
1674         if (event == CompilationEvent::kFinishedRecompilation) {
1675           recompilation_finished_semaphore->Signal();
1676         }
1677       });
1678 
1679   // Now wait until all compilation units finished.
1680   // TODO(clemensb): Contribute to compilation while waiting.
1681   recompilation_finished_semaphore->Wait();
1682   DCHECK(!compilation_state->failed());
1683 }
1684 
AsyncCompileJob(Isolate * isolate,const WasmFeatures & enabled,std::unique_ptr<byte[]> bytes_copy,size_t length,Handle<Context> context,Handle<Context> incumbent_context,const char * api_method_name,std::shared_ptr<CompilationResultResolver> resolver)1685 AsyncCompileJob::AsyncCompileJob(
1686     Isolate* isolate, const WasmFeatures& enabled,
1687     std::unique_ptr<byte[]> bytes_copy, size_t length, Handle<Context> context,
1688     Handle<Context> incumbent_context, const char* api_method_name,
1689     std::shared_ptr<CompilationResultResolver> resolver)
1690     : isolate_(isolate),
1691       api_method_name_(api_method_name),
1692       enabled_features_(enabled),
1693       wasm_lazy_compilation_(FLAG_wasm_lazy_compilation),
1694       start_time_(base::TimeTicks::Now()),
1695       bytes_copy_(std::move(bytes_copy)),
1696       wire_bytes_(bytes_copy_.get(), bytes_copy_.get() + length),
1697       resolver_(std::move(resolver)) {
1698   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
1699                "wasm.AsyncCompileJob");
1700   CHECK(FLAG_wasm_async_compilation);
1701   CHECK(!FLAG_jitless);
1702   v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
1703   v8::Platform* platform = V8::GetCurrentPlatform();
1704   foreground_task_runner_ = platform->GetForegroundTaskRunner(v8_isolate);
1705   native_context_ =
1706       isolate->global_handles()->Create(context->native_context());
1707   incumbent_context_ = isolate->global_handles()->Create(*incumbent_context);
1708   DCHECK(native_context_->IsNativeContext());
1709   context_id_ = isolate->GetOrRegisterRecorderContextId(native_context_);
1710   metrics_event_.async = true;
1711 }
1712 
Start()1713 void AsyncCompileJob::Start() {
1714   DoAsync<DecodeModule>(isolate_->counters(),
1715                         isolate_->metrics_recorder());  // --
1716 }
1717 
Abort()1718 void AsyncCompileJob::Abort() {
1719   // Removing this job will trigger the destructor, which will cancel all
1720   // compilation.
1721   isolate_->wasm_engine()->RemoveCompileJob(this);
1722 }
1723 
1724 class AsyncStreamingProcessor final : public StreamingProcessor {
1725  public:
1726   explicit AsyncStreamingProcessor(AsyncCompileJob* job,
1727                                    std::shared_ptr<Counters> counters,
1728                                    AccountingAllocator* allocator);
1729 
1730   ~AsyncStreamingProcessor() override;
1731 
1732   bool ProcessModuleHeader(Vector<const uint8_t> bytes,
1733                            uint32_t offset) override;
1734 
1735   bool ProcessSection(SectionCode section_code, Vector<const uint8_t> bytes,
1736                       uint32_t offset) override;
1737 
1738   bool ProcessCodeSectionHeader(int num_functions, uint32_t offset,
1739                                 std::shared_ptr<WireBytesStorage>,
1740                                 int code_section_length) override;
1741 
1742   bool ProcessFunctionBody(Vector<const uint8_t> bytes,
1743                            uint32_t offset) override;
1744 
1745   void OnFinishedChunk() override;
1746 
1747   void OnFinishedStream(OwnedVector<uint8_t> bytes) override;
1748 
1749   void OnError(const WasmError&) override;
1750 
1751   void OnAbort() override;
1752 
1753   bool Deserialize(Vector<const uint8_t> wire_bytes,
1754                    Vector<const uint8_t> module_bytes) override;
1755 
1756  private:
1757   // Finishes the AsyncCompileJob with an error.
1758   void FinishAsyncCompileJobWithError(const WasmError&);
1759 
1760   void CommitCompilationUnits();
1761 
1762   ModuleDecoder decoder_;
1763   AsyncCompileJob* job_;
1764   WasmEngine* wasm_engine_;
1765   std::unique_ptr<CompilationUnitBuilder> compilation_unit_builder_;
1766   int num_functions_ = 0;
1767   bool prefix_cache_hit_ = false;
1768   bool before_code_section_ = true;
1769   std::shared_ptr<Counters> async_counters_;
1770   AccountingAllocator* allocator_;
1771 
1772   // Running hash of the wire bytes up to code section size, but excluding the
1773   // code section itself. Used by the {NativeModuleCache} to detect potential
1774   // duplicate modules.
1775   size_t prefix_hash_;
1776 };
1777 
CreateStreamingDecoder()1778 std::shared_ptr<StreamingDecoder> AsyncCompileJob::CreateStreamingDecoder() {
1779   DCHECK_NULL(stream_);
1780   stream_ = StreamingDecoder::CreateAsyncStreamingDecoder(
1781       std::make_unique<AsyncStreamingProcessor>(
1782           this, isolate_->async_counters(), isolate_->allocator()));
1783   return stream_;
1784 }
1785 
~AsyncCompileJob()1786 AsyncCompileJob::~AsyncCompileJob() {
1787   // Note: This destructor always runs on the foreground thread of the isolate.
1788   background_task_manager_.CancelAndWait();
1789   // If the runtime objects were not created yet, then initial compilation did
1790   // not finish yet. In this case we can abort compilation.
1791   if (native_module_ && module_object_.is_null()) {
1792     Impl(native_module_->compilation_state())->CancelCompilation();
1793   }
1794   // Tell the streaming decoder that the AsyncCompileJob is not available
1795   // anymore.
1796   // TODO(ahaas): Is this notification really necessary? Check
1797   // https://crbug.com/888170.
1798   if (stream_) stream_->NotifyCompilationEnded();
1799   CancelPendingForegroundTask();
1800   isolate_->global_handles()->Destroy(native_context_.location());
1801   isolate_->global_handles()->Destroy(incumbent_context_.location());
1802   if (!module_object_.is_null()) {
1803     isolate_->global_handles()->Destroy(module_object_.location());
1804   }
1805 }
1806 
CreateNativeModule(std::shared_ptr<const WasmModule> module,size_t code_size_estimate)1807 void AsyncCompileJob::CreateNativeModule(
1808     std::shared_ptr<const WasmModule> module, size_t code_size_estimate) {
1809   // Embedder usage count for declared shared memories.
1810   if (module->has_shared_memory) {
1811     isolate_->CountUsage(v8::Isolate::UseCounterFeature::kWasmSharedMemory);
1812   }
1813 
1814   // TODO(wasm): Improve efficiency of storing module wire bytes. Only store
1815   // relevant sections, not function bodies
1816 
1817   // Create the module object and populate with compiled functions and
1818   // information needed at instantiation time.
1819 
1820   native_module_ = isolate_->wasm_engine()->NewNativeModule(
1821       isolate_, enabled_features_, std::move(module), code_size_estimate);
1822   native_module_->SetWireBytes({std::move(bytes_copy_), wire_bytes_.length()});
1823 }
1824 
GetOrCreateNativeModule(std::shared_ptr<const WasmModule> module,size_t code_size_estimate)1825 bool AsyncCompileJob::GetOrCreateNativeModule(
1826     std::shared_ptr<const WasmModule> module, size_t code_size_estimate) {
1827   native_module_ = isolate_->wasm_engine()->MaybeGetNativeModule(
1828       module->origin, wire_bytes_.module_bytes(), isolate_);
1829   if (native_module_ == nullptr) {
1830     CreateNativeModule(std::move(module), code_size_estimate);
1831     return false;
1832   }
1833   return true;
1834 }
1835 
PrepareRuntimeObjects()1836 void AsyncCompileJob::PrepareRuntimeObjects() {
1837   // Create heap objects for script and module bytes to be stored in the
1838   // module object. Asm.js is not compiled asynchronously.
1839   DCHECK(module_object_.is_null());
1840   auto source_url = stream_ ? stream_->url() : Vector<const char>();
1841   auto script = isolate_->wasm_engine()->GetOrCreateScript(
1842       isolate_, native_module_, source_url);
1843   Handle<WasmModuleObject> module_object =
1844       WasmModuleObject::New(isolate_, native_module_, script);
1845 
1846   module_object_ = isolate_->global_handles()->Create(*module_object);
1847 }
1848 
1849 // This function assumes that it is executed in a HandleScope, and that a
1850 // context is set on the isolate.
FinishCompile(bool is_after_cache_hit)1851 void AsyncCompileJob::FinishCompile(bool is_after_cache_hit) {
1852   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
1853                "wasm.FinishAsyncCompile");
1854   bool is_after_deserialization = !module_object_.is_null();
1855   auto compilation_state = Impl(native_module_->compilation_state());
1856   if (!is_after_deserialization) {
1857     if (stream_) {
1858       stream_->NotifyNativeModuleCreated(native_module_);
1859     }
1860     PrepareRuntimeObjects();
1861   }
1862 
1863   // Measure duration of baseline compilation or deserialization from cache.
1864   if (base::TimeTicks::IsHighResolution()) {
1865     base::TimeDelta duration = base::TimeTicks::Now() - start_time_;
1866     int duration_usecs = static_cast<int>(duration.InMicroseconds());
1867     isolate_->counters()->wasm_streaming_finish_wasm_module_time()->AddSample(
1868         duration_usecs);
1869 
1870     if (is_after_cache_hit || is_after_deserialization) {
1871       v8::metrics::WasmModuleCompiled event{
1872           true,                                     // async
1873           true,                                     // streamed
1874           is_after_cache_hit,                       // cached
1875           is_after_deserialization,                 // deserialized
1876           wasm_lazy_compilation_,                   // lazy
1877           !compilation_state->failed(),             // success
1878           native_module_->liftoff_code_size(),      // code_size_in_bytes
1879           native_module_->liftoff_bailout_count(),  // liftoff_bailout_count
1880           duration.InMicroseconds(),                // wall_clock_time_in_us
1881           duration.InMicroseconds()                 // wall_clock_duration_in_us
1882       };
1883       isolate_->metrics_recorder()->DelayMainThreadEvent(event, context_id_);
1884     }
1885   }
1886 
1887   DCHECK(!isolate_->context().is_null());
1888   // Finish the wasm script now and make it public to the debugger.
1889   Handle<Script> script(module_object_->script(), isolate_);
1890   const WasmModule* module = module_object_->module();
1891   if (script->type() == Script::TYPE_WASM &&
1892       module->debug_symbols.type == WasmDebugSymbols::Type::SourceMap &&
1893       !module->debug_symbols.external_url.is_empty()) {
1894     ModuleWireBytes wire_bytes(module_object_->native_module()->wire_bytes());
1895     MaybeHandle<String> src_map_str = isolate_->factory()->NewStringFromUtf8(
1896         wire_bytes.GetNameOrNull(module->debug_symbols.external_url),
1897         AllocationType::kOld);
1898     script->set_source_mapping_url(*src_map_str.ToHandleChecked());
1899   }
1900   {
1901     TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
1902                  "wasm.Debug.OnAfterCompile");
1903     isolate_->debug()->OnAfterCompile(script);
1904   }
1905 
1906   // TODO(bbudge) Allow deserialization without wrapper compilation, so we can
1907   // just compile wrappers here.
1908   if (!is_after_deserialization) {
1909     Handle<FixedArray> export_wrappers;
1910     if (is_after_cache_hit) {
1911       // TODO(thibaudm): Look into sharing wrappers.
1912       CompileJsToWasmWrappers(isolate_, module, &export_wrappers);
1913     } else {
1914       compilation_state->FinalizeJSToWasmWrappers(isolate_, module,
1915                                                   &export_wrappers);
1916     }
1917     module_object_->set_export_wrappers(*export_wrappers);
1918   }
1919   // We can only update the feature counts once the entire compile is done.
1920   compilation_state->PublishDetectedFeatures(isolate_);
1921 
1922   FinishModule();
1923 }
1924 
DecodeFailed(const WasmError & error)1925 void AsyncCompileJob::DecodeFailed(const WasmError& error) {
1926   ErrorThrower thrower(isolate_, api_method_name_);
1927   thrower.CompileFailed(error);
1928   // {job} keeps the {this} pointer alive.
1929   std::shared_ptr<AsyncCompileJob> job =
1930       isolate_->wasm_engine()->RemoveCompileJob(this);
1931   resolver_->OnCompilationFailed(thrower.Reify());
1932 }
1933 
AsyncCompileFailed()1934 void AsyncCompileJob::AsyncCompileFailed() {
1935   ErrorThrower thrower(isolate_, api_method_name_);
1936   DCHECK_EQ(native_module_->module()->origin, kWasmOrigin);
1937   const bool lazy_module = wasm_lazy_compilation_;
1938   ValidateSequentially(native_module_->module(), native_module_.get(),
1939                        isolate_->counters(), isolate_->allocator(), &thrower,
1940                        lazy_module);
1941   DCHECK(thrower.error());
1942   // {job} keeps the {this} pointer alive.
1943   std::shared_ptr<AsyncCompileJob> job =
1944       isolate_->wasm_engine()->RemoveCompileJob(this);
1945   resolver_->OnCompilationFailed(thrower.Reify());
1946 }
1947 
AsyncCompileSucceeded(Handle<WasmModuleObject> result)1948 void AsyncCompileJob::AsyncCompileSucceeded(Handle<WasmModuleObject> result) {
1949   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
1950                "wasm.OnCompilationSucceeded");
1951   // We have to make sure that an "incumbent context" is available in case
1952   // the module's start function calls out to Blink.
1953   Local<v8::Context> backup_incumbent_context =
1954       Utils::ToLocal(incumbent_context_);
1955   v8::Context::BackupIncumbentScope incumbent(backup_incumbent_context);
1956   resolver_->OnCompilationSucceeded(result);
1957 }
1958 
1959 class AsyncCompileJob::CompilationStateCallback {
1960  public:
CompilationStateCallback(AsyncCompileJob * job)1961   explicit CompilationStateCallback(AsyncCompileJob* job) : job_(job) {}
1962 
operator ()(CompilationEvent event)1963   void operator()(CompilationEvent event) {
1964     // This callback is only being called from a foreground task.
1965     switch (event) {
1966       case CompilationEvent::kFinishedExportWrappers:
1967         // Even if baseline compilation units finish first, we trigger the
1968         // "kFinishedExportWrappers" event first.
1969         DCHECK(!last_event_.has_value());
1970         break;
1971       case CompilationEvent::kFinishedBaselineCompilation:
1972         DCHECK_EQ(CompilationEvent::kFinishedExportWrappers, last_event_);
1973         if (job_->DecrementAndCheckFinisherCount()) {
1974           // Install the native module in the cache, or reuse a conflicting one.
1975           // If we get a conflicting module, wait until we are back in the
1976           // main thread to update {job_->native_module_} to avoid a data race.
1977           std::shared_ptr<NativeModule> native_module = job_->native_module_;
1978           bool cache_hit =
1979               !job_->isolate_->wasm_engine()->UpdateNativeModuleCache(
1980                   false, &native_module, job_->isolate_);
1981           DCHECK_EQ(cache_hit, native_module != job_->native_module_);
1982           job_->DoSync<CompileFinished>(cache_hit ? std::move(native_module)
1983                                                   : nullptr);
1984         }
1985         break;
1986       case CompilationEvent::kFinishedTopTierCompilation:
1987         DCHECK_EQ(CompilationEvent::kFinishedBaselineCompilation, last_event_);
1988         // At this point, the job will already be gone, thus do not access it
1989         // here.
1990         break;
1991       case CompilationEvent::kFailedCompilation:
1992         DCHECK(!last_event_.has_value() ||
1993                last_event_ == CompilationEvent::kFinishedExportWrappers);
1994         if (job_->DecrementAndCheckFinisherCount()) {
1995           // Don't update {job_->native_module_} to avoid data races with other
1996           // compilation threads. Use a copy of the shared pointer instead.
1997           std::shared_ptr<NativeModule> native_module = job_->native_module_;
1998           job_->isolate_->wasm_engine()->UpdateNativeModuleCache(
1999               true, &native_module, job_->isolate_);
2000           job_->DoSync<CompileFailed>();
2001         }
2002         break;
2003       case CompilationEvent::kFinishedRecompilation:
2004         // This event can happen either before or after
2005         // {kFinishedTopTierCompilation}, hence don't remember this in
2006         // {last_event_}.
2007         return;
2008     }
2009 #ifdef DEBUG
2010     last_event_ = event;
2011 #endif
2012   }
2013 
2014  private:
2015   AsyncCompileJob* job_;
2016 #ifdef DEBUG
2017   // This will be modified by different threads, but they externally
2018   // synchronize, so no explicit synchronization (currently) needed here.
2019   base::Optional<CompilationEvent> last_event_;
2020 #endif
2021 };
2022 
2023 // A closure to run a compilation step (either as foreground or background
2024 // task) and schedule the next step(s), if any.
2025 class AsyncCompileJob::CompileStep {
2026  public:
2027   virtual ~CompileStep() = default;
2028 
Run(AsyncCompileJob * job,bool on_foreground)2029   void Run(AsyncCompileJob* job, bool on_foreground) {
2030     if (on_foreground) {
2031       HandleScope scope(job->isolate_);
2032       SaveAndSwitchContext saved_context(job->isolate_, *job->native_context_);
2033       RunInForeground(job);
2034     } else {
2035       RunInBackground(job);
2036     }
2037   }
2038 
RunInForeground(AsyncCompileJob *)2039   virtual void RunInForeground(AsyncCompileJob*) { UNREACHABLE(); }
RunInBackground(AsyncCompileJob *)2040   virtual void RunInBackground(AsyncCompileJob*) { UNREACHABLE(); }
2041 };
2042 
2043 class AsyncCompileJob::CompileTask : public CancelableTask {
2044  public:
CompileTask(AsyncCompileJob * job,bool on_foreground)2045   CompileTask(AsyncCompileJob* job, bool on_foreground)
2046       // We only manage the background tasks with the {CancelableTaskManager} of
2047       // the {AsyncCompileJob}. Foreground tasks are managed by the system's
2048       // {CancelableTaskManager}. Background tasks cannot spawn tasks managed by
2049       // their own task manager.
2050       : CancelableTask(on_foreground ? job->isolate_->cancelable_task_manager()
2051                                      : &job->background_task_manager_),
2052         job_(job),
2053         on_foreground_(on_foreground) {}
2054 
~CompileTask()2055   ~CompileTask() override {
2056     if (job_ != nullptr && on_foreground_) ResetPendingForegroundTask();
2057   }
2058 
RunInternal()2059   void RunInternal() final {
2060     if (!job_) return;
2061     if (on_foreground_) ResetPendingForegroundTask();
2062     job_->step_->Run(job_, on_foreground_);
2063     // After execution, reset {job_} such that we don't try to reset the pending
2064     // foreground task when the task is deleted.
2065     job_ = nullptr;
2066   }
2067 
Cancel()2068   void Cancel() {
2069     DCHECK_NOT_NULL(job_);
2070     job_ = nullptr;
2071   }
2072 
2073  private:
2074   // {job_} will be cleared to cancel a pending task.
2075   AsyncCompileJob* job_;
2076   bool on_foreground_;
2077 
ResetPendingForegroundTask() const2078   void ResetPendingForegroundTask() const {
2079     DCHECK_EQ(this, job_->pending_foreground_task_);
2080     job_->pending_foreground_task_ = nullptr;
2081   }
2082 };
2083 
StartForegroundTask()2084 void AsyncCompileJob::StartForegroundTask() {
2085   DCHECK_NULL(pending_foreground_task_);
2086 
2087   auto new_task = std::make_unique<CompileTask>(this, true);
2088   pending_foreground_task_ = new_task.get();
2089   foreground_task_runner_->PostTask(std::move(new_task));
2090 }
2091 
ExecuteForegroundTaskImmediately()2092 void AsyncCompileJob::ExecuteForegroundTaskImmediately() {
2093   DCHECK_NULL(pending_foreground_task_);
2094 
2095   auto new_task = std::make_unique<CompileTask>(this, true);
2096   pending_foreground_task_ = new_task.get();
2097   new_task->Run();
2098 }
2099 
CancelPendingForegroundTask()2100 void AsyncCompileJob::CancelPendingForegroundTask() {
2101   if (!pending_foreground_task_) return;
2102   pending_foreground_task_->Cancel();
2103   pending_foreground_task_ = nullptr;
2104 }
2105 
StartBackgroundTask()2106 void AsyncCompileJob::StartBackgroundTask() {
2107   auto task = std::make_unique<CompileTask>(this, false);
2108 
2109   // If --wasm-num-compilation-tasks=0 is passed, do only spawn foreground
2110   // tasks. This is used to make timing deterministic.
2111   if (FLAG_wasm_num_compilation_tasks > 0) {
2112     V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(task));
2113   } else {
2114     foreground_task_runner_->PostTask(std::move(task));
2115   }
2116 }
2117 
2118 template <typename Step,
2119           AsyncCompileJob::UseExistingForegroundTask use_existing_fg_task,
2120           typename... Args>
DoSync(Args &&...args)2121 void AsyncCompileJob::DoSync(Args&&... args) {
2122   NextStep<Step>(std::forward<Args>(args)...);
2123   if (use_existing_fg_task && pending_foreground_task_ != nullptr) return;
2124   StartForegroundTask();
2125 }
2126 
2127 template <typename Step, typename... Args>
DoImmediately(Args &&...args)2128 void AsyncCompileJob::DoImmediately(Args&&... args) {
2129   NextStep<Step>(std::forward<Args>(args)...);
2130   ExecuteForegroundTaskImmediately();
2131 }
2132 
2133 template <typename Step, typename... Args>
DoAsync(Args &&...args)2134 void AsyncCompileJob::DoAsync(Args&&... args) {
2135   NextStep<Step>(std::forward<Args>(args)...);
2136   StartBackgroundTask();
2137 }
2138 
2139 template <typename Step, typename... Args>
NextStep(Args &&...args)2140 void AsyncCompileJob::NextStep(Args&&... args) {
2141   step_.reset(new Step(std::forward<Args>(args)...));
2142 }
2143 
2144 //==========================================================================
2145 // Step 1: (async) Decode the module.
2146 //==========================================================================
2147 class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep {
2148  public:
DecodeModule(Counters * counters,std::shared_ptr<metrics::Recorder> metrics_recorder)2149   explicit DecodeModule(Counters* counters,
2150                         std::shared_ptr<metrics::Recorder> metrics_recorder)
2151       : counters_(counters), metrics_recorder_(std::move(metrics_recorder)) {}
2152 
RunInBackground(AsyncCompileJob * job)2153   void RunInBackground(AsyncCompileJob* job) override {
2154     ModuleResult result;
2155     {
2156       DisallowHandleAllocation no_handle;
2157       DisallowHeapAllocation no_allocation;
2158       // Decode the module bytes.
2159       TRACE_COMPILE("(1) Decoding module...\n");
2160       TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
2161                    "wasm.DecodeModule");
2162       auto enabled_features = job->enabled_features_;
2163       result = DecodeWasmModule(
2164           enabled_features, job->wire_bytes_.start(), job->wire_bytes_.end(),
2165           false, kWasmOrigin, counters_, metrics_recorder_, job->context_id(),
2166           DecodingMethod::kAsync, job->isolate()->wasm_engine()->allocator());
2167 
2168       // Validate lazy functions here if requested.
2169       if (!FLAG_wasm_lazy_validation && result.ok()) {
2170         const WasmModule* module = result.value().get();
2171         DCHECK_EQ(module->origin, kWasmOrigin);
2172         const bool lazy_module = job->wasm_lazy_compilation_;
2173         if (MayCompriseLazyFunctions(module, enabled_features, lazy_module)) {
2174           auto allocator = job->isolate()->wasm_engine()->allocator();
2175           int start = module->num_imported_functions;
2176           int end = start + module->num_declared_functions;
2177 
2178           for (int func_index = start; func_index < end; func_index++) {
2179             const WasmFunction* func = &module->functions[func_index];
2180             Vector<const uint8_t> code =
2181                 job->wire_bytes_.GetFunctionBytes(func);
2182 
2183             CompileStrategy strategy = GetCompileStrategy(
2184                 module, enabled_features, func_index, lazy_module);
2185             bool validate_lazily_compiled_function =
2186                 strategy == CompileStrategy::kLazy ||
2187                 strategy == CompileStrategy::kLazyBaselineEagerTopTier;
2188             if (validate_lazily_compiled_function) {
2189               DecodeResult function_result =
2190                   ValidateSingleFunction(module, func_index, code, counters_,
2191                                          allocator, enabled_features);
2192               if (function_result.failed()) {
2193                 result = ModuleResult(function_result.error());
2194                 break;
2195               }
2196             }
2197           }
2198         }
2199       }
2200     }
2201     if (result.failed()) {
2202       // Decoding failure; reject the promise and clean up.
2203       job->DoSync<DecodeFail>(std::move(result).error());
2204     } else {
2205       // Decode passed.
2206       std::shared_ptr<WasmModule> module = std::move(result).value();
2207       const bool kUsesLiftoff = false;
2208       size_t code_size_estimate =
2209           wasm::WasmCodeManager::EstimateNativeModuleCodeSize(module.get(),
2210                                                               kUsesLiftoff);
2211       job->DoSync<PrepareAndStartCompile>(std::move(module), true,
2212                                           code_size_estimate);
2213     }
2214   }
2215 
2216  private:
2217   Counters* const counters_;
2218   std::shared_ptr<metrics::Recorder> metrics_recorder_;
2219 };
2220 
2221 //==========================================================================
2222 // Step 1b: (sync) Fail decoding the module.
2223 //==========================================================================
2224 class AsyncCompileJob::DecodeFail : public CompileStep {
2225  public:
DecodeFail(WasmError error)2226   explicit DecodeFail(WasmError error) : error_(std::move(error)) {}
2227 
2228  private:
2229   WasmError error_;
2230 
RunInForeground(AsyncCompileJob * job)2231   void RunInForeground(AsyncCompileJob* job) override {
2232     TRACE_COMPILE("(1b) Decoding failed.\n");
2233     // {job_} is deleted in DecodeFailed, therefore the {return}.
2234     return job->DecodeFailed(error_);
2235   }
2236 };
2237 
2238 //==========================================================================
2239 // Step 2 (sync): Create heap-allocated data and start compile.
2240 //==========================================================================
2241 class AsyncCompileJob::PrepareAndStartCompile : public CompileStep {
2242  public:
PrepareAndStartCompile(std::shared_ptr<const WasmModule> module,bool start_compilation,size_t code_size_estimate)2243   PrepareAndStartCompile(std::shared_ptr<const WasmModule> module,
2244                          bool start_compilation, size_t code_size_estimate)
2245       : module_(std::move(module)),
2246         start_compilation_(start_compilation),
2247         code_size_estimate_(code_size_estimate) {}
2248 
2249  private:
2250   const std::shared_ptr<const WasmModule> module_;
2251   const bool start_compilation_;
2252   const size_t code_size_estimate_;
2253 
RunInForeground(AsyncCompileJob * job)2254   void RunInForeground(AsyncCompileJob* job) override {
2255     TRACE_COMPILE("(2) Prepare and start compile...\n");
2256 
2257     const bool streaming = job->wire_bytes_.length() == 0;
2258     if (streaming) {
2259       // Streaming compilation already checked for cache hits.
2260       job->CreateNativeModule(module_, code_size_estimate_);
2261     } else if (job->GetOrCreateNativeModule(std::move(module_),
2262                                             code_size_estimate_)) {
2263       job->FinishCompile(true);
2264       return;
2265     }
2266 
2267     // Make sure all compilation tasks stopped running. Decoding (async step)
2268     // is done.
2269     job->background_task_manager_.CancelAndWait();
2270 
2271     CompilationStateImpl* compilation_state =
2272         Impl(job->native_module_->compilation_state());
2273     compilation_state->AddCallback(CompilationStateCallback{job});
2274     if (base::TimeTicks::IsHighResolution()) {
2275       auto compile_mode = job->stream_ == nullptr
2276                               ? CompilationTimeCallback::kAsync
2277                               : CompilationTimeCallback::kStreaming;
2278       compilation_state->AddCallback(CompilationTimeCallback{
2279           job->isolate_->async_counters(), job->isolate_->metrics_recorder(),
2280           job->context_id_, job->native_module_, compile_mode});
2281     }
2282 
2283     if (start_compilation_) {
2284       // TODO(ahaas): Try to remove the {start_compilation_} check when
2285       // streaming decoding is done in the background. If
2286       // InitializeCompilationUnits always returns 0 for streaming compilation,
2287       // then DoAsync would do the same as NextStep already.
2288 
2289       // Add compilation units and kick off compilation.
2290       InitializeCompilationUnits(job->isolate(), job->native_module_.get());
2291     }
2292   }
2293 };
2294 
2295 //==========================================================================
2296 // Step 3a (sync): Compilation failed.
2297 //==========================================================================
2298 class AsyncCompileJob::CompileFailed : public CompileStep {
2299  private:
RunInForeground(AsyncCompileJob * job)2300   void RunInForeground(AsyncCompileJob* job) override {
2301     TRACE_COMPILE("(3a) Compilation failed\n");
2302     DCHECK(job->native_module_->compilation_state()->failed());
2303 
2304     // {job_} is deleted in AsyncCompileFailed, therefore the {return}.
2305     return job->AsyncCompileFailed();
2306   }
2307 };
2308 
2309 namespace {
2310 class SampleTopTierCodeSizeCallback {
2311  public:
SampleTopTierCodeSizeCallback(std::weak_ptr<NativeModule> native_module)2312   explicit SampleTopTierCodeSizeCallback(
2313       std::weak_ptr<NativeModule> native_module)
2314       : native_module_(std::move(native_module)) {}
2315 
operator ()(CompilationEvent event)2316   void operator()(CompilationEvent event) {
2317     if (event != CompilationEvent::kFinishedTopTierCompilation) return;
2318     if (std::shared_ptr<NativeModule> native_module = native_module_.lock()) {
2319       native_module->engine()->SampleTopTierCodeSizeInAllIsolates(
2320           native_module);
2321     }
2322   }
2323 
2324  private:
2325   std::weak_ptr<NativeModule> native_module_;
2326 };
2327 }  // namespace
2328 
2329 //==========================================================================
2330 // Step 3b (sync): Compilation finished.
2331 //==========================================================================
2332 class AsyncCompileJob::CompileFinished : public CompileStep {
2333  public:
CompileFinished(std::shared_ptr<NativeModule> cached_native_module)2334   explicit CompileFinished(std::shared_ptr<NativeModule> cached_native_module)
2335       : cached_native_module_(std::move(cached_native_module)) {}
2336 
2337  private:
RunInForeground(AsyncCompileJob * job)2338   void RunInForeground(AsyncCompileJob* job) override {
2339     TRACE_COMPILE("(3b) Compilation finished\n");
2340     if (cached_native_module_) {
2341       job->native_module_ = cached_native_module_;
2342     } else {
2343       DCHECK(!job->native_module_->compilation_state()->failed());
2344       // Sample the generated code size when baseline compilation finished.
2345       job->native_module_->SampleCodeSize(job->isolate_->counters(),
2346                                           NativeModule::kAfterBaseline);
2347       // Also, set a callback to sample the code size after top-tier compilation
2348       // finished. This callback will *not* keep the NativeModule alive.
2349       job->native_module_->compilation_state()->AddCallback(
2350           SampleTopTierCodeSizeCallback{job->native_module_});
2351     }
2352     // Then finalize and publish the generated module.
2353     job->FinishCompile(cached_native_module_ != nullptr);
2354   }
2355 
2356   std::shared_ptr<NativeModule> cached_native_module_;
2357 };
2358 
FinishModule()2359 void AsyncCompileJob::FinishModule() {
2360   TRACE_COMPILE("(4) Finish module...\n");
2361   AsyncCompileSucceeded(module_object_);
2362   isolate_->wasm_engine()->RemoveCompileJob(this);
2363 }
2364 
AsyncStreamingProcessor(AsyncCompileJob * job,std::shared_ptr<Counters> async_counters,AccountingAllocator * allocator)2365 AsyncStreamingProcessor::AsyncStreamingProcessor(
2366     AsyncCompileJob* job, std::shared_ptr<Counters> async_counters,
2367     AccountingAllocator* allocator)
2368     : decoder_(job->enabled_features_),
2369       job_(job),
2370       wasm_engine_(job_->isolate_->wasm_engine()),
2371       compilation_unit_builder_(nullptr),
2372       async_counters_(async_counters),
2373       allocator_(allocator) {}
2374 
~AsyncStreamingProcessor()2375 AsyncStreamingProcessor::~AsyncStreamingProcessor() {
2376   if (job_->native_module_ && job_->native_module_->wire_bytes().empty()) {
2377     // Clean up the temporary cache entry.
2378     job_->isolate_->wasm_engine()->StreamingCompilationFailed(prefix_hash_);
2379   }
2380 }
2381 
FinishAsyncCompileJobWithError(const WasmError & error)2382 void AsyncStreamingProcessor::FinishAsyncCompileJobWithError(
2383     const WasmError& error) {
2384   DCHECK(error.has_error());
2385   // Make sure all background tasks stopped executing before we change the state
2386   // of the AsyncCompileJob to DecodeFail.
2387   job_->background_task_manager_.CancelAndWait();
2388 
2389   // Record event metrics.
2390   auto duration = base::TimeTicks::Now() - job_->start_time_;
2391   job_->metrics_event_.success = false;
2392   job_->metrics_event_.streamed = true;
2393   job_->metrics_event_.module_size_in_bytes = job_->wire_bytes_.length();
2394   job_->metrics_event_.function_count = num_functions_;
2395   job_->metrics_event_.wall_clock_time_in_us = duration.InMicroseconds();
2396   job_->metrics_event_.wall_clock_duration_in_us = duration.InMicroseconds();
2397   job_->isolate_->metrics_recorder()->DelayMainThreadEvent(job_->metrics_event_,
2398                                                            job_->context_id_);
2399 
2400   // Check if there is already a CompiledModule, in which case we have to clean
2401   // up the CompilationStateImpl as well.
2402   if (job_->native_module_) {
2403     Impl(job_->native_module_->compilation_state())->CancelCompilation();
2404 
2405     job_->DoSync<AsyncCompileJob::DecodeFail,
2406                  AsyncCompileJob::kUseExistingForegroundTask>(error);
2407 
2408     // Clear the {compilation_unit_builder_} if it exists. This is needed
2409     // because there is a check in the destructor of the
2410     // {CompilationUnitBuilder} that it is empty.
2411     if (compilation_unit_builder_) compilation_unit_builder_->Clear();
2412   } else {
2413     job_->DoSync<AsyncCompileJob::DecodeFail>(error);
2414   }
2415 }
2416 
2417 // Process the module header.
ProcessModuleHeader(Vector<const uint8_t> bytes,uint32_t offset)2418 bool AsyncStreamingProcessor::ProcessModuleHeader(Vector<const uint8_t> bytes,
2419                                                   uint32_t offset) {
2420   TRACE_STREAMING("Process module header...\n");
2421   decoder_.StartDecoding(
2422       job_->isolate()->counters(), job_->isolate()->metrics_recorder(),
2423       job_->context_id(), job_->isolate()->wasm_engine()->allocator());
2424   decoder_.DecodeModuleHeader(bytes, offset);
2425   if (!decoder_.ok()) {
2426     FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false).error());
2427     return false;
2428   }
2429   prefix_hash_ = NativeModuleCache::WireBytesHash(bytes);
2430   return true;
2431 }
2432 
2433 // Process all sections except for the code section.
ProcessSection(SectionCode section_code,Vector<const uint8_t> bytes,uint32_t offset)2434 bool AsyncStreamingProcessor::ProcessSection(SectionCode section_code,
2435                                              Vector<const uint8_t> bytes,
2436                                              uint32_t offset) {
2437   TRACE_STREAMING("Process section %d ...\n", section_code);
2438   if (compilation_unit_builder_) {
2439     // We reached a section after the code section, we do not need the
2440     // compilation_unit_builder_ anymore.
2441     CommitCompilationUnits();
2442     compilation_unit_builder_.reset();
2443   }
2444   if (before_code_section_) {
2445     // Combine section hashes until code section.
2446     prefix_hash_ = base::hash_combine(prefix_hash_,
2447                                       NativeModuleCache::WireBytesHash(bytes));
2448   }
2449   if (section_code == SectionCode::kUnknownSectionCode) {
2450     size_t bytes_consumed = ModuleDecoder::IdentifyUnknownSection(
2451         &decoder_, bytes, offset, &section_code);
2452     if (!decoder_.ok()) {
2453       FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false).error());
2454       return false;
2455     }
2456     if (section_code == SectionCode::kUnknownSectionCode) {
2457       // Skip unknown sections that we do not know how to handle.
2458       return true;
2459     }
2460     // Remove the unknown section tag from the payload bytes.
2461     offset += bytes_consumed;
2462     bytes = bytes.SubVector(bytes_consumed, bytes.size());
2463   }
2464   constexpr bool verify_functions = false;
2465   decoder_.DecodeSection(section_code, bytes, offset, verify_functions);
2466   if (!decoder_.ok()) {
2467     FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false).error());
2468     return false;
2469   }
2470   return true;
2471 }
2472 
2473 // Start the code section.
ProcessCodeSectionHeader(int num_functions,uint32_t offset,std::shared_ptr<WireBytesStorage> wire_bytes_storage,int code_section_length)2474 bool AsyncStreamingProcessor::ProcessCodeSectionHeader(
2475     int num_functions, uint32_t offset,
2476     std::shared_ptr<WireBytesStorage> wire_bytes_storage,
2477     int code_section_length) {
2478   DCHECK_LE(0, code_section_length);
2479   before_code_section_ = false;
2480   TRACE_STREAMING("Start the code section with %d functions...\n",
2481                   num_functions);
2482   if (!decoder_.CheckFunctionsCount(static_cast<uint32_t>(num_functions),
2483                                     offset)) {
2484     FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false).error());
2485     return false;
2486   }
2487 
2488   decoder_.set_code_section(offset, static_cast<uint32_t>(code_section_length));
2489 
2490   prefix_hash_ = base::hash_combine(prefix_hash_,
2491                                     static_cast<uint32_t>(code_section_length));
2492   if (!wasm_engine_->GetStreamingCompilationOwnership(prefix_hash_)) {
2493     // Known prefix, wait until the end of the stream and check the cache.
2494     prefix_cache_hit_ = true;
2495     return true;
2496   }
2497 
2498   // Execute the PrepareAndStartCompile step immediately and not in a separate
2499   // task.
2500   int num_imported_functions =
2501       static_cast<int>(decoder_.module()->num_imported_functions);
2502   DCHECK_EQ(kWasmOrigin, decoder_.module()->origin);
2503   const bool uses_liftoff = FLAG_liftoff;
2504   size_t code_size_estimate =
2505       wasm::WasmCodeManager::EstimateNativeModuleCodeSize(
2506           num_functions, num_imported_functions, code_section_length,
2507           uses_liftoff);
2508   job_->DoImmediately<AsyncCompileJob::PrepareAndStartCompile>(
2509       decoder_.shared_module(), false, code_size_estimate);
2510 
2511   auto* compilation_state = Impl(job_->native_module_->compilation_state());
2512   compilation_state->SetWireBytesStorage(std::move(wire_bytes_storage));
2513   DCHECK_EQ(job_->native_module_->module()->origin, kWasmOrigin);
2514   const bool lazy_module = job_->wasm_lazy_compilation_;
2515 
2516   // Set outstanding_finishers_ to 2, because both the AsyncCompileJob and the
2517   // AsyncStreamingProcessor have to finish.
2518   job_->outstanding_finishers_.store(2);
2519   compilation_unit_builder_.reset(
2520       new CompilationUnitBuilder(job_->native_module_.get()));
2521 
2522   NativeModule* native_module = job_->native_module_.get();
2523 
2524   int num_import_wrappers =
2525       AddImportWrapperUnits(native_module, compilation_unit_builder_.get());
2526   int num_export_wrappers = AddExportWrapperUnits(
2527       job_->isolate_, wasm_engine_, native_module,
2528       compilation_unit_builder_.get(), job_->enabled_features_);
2529   compilation_state->InitializeCompilationProgress(
2530       lazy_module, num_import_wrappers, num_export_wrappers);
2531   return true;
2532 }
2533 
2534 // Process a function body.
ProcessFunctionBody(Vector<const uint8_t> bytes,uint32_t offset)2535 bool AsyncStreamingProcessor::ProcessFunctionBody(Vector<const uint8_t> bytes,
2536                                                   uint32_t offset) {
2537   TRACE_STREAMING("Process function body %d ...\n", num_functions_);
2538 
2539   decoder_.DecodeFunctionBody(
2540       num_functions_, static_cast<uint32_t>(bytes.length()), offset, false);
2541 
2542   const WasmModule* module = decoder_.module();
2543   auto enabled_features = job_->enabled_features_;
2544   uint32_t func_index =
2545       num_functions_ + decoder_.module()->num_imported_functions;
2546   DCHECK_EQ(module->origin, kWasmOrigin);
2547   const bool lazy_module = job_->wasm_lazy_compilation_;
2548   CompileStrategy strategy =
2549       GetCompileStrategy(module, enabled_features, func_index, lazy_module);
2550   bool validate_lazily_compiled_function =
2551       !FLAG_wasm_lazy_validation &&
2552       (strategy == CompileStrategy::kLazy ||
2553        strategy == CompileStrategy::kLazyBaselineEagerTopTier);
2554   if (validate_lazily_compiled_function) {
2555     // The native module does not own the wire bytes until {SetWireBytes} is
2556     // called in {OnFinishedStream}. Validation must use {bytes} parameter.
2557     DecodeResult result =
2558         ValidateSingleFunction(module, func_index, bytes, async_counters_.get(),
2559                                allocator_, enabled_features);
2560 
2561     if (result.failed()) {
2562       FinishAsyncCompileJobWithError(result.error());
2563       return false;
2564     }
2565   }
2566 
2567   // Don't compile yet if we might have a cache hit.
2568   if (prefix_cache_hit_) {
2569     num_functions_++;
2570     return true;
2571   }
2572 
2573   NativeModule* native_module = job_->native_module_.get();
2574   if (strategy == CompileStrategy::kLazy) {
2575     native_module->UseLazyStub(func_index);
2576   } else if (strategy == CompileStrategy::kLazyBaselineEagerTopTier) {
2577     compilation_unit_builder_->AddTopTierUnit(func_index);
2578     native_module->UseLazyStub(func_index);
2579   } else {
2580     DCHECK_EQ(strategy, CompileStrategy::kEager);
2581     compilation_unit_builder_->AddUnits(func_index);
2582   }
2583 
2584   ++num_functions_;
2585 
2586   return true;
2587 }
2588 
CommitCompilationUnits()2589 void AsyncStreamingProcessor::CommitCompilationUnits() {
2590   DCHECK(compilation_unit_builder_);
2591   compilation_unit_builder_->Commit();
2592 }
2593 
OnFinishedChunk()2594 void AsyncStreamingProcessor::OnFinishedChunk() {
2595   TRACE_STREAMING("FinishChunk...\n");
2596   if (compilation_unit_builder_) CommitCompilationUnits();
2597 }
2598 
2599 // Finish the processing of the stream.
OnFinishedStream(OwnedVector<uint8_t> bytes)2600 void AsyncStreamingProcessor::OnFinishedStream(OwnedVector<uint8_t> bytes) {
2601   TRACE_STREAMING("Finish stream...\n");
2602   DCHECK_EQ(NativeModuleCache::PrefixHash(bytes.as_vector()), prefix_hash_);
2603   ModuleResult result = decoder_.FinishDecoding(false);
2604   if (result.failed()) {
2605     FinishAsyncCompileJobWithError(result.error());
2606     return;
2607   }
2608 
2609   job_->wire_bytes_ = ModuleWireBytes(bytes.as_vector());
2610   job_->bytes_copy_ = bytes.ReleaseData();
2611 
2612   // Record event metrics.
2613   auto duration = base::TimeTicks::Now() - job_->start_time_;
2614   job_->metrics_event_.success = true;
2615   job_->metrics_event_.streamed = true;
2616   job_->metrics_event_.module_size_in_bytes = job_->wire_bytes_.length();
2617   job_->metrics_event_.function_count = num_functions_;
2618   job_->metrics_event_.wall_clock_time_in_us = duration.InMicroseconds();
2619   job_->metrics_event_.wall_clock_duration_in_us = duration.InMicroseconds();
2620   job_->isolate_->metrics_recorder()->DelayMainThreadEvent(job_->metrics_event_,
2621                                                            job_->context_id_);
2622 
2623   if (prefix_cache_hit_) {
2624     // Restart as an asynchronous, non-streaming compilation. Most likely
2625     // {PrepareAndStartCompile} will get the native module from the cache.
2626     size_t code_size_estimate =
2627         wasm::WasmCodeManager::EstimateNativeModuleCodeSize(
2628             result.value().get(), FLAG_liftoff);
2629     job_->DoSync<AsyncCompileJob::PrepareAndStartCompile>(
2630         std::move(result).value(), true, code_size_estimate);
2631     return;
2632   }
2633 
2634   // We have to open a HandleScope and prepare the Context for
2635   // CreateNativeModule, PrepareRuntimeObjects and FinishCompile as this is a
2636   // callback from the embedder.
2637   HandleScope scope(job_->isolate_);
2638   SaveAndSwitchContext saved_context(job_->isolate_, *job_->native_context_);
2639 
2640   // Record the size of the wire bytes. In synchronous and asynchronous
2641   // (non-streaming) compilation, this happens in {DecodeWasmModule}.
2642   auto* histogram = job_->isolate_->counters()->wasm_wasm_module_size_bytes();
2643   histogram->AddSample(job_->wire_bytes_.module_bytes().length());
2644 
2645   const bool has_code_section = job_->native_module_ != nullptr;
2646   bool cache_hit = false;
2647   if (!has_code_section) {
2648     // We are processing a WebAssembly module without code section. Create the
2649     // native module now (would otherwise happen in {PrepareAndStartCompile} or
2650     // {ProcessCodeSectionHeader}).
2651     constexpr size_t kCodeSizeEstimate = 0;
2652     cache_hit = job_->GetOrCreateNativeModule(std::move(result).value(),
2653                                               kCodeSizeEstimate);
2654   } else {
2655     job_->native_module_->SetWireBytes(
2656         {std::move(job_->bytes_copy_), job_->wire_bytes_.length()});
2657     job_->native_module_->LogWasmCodes(job_->isolate_);
2658   }
2659   const bool needs_finish = job_->DecrementAndCheckFinisherCount();
2660   DCHECK_IMPLIES(!has_code_section, needs_finish);
2661   // We might need to recompile the module for debugging, if the debugger was
2662   // enabled while streaming compilation was running. Since handling this while
2663   // compiling via streaming is tricky, we just tier down now, before publishing
2664   // the module.
2665   if (job_->native_module_->IsTieredDown()) {
2666     job_->native_module_->RecompileForTiering();
2667   }
2668   if (needs_finish) {
2669     const bool failed = job_->native_module_->compilation_state()->failed();
2670     if (!cache_hit) {
2671       cache_hit = !job_->isolate_->wasm_engine()->UpdateNativeModuleCache(
2672           failed, &job_->native_module_, job_->isolate_);
2673     }
2674     if (failed) {
2675       job_->AsyncCompileFailed();
2676     } else {
2677       job_->FinishCompile(cache_hit);
2678     }
2679   }
2680 }
2681 
2682 // Report an error detected in the StreamingDecoder.
OnError(const WasmError & error)2683 void AsyncStreamingProcessor::OnError(const WasmError& error) {
2684   TRACE_STREAMING("Stream error...\n");
2685   FinishAsyncCompileJobWithError(error);
2686 }
2687 
OnAbort()2688 void AsyncStreamingProcessor::OnAbort() {
2689   TRACE_STREAMING("Abort stream...\n");
2690   job_->Abort();
2691 }
2692 
Deserialize(Vector<const uint8_t> module_bytes,Vector<const uint8_t> wire_bytes)2693 bool AsyncStreamingProcessor::Deserialize(Vector<const uint8_t> module_bytes,
2694                                           Vector<const uint8_t> wire_bytes) {
2695   TRACE_EVENT0("v8.wasm", "wasm.Deserialize");
2696   // DeserializeNativeModule and FinishCompile assume that they are executed in
2697   // a HandleScope, and that a context is set on the isolate.
2698   HandleScope scope(job_->isolate_);
2699   SaveAndSwitchContext saved_context(job_->isolate_, *job_->native_context_);
2700 
2701   MaybeHandle<WasmModuleObject> result = DeserializeNativeModule(
2702       job_->isolate_, module_bytes, wire_bytes, job_->stream_->url());
2703 
2704   if (result.is_null()) return false;
2705 
2706   job_->module_object_ =
2707       job_->isolate_->global_handles()->Create(*result.ToHandleChecked());
2708   job_->native_module_ = job_->module_object_->shared_native_module();
2709   job_->wire_bytes_ = ModuleWireBytes(job_->native_module_->wire_bytes());
2710   job_->FinishCompile(false);
2711   return true;
2712 }
2713 
CompilationStateImpl(const std::shared_ptr<NativeModule> & native_module,std::shared_ptr<Counters> async_counters)2714 CompilationStateImpl::CompilationStateImpl(
2715     const std::shared_ptr<NativeModule>& native_module,
2716     std::shared_ptr<Counters> async_counters)
2717     : native_module_(native_module.get()),
2718       native_module_weak_(std::move(native_module)),
2719       compile_mode_(FLAG_wasm_tier_up &&
2720                             native_module->module()->origin == kWasmOrigin
2721                         ? CompileMode::kTiering
2722                         : CompileMode::kRegular),
2723       async_counters_(std::move(async_counters)),
2724       compilation_unit_queues_(native_module->num_functions()) {}
2725 
CancelCompilation()2726 void CompilationStateImpl::CancelCompilation() {
2727   // No more callbacks after abort.
2728   base::MutexGuard callbacks_guard(&callbacks_mutex_);
2729   // std::memory_order_relaxed is sufficient because no other state is
2730   // synchronized with |compile_cancelled_|.
2731   compile_cancelled_.store(true, std::memory_order_relaxed);
2732   callbacks_.clear();
2733 }
2734 
cancelled() const2735 bool CompilationStateImpl::cancelled() const {
2736   return compile_cancelled_.load(std::memory_order_relaxed);
2737 }
2738 
InitializeCompilationProgress(bool lazy_module,int num_import_wrappers,int num_export_wrappers)2739 void CompilationStateImpl::InitializeCompilationProgress(
2740     bool lazy_module, int num_import_wrappers, int num_export_wrappers) {
2741   DCHECK(!failed());
2742   auto enabled_features = native_module_->enabled_features();
2743   auto* module = native_module_->module();
2744 
2745   base::MutexGuard guard(&callbacks_mutex_);
2746   DCHECK_EQ(0, outstanding_baseline_units_);
2747   DCHECK_EQ(0, outstanding_export_wrappers_);
2748   DCHECK_EQ(0, outstanding_top_tier_functions_);
2749   compilation_progress_.reserve(module->num_declared_functions);
2750   int start = module->num_imported_functions;
2751   int end = start + module->num_declared_functions;
2752 
2753   const bool prefer_liftoff = native_module_->IsTieredDown();
2754   for (int func_index = start; func_index < end; func_index++) {
2755     if (prefer_liftoff) {
2756       constexpr uint8_t kLiftoffOnlyFunctionProgress =
2757           RequiredTopTierField::encode(ExecutionTier::kLiftoff) |
2758           RequiredBaselineTierField::encode(ExecutionTier::kLiftoff) |
2759           ReachedTierField::encode(ExecutionTier::kNone);
2760       compilation_progress_.push_back(kLiftoffOnlyFunctionProgress);
2761       outstanding_baseline_units_++;
2762       outstanding_top_tier_functions_++;
2763       continue;
2764     }
2765     ExecutionTierPair requested_tiers = GetRequestedExecutionTiers(
2766         module, compile_mode(), enabled_features, func_index);
2767     CompileStrategy strategy =
2768         GetCompileStrategy(module, enabled_features, func_index, lazy_module);
2769 
2770     bool required_for_baseline = strategy == CompileStrategy::kEager;
2771     bool required_for_top_tier = strategy != CompileStrategy::kLazy;
2772     DCHECK_EQ(required_for_top_tier,
2773               strategy == CompileStrategy::kEager ||
2774                   strategy == CompileStrategy::kLazyBaselineEagerTopTier);
2775 
2776     // Count functions to complete baseline and top tier compilation.
2777     if (required_for_baseline) outstanding_baseline_units_++;
2778     if (required_for_top_tier) outstanding_top_tier_functions_++;
2779 
2780     // Initialize function's compilation progress.
2781     ExecutionTier required_baseline_tier = required_for_baseline
2782                                                ? requested_tiers.baseline_tier
2783                                                : ExecutionTier::kNone;
2784     ExecutionTier required_top_tier =
2785         required_for_top_tier ? requested_tiers.top_tier : ExecutionTier::kNone;
2786     uint8_t function_progress = ReachedTierField::encode(ExecutionTier::kNone);
2787     function_progress = RequiredBaselineTierField::update(
2788         function_progress, required_baseline_tier);
2789     function_progress =
2790         RequiredTopTierField::update(function_progress, required_top_tier);
2791     compilation_progress_.push_back(function_progress);
2792   }
2793   DCHECK_IMPLIES(lazy_module, outstanding_baseline_units_ == 0);
2794   DCHECK_IMPLIES(lazy_module, outstanding_top_tier_functions_ == 0);
2795   DCHECK_LE(0, outstanding_baseline_units_);
2796   DCHECK_LE(outstanding_baseline_units_, outstanding_top_tier_functions_);
2797   outstanding_baseline_units_ += num_import_wrappers;
2798   outstanding_export_wrappers_ = num_export_wrappers;
2799 
2800   // Trigger callbacks if module needs no baseline or top tier compilation. This
2801   // can be the case for an empty or fully lazy module.
2802   TriggerCallbacks();
2803 }
2804 
InitializeCompilationProgressAfterDeserialization()2805 void CompilationStateImpl::InitializeCompilationProgressAfterDeserialization() {
2806   auto* module = native_module_->module();
2807   base::MutexGuard guard(&callbacks_mutex_);
2808   DCHECK(compilation_progress_.empty());
2809   constexpr uint8_t kProgressAfterDeserialization =
2810       RequiredBaselineTierField::encode(ExecutionTier::kTurbofan) |
2811       RequiredTopTierField::encode(ExecutionTier::kTurbofan) |
2812       ReachedTierField::encode(ExecutionTier::kTurbofan);
2813   finished_events_.Add(CompilationEvent::kFinishedExportWrappers);
2814   finished_events_.Add(CompilationEvent::kFinishedBaselineCompilation);
2815   finished_events_.Add(CompilationEvent::kFinishedTopTierCompilation);
2816   compilation_progress_.assign(module->num_declared_functions,
2817                                kProgressAfterDeserialization);
2818 }
2819 
InitializeRecompilation(TieringState new_tiering_state,CompilationState::callback_t recompilation_finished_callback)2820 void CompilationStateImpl::InitializeRecompilation(
2821     TieringState new_tiering_state,
2822     CompilationState::callback_t recompilation_finished_callback) {
2823   DCHECK(!failed());
2824 
2825   // Hold the mutex as long as possible, to synchronize between multiple
2826   // recompilations that are triggered at the same time (e.g. when the profiler
2827   // is disabled).
2828   base::Optional<base::MutexGuard> guard(&callbacks_mutex_);
2829 
2830   // For now, we cannot contribute to compilation here, because this would bump
2831   // the number of workers above the expected maximum concurrency. This can be
2832   // fixed once we grow the number of compilation unit queues dynamically.
2833   // TODO(clemensb): Contribute to compilation once the queues grow dynamically.
2834   while (outstanding_recompilation_functions_ > 0) {
2835     auto semaphore = std::make_shared<base::Semaphore>(0);
2836     callbacks_.emplace_back([semaphore](CompilationEvent event) {
2837       if (event == CompilationEvent::kFinishedRecompilation) {
2838         semaphore->Signal();
2839       }
2840     });
2841     guard.reset();
2842     semaphore->Wait();
2843     guard.emplace(&callbacks_mutex_);
2844   }
2845 
2846   // Information about compilation progress is shared between this class and the
2847   // NativeModule. Before updating information here, consult the NativeModule to
2848   // find all functions that need recompilation.
2849   // Since the current tiering state is updated on the NativeModule before
2850   // triggering recompilation, it's OK if the information is slightly outdated.
2851   // If we compile functions twice, the NativeModule will ignore all redundant
2852   // code (or code compiled for the wrong tier).
2853   std::vector<int> recompile_function_indexes =
2854       native_module_->FindFunctionsToRecompile(new_tiering_state);
2855 
2856   callbacks_.emplace_back(std::move(recompilation_finished_callback));
2857   tiering_state_ = new_tiering_state;
2858 
2859   // If compilation progress is not initialized yet, then compilation didn't
2860   // start yet, and new code will be kept tiered-down from the start. For
2861   // streaming compilation, there is a special path to tier down later, when
2862   // the module is complete. In any case, we don't need to recompile here.
2863   base::Optional<CompilationUnitBuilder> builder;
2864   if (compilation_progress_.size() > 0) {
2865     builder.emplace(native_module_);
2866     const WasmModule* module = native_module_->module();
2867     DCHECK_EQ(module->num_declared_functions, compilation_progress_.size());
2868     DCHECK_GE(module->num_declared_functions,
2869               recompile_function_indexes.size());
2870     outstanding_recompilation_functions_ =
2871         static_cast<int>(recompile_function_indexes.size());
2872     // Restart recompilation if another recompilation is already happening.
2873     for (auto& progress : compilation_progress_) {
2874       progress = MissingRecompilationField::update(progress, false);
2875     }
2876     auto new_tier = new_tiering_state == kTieredDown ? ExecutionTier::kLiftoff
2877                                                      : ExecutionTier::kTurbofan;
2878     int imported = module->num_imported_functions;
2879     // Generate necessary compilation units on the fly.
2880     for (int function_index : recompile_function_indexes) {
2881       DCHECK_LE(imported, function_index);
2882       int slot_index = function_index - imported;
2883       auto& progress = compilation_progress_[slot_index];
2884       progress = MissingRecompilationField::update(progress, true);
2885       builder->AddRecompilationUnit(function_index, new_tier);
2886     }
2887   }
2888 
2889   // Trigger callback if module needs no recompilation.
2890   if (outstanding_recompilation_functions_ == 0) {
2891     TriggerCallbacks(base::EnumSet<CompilationEvent>(
2892         {CompilationEvent::kFinishedRecompilation}));
2893   }
2894 
2895   if (builder.has_value()) {
2896     // Avoid holding lock while scheduling a compile job.
2897     guard.reset();
2898     builder->Commit();
2899   }
2900 }
2901 
AddCallback(CompilationState::callback_t callback)2902 void CompilationStateImpl::AddCallback(CompilationState::callback_t callback) {
2903   base::MutexGuard callbacks_guard(&callbacks_mutex_);
2904   // Immediately trigger events that already happened.
2905   for (auto event : {CompilationEvent::kFinishedExportWrappers,
2906                      CompilationEvent::kFinishedBaselineCompilation,
2907                      CompilationEvent::kFinishedTopTierCompilation,
2908                      CompilationEvent::kFailedCompilation}) {
2909     if (finished_events_.contains(event)) {
2910       callback(event);
2911     }
2912   }
2913   constexpr base::EnumSet<CompilationEvent> kFinalEvents{
2914       CompilationEvent::kFinishedTopTierCompilation,
2915       CompilationEvent::kFailedCompilation};
2916   if (!finished_events_.contains_any(kFinalEvents)) {
2917     callbacks_.emplace_back(std::move(callback));
2918   }
2919 }
2920 
AddCompilationUnits(Vector<WasmCompilationUnit> baseline_units,Vector<WasmCompilationUnit> top_tier_units,Vector<std::shared_ptr<JSToWasmWrapperCompilationUnit>> js_to_wasm_wrapper_units)2921 void CompilationStateImpl::AddCompilationUnits(
2922     Vector<WasmCompilationUnit> baseline_units,
2923     Vector<WasmCompilationUnit> top_tier_units,
2924     Vector<std::shared_ptr<JSToWasmWrapperCompilationUnit>>
2925         js_to_wasm_wrapper_units) {
2926   if (!baseline_units.empty() || !top_tier_units.empty()) {
2927     compilation_unit_queues_.AddUnits(baseline_units, top_tier_units,
2928                                       native_module_->module());
2929   }
2930   if (!js_to_wasm_wrapper_units.empty()) {
2931     // |js_to_wasm_wrapper_units_| can only be modified before background
2932     // compilation started.
2933     DCHECK(!current_compile_job_ || !current_compile_job_->IsRunning());
2934     js_to_wasm_wrapper_units_.insert(js_to_wasm_wrapper_units_.end(),
2935                                      js_to_wasm_wrapper_units.begin(),
2936                                      js_to_wasm_wrapper_units.end());
2937   }
2938   ScheduleCompileJobForNewUnits();
2939 }
2940 
AddTopTierCompilationUnit(WasmCompilationUnit unit)2941 void CompilationStateImpl::AddTopTierCompilationUnit(WasmCompilationUnit unit) {
2942   AddCompilationUnits({}, {&unit, 1}, {});
2943 }
2944 
AddTopTierPriorityCompilationUnit(WasmCompilationUnit unit,size_t priority)2945 void CompilationStateImpl::AddTopTierPriorityCompilationUnit(
2946     WasmCompilationUnit unit, size_t priority) {
2947   compilation_unit_queues_.AddTopTierPriorityUnit(unit, priority);
2948   ScheduleCompileJobForNewUnits();
2949 }
2950 
2951 std::shared_ptr<JSToWasmWrapperCompilationUnit>
GetNextJSToWasmWrapperCompilationUnit()2952 CompilationStateImpl::GetNextJSToWasmWrapperCompilationUnit() {
2953   int wrapper_id =
2954       js_to_wasm_wrapper_id_.fetch_add(1, std::memory_order_relaxed);
2955   if (wrapper_id < static_cast<int>(js_to_wasm_wrapper_units_.size())) {
2956     return js_to_wasm_wrapper_units_[wrapper_id];
2957   }
2958   return nullptr;
2959 }
2960 
FinalizeJSToWasmWrappers(Isolate * isolate,const WasmModule * module,Handle<FixedArray> * export_wrappers_out)2961 void CompilationStateImpl::FinalizeJSToWasmWrappers(
2962     Isolate* isolate, const WasmModule* module,
2963     Handle<FixedArray>* export_wrappers_out) {
2964   *export_wrappers_out = isolate->factory()->NewFixedArray(
2965       MaxNumExportWrappers(module), AllocationType::kOld);
2966   // TODO(6792): Wrappers below are allocated with {Factory::NewCode}. As an
2967   // optimization we keep the code space unlocked to avoid repeated unlocking
2968   // because many such wrapper are allocated in sequence below.
2969   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
2970                "wasm.FinalizeJSToWasmWrappers", "wrappers",
2971                js_to_wasm_wrapper_units_.size());
2972   CodeSpaceMemoryModificationScope modification_scope(isolate->heap());
2973   for (auto& unit : js_to_wasm_wrapper_units_) {
2974     Handle<Code> code = unit->Finalize(isolate);
2975     int wrapper_index =
2976         GetExportWrapperIndex(module, unit->sig(), unit->is_import());
2977     (*export_wrappers_out)->set(wrapper_index, *code);
2978     RecordStats(*code, isolate->counters());
2979   }
2980 }
2981 
GetQueueForCompileTask(int task_id)2982 CompilationUnitQueues::Queue* CompilationStateImpl::GetQueueForCompileTask(
2983     int task_id) {
2984   return compilation_unit_queues_.GetQueueForTask(task_id);
2985 }
2986 
2987 base::Optional<WasmCompilationUnit>
GetNextCompilationUnit(CompilationUnitQueues::Queue * queue,CompileBaselineOnly baseline_only)2988 CompilationStateImpl::GetNextCompilationUnit(
2989     CompilationUnitQueues::Queue* queue, CompileBaselineOnly baseline_only) {
2990   return compilation_unit_queues_.GetNextUnit(queue, baseline_only);
2991 }
2992 
OnFinishedUnits(Vector<WasmCode * > code_vector)2993 void CompilationStateImpl::OnFinishedUnits(Vector<WasmCode*> code_vector) {
2994   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
2995                "wasm.OnFinishedUnits", "units", code_vector.size());
2996 
2997   base::MutexGuard guard(&callbacks_mutex_);
2998 
2999   // In case of no outstanding compilation units we can return early.
3000   // This is especially important for lazy modules that were deserialized.
3001   // Compilation progress was not set up in these cases.
3002   if (outstanding_baseline_units_ == 0 && outstanding_export_wrappers_ == 0 &&
3003       outstanding_top_tier_functions_ == 0 &&
3004       outstanding_recompilation_functions_ == 0) {
3005     return;
3006   }
3007 
3008   // Assume an order of execution tiers that represents the quality of their
3009   // generated code.
3010   static_assert(ExecutionTier::kNone < ExecutionTier::kLiftoff &&
3011                     ExecutionTier::kLiftoff < ExecutionTier::kTurbofan,
3012                 "Assume an order on execution tiers");
3013 
3014   DCHECK_EQ(compilation_progress_.size(),
3015             native_module_->module()->num_declared_functions);
3016 
3017   base::EnumSet<CompilationEvent> triggered_events;
3018 
3019   for (size_t i = 0; i < code_vector.size(); i++) {
3020     WasmCode* code = code_vector[i];
3021     DCHECK_NOT_NULL(code);
3022     DCHECK_LT(code->index(), native_module_->num_functions());
3023 
3024     if (code->index() < native_module_->num_imported_functions()) {
3025       // Import wrapper.
3026       DCHECK_EQ(code->tier(), ExecutionTier::kTurbofan);
3027       outstanding_baseline_units_--;
3028     } else {
3029       // Function.
3030       DCHECK_NE(code->tier(), ExecutionTier::kNone);
3031 
3032       // Read function's compilation progress.
3033       // This view on the compilation progress may differ from the actually
3034       // compiled code. Any lazily compiled function does not contribute to the
3035       // compilation progress but may publish code to the code manager.
3036       int slot_index =
3037           declared_function_index(native_module_->module(), code->index());
3038       uint8_t function_progress = compilation_progress_[slot_index];
3039       ExecutionTier required_baseline_tier =
3040           RequiredBaselineTierField::decode(function_progress);
3041       ExecutionTier required_top_tier =
3042           RequiredTopTierField::decode(function_progress);
3043       ExecutionTier reached_tier = ReachedTierField::decode(function_progress);
3044 
3045       // Check whether required baseline or top tier are reached.
3046       if (reached_tier < required_baseline_tier &&
3047           required_baseline_tier <= code->tier()) {
3048         DCHECK_GT(outstanding_baseline_units_, 0);
3049         outstanding_baseline_units_--;
3050       }
3051       if (reached_tier < required_top_tier &&
3052           required_top_tier <= code->tier()) {
3053         DCHECK_GT(outstanding_top_tier_functions_, 0);
3054         outstanding_top_tier_functions_--;
3055       }
3056 
3057       if (V8_UNLIKELY(MissingRecompilationField::decode(function_progress))) {
3058         DCHECK_LT(0, outstanding_recompilation_functions_);
3059         // If tiering up, accept any TurboFan code. For tiering down, look at
3060         // the {for_debugging} flag. The tier can be Liftoff or TurboFan and is
3061         // irrelevant here. In particular, we want to ignore any outstanding
3062         // non-debugging units.
3063         bool matches = tiering_state_ == kTieredDown
3064                            ? code->for_debugging()
3065                            : code->tier() == ExecutionTier::kTurbofan;
3066         if (matches) {
3067           outstanding_recompilation_functions_--;
3068           compilation_progress_[slot_index] = MissingRecompilationField::update(
3069               compilation_progress_[slot_index], false);
3070           if (outstanding_recompilation_functions_ == 0) {
3071             triggered_events.Add(CompilationEvent::kFinishedRecompilation);
3072           }
3073         }
3074       }
3075 
3076       // Update function's compilation progress.
3077       if (code->tier() > reached_tier) {
3078         compilation_progress_[slot_index] = ReachedTierField::update(
3079             compilation_progress_[slot_index], code->tier());
3080       }
3081       DCHECK_LE(0, outstanding_baseline_units_);
3082     }
3083   }
3084 
3085   TriggerCallbacks(triggered_events);
3086 }
3087 
OnFinishedJSToWasmWrapperUnits(int num)3088 void CompilationStateImpl::OnFinishedJSToWasmWrapperUnits(int num) {
3089   if (num == 0) return;
3090   base::MutexGuard guard(&callbacks_mutex_);
3091   DCHECK_GE(outstanding_export_wrappers_, num);
3092   outstanding_export_wrappers_ -= num;
3093   TriggerCallbacks();
3094 }
3095 
TriggerCallbacks(base::EnumSet<CompilationEvent> triggered_events)3096 void CompilationStateImpl::TriggerCallbacks(
3097     base::EnumSet<CompilationEvent> triggered_events) {
3098   DCHECK(!callbacks_mutex_.TryLock());
3099 
3100   if (outstanding_export_wrappers_ == 0) {
3101     triggered_events.Add(CompilationEvent::kFinishedExportWrappers);
3102     if (outstanding_baseline_units_ == 0) {
3103       triggered_events.Add(CompilationEvent::kFinishedBaselineCompilation);
3104       if (outstanding_top_tier_functions_ == 0) {
3105         triggered_events.Add(CompilationEvent::kFinishedTopTierCompilation);
3106       }
3107     }
3108   }
3109 
3110   if (compile_failed_.load(std::memory_order_relaxed)) {
3111     // *Only* trigger the "failed" event.
3112     triggered_events =
3113         base::EnumSet<CompilationEvent>({CompilationEvent::kFailedCompilation});
3114   }
3115 
3116   if (triggered_events.empty()) return;
3117 
3118   // Don't trigger past events again.
3119   triggered_events -= finished_events_;
3120   // Recompilation can happen multiple times, thus do not store this.
3121   finished_events_ |=
3122       triggered_events - CompilationEvent::kFinishedRecompilation;
3123 
3124   for (auto event :
3125        {std::make_pair(CompilationEvent::kFailedCompilation,
3126                        "wasm.CompilationFailed"),
3127         std::make_pair(CompilationEvent::kFinishedExportWrappers,
3128                        "wasm.ExportWrappersFinished"),
3129         std::make_pair(CompilationEvent::kFinishedBaselineCompilation,
3130                        "wasm.BaselineFinished"),
3131         std::make_pair(CompilationEvent::kFinishedTopTierCompilation,
3132                        "wasm.TopTierFinished"),
3133         std::make_pair(CompilationEvent::kFinishedRecompilation,
3134                        "wasm.RecompilationFinished")}) {
3135     if (!triggered_events.contains(event.first)) continue;
3136     TRACE_EVENT0("v8.wasm", event.second);
3137     for (auto& callback : callbacks_) {
3138       callback(event.first);
3139     }
3140   }
3141 
3142   if (outstanding_baseline_units_ == 0 && outstanding_export_wrappers_ == 0 &&
3143       outstanding_top_tier_functions_ == 0 &&
3144       outstanding_recompilation_functions_ == 0) {
3145     // Clear the callbacks because no more events will be delivered.
3146     callbacks_.clear();
3147   }
3148 }
3149 
OnCompilationStopped(const WasmFeatures & detected)3150 void CompilationStateImpl::OnCompilationStopped(const WasmFeatures& detected) {
3151   base::MutexGuard guard(&mutex_);
3152   detected_features_.Add(detected);
3153 }
3154 
PublishDetectedFeatures(Isolate * isolate)3155 void CompilationStateImpl::PublishDetectedFeatures(Isolate* isolate) {
3156   // Notifying the isolate of the feature counts must take place under
3157   // the mutex, because even if we have finished baseline compilation,
3158   // tiering compilations may still occur in the background.
3159   base::MutexGuard guard(&mutex_);
3160   UpdateFeatureUseCounts(isolate, detected_features_);
3161 }
3162 
PublishCompilationResults(std::vector<std::unique_ptr<WasmCode>> unpublished_code)3163 void CompilationStateImpl::PublishCompilationResults(
3164     std::vector<std::unique_ptr<WasmCode>> unpublished_code) {
3165   if (unpublished_code.empty()) return;
3166 
3167   // For import wrapper compilation units, add result to the cache.
3168   int num_imported_functions = native_module_->num_imported_functions();
3169   WasmImportWrapperCache* cache = native_module_->import_wrapper_cache();
3170   for (const auto& code : unpublished_code) {
3171     int func_index = code->index();
3172     DCHECK_LE(0, func_index);
3173     DCHECK_LT(func_index, native_module_->num_functions());
3174     if (func_index < num_imported_functions) {
3175       const FunctionSig* sig =
3176           native_module_->module()->functions[func_index].sig;
3177       WasmImportWrapperCache::CacheKey key(
3178           compiler::kDefaultImportCallKind, sig,
3179           static_cast<int>(sig->parameter_count()));
3180       // If two imported functions have the same key, only one of them should
3181       // have been added as a compilation unit. So it is always the first time
3182       // we compile a wrapper for this key here.
3183       DCHECK_NULL((*cache)[key]);
3184       (*cache)[key] = code.get();
3185       code->IncRef();
3186     }
3187   }
3188   PublishCode(VectorOf(unpublished_code));
3189 }
3190 
PublishCode(Vector<std::unique_ptr<WasmCode>> code)3191 void CompilationStateImpl::PublishCode(Vector<std::unique_ptr<WasmCode>> code) {
3192   WasmCodeRefScope code_ref_scope;
3193   std::vector<WasmCode*> published_code =
3194       native_module_->PublishCode(std::move(code));
3195   // Defer logging code in case wire bytes were not fully received yet.
3196   if (native_module_->HasWireBytes()) {
3197     native_module_->engine()->LogCode(VectorOf(published_code));
3198   }
3199 
3200   OnFinishedUnits(VectorOf(std::move(published_code)));
3201 }
3202 
SchedulePublishCompilationResults(std::vector<std::unique_ptr<WasmCode>> unpublished_code)3203 void CompilationStateImpl::SchedulePublishCompilationResults(
3204     std::vector<std::unique_ptr<WasmCode>> unpublished_code) {
3205   {
3206     base::MutexGuard guard(&publish_mutex_);
3207     if (publisher_running_) {
3208       // Add new code to the queue and return.
3209       publish_queue_.reserve(publish_queue_.size() + unpublished_code.size());
3210       for (auto& c : unpublished_code) {
3211         publish_queue_.emplace_back(std::move(c));
3212       }
3213       return;
3214     }
3215     publisher_running_ = true;
3216   }
3217   while (true) {
3218     PublishCompilationResults(std::move(unpublished_code));
3219     unpublished_code.clear();
3220 
3221     // Keep publishing new code that came in.
3222     base::MutexGuard guard(&publish_mutex_);
3223     DCHECK(publisher_running_);
3224     if (publish_queue_.empty()) {
3225       publisher_running_ = false;
3226       return;
3227     }
3228     unpublished_code.swap(publish_queue_);
3229   }
3230 }
3231 
ScheduleCompileJobForNewUnits()3232 void CompilationStateImpl::ScheduleCompileJobForNewUnits() {
3233   if (failed()) return;
3234 
3235   std::shared_ptr<JobHandle> new_job_handle;
3236   {
3237     base::MutexGuard guard(&mutex_);
3238     if (current_compile_job_ && current_compile_job_->IsValid()) {
3239       current_compile_job_->NotifyConcurrencyIncrease();
3240       return;
3241     }
3242 
3243     std::unique_ptr<JobTask> new_compile_job =
3244         std::make_unique<BackgroundCompileJob>(native_module_weak_,
3245                                                async_counters_);
3246     // TODO(wasm): Lower priority for TurboFan-only jobs.
3247     new_job_handle = V8::GetCurrentPlatform()->PostJob(
3248         has_priority_ ? TaskPriority::kUserBlocking
3249                       : TaskPriority::kUserVisible,
3250         std::move(new_compile_job));
3251     current_compile_job_ = new_job_handle;
3252     // Reset the priority. Later uses of the compilation state, e.g. for
3253     // debugging, should compile with the default priority again.
3254     has_priority_ = false;
3255   }
3256 
3257   if (new_job_handle) {
3258     native_module_->engine()->ShepherdCompileJobHandle(
3259         std::move(new_job_handle));
3260   }
3261 }
3262 
NumOutstandingCompilations() const3263 size_t CompilationStateImpl::NumOutstandingCompilations() const {
3264   size_t next_wrapper = js_to_wasm_wrapper_id_.load(std::memory_order_relaxed);
3265   size_t outstanding_wrappers =
3266       next_wrapper >= js_to_wasm_wrapper_units_.size()
3267           ? 0
3268           : js_to_wasm_wrapper_units_.size() - next_wrapper;
3269   size_t outstanding_functions = compilation_unit_queues_.GetTotalSize();
3270   return outstanding_wrappers + outstanding_functions;
3271 }
3272 
SetError()3273 void CompilationStateImpl::SetError() {
3274   compile_cancelled_.store(true, std::memory_order_relaxed);
3275   if (compile_failed_.exchange(true, std::memory_order_relaxed)) {
3276     return;  // Already failed before.
3277   }
3278 
3279   base::MutexGuard callbacks_guard(&callbacks_mutex_);
3280   TriggerCallbacks();
3281   callbacks_.clear();
3282 }
3283 
WaitForCompilationEvent(CompilationEvent expect_event)3284 void CompilationStateImpl::WaitForCompilationEvent(
3285     CompilationEvent expect_event) {
3286   auto compilation_event_semaphore = std::make_shared<base::Semaphore>(0);
3287   base::EnumSet<CompilationEvent> events{expect_event,
3288                                          CompilationEvent::kFailedCompilation};
3289   {
3290     base::MutexGuard callbacks_guard(&callbacks_mutex_);
3291     if (finished_events_.contains_any(events)) return;
3292     callbacks_.emplace_back(
3293         [compilation_event_semaphore, events](CompilationEvent event) {
3294           if (events.contains(event)) compilation_event_semaphore->Signal();
3295         });
3296   }
3297 
3298   constexpr JobDelegate* kNoDelegate = nullptr;
3299   ExecuteCompilationUnits(native_module_weak_, async_counters_.get(),
3300                           kNoDelegate, kBaselineOnly);
3301   compilation_event_semaphore->Wait();
3302 }
3303 
3304 namespace {
3305 using JSToWasmWrapperQueue =
3306     WrapperQueue<JSToWasmWrapperKey, base::hash<JSToWasmWrapperKey>>;
3307 using JSToWasmWrapperUnitMap =
3308     std::unordered_map<JSToWasmWrapperKey,
3309                        std::unique_ptr<JSToWasmWrapperCompilationUnit>,
3310                        base::hash<JSToWasmWrapperKey>>;
3311 
3312 class CompileJSToWasmWrapperJob final : public JobTask {
3313  public:
CompileJSToWasmWrapperJob(JSToWasmWrapperQueue * queue,JSToWasmWrapperUnitMap * compilation_units,size_t max_concurrency)3314   CompileJSToWasmWrapperJob(JSToWasmWrapperQueue* queue,
3315                             JSToWasmWrapperUnitMap* compilation_units,
3316                             size_t max_concurrency)
3317       : queue_(queue),
3318         compilation_units_(compilation_units),
3319         outstanding_units_(queue->size()) {}
3320 
Run(JobDelegate * delegate)3321   void Run(JobDelegate* delegate) override {
3322     while (base::Optional<JSToWasmWrapperKey> key = queue_->pop()) {
3323       JSToWasmWrapperCompilationUnit* unit = (*compilation_units_)[*key].get();
3324       unit->Execute();
3325       outstanding_units_.fetch_sub(1, std::memory_order_relaxed);
3326       if (delegate->ShouldYield()) return;
3327     }
3328   }
3329 
GetMaxConcurrency(size_t) const3330   size_t GetMaxConcurrency(size_t /* worker_count */) const override {
3331     // {outstanding_units_} includes the units that other workers are currently
3332     // working on, so we can safely ignore the {worker_count} and just return
3333     // the current number of outstanding units.
3334     size_t flag_limit =
3335         static_cast<size_t>(std::max(1, FLAG_wasm_num_compilation_tasks));
3336     return std::min(flag_limit,
3337                     outstanding_units_.load(std::memory_order_relaxed));
3338   }
3339 
3340  private:
3341   JSToWasmWrapperQueue* const queue_;
3342   JSToWasmWrapperUnitMap* const compilation_units_;
3343   std::atomic<size_t> outstanding_units_;
3344 };
3345 }  // namespace
3346 
CompileJsToWasmWrappers(Isolate * isolate,const WasmModule * module,Handle<FixedArray> * export_wrappers_out)3347 void CompileJsToWasmWrappers(Isolate* isolate, const WasmModule* module,
3348                              Handle<FixedArray>* export_wrappers_out) {
3349   *export_wrappers_out = isolate->factory()->NewFixedArray(
3350       MaxNumExportWrappers(module), AllocationType::kOld);
3351 
3352   JSToWasmWrapperQueue queue;
3353   JSToWasmWrapperUnitMap compilation_units;
3354   WasmFeatures enabled_features = WasmFeatures::FromIsolate(isolate);
3355 
3356   // Prepare compilation units in the main thread.
3357   for (auto exp : module->export_table) {
3358     if (exp.kind != kExternalFunction) continue;
3359     auto& function = module->functions[exp.index];
3360     JSToWasmWrapperKey key(function.imported, *function.sig);
3361     if (queue.insert(key)) {
3362       auto unit = std::make_unique<JSToWasmWrapperCompilationUnit>(
3363           isolate, isolate->wasm_engine(), function.sig, module,
3364           function.imported, enabled_features,
3365           JSToWasmWrapperCompilationUnit::kAllowGeneric);
3366       compilation_units.emplace(key, std::move(unit));
3367     }
3368   }
3369 
3370   // Execute wrapper compilation in the background.
3371   int flag_value = FLAG_wasm_num_compilation_tasks;
3372   size_t max_concurrency = flag_value < 1 ? std::numeric_limits<size_t>::max()
3373                                           : static_cast<size_t>(flag_value);
3374   auto job = std::make_unique<CompileJSToWasmWrapperJob>(
3375       &queue, &compilation_units, max_concurrency);
3376   auto job_handle = V8::GetCurrentPlatform()->PostJob(
3377       TaskPriority::kUserVisible, std::move(job));
3378 
3379   // Wait for completion, while contributing to the work.
3380   job_handle->Join();
3381 
3382   // Finalize compilation jobs in the main thread.
3383   // TODO(6792): Wrappers below are allocated with {Factory::NewCode}. As an
3384   // optimization we keep the code space unlocked to avoid repeated unlocking
3385   // because many such wrapper are allocated in sequence below.
3386   CodeSpaceMemoryModificationScope modification_scope(isolate->heap());
3387   for (auto& pair : compilation_units) {
3388     JSToWasmWrapperKey key = pair.first;
3389     JSToWasmWrapperCompilationUnit* unit = pair.second.get();
3390     Handle<Code> code = unit->Finalize(isolate);
3391     int wrapper_index = GetExportWrapperIndex(module, &key.second, key.first);
3392     (*export_wrappers_out)->set(wrapper_index, *code);
3393     RecordStats(*code, isolate->counters());
3394   }
3395 }
3396 
CompileImportWrapper(WasmEngine * wasm_engine,NativeModule * native_module,Counters * counters,compiler::WasmImportCallKind kind,const FunctionSig * sig,int expected_arity,WasmImportWrapperCache::ModificationScope * cache_scope)3397 WasmCode* CompileImportWrapper(
3398     WasmEngine* wasm_engine, NativeModule* native_module, Counters* counters,
3399     compiler::WasmImportCallKind kind, const FunctionSig* sig,
3400     int expected_arity,
3401     WasmImportWrapperCache::ModificationScope* cache_scope) {
3402   // Entry should exist, so that we don't insert a new one and invalidate
3403   // other threads' iterators/references, but it should not have been compiled
3404   // yet.
3405   WasmImportWrapperCache::CacheKey key(kind, sig, expected_arity);
3406   DCHECK_NULL((*cache_scope)[key]);
3407   bool source_positions = is_asmjs_module(native_module->module());
3408   // Keep the {WasmCode} alive until we explicitly call {IncRef}.
3409   WasmCodeRefScope code_ref_scope;
3410   CompilationEnv env = native_module->CreateCompilationEnv();
3411   WasmCompilationResult result = compiler::CompileWasmImportCallWrapper(
3412       wasm_engine, &env, kind, sig, source_positions, expected_arity);
3413   std::unique_ptr<WasmCode> wasm_code = native_module->AddCode(
3414       result.func_index, result.code_desc, result.frame_slot_count,
3415       result.tagged_parameter_slots,
3416       result.protected_instructions_data.as_vector(),
3417       result.source_positions.as_vector(), GetCodeKind(result),
3418       ExecutionTier::kNone, kNoDebugging);
3419   WasmCode* published_code = native_module->PublishCode(std::move(wasm_code));
3420   (*cache_scope)[key] = published_code;
3421   published_code->IncRef();
3422   counters->wasm_generated_code_size()->Increment(
3423       published_code->instructions().length());
3424   counters->wasm_reloc_size()->Increment(published_code->reloc_info().length());
3425   return published_code;
3426 }
3427 
3428 }  // namespace wasm
3429 }  // namespace internal
3430 }  // namespace v8
3431 
3432 #undef TRACE_COMPILE
3433 #undef TRACE_STREAMING
3434 #undef TRACE_LAZY
3435