• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/handles/global-handles-inl.h"
20 #include "src/heap/heap-inl.h"  // For CodePageCollectionMemoryModificationScope.
21 #include "src/logging/counters-scopes.h"
22 #include "src/logging/metrics.h"
23 #include "src/objects/property-descriptor.h"
24 #include "src/tasks/task-utils.h"
25 #include "src/tracing/trace-event.h"
26 #include "src/trap-handler/trap-handler.h"
27 #include "src/utils/identity-map.h"
28 #include "src/wasm/code-space-access.h"
29 #include "src/wasm/module-decoder.h"
30 #include "src/wasm/streaming-decoder.h"
31 #include "src/wasm/wasm-code-manager.h"
32 #include "src/wasm/wasm-engine.h"
33 #include "src/wasm/wasm-import-wrapper-cache.h"
34 #include "src/wasm/wasm-js.h"
35 #include "src/wasm/wasm-limits.h"
36 #include "src/wasm/wasm-objects-inl.h"
37 #include "src/wasm/wasm-result.h"
38 #include "src/wasm/wasm-serialization.h"
39 
40 #define TRACE_COMPILE(...)                             \
41   do {                                                 \
42     if (FLAG_trace_wasm_compiler) PrintF(__VA_ARGS__); \
43   } while (false)
44 
45 #define TRACE_STREAMING(...)                            \
46   do {                                                  \
47     if (FLAG_trace_wasm_streaming) PrintF(__VA_ARGS__); \
48   } while (false)
49 
50 #define TRACE_LAZY(...)                                        \
51   do {                                                         \
52     if (FLAG_trace_wasm_lazy_compilation) PrintF(__VA_ARGS__); \
53   } while (false)
54 
55 namespace v8 {
56 namespace internal {
57 namespace wasm {
58 
59 namespace {
60 
61 enum class CompileStrategy : uint8_t {
62   // Compiles functions on first use. In this case, execution will block until
63   // the function's baseline is reached and top tier compilation starts in
64   // background (if applicable).
65   // Lazy compilation can help to reduce startup time and code size at the risk
66   // of blocking execution.
67   kLazy,
68   // Compiles baseline ahead of execution and starts top tier compilation in
69   // background (if applicable).
70   kEager,
71   // Triggers baseline compilation on first use (just like {kLazy}) with the
72   // difference that top tier compilation is started eagerly.
73   // This strategy can help to reduce startup time at the risk of blocking
74   // execution, but only in its early phase (until top tier compilation
75   // finishes).
76   kLazyBaselineEagerTopTier,
77   // Marker for default strategy.
78   kDefault = kEager,
79 };
80 
81 class CompilationStateImpl;
82 class CompilationUnitBuilder;
83 
84 class V8_NODISCARD 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(base::Vector<WasmCompilationUnit> baseline_units,base::Vector<WasmCompilationUnit> top_tier_units,const WasmModule * module)190   void AddUnits(base::Vector<WasmCompilationUnit> baseline_units,
191                 base::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       base::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::__anon54d755100111::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::__anon54d755100111::CompilationUnitQueues::BigUnit281     bool operator<(const BigUnit& other) const {
282       return func_size < other.func_size;
283     }
284   };
285 
286   struct TopTierPriorityUnit {
TopTierPriorityUnitv8::internal::wasm::__anon54d755100111::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::__anon54d755100111::CompilationUnitQueues::TopTierPriorityUnit293     bool operator<(const TopTierPriorityUnit& other) const {
294       return priority < other.priority;
295     }
296   };
297 
298   struct BigUnitsQueue {
BigUnitsQueuev8::internal::wasm::__anon54d755100111::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::__anon54d755100111::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                        DynamicTiering dynamic_tiering);
~CompilationStateImpl()534   ~CompilationStateImpl() {
535     if (compile_job_->IsValid()) compile_job_->CancelAndDetach();
536   }
537 
538   // Call right after the constructor, after the {compilation_state_} field in
539   // the {NativeModule} has been initialized.
540   void InitCompileJob();
541 
542   // {kCancelUnconditionally}: Cancel all compilation.
543   // {kCancelInitialCompilation}: Cancel all compilation if initial (baseline)
544   // compilation is not finished yet.
545   enum CancellationPolicy { kCancelUnconditionally, kCancelInitialCompilation };
546   void CancelCompilation(CancellationPolicy);
547 
548   bool cancelled() const;
549 
550   // Initialize compilation progress. Set compilation tiers to expect for
551   // baseline and top tier compilation. Must be set before
552   // {CommitCompilationUnits} is invoked which triggers background compilation.
553   void InitializeCompilationProgress(bool lazy_module, int num_import_wrappers,
554                                      int num_export_wrappers);
555 
556   // Initialize the compilation progress after deserialization. This is needed
557   // for recompilation (e.g. for tier down) to work later.
558   void InitializeCompilationProgressAfterDeserialization(
559       base::Vector<const int> lazy_functions,
560       base::Vector<const int> liftoff_functions);
561 
562   // Initializes compilation units based on the information encoded in the
563   // {compilation_progress_}.
564   void InitializeCompilationUnits(
565       std::unique_ptr<CompilationUnitBuilder> builder);
566 
567   // Adds compilation units for another function to the
568   // {CompilationUnitBuilder}. This function is the streaming compilation
569   // equivalent to {InitializeCompilationUnits}.
570   void AddCompilationUnit(CompilationUnitBuilder* builder, int func_index);
571 
572   // Initialize recompilation of the whole module: Setup compilation progress
573   // for recompilation and add the respective compilation units. The callback is
574   // called immediately if no recompilation is needed, or called later
575   // otherwise.
576   void InitializeRecompilation(TieringState new_tiering_state,
577                                std::unique_ptr<CompilationEventCallback>
578                                    recompilation_finished_callback);
579 
580   // Add the callback to be called on compilation events. Needs to be
581   // set before {CommitCompilationUnits} is run to ensure that it receives all
582   // events. The callback object must support being deleted from any thread.
583   void AddCallback(std::unique_ptr<CompilationEventCallback> callback);
584 
585   // Inserts new functions to compile and kicks off compilation.
586   void CommitCompilationUnits(
587       base::Vector<WasmCompilationUnit> baseline_units,
588       base::Vector<WasmCompilationUnit> top_tier_units,
589       base::Vector<std::shared_ptr<JSToWasmWrapperCompilationUnit>>
590           js_to_wasm_wrapper_units);
591   void CommitTopTierCompilationUnit(WasmCompilationUnit);
592   void AddTopTierPriorityCompilationUnit(WasmCompilationUnit, size_t);
593 
594   CompilationUnitQueues::Queue* GetQueueForCompileTask(int task_id);
595 
596   base::Optional<WasmCompilationUnit> GetNextCompilationUnit(
597       CompilationUnitQueues::Queue*, CompileBaselineOnly);
598 
599   std::shared_ptr<JSToWasmWrapperCompilationUnit>
600   GetNextJSToWasmWrapperCompilationUnit();
601   void FinalizeJSToWasmWrappers(Isolate* isolate, const WasmModule* module,
602                                 Handle<FixedArray>* export_wrappers_out);
603 
604   void OnFinishedUnits(base::Vector<WasmCode*>);
605   void OnFinishedJSToWasmWrapperUnits(int num);
606 
607   void OnCompilationStopped(WasmFeatures detected);
608   void PublishDetectedFeatures(Isolate*);
609   void SchedulePublishCompilationResults(
610       std::vector<std::unique_ptr<WasmCode>> unpublished_code);
611 
612   size_t NumOutstandingCompilations() const;
613 
614   void SetError();
615 
616   void WaitForCompilationEvent(CompilationEvent event);
617 
SetHighPriority()618   void SetHighPriority() {
619     // TODO(wasm): Keep a lower priority for TurboFan-only jobs.
620     compile_job_->UpdatePriority(TaskPriority::kUserBlocking);
621   }
622 
failed() const623   bool failed() const {
624     return compile_failed_.load(std::memory_order_relaxed);
625   }
626 
baseline_compilation_finished() const627   bool baseline_compilation_finished() const {
628     base::MutexGuard guard(&callbacks_mutex_);
629     return outstanding_baseline_units_ == 0 &&
630            outstanding_export_wrappers_ == 0;
631   }
632 
top_tier_compilation_finished() const633   bool top_tier_compilation_finished() const {
634     base::MutexGuard guard(&callbacks_mutex_);
635     return outstanding_top_tier_functions_ == 0;
636   }
637 
recompilation_finished() const638   bool recompilation_finished() const {
639     base::MutexGuard guard(&callbacks_mutex_);
640     return outstanding_recompilation_functions_ == 0;
641   }
642 
dynamic_tiering() const643   DynamicTiering dynamic_tiering() const { return dynamic_tiering_; }
644 
counters() const645   Counters* counters() const { return async_counters_.get(); }
646 
SetWireBytesStorage(std::shared_ptr<WireBytesStorage> wire_bytes_storage)647   void SetWireBytesStorage(
648       std::shared_ptr<WireBytesStorage> wire_bytes_storage) {
649     base::MutexGuard guard(&mutex_);
650     wire_bytes_storage_ = std::move(wire_bytes_storage);
651   }
652 
GetWireBytesStorage() const653   std::shared_ptr<WireBytesStorage> GetWireBytesStorage() const {
654     base::MutexGuard guard(&mutex_);
655     DCHECK_NOT_NULL(wire_bytes_storage_);
656     return wire_bytes_storage_;
657   }
658 
set_compilation_id(int compilation_id)659   void set_compilation_id(int compilation_id) {
660     DCHECK_EQ(compilation_id_, kInvalidCompilationID);
661     compilation_id_ = compilation_id;
662   }
663 
native_module_weak() const664   std::weak_ptr<NativeModule> const native_module_weak() const {
665     return native_module_weak_;
666   }
667 
668  private:
669   uint8_t SetupCompilationProgressForFunction(
670       bool lazy_function, NativeModule* module,
671       const WasmFeatures& enabled_features, int func_index);
672 
673   // Returns the potentially-updated {function_progress}.
674   uint8_t AddCompilationUnitInternal(CompilationUnitBuilder* builder,
675                                      int function_index,
676                                      uint8_t function_progress);
677 
678   // Trigger callbacks according to the internal counters below
679   // (outstanding_...), plus the given events.
680   // Hold the {callbacks_mutex_} when calling this method.
681   void TriggerCallbacks(base::EnumSet<CompilationEvent> additional_events = {});
682 
683   void PublishCompilationResults(
684       std::vector<std::unique_ptr<WasmCode>> unpublished_code);
685   void PublishCode(base::Vector<std::unique_ptr<WasmCode>> codes);
686 
687   NativeModule* const native_module_;
688   std::weak_ptr<NativeModule> const native_module_weak_;
689   const std::shared_ptr<Counters> async_counters_;
690 
691   // Compilation error, atomically updated. This flag can be updated and read
692   // using relaxed semantics.
693   std::atomic<bool> compile_failed_{false};
694 
695   // True if compilation was cancelled and worker threads should return. This
696   // flag can be updated and read using relaxed semantics.
697   std::atomic<bool> compile_cancelled_{false};
698 
699   CompilationUnitQueues compilation_unit_queues_;
700 
701   // Number of wrappers to be compiled. Initialized once, counted down in
702   // {GetNextJSToWasmWrapperCompilationUnit}.
703   std::atomic<size_t> outstanding_js_to_wasm_wrappers_{0};
704   // Wrapper compilation units are stored in shared_ptrs so that they are kept
705   // alive by the tasks even if the NativeModule dies.
706   std::vector<std::shared_ptr<JSToWasmWrapperCompilationUnit>>
707       js_to_wasm_wrapper_units_;
708 
709   // Cache the dynamic tiering configuration to be consistent for the whole
710   // compilation.
711   const DynamicTiering dynamic_tiering_;
712 
713   // This mutex protects all information of this {CompilationStateImpl} which is
714   // being accessed concurrently.
715   mutable base::Mutex mutex_;
716 
717   // The compile job handle, initialized right after construction of
718   // {CompilationStateImpl}.
719   std::unique_ptr<JobHandle> compile_job_;
720 
721   // The compilation id to identify trace events linked to this compilation.
722   static constexpr int kInvalidCompilationID = -1;
723   int compilation_id_ = kInvalidCompilationID;
724 
725   //////////////////////////////////////////////////////////////////////////////
726   // Protected by {mutex_}:
727 
728   // Features detected to be used in this module. Features can be detected
729   // as a module is being compiled.
730   WasmFeatures detected_features_ = WasmFeatures::None();
731 
732   // Abstraction over the storage of the wire bytes. Held in a shared_ptr so
733   // that background compilation jobs can keep the storage alive while
734   // compiling.
735   std::shared_ptr<WireBytesStorage> wire_bytes_storage_;
736 
737   // End of fields protected by {mutex_}.
738   //////////////////////////////////////////////////////////////////////////////
739 
740   // This mutex protects the callbacks vector, and the counters used to
741   // determine which callbacks to call. The counters plus the callbacks
742   // themselves need to be synchronized to ensure correct order of events.
743   mutable base::Mutex callbacks_mutex_;
744 
745   //////////////////////////////////////////////////////////////////////////////
746   // Protected by {callbacks_mutex_}:
747 
748   // Callbacks to be called on compilation events.
749   std::vector<std::unique_ptr<CompilationEventCallback>> callbacks_;
750 
751   // Events that already happened.
752   base::EnumSet<CompilationEvent> finished_events_;
753 
754   int outstanding_baseline_units_ = 0;
755   int outstanding_export_wrappers_ = 0;
756   int outstanding_top_tier_functions_ = 0;
757   // The amount of generated top tier code since the last
758   // {kFinishedCompilationChunk} event.
759   size_t bytes_since_last_chunk_ = 0;
760   std::vector<uint8_t> compilation_progress_;
761 
762   int outstanding_recompilation_functions_ = 0;
763   TieringState tiering_state_ = kTieredUp;
764 
765   // End of fields protected by {callbacks_mutex_}.
766   //////////////////////////////////////////////////////////////////////////////
767 
768   // {publish_mutex_} protects {publish_queue_} and {publisher_running_}.
769   base::Mutex publish_mutex_;
770   std::vector<std::unique_ptr<WasmCode>> publish_queue_;
771   bool publisher_running_ = false;
772 
773   // Encoding of fields in the {compilation_progress_} vector.
774   using RequiredBaselineTierField = base::BitField8<ExecutionTier, 0, 2>;
775   using RequiredTopTierField = base::BitField8<ExecutionTier, 2, 2>;
776   using ReachedTierField = base::BitField8<ExecutionTier, 4, 2>;
777   using MissingRecompilationField = base::BitField8<bool, 6, 1>;
778 };
779 
Impl(CompilationState * compilation_state)780 CompilationStateImpl* Impl(CompilationState* compilation_state) {
781   return reinterpret_cast<CompilationStateImpl*>(compilation_state);
782 }
Impl(const CompilationState * compilation_state)783 const CompilationStateImpl* Impl(const CompilationState* compilation_state) {
784   return reinterpret_cast<const CompilationStateImpl*>(compilation_state);
785 }
786 
compilation_state() const787 CompilationStateImpl* BackgroundCompileScope::compilation_state() const {
788   DCHECK(native_module_);
789   return Impl(native_module_->compilation_state());
790 }
791 
cancelled() const792 bool BackgroundCompileScope::cancelled() const {
793   return native_module_ == nullptr ||
794          Impl(native_module_->compilation_state())->cancelled();
795 }
796 
UpdateFeatureUseCounts(Isolate * isolate,const WasmFeatures & detected)797 void UpdateFeatureUseCounts(Isolate* isolate, const WasmFeatures& detected) {
798   using Feature = v8::Isolate::UseCounterFeature;
799   constexpr static std::pair<WasmFeature, Feature> kUseCounters[] = {
800       {kFeature_reftypes, Feature::kWasmRefTypes},
801       {kFeature_simd, Feature::kWasmSimdOpcodes},
802       {kFeature_threads, Feature::kWasmThreadOpcodes},
803       {kFeature_eh, Feature::kWasmExceptionHandling}};
804 
805   for (auto& feature : kUseCounters) {
806     if (detected.contains(feature.first)) isolate->CountUsage(feature.second);
807   }
808 }
809 
810 }  // namespace
811 
812 //////////////////////////////////////////////////////
813 // PIMPL implementation of {CompilationState}.
814 
~CompilationState()815 CompilationState::~CompilationState() { Impl(this)->~CompilationStateImpl(); }
816 
InitCompileJob()817 void CompilationState::InitCompileJob() { Impl(this)->InitCompileJob(); }
818 
CancelCompilation()819 void CompilationState::CancelCompilation() {
820   Impl(this)->CancelCompilation(CompilationStateImpl::kCancelUnconditionally);
821 }
822 
CancelInitialCompilation()823 void CompilationState::CancelInitialCompilation() {
824   Impl(this)->CancelCompilation(
825       CompilationStateImpl::kCancelInitialCompilation);
826 }
827 
SetError()828 void CompilationState::SetError() { Impl(this)->SetError(); }
829 
SetWireBytesStorage(std::shared_ptr<WireBytesStorage> wire_bytes_storage)830 void CompilationState::SetWireBytesStorage(
831     std::shared_ptr<WireBytesStorage> wire_bytes_storage) {
832   Impl(this)->SetWireBytesStorage(std::move(wire_bytes_storage));
833 }
834 
GetWireBytesStorage() const835 std::shared_ptr<WireBytesStorage> CompilationState::GetWireBytesStorage()
836     const {
837   return Impl(this)->GetWireBytesStorage();
838 }
839 
AddCallback(std::unique_ptr<CompilationEventCallback> callback)840 void CompilationState::AddCallback(
841     std::unique_ptr<CompilationEventCallback> callback) {
842   return Impl(this)->AddCallback(std::move(callback));
843 }
844 
WaitForTopTierFinished()845 void CompilationState::WaitForTopTierFinished() {
846   Impl(this)->WaitForCompilationEvent(
847       CompilationEvent::kFinishedTopTierCompilation);
848 }
849 
SetHighPriority()850 void CompilationState::SetHighPriority() { Impl(this)->SetHighPriority(); }
851 
InitializeAfterDeserialization(base::Vector<const int> lazy_functions,base::Vector<const int> liftoff_functions)852 void CompilationState::InitializeAfterDeserialization(
853     base::Vector<const int> lazy_functions,
854     base::Vector<const int> liftoff_functions) {
855   Impl(this)->InitializeCompilationProgressAfterDeserialization(
856       lazy_functions, liftoff_functions);
857 }
858 
failed() const859 bool CompilationState::failed() const { return Impl(this)->failed(); }
860 
baseline_compilation_finished() const861 bool CompilationState::baseline_compilation_finished() const {
862   return Impl(this)->baseline_compilation_finished();
863 }
864 
top_tier_compilation_finished() const865 bool CompilationState::top_tier_compilation_finished() const {
866   return Impl(this)->top_tier_compilation_finished();
867 }
868 
recompilation_finished() const869 bool CompilationState::recompilation_finished() const {
870   return Impl(this)->recompilation_finished();
871 }
872 
set_compilation_id(int compilation_id)873 void CompilationState::set_compilation_id(int compilation_id) {
874   Impl(this)->set_compilation_id(compilation_id);
875 }
876 
dynamic_tiering() const877 DynamicTiering CompilationState::dynamic_tiering() const {
878   return Impl(this)->dynamic_tiering();
879 }
880 
881 // static
New(const std::shared_ptr<NativeModule> & native_module,std::shared_ptr<Counters> async_counters,DynamicTiering dynamic_tiering)882 std::unique_ptr<CompilationState> CompilationState::New(
883     const std::shared_ptr<NativeModule>& native_module,
884     std::shared_ptr<Counters> async_counters, DynamicTiering dynamic_tiering) {
885   return std::unique_ptr<CompilationState>(reinterpret_cast<CompilationState*>(
886       new CompilationStateImpl(std::move(native_module),
887                                std::move(async_counters), dynamic_tiering)));
888 }
889 
890 // End of PIMPL implementation of {CompilationState}.
891 //////////////////////////////////////////////////////
892 
893 namespace {
894 
ApplyHintToExecutionTier(WasmCompilationHintTier hint,ExecutionTier default_tier)895 ExecutionTier ApplyHintToExecutionTier(WasmCompilationHintTier hint,
896                                        ExecutionTier default_tier) {
897   switch (hint) {
898     case WasmCompilationHintTier::kDefault:
899       return default_tier;
900     case WasmCompilationHintTier::kBaseline:
901       return ExecutionTier::kLiftoff;
902     case WasmCompilationHintTier::kOptimized:
903       return ExecutionTier::kTurbofan;
904   }
905   UNREACHABLE();
906 }
907 
GetCompilationHint(const WasmModule * module,uint32_t func_index)908 const WasmCompilationHint* GetCompilationHint(const WasmModule* module,
909                                               uint32_t func_index) {
910   DCHECK_LE(module->num_imported_functions, func_index);
911   uint32_t hint_index = declared_function_index(module, func_index);
912   const std::vector<WasmCompilationHint>& compilation_hints =
913       module->compilation_hints;
914   if (hint_index < compilation_hints.size()) {
915     return &compilation_hints[hint_index];
916   }
917   return nullptr;
918 }
919 
GetCompileStrategy(const WasmModule * module,const WasmFeatures & enabled_features,uint32_t func_index,bool lazy_module)920 CompileStrategy GetCompileStrategy(const WasmModule* module,
921                                    const WasmFeatures& enabled_features,
922                                    uint32_t func_index, bool lazy_module) {
923   if (lazy_module) return CompileStrategy::kLazy;
924   if (!enabled_features.has_compilation_hints()) {
925     return CompileStrategy::kDefault;
926   }
927   auto* hint = GetCompilationHint(module, func_index);
928   if (hint == nullptr) return CompileStrategy::kDefault;
929   switch (hint->strategy) {
930     case WasmCompilationHintStrategy::kLazy:
931       return CompileStrategy::kLazy;
932     case WasmCompilationHintStrategy::kEager:
933       return CompileStrategy::kEager;
934     case WasmCompilationHintStrategy::kLazyBaselineEagerTopTier:
935       return CompileStrategy::kLazyBaselineEagerTopTier;
936     case WasmCompilationHintStrategy::kDefault:
937       return CompileStrategy::kDefault;
938   }
939 }
940 
941 struct ExecutionTierPair {
942   ExecutionTier baseline_tier;
943   ExecutionTier top_tier;
944 };
945 
GetRequestedExecutionTiers(NativeModule * native_module,const WasmFeatures & enabled_features,uint32_t func_index)946 ExecutionTierPair GetRequestedExecutionTiers(
947     NativeModule* native_module, const WasmFeatures& enabled_features,
948     uint32_t func_index) {
949   const WasmModule* module = native_module->module();
950   ExecutionTierPair result;
951 
952   result.baseline_tier = WasmCompilationUnit::GetBaselineExecutionTier(module);
953 
954   bool dynamic_tiering =
955       Impl(native_module->compilation_state())->dynamic_tiering() ==
956       DynamicTiering::kEnabled;
957   bool tier_up_enabled = !dynamic_tiering && FLAG_wasm_tier_up;
958   if (module->origin != kWasmOrigin || !tier_up_enabled ||
959       V8_UNLIKELY(FLAG_wasm_tier_up_filter >= 0 &&
960                   func_index !=
961                       static_cast<uint32_t>(FLAG_wasm_tier_up_filter))) {
962     result.top_tier = result.baseline_tier;
963     return result;
964   }
965 
966   // Default tiering behaviour.
967   result.top_tier = ExecutionTier::kTurbofan;
968 
969   // Check if compilation hints override default tiering behaviour.
970   if (enabled_features.has_compilation_hints()) {
971     const WasmCompilationHint* hint = GetCompilationHint(module, func_index);
972     if (hint != nullptr) {
973       result.baseline_tier =
974           ApplyHintToExecutionTier(hint->baseline_tier, result.baseline_tier);
975       result.top_tier =
976           ApplyHintToExecutionTier(hint->top_tier, result.top_tier);
977     }
978   }
979 
980   // Correct top tier if necessary.
981   static_assert(ExecutionTier::kLiftoff < ExecutionTier::kTurbofan,
982                 "Assume an order on execution tiers");
983   if (result.baseline_tier > result.top_tier) {
984     result.top_tier = result.baseline_tier;
985   }
986   return result;
987 }
988 
989 // The {CompilationUnitBuilder} builds compilation units and stores them in an
990 // internal buffer. The buffer is moved into the working queue of the
991 // {CompilationStateImpl} when {Commit} is called.
992 class CompilationUnitBuilder {
993  public:
CompilationUnitBuilder(NativeModule * native_module)994   explicit CompilationUnitBuilder(NativeModule* native_module)
995       : native_module_(native_module) {}
996 
AddUnits(uint32_t func_index)997   void AddUnits(uint32_t func_index) {
998     if (func_index < native_module_->module()->num_imported_functions) {
999       baseline_units_.emplace_back(func_index, ExecutionTier::kNone,
1000                                    kNoDebugging);
1001       return;
1002     }
1003     ExecutionTierPair tiers = GetRequestedExecutionTiers(
1004         native_module_, native_module_->enabled_features(), func_index);
1005     // Compile everything for non-debugging initially. If needed, we will tier
1006     // down when the module is fully compiled. Synchronization would be pretty
1007     // difficult otherwise.
1008     baseline_units_.emplace_back(func_index, tiers.baseline_tier, kNoDebugging);
1009     if (tiers.baseline_tier != tiers.top_tier) {
1010       tiering_units_.emplace_back(func_index, tiers.top_tier, kNoDebugging);
1011     }
1012   }
1013 
AddJSToWasmWrapperUnit(std::shared_ptr<JSToWasmWrapperCompilationUnit> unit)1014   void AddJSToWasmWrapperUnit(
1015       std::shared_ptr<JSToWasmWrapperCompilationUnit> unit) {
1016     js_to_wasm_wrapper_units_.emplace_back(std::move(unit));
1017   }
1018 
AddBaselineUnit(int func_index,ExecutionTier tier)1019   void AddBaselineUnit(int func_index, ExecutionTier tier) {
1020     baseline_units_.emplace_back(func_index, tier, kNoDebugging);
1021   }
1022 
AddTopTierUnit(int func_index,ExecutionTier tier)1023   void AddTopTierUnit(int func_index, ExecutionTier tier) {
1024     tiering_units_.emplace_back(func_index, tier, kNoDebugging);
1025   }
1026 
AddDebugUnit(int func_index)1027   void AddDebugUnit(int func_index) {
1028     baseline_units_.emplace_back(func_index, ExecutionTier::kLiftoff,
1029                                  kForDebugging);
1030   }
1031 
AddRecompilationUnit(int func_index,ExecutionTier tier)1032   void AddRecompilationUnit(int func_index, ExecutionTier tier) {
1033     // For recompilation, just treat all units like baseline units.
1034     baseline_units_.emplace_back(
1035         func_index, tier,
1036         tier == ExecutionTier::kLiftoff ? kForDebugging : kNoDebugging);
1037   }
1038 
Commit()1039   bool Commit() {
1040     if (baseline_units_.empty() && tiering_units_.empty() &&
1041         js_to_wasm_wrapper_units_.empty()) {
1042       return false;
1043     }
1044     compilation_state()->CommitCompilationUnits(
1045         base::VectorOf(baseline_units_), base::VectorOf(tiering_units_),
1046         base::VectorOf(js_to_wasm_wrapper_units_));
1047     Clear();
1048     return true;
1049   }
1050 
Clear()1051   void Clear() {
1052     baseline_units_.clear();
1053     tiering_units_.clear();
1054     js_to_wasm_wrapper_units_.clear();
1055   }
1056 
module()1057   const WasmModule* module() { return native_module_->module(); }
1058 
1059  private:
compilation_state() const1060   CompilationStateImpl* compilation_state() const {
1061     return Impl(native_module_->compilation_state());
1062   }
1063 
1064   NativeModule* const native_module_;
1065   std::vector<WasmCompilationUnit> baseline_units_;
1066   std::vector<WasmCompilationUnit> tiering_units_;
1067   std::vector<std::shared_ptr<JSToWasmWrapperCompilationUnit>>
1068       js_to_wasm_wrapper_units_;
1069 };
1070 
SetCompileError(ErrorThrower * thrower,ModuleWireBytes wire_bytes,const WasmFunction * func,const WasmModule * module,WasmError error)1071 void SetCompileError(ErrorThrower* thrower, ModuleWireBytes wire_bytes,
1072                      const WasmFunction* func, const WasmModule* module,
1073                      WasmError error) {
1074   WasmName name = wire_bytes.GetNameOrNull(func, module);
1075   if (name.begin() == nullptr) {
1076     thrower->CompileError("Compiling function #%d failed: %s @+%u",
1077                           func->func_index, error.message().c_str(),
1078                           error.offset());
1079   } else {
1080     TruncatedUserString<> truncated_name(name);
1081     thrower->CompileError("Compiling function #%d:\"%.*s\" failed: %s @+%u",
1082                           func->func_index, truncated_name.length(),
1083                           truncated_name.start(), error.message().c_str(),
1084                           error.offset());
1085   }
1086 }
1087 
ValidateSingleFunction(const WasmModule * module,int func_index,base::Vector<const uint8_t> code,Counters * counters,AccountingAllocator * allocator,WasmFeatures enabled_features)1088 DecodeResult ValidateSingleFunction(const WasmModule* module, int func_index,
1089                                     base::Vector<const uint8_t> code,
1090                                     Counters* counters,
1091                                     AccountingAllocator* allocator,
1092                                     WasmFeatures enabled_features) {
1093   const WasmFunction* func = &module->functions[func_index];
1094   FunctionBody body{func->sig, func->code.offset(), code.begin(), code.end()};
1095   DecodeResult result;
1096 
1097   WasmFeatures detected;
1098   return VerifyWasmCode(allocator, enabled_features, module, &detected, body);
1099 }
1100 
1101 enum OnlyLazyFunctions : bool {
1102   kAllFunctions = false,
1103   kOnlyLazyFunctions = true,
1104 };
1105 
ValidateSequentially(const WasmModule * module,NativeModule * native_module,Counters * counters,AccountingAllocator * allocator,ErrorThrower * thrower,bool lazy_module,OnlyLazyFunctions only_lazy_functions=kAllFunctions)1106 void ValidateSequentially(
1107     const WasmModule* module, NativeModule* native_module, Counters* counters,
1108     AccountingAllocator* allocator, ErrorThrower* thrower, bool lazy_module,
1109     OnlyLazyFunctions only_lazy_functions = kAllFunctions) {
1110   DCHECK(!thrower->error());
1111   uint32_t start = module->num_imported_functions;
1112   uint32_t end = start + module->num_declared_functions;
1113   auto enabled_features = native_module->enabled_features();
1114   for (uint32_t func_index = start; func_index < end; func_index++) {
1115     // Skip non-lazy functions if requested.
1116     if (only_lazy_functions) {
1117       CompileStrategy strategy =
1118           GetCompileStrategy(module, enabled_features, func_index, lazy_module);
1119       if (strategy != CompileStrategy::kLazy &&
1120           strategy != CompileStrategy::kLazyBaselineEagerTopTier) {
1121         continue;
1122       }
1123     }
1124 
1125     ModuleWireBytes wire_bytes{native_module->wire_bytes()};
1126     const WasmFunction* func = &module->functions[func_index];
1127     base::Vector<const uint8_t> code = wire_bytes.GetFunctionBytes(func);
1128     DecodeResult result = ValidateSingleFunction(
1129         module, func_index, code, counters, allocator, enabled_features);
1130     if (result.failed()) {
1131       SetCompileError(thrower, wire_bytes, func, module, result.error());
1132     }
1133   }
1134 }
1135 
IsLazyModule(const WasmModule * module)1136 bool IsLazyModule(const WasmModule* module) {
1137   return FLAG_wasm_lazy_compilation ||
1138          (FLAG_asm_wasm_lazy_compilation && is_asmjs_module(module));
1139 }
1140 
1141 }  // namespace
1142 
CompileLazy(Isolate * isolate,Handle<WasmInstanceObject> instance,int func_index)1143 bool CompileLazy(Isolate* isolate, Handle<WasmInstanceObject> instance,
1144                  int func_index) {
1145   Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
1146   NativeModule* native_module = module_object->native_module();
1147   const WasmModule* module = native_module->module();
1148   auto enabled_features = native_module->enabled_features();
1149   Counters* counters = isolate->counters();
1150 
1151   // Put the timer scope around everything, including the {CodeSpaceWriteScope}
1152   // and its destruction, to measure complete overhead (apart from the runtime
1153   // function itself, which has constant overhead).
1154   base::Optional<TimedHistogramScope> lazy_compile_time_scope;
1155   if (base::TimeTicks::IsHighResolution()) {
1156     lazy_compile_time_scope.emplace(counters->wasm_lazy_compile_time());
1157   }
1158 
1159   DCHECK(!native_module->lazy_compile_frozen());
1160 
1161   TRACE_LAZY("Compiling wasm-function#%d.\n", func_index);
1162 
1163   base::ThreadTicks thread_ticks = base::ThreadTicks::IsSupported()
1164                                        ? base::ThreadTicks::Now()
1165                                        : base::ThreadTicks();
1166 
1167   CompilationStateImpl* compilation_state =
1168       Impl(native_module->compilation_state());
1169   ExecutionTierPair tiers =
1170       GetRequestedExecutionTiers(native_module, enabled_features, func_index);
1171 
1172   DCHECK_LE(native_module->num_imported_functions(), func_index);
1173   DCHECK_LT(func_index, native_module->num_functions());
1174   WasmCompilationUnit baseline_unit{func_index, tiers.baseline_tier,
1175                                     kNoDebugging};
1176   CompilationEnv env = native_module->CreateCompilationEnv();
1177   WasmEngine* engine = GetWasmEngine();
1178   WasmFeatures detected_features;
1179   WasmCompilationResult result = baseline_unit.ExecuteCompilation(
1180       &env, compilation_state->GetWireBytesStorage().get(), counters,
1181       &detected_features);
1182   compilation_state->OnCompilationStopped(detected_features);
1183   if (!thread_ticks.IsNull()) {
1184     native_module->UpdateCPUDuration(
1185         (base::ThreadTicks::Now() - thread_ticks).InMicroseconds(),
1186         tiers.baseline_tier);
1187   }
1188 
1189   // During lazy compilation, we can only get compilation errors when
1190   // {--wasm-lazy-validation} is enabled. Otherwise, the module was fully
1191   // verified before starting its execution.
1192   CHECK_IMPLIES(result.failed(), FLAG_wasm_lazy_validation);
1193   const WasmFunction* func = &module->functions[func_index];
1194   if (result.failed()) {
1195     ErrorThrower thrower(isolate, nullptr);
1196     base::Vector<const uint8_t> code =
1197         compilation_state->GetWireBytesStorage()->GetCode(func->code);
1198     DecodeResult decode_result =
1199         ValidateSingleFunction(module, func_index, code, counters,
1200                                engine->allocator(), enabled_features);
1201     CHECK(decode_result.failed());
1202     SetCompileError(&thrower, ModuleWireBytes(native_module->wire_bytes()),
1203                     func, module, decode_result.error());
1204     return false;
1205   }
1206 
1207   // Allocate feedback vector if needed.
1208   if (result.feedback_vector_slots > 0) {
1209     DCHECK(FLAG_wasm_speculative_inlining);
1210     Handle<FixedArray> vector = isolate->factory()->NewFixedArrayWithZeroes(
1211         result.feedback_vector_slots);
1212     instance->feedback_vectors().set(
1213         declared_function_index(module, func_index), *vector);
1214   }
1215 
1216   WasmCodeRefScope code_ref_scope;
1217   WasmCode* code;
1218   {
1219     CodeSpaceWriteScope code_space_write_scope(native_module);
1220     code = native_module->PublishCode(
1221         native_module->AddCompiledCode(std::move(result)));
1222   }
1223   DCHECK_EQ(func_index, code->index());
1224 
1225   if (WasmCode::ShouldBeLogged(isolate)) {
1226     DisallowGarbageCollection no_gc;
1227     Object url_obj = module_object->script().name();
1228     DCHECK(url_obj.IsString() || url_obj.IsUndefined());
1229     std::unique_ptr<char[]> url =
1230         url_obj.IsString() ? String::cast(url_obj).ToCString() : nullptr;
1231     code->LogCode(isolate, url.get(), module_object->script().id());
1232   }
1233 
1234   counters->wasm_lazily_compiled_functions()->Increment();
1235 
1236   const bool lazy_module = IsLazyModule(module);
1237   if (GetCompileStrategy(module, enabled_features, func_index, lazy_module) ==
1238           CompileStrategy::kLazy &&
1239       tiers.baseline_tier < tiers.top_tier) {
1240     WasmCompilationUnit tiering_unit{func_index, tiers.top_tier, kNoDebugging};
1241     compilation_state->CommitTopTierCompilationUnit(tiering_unit);
1242   }
1243 
1244   return true;
1245 }
1246 
1247 class TransitiveTypeFeedbackProcessor {
1248  public:
TransitiveTypeFeedbackProcessor(const WasmModule * module,Handle<WasmInstanceObject> instance,int func_index)1249   TransitiveTypeFeedbackProcessor(const WasmModule* module,
1250                                   Handle<WasmInstanceObject> instance,
1251                                   int func_index)
1252       : instance_(instance),
1253         feedback_for_function_(module->type_feedback.feedback_for_function) {
1254     base::MutexGuard mutex_guard(&module->type_feedback.mutex);
1255     queue_.insert(func_index);
1256     while (!queue_.empty()) {
1257       auto next = queue_.cbegin();
1258       Process(*next);
1259       queue_.erase(next);
1260     }
1261   }
1262 
1263  private:
1264   void Process(int func_index);
1265 
EnqueueCallees(std::vector<CallSiteFeedback> feedback)1266   void EnqueueCallees(std::vector<CallSiteFeedback> feedback) {
1267     for (size_t i = 0; i < feedback.size(); i++) {
1268       int func = feedback[i].function_index;
1269       // TODO(jkummerow): Find a way to get the target function ID for
1270       // direct calls (which currently requires decoding the function).
1271       if (func == -1) continue;
1272       // Don't spend time on calls that have never been executed.
1273       if (feedback[i].absolute_call_frequency == 0) continue;
1274       // Don't recompute feedback that has already been processed.
1275       auto existing = feedback_for_function_.find(func);
1276       if (existing != feedback_for_function_.end() &&
1277           existing->second.feedback_vector.size() > 0) {
1278         continue;
1279       }
1280       queue_.insert(func);
1281     }
1282   }
1283 
1284   Handle<WasmInstanceObject> instance_;
1285   std::map<uint32_t, FunctionTypeFeedback>& feedback_for_function_;
1286   std::unordered_set<int> queue_;
1287 };
1288 
Process(int func_index)1289 void TransitiveTypeFeedbackProcessor::Process(int func_index) {
1290   int which_vector = declared_function_index(instance_->module(), func_index);
1291   Object maybe_feedback = instance_->feedback_vectors().get(which_vector);
1292   if (!maybe_feedback.IsFixedArray()) return;
1293   FixedArray feedback = FixedArray::cast(maybe_feedback);
1294   std::vector<CallSiteFeedback> result(feedback.length() / 2);
1295   int imported_functions =
1296       static_cast<int>(instance_->module()->num_imported_functions);
1297   for (int i = 0; i < feedback.length(); i += 2) {
1298     Object value = feedback.get(i);
1299     if (value.IsWasmInternalFunction() &&
1300         WasmExportedFunction::IsWasmExportedFunction(
1301             WasmInternalFunction::cast(value).external())) {
1302       // Monomorphic, and the internal function points to a wasm-generated
1303       // external function (WasmExportedFunction). Mark the target for inlining
1304       // if it's defined in the same module.
1305       WasmExportedFunction target = WasmExportedFunction::cast(
1306           WasmInternalFunction::cast(value).external());
1307       if (target.instance() == *instance_ &&
1308           target.function_index() >= imported_functions) {
1309         if (FLAG_trace_wasm_speculative_inlining) {
1310           PrintF("[Function #%d call_ref #%d inlineable (monomorphic)]\n",
1311                  func_index, i / 2);
1312         }
1313         int32_t count = Smi::cast(feedback.get(i + 1)).value();
1314         result[i / 2] = {target.function_index(), count};
1315         continue;
1316       }
1317     } else if (value.IsFixedArray()) {
1318       // Polymorphic. Pick a target for inlining if there is one that was
1319       // seen for most calls, and matches the requirements of the monomorphic
1320       // case.
1321       FixedArray polymorphic = FixedArray::cast(value);
1322       size_t total_count = 0;
1323       for (int j = 0; j < polymorphic.length(); j += 2) {
1324         total_count += Smi::cast(polymorphic.get(j + 1)).value();
1325       }
1326       int found_target = -1;
1327       int found_count = -1;
1328       double best_frequency = 0;
1329       for (int j = 0; j < polymorphic.length(); j += 2) {
1330         int32_t this_count = Smi::cast(polymorphic.get(j + 1)).value();
1331         double frequency = static_cast<double>(this_count) / total_count;
1332         if (frequency > best_frequency) best_frequency = frequency;
1333         if (frequency < 0.8) continue;
1334 
1335         // We reject this polymorphic entry if:
1336         // - it is not defined,
1337         // - it is not a wasm-defined function (WasmExportedFunction)
1338         // - it was not defined in this module.
1339         if (!polymorphic.get(j).IsWasmInternalFunction()) continue;
1340         WasmInternalFunction internal =
1341             WasmInternalFunction::cast(polymorphic.get(j));
1342         if (!WasmExportedFunction::IsWasmExportedFunction(
1343                 internal.external())) {
1344           continue;
1345         }
1346         WasmExportedFunction target =
1347             WasmExportedFunction::cast(internal.external());
1348         if (target.instance() != *instance_ ||
1349             target.function_index() < imported_functions) {
1350           continue;
1351         }
1352 
1353         found_target = target.function_index();
1354         found_count = static_cast<int>(this_count);
1355         if (FLAG_trace_wasm_speculative_inlining) {
1356           PrintF("[Function #%d call_ref #%d inlineable (polymorphic %f)]\n",
1357                  func_index, i / 2, frequency);
1358         }
1359         break;
1360       }
1361       if (found_target >= 0) {
1362         result[i / 2] = {found_target, found_count};
1363         continue;
1364       } else if (FLAG_trace_wasm_speculative_inlining) {
1365         PrintF("[Function #%d call_ref #%d: best frequency %f]\n", func_index,
1366                i / 2, best_frequency);
1367       }
1368     } else if (value.IsSmi()) {
1369       // Direct call, just collecting call count.
1370       int count = Smi::cast(value).value();
1371       if (FLAG_trace_wasm_speculative_inlining) {
1372         PrintF("[Function #%d call_direct #%d: frequency %d]\n", func_index,
1373                i / 2, count);
1374       }
1375       result[i / 2] = {-1, count};
1376       continue;
1377     }
1378     // If we fall through to here, then this call isn't eligible for inlining.
1379     // Possible reasons: uninitialized or megamorphic feedback; or monomorphic
1380     // or polymorphic that didn't meet our requirements.
1381     if (FLAG_trace_wasm_speculative_inlining) {
1382       PrintF("[Function #%d call_ref #%d *not* inlineable]\n", func_index,
1383              i / 2);
1384     }
1385     result[i / 2] = {-1, -1};
1386   }
1387   EnqueueCallees(result);
1388   feedback_for_function_[func_index].feedback_vector = std::move(result);
1389 }
1390 
TriggerTierUp(Isolate * isolate,NativeModule * native_module,int func_index,Handle<WasmInstanceObject> instance)1391 void TriggerTierUp(Isolate* isolate, NativeModule* native_module,
1392                    int func_index, Handle<WasmInstanceObject> instance) {
1393   CompilationStateImpl* compilation_state =
1394       Impl(native_module->compilation_state());
1395   WasmCompilationUnit tiering_unit{func_index, ExecutionTier::kTurbofan,
1396                                    kNoDebugging};
1397 
1398   const WasmModule* module = native_module->module();
1399   size_t priority;
1400   {
1401     base::MutexGuard mutex_guard(&module->type_feedback.mutex);
1402     int saved_priority =
1403         module->type_feedback.feedback_for_function[func_index].tierup_priority;
1404     saved_priority++;
1405     module->type_feedback.feedback_for_function[func_index].tierup_priority =
1406         saved_priority;
1407     // Continue to creating a compilation unit if this is the first time
1408     // we detect this function as hot, and create a new higher-priority unit
1409     // if the number of tierup checks is a power of two (at least 4).
1410     if (saved_priority > 1 &&
1411         (saved_priority < 4 || (saved_priority & (saved_priority - 1)) != 0)) {
1412       return;
1413     }
1414     priority = saved_priority;
1415   }
1416   if (FLAG_wasm_speculative_inlining) {
1417     // TODO(jkummerow): we could have collisions here if different instances
1418     // of the same module have collected different feedback. If that ever
1419     // becomes a problem, figure out a solution.
1420     TransitiveTypeFeedbackProcessor process(module, instance, func_index);
1421   }
1422 
1423   compilation_state->AddTopTierPriorityCompilationUnit(tiering_unit, priority);
1424 }
1425 
1426 namespace {
1427 
RecordStats(const Code code,Counters * counters)1428 void RecordStats(const Code code, Counters* counters) {
1429   counters->wasm_generated_code_size()->Increment(code.raw_body_size());
1430   counters->wasm_reloc_size()->Increment(code.relocation_info().length());
1431 }
1432 
1433 enum CompilationExecutionResult : int8_t { kNoMoreUnits, kYield };
1434 
ExecuteJSToWasmWrapperCompilationUnits(std::weak_ptr<NativeModule> native_module,JobDelegate * delegate)1435 CompilationExecutionResult ExecuteJSToWasmWrapperCompilationUnits(
1436     std::weak_ptr<NativeModule> native_module, JobDelegate* delegate) {
1437   std::shared_ptr<JSToWasmWrapperCompilationUnit> wrapper_unit = nullptr;
1438   int num_processed_wrappers = 0;
1439 
1440   OperationsBarrier::Token wrapper_compilation_token;
1441   Isolate* isolate;
1442 
1443   {
1444     BackgroundCompileScope compile_scope(native_module);
1445     if (compile_scope.cancelled()) return kYield;
1446     wrapper_unit = compile_scope.compilation_state()
1447                        ->GetNextJSToWasmWrapperCompilationUnit();
1448     if (!wrapper_unit) return kNoMoreUnits;
1449     isolate = wrapper_unit->isolate();
1450     wrapper_compilation_token =
1451         wasm::GetWasmEngine()->StartWrapperCompilation(isolate);
1452     if (!wrapper_compilation_token) return kNoMoreUnits;
1453   }
1454 
1455   TRACE_EVENT0("v8.wasm", "wasm.JSToWasmWrapperCompilation");
1456   while (true) {
1457     DCHECK_EQ(isolate, wrapper_unit->isolate());
1458     wrapper_unit->Execute();
1459     ++num_processed_wrappers;
1460     bool yield = delegate && delegate->ShouldYield();
1461     BackgroundCompileScope compile_scope(native_module);
1462     if (compile_scope.cancelled()) return kYield;
1463     if (yield ||
1464         !(wrapper_unit = compile_scope.compilation_state()
1465                              ->GetNextJSToWasmWrapperCompilationUnit())) {
1466       compile_scope.compilation_state()->OnFinishedJSToWasmWrapperUnits(
1467           num_processed_wrappers);
1468       return yield ? kYield : kNoMoreUnits;
1469     }
1470   }
1471 }
1472 
1473 namespace {
GetCompilationEventName(const WasmCompilationUnit & unit,const CompilationEnv & env)1474 const char* GetCompilationEventName(const WasmCompilationUnit& unit,
1475                                     const CompilationEnv& env) {
1476   ExecutionTier tier = unit.tier();
1477   if (tier == ExecutionTier::kLiftoff) {
1478     return "wasm.BaselineCompilation";
1479   }
1480   if (tier == ExecutionTier::kTurbofan) {
1481     return "wasm.TopTierCompilation";
1482   }
1483   if (unit.func_index() <
1484       static_cast<int>(env.module->num_imported_functions)) {
1485     return "wasm.WasmToJSWrapperCompilation";
1486   }
1487   return "wasm.OtherCompilation";
1488 }
1489 }  // namespace
1490 
1491 constexpr uint8_t kMainTaskId = 0;
1492 
1493 // Run by the {BackgroundCompileJob} (on any thread).
ExecuteCompilationUnits(std::weak_ptr<NativeModule> native_module,Counters * counters,JobDelegate * delegate,CompileBaselineOnly baseline_only)1494 CompilationExecutionResult ExecuteCompilationUnits(
1495     std::weak_ptr<NativeModule> native_module, Counters* counters,
1496     JobDelegate* delegate, CompileBaselineOnly baseline_only) {
1497   TRACE_EVENT0("v8.wasm", "wasm.ExecuteCompilationUnits");
1498 
1499   // Execute JS to Wasm wrapper units first, so that they are ready to be
1500   // finalized by the main thread when the kFinishedBaselineCompilation event is
1501   // triggered.
1502   if (ExecuteJSToWasmWrapperCompilationUnits(native_module, delegate) ==
1503       kYield) {
1504     return kYield;
1505   }
1506 
1507   // These fields are initialized in a {BackgroundCompileScope} before
1508   // starting compilation.
1509   base::Optional<CompilationEnv> env;
1510   std::shared_ptr<WireBytesStorage> wire_bytes;
1511   std::shared_ptr<const WasmModule> module;
1512   // Task 0 is any main thread (there might be multiple from multiple isolates),
1513   // worker threads start at 1 (thus the "+ 1").
1514   STATIC_ASSERT(kMainTaskId == 0);
1515   int task_id = delegate ? (int{delegate->GetTaskId()} + 1) : kMainTaskId;
1516   DCHECK_LE(0, task_id);
1517   CompilationUnitQueues::Queue* queue;
1518   base::Optional<WasmCompilationUnit> unit;
1519 
1520   WasmFeatures detected_features = WasmFeatures::None();
1521 
1522   base::ThreadTicks thread_ticks = base::ThreadTicks::IsSupported()
1523                                        ? base::ThreadTicks::Now()
1524                                        : base::ThreadTicks();
1525 
1526   // Preparation (synchronized): Initialize the fields above and get the first
1527   // compilation unit.
1528   {
1529     BackgroundCompileScope compile_scope(native_module);
1530     if (compile_scope.cancelled()) return kYield;
1531     env.emplace(compile_scope.native_module()->CreateCompilationEnv());
1532     wire_bytes = compile_scope.compilation_state()->GetWireBytesStorage();
1533     module = compile_scope.native_module()->shared_module();
1534     queue = compile_scope.compilation_state()->GetQueueForCompileTask(task_id);
1535     unit = compile_scope.compilation_state()->GetNextCompilationUnit(
1536         queue, baseline_only);
1537     if (!unit) return kNoMoreUnits;
1538   }
1539   TRACE_COMPILE("ExecuteCompilationUnits (task id %d)\n", task_id);
1540 
1541   std::vector<WasmCompilationResult> results_to_publish;
1542   while (true) {
1543     ExecutionTier current_tier = unit->tier();
1544     const char* event_name = GetCompilationEventName(unit.value(), env.value());
1545     TRACE_EVENT0("v8.wasm", event_name);
1546     while (unit->tier() == current_tier) {
1547       // (asynchronous): Execute the compilation.
1548       WasmCompilationResult result = unit->ExecuteCompilation(
1549           &env.value(), wire_bytes.get(), counters, &detected_features);
1550       results_to_publish.emplace_back(std::move(result));
1551 
1552       bool yield = delegate && delegate->ShouldYield();
1553 
1554       // (synchronized): Publish the compilation result and get the next unit.
1555       BackgroundCompileScope compile_scope(native_module);
1556       if (compile_scope.cancelled()) return kYield;
1557 
1558       if (!results_to_publish.back().succeeded()) {
1559         compile_scope.compilation_state()->SetError();
1560         return kNoMoreUnits;
1561       }
1562 
1563       if (!unit->for_debugging() && result.result_tier != current_tier) {
1564         compile_scope.native_module()->AddLiftoffBailout();
1565       }
1566 
1567       // Yield or get next unit.
1568       if (yield ||
1569           !(unit = compile_scope.compilation_state()->GetNextCompilationUnit(
1570                 queue, baseline_only))) {
1571         if (!thread_ticks.IsNull()) {
1572           compile_scope.native_module()->UpdateCPUDuration(
1573               (base::ThreadTicks::Now() - thread_ticks).InMicroseconds(),
1574               current_tier);
1575         }
1576         std::vector<std::unique_ptr<WasmCode>> unpublished_code =
1577             compile_scope.native_module()->AddCompiledCode(
1578                 base::VectorOf(std::move(results_to_publish)));
1579         results_to_publish.clear();
1580         compile_scope.compilation_state()->SchedulePublishCompilationResults(
1581             std::move(unpublished_code));
1582         compile_scope.compilation_state()->OnCompilationStopped(
1583             detected_features);
1584         return yield ? kYield : kNoMoreUnits;
1585       }
1586 
1587       // Publish after finishing a certain amount of units, to avoid contention
1588       // when all threads publish at the end.
1589       bool batch_full =
1590           queue->ShouldPublish(static_cast<int>(results_to_publish.size()));
1591       // Also publish each time the compilation tier changes from Liftoff to
1592       // TurboFan, such that we immediately publish the baseline compilation
1593       // results to start execution, and do not wait for a batch to fill up.
1594       bool liftoff_finished = unit->tier() != current_tier &&
1595                               unit->tier() == ExecutionTier::kTurbofan;
1596       if (batch_full || liftoff_finished) {
1597         if (!thread_ticks.IsNull()) {
1598           base::ThreadTicks thread_ticks_now = base::ThreadTicks::Now();
1599           compile_scope.native_module()->UpdateCPUDuration(
1600               (thread_ticks_now - thread_ticks).InMicroseconds(), current_tier);
1601           thread_ticks = thread_ticks_now;
1602         }
1603         std::vector<std::unique_ptr<WasmCode>> unpublished_code =
1604             compile_scope.native_module()->AddCompiledCode(
1605                 base::VectorOf(std::move(results_to_publish)));
1606         results_to_publish.clear();
1607         compile_scope.compilation_state()->SchedulePublishCompilationResults(
1608             std::move(unpublished_code));
1609       }
1610     }
1611   }
1612   UNREACHABLE();
1613 }
1614 
1615 using JSToWasmWrapperKey = std::pair<bool, FunctionSig>;
1616 
1617 // Returns the number of units added.
AddExportWrapperUnits(Isolate * isolate,NativeModule * native_module,CompilationUnitBuilder * builder)1618 int AddExportWrapperUnits(Isolate* isolate, NativeModule* native_module,
1619                           CompilationUnitBuilder* builder) {
1620   std::unordered_set<JSToWasmWrapperKey, base::hash<JSToWasmWrapperKey>> keys;
1621   for (auto exp : native_module->module()->export_table) {
1622     if (exp.kind != kExternalFunction) continue;
1623     auto& function = native_module->module()->functions[exp.index];
1624     JSToWasmWrapperKey key(function.imported, *function.sig);
1625     if (keys.insert(key).second) {
1626       auto unit = std::make_shared<JSToWasmWrapperCompilationUnit>(
1627           isolate, function.sig, native_module->module(), function.imported,
1628           native_module->enabled_features(),
1629           JSToWasmWrapperCompilationUnit::kAllowGeneric);
1630       builder->AddJSToWasmWrapperUnit(std::move(unit));
1631     }
1632   }
1633 
1634   return static_cast<int>(keys.size());
1635 }
1636 
1637 // Returns the number of units added.
AddImportWrapperUnits(NativeModule * native_module,CompilationUnitBuilder * builder)1638 int AddImportWrapperUnits(NativeModule* native_module,
1639                           CompilationUnitBuilder* builder) {
1640   std::unordered_set<WasmImportWrapperCache::CacheKey,
1641                      WasmImportWrapperCache::CacheKeyHash>
1642       keys;
1643   int num_imported_functions = native_module->num_imported_functions();
1644   for (int func_index = 0; func_index < num_imported_functions; func_index++) {
1645     const FunctionSig* sig = native_module->module()->functions[func_index].sig;
1646     if (!IsJSCompatibleSignature(sig, native_module->module(),
1647                                  native_module->enabled_features())) {
1648       continue;
1649     }
1650     WasmImportWrapperCache::CacheKey key(
1651         compiler::kDefaultImportCallKind, sig,
1652         static_cast<int>(sig->parameter_count()), kNoSuspend);
1653     auto it = keys.insert(key);
1654     if (it.second) {
1655       // Ensure that all keys exist in the cache, so that we can populate the
1656       // cache later without locking.
1657       (*native_module->import_wrapper_cache())[key] = nullptr;
1658       builder->AddUnits(func_index);
1659     }
1660   }
1661   return static_cast<int>(keys.size());
1662 }
1663 
InitializeLazyCompilation(NativeModule * native_module)1664 void InitializeLazyCompilation(NativeModule* native_module) {
1665   const bool lazy_module = IsLazyModule(native_module->module());
1666   auto* module = native_module->module();
1667 
1668   uint32_t start = module->num_imported_functions;
1669   uint32_t end = start + module->num_declared_functions;
1670   base::Optional<CodeSpaceWriteScope> lazy_code_space_write_scope;
1671   for (uint32_t func_index = start; func_index < end; func_index++) {
1672     CompileStrategy strategy = GetCompileStrategy(
1673         module, native_module->enabled_features(), func_index, lazy_module);
1674     if (strategy == CompileStrategy::kLazy ||
1675         strategy == CompileStrategy::kLazyBaselineEagerTopTier) {
1676       // Open a single scope for all following calls to {UseLazyStub()}, instead
1677       // of flipping page permissions for each {func_index} individually.
1678       if (!lazy_code_space_write_scope.has_value()) {
1679         lazy_code_space_write_scope.emplace(native_module);
1680       }
1681       native_module->UseLazyStub(func_index);
1682     }
1683   }
1684 }
1685 
InitializeCompilation(Isolate * isolate,NativeModule * native_module)1686 std::unique_ptr<CompilationUnitBuilder> InitializeCompilation(
1687     Isolate* isolate, NativeModule* native_module) {
1688   InitializeLazyCompilation(native_module);
1689   CompilationStateImpl* compilation_state =
1690       Impl(native_module->compilation_state());
1691   const bool lazy_module = IsLazyModule(native_module->module());
1692   auto builder = std::make_unique<CompilationUnitBuilder>(native_module);
1693   int num_import_wrappers = AddImportWrapperUnits(native_module, builder.get());
1694   int num_export_wrappers =
1695       AddExportWrapperUnits(isolate, native_module, builder.get());
1696   compilation_state->InitializeCompilationProgress(
1697       lazy_module, num_import_wrappers, num_export_wrappers);
1698   return builder;
1699 }
1700 
MayCompriseLazyFunctions(const WasmModule * module,const WasmFeatures & enabled_features,bool lazy_module)1701 bool MayCompriseLazyFunctions(const WasmModule* module,
1702                               const WasmFeatures& enabled_features,
1703                               bool lazy_module) {
1704   if (lazy_module || enabled_features.has_compilation_hints()) return true;
1705 #ifdef ENABLE_SLOW_DCHECKS
1706   int start = module->num_imported_functions;
1707   int end = start + module->num_declared_functions;
1708   for (int func_index = start; func_index < end; func_index++) {
1709     SLOW_DCHECK(GetCompileStrategy(module, enabled_features, func_index,
1710                                    lazy_module) != CompileStrategy::kLazy);
1711   }
1712 #endif
1713   return false;
1714 }
1715 
1716 class CompilationTimeCallback : public CompilationEventCallback {
1717  public:
1718   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)1719   explicit CompilationTimeCallback(
1720       std::shared_ptr<Counters> async_counters,
1721       std::shared_ptr<metrics::Recorder> metrics_recorder,
1722       v8::metrics::Recorder::ContextId context_id,
1723       std::weak_ptr<NativeModule> native_module, CompileMode compile_mode)
1724       : start_time_(base::TimeTicks::Now()),
1725         async_counters_(std::move(async_counters)),
1726         metrics_recorder_(std::move(metrics_recorder)),
1727         context_id_(context_id),
1728         native_module_(std::move(native_module)),
1729         compile_mode_(compile_mode) {}
1730 
1731   // Keep this callback alive to be able to record caching metrics.
release_after_final_event()1732   ReleaseAfterFinalEvent release_after_final_event() override {
1733     return CompilationEventCallback::ReleaseAfterFinalEvent::kKeep;
1734   }
1735 
call(CompilationEvent compilation_event)1736   void call(CompilationEvent compilation_event) override {
1737     DCHECK(base::TimeTicks::IsHighResolution());
1738     std::shared_ptr<NativeModule> native_module = native_module_.lock();
1739     if (!native_module) return;
1740     auto now = base::TimeTicks::Now();
1741     auto duration = now - start_time_;
1742     if (compilation_event == CompilationEvent::kFinishedBaselineCompilation) {
1743       // Reset {start_time_} to measure tier-up time.
1744       start_time_ = now;
1745       if (compile_mode_ != kSynchronous) {
1746         TimedHistogram* histogram =
1747             compile_mode_ == kAsync
1748                 ? async_counters_->wasm_async_compile_wasm_module_time()
1749                 : async_counters_->wasm_streaming_compile_wasm_module_time();
1750         histogram->AddSample(static_cast<int>(duration.InMicroseconds()));
1751       }
1752 
1753       v8::metrics::WasmModuleCompiled event{
1754           (compile_mode_ != kSynchronous),         // async
1755           (compile_mode_ == kStreaming),           // streamed
1756           false,                                   // cached
1757           false,                                   // deserialized
1758           FLAG_wasm_lazy_compilation,              // lazy
1759           true,                                    // success
1760           native_module->liftoff_code_size(),      // code_size_in_bytes
1761           native_module->liftoff_bailout_count(),  // liftoff_bailout_count
1762           duration.InMicroseconds(),               // wall_clock_duration_in_us
1763           static_cast<int64_t>(                    // cpu_time_duration_in_us
1764               native_module->baseline_compilation_cpu_duration())};
1765       metrics_recorder_->DelayMainThreadEvent(event, context_id_);
1766     }
1767     if (compilation_event == CompilationEvent::kFinishedTopTierCompilation) {
1768       TimedHistogram* histogram = async_counters_->wasm_tier_up_module_time();
1769       histogram->AddSample(static_cast<int>(duration.InMicroseconds()));
1770 
1771       v8::metrics::WasmModuleTieredUp event{
1772           FLAG_wasm_lazy_compilation,           // lazy
1773           native_module->turbofan_code_size(),  // code_size_in_bytes
1774           duration.InMicroseconds(),            // wall_clock_duration_in_us
1775           static_cast<int64_t>(                 // cpu_time_duration_in_us
1776               native_module->tier_up_cpu_duration())};
1777       metrics_recorder_->DelayMainThreadEvent(event, context_id_);
1778     }
1779     if (compilation_event == CompilationEvent::kFailedCompilation) {
1780       v8::metrics::WasmModuleCompiled event{
1781           (compile_mode_ != kSynchronous),         // async
1782           (compile_mode_ == kStreaming),           // streamed
1783           false,                                   // cached
1784           false,                                   // deserialized
1785           FLAG_wasm_lazy_compilation,              // lazy
1786           false,                                   // success
1787           native_module->liftoff_code_size(),      // code_size_in_bytes
1788           native_module->liftoff_bailout_count(),  // liftoff_bailout_count
1789           duration.InMicroseconds(),               // wall_clock_duration_in_us
1790           static_cast<int64_t>(                    // cpu_time_duration_in_us
1791               native_module->baseline_compilation_cpu_duration())};
1792       metrics_recorder_->DelayMainThreadEvent(event, context_id_);
1793     }
1794   }
1795 
1796  private:
1797   base::TimeTicks start_time_;
1798   const std::shared_ptr<Counters> async_counters_;
1799   std::shared_ptr<metrics::Recorder> metrics_recorder_;
1800   v8::metrics::Recorder::ContextId context_id_;
1801   std::weak_ptr<NativeModule> native_module_;
1802   const CompileMode compile_mode_;
1803 };
1804 
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)1805 void CompileNativeModule(Isolate* isolate,
1806                          v8::metrics::Recorder::ContextId context_id,
1807                          ErrorThrower* thrower, const WasmModule* wasm_module,
1808                          std::shared_ptr<NativeModule> native_module,
1809                          Handle<FixedArray>* export_wrappers_out) {
1810   CHECK(!FLAG_jitless);
1811   ModuleWireBytes wire_bytes(native_module->wire_bytes());
1812   const bool lazy_module = IsLazyModule(wasm_module);
1813   if (!FLAG_wasm_lazy_validation && wasm_module->origin == kWasmOrigin &&
1814       MayCompriseLazyFunctions(wasm_module, native_module->enabled_features(),
1815                                lazy_module)) {
1816     // Validate wasm modules for lazy compilation if requested. Never validate
1817     // asm.js modules as these are valid by construction (additionally a CHECK
1818     // will catch this during lazy compilation).
1819     ValidateSequentially(wasm_module, native_module.get(), isolate->counters(),
1820                          isolate->allocator(), thrower, lazy_module,
1821                          kOnlyLazyFunctions);
1822     // On error: Return and leave the module in an unexecutable state.
1823     if (thrower->error()) return;
1824   }
1825 
1826   DCHECK_GE(kMaxInt, native_module->module()->num_declared_functions);
1827 
1828   // The callback captures a shared ptr to the semaphore.
1829   auto* compilation_state = Impl(native_module->compilation_state());
1830   if (base::TimeTicks::IsHighResolution()) {
1831     compilation_state->AddCallback(std::make_unique<CompilationTimeCallback>(
1832         isolate->async_counters(), isolate->metrics_recorder(), context_id,
1833         native_module, CompilationTimeCallback::kSynchronous));
1834   }
1835 
1836   // Initialize the compilation units and kick off background compile tasks.
1837   std::unique_ptr<CompilationUnitBuilder> builder =
1838       InitializeCompilation(isolate, native_module.get());
1839   compilation_state->InitializeCompilationUnits(std::move(builder));
1840 
1841   compilation_state->WaitForCompilationEvent(
1842       CompilationEvent::kFinishedExportWrappers);
1843 
1844   if (compilation_state->failed()) {
1845     DCHECK_IMPLIES(lazy_module, !FLAG_wasm_lazy_validation);
1846     ValidateSequentially(wasm_module, native_module.get(), isolate->counters(),
1847                          isolate->allocator(), thrower, lazy_module);
1848     CHECK(thrower->error());
1849     return;
1850   }
1851 
1852   compilation_state->FinalizeJSToWasmWrappers(isolate, native_module->module(),
1853                                               export_wrappers_out);
1854 
1855   compilation_state->WaitForCompilationEvent(
1856       CompilationEvent::kFinishedBaselineCompilation);
1857 
1858   compilation_state->PublishDetectedFeatures(isolate);
1859 
1860   if (compilation_state->failed()) {
1861     DCHECK_IMPLIES(lazy_module, !FLAG_wasm_lazy_validation);
1862     ValidateSequentially(wasm_module, native_module.get(), isolate->counters(),
1863                          isolate->allocator(), thrower, lazy_module);
1864     CHECK(thrower->error());
1865   }
1866 }
1867 
1868 class BackgroundCompileJob final : public JobTask {
1869  public:
BackgroundCompileJob(std::weak_ptr<NativeModule> native_module,std::shared_ptr<Counters> async_counters)1870   explicit BackgroundCompileJob(std::weak_ptr<NativeModule> native_module,
1871                                 std::shared_ptr<Counters> async_counters)
1872       : native_module_(std::move(native_module)),
1873         engine_barrier_(GetWasmEngine()->GetBarrierForBackgroundCompile()),
1874         async_counters_(std::move(async_counters)) {}
1875 
Run(JobDelegate * delegate)1876   void Run(JobDelegate* delegate) override {
1877     auto engine_scope = engine_barrier_->TryLock();
1878     if (!engine_scope) return;
1879     ExecuteCompilationUnits(native_module_, async_counters_.get(), delegate,
1880                             kBaselineOrTopTier);
1881   }
1882 
GetMaxConcurrency(size_t worker_count) const1883   size_t GetMaxConcurrency(size_t worker_count) const override {
1884     BackgroundCompileScope compile_scope(native_module_);
1885     if (compile_scope.cancelled()) return 0;
1886     // NumOutstandingCompilations() does not reflect the units that running
1887     // workers are processing, thus add the current worker count to that number.
1888     return std::min(
1889         static_cast<size_t>(FLAG_wasm_num_compilation_tasks),
1890         worker_count +
1891             compile_scope.compilation_state()->NumOutstandingCompilations());
1892   }
1893 
1894  private:
1895   std::weak_ptr<NativeModule> native_module_;
1896   std::shared_ptr<OperationsBarrier> engine_barrier_;
1897   const std::shared_ptr<Counters> async_counters_;
1898 };
1899 
1900 }  // namespace
1901 
CompileToNativeModule(Isolate * isolate,const WasmFeatures & enabled,ErrorThrower * thrower,std::shared_ptr<const WasmModule> module,const ModuleWireBytes & wire_bytes,Handle<FixedArray> * export_wrappers_out,int compilation_id,v8::metrics::Recorder::ContextId context_id)1902 std::shared_ptr<NativeModule> CompileToNativeModule(
1903     Isolate* isolate, const WasmFeatures& enabled, ErrorThrower* thrower,
1904     std::shared_ptr<const WasmModule> module, const ModuleWireBytes& wire_bytes,
1905     Handle<FixedArray>* export_wrappers_out, int compilation_id,
1906     v8::metrics::Recorder::ContextId context_id) {
1907   const WasmModule* wasm_module = module.get();
1908   WasmEngine* engine = GetWasmEngine();
1909   base::OwnedVector<uint8_t> wire_bytes_copy =
1910       base::OwnedVector<uint8_t>::Of(wire_bytes.module_bytes());
1911   // Prefer {wire_bytes_copy} to {wire_bytes.module_bytes()} for the temporary
1912   // cache key. When we eventually install the module in the cache, the wire
1913   // bytes of the temporary key and the new key have the same base pointer and
1914   // we can skip the full bytes comparison.
1915   std::shared_ptr<NativeModule> native_module = engine->MaybeGetNativeModule(
1916       wasm_module->origin, wire_bytes_copy.as_vector(), isolate);
1917   if (native_module) {
1918     // TODO(thibaudm): Look into sharing export wrappers.
1919     CompileJsToWasmWrappers(isolate, wasm_module, export_wrappers_out);
1920     return native_module;
1921   }
1922 
1923   TimedHistogramScope wasm_compile_module_time_scope(SELECT_WASM_COUNTER(
1924       isolate->counters(), wasm_module->origin, wasm_compile, module_time));
1925 
1926   // Embedder usage count for declared shared memories.
1927   if (wasm_module->has_shared_memory) {
1928     isolate->CountUsage(v8::Isolate::UseCounterFeature::kWasmSharedMemory);
1929   }
1930 
1931   // Create a new {NativeModule} first.
1932   const bool include_liftoff = module->origin == kWasmOrigin && FLAG_liftoff;
1933   DynamicTiering dynamic_tiering = isolate->IsWasmDynamicTieringEnabled()
1934                                        ? DynamicTiering::kEnabled
1935                                        : DynamicTiering::kDisabled;
1936   size_t code_size_estimate =
1937       wasm::WasmCodeManager::EstimateNativeModuleCodeSize(
1938           module.get(), include_liftoff, dynamic_tiering);
1939   native_module =
1940       engine->NewNativeModule(isolate, enabled, module, code_size_estimate);
1941   native_module->SetWireBytes(std::move(wire_bytes_copy));
1942   native_module->compilation_state()->set_compilation_id(compilation_id);
1943   // Sync compilation is user blocking, so we increase the priority.
1944   native_module->compilation_state()->SetHighPriority();
1945 
1946   CompileNativeModule(isolate, context_id, thrower, wasm_module, native_module,
1947                       export_wrappers_out);
1948   bool cache_hit = !engine->UpdateNativeModuleCache(thrower->error(),
1949                                                     &native_module, isolate);
1950   if (thrower->error()) return {};
1951 
1952   if (cache_hit) {
1953     CompileJsToWasmWrappers(isolate, wasm_module, export_wrappers_out);
1954     return native_module;
1955   }
1956 
1957   // Ensure that the code objects are logged before returning.
1958   engine->LogOutstandingCodesForIsolate(isolate);
1959 
1960   return native_module;
1961 }
1962 
RecompileNativeModule(NativeModule * native_module,TieringState tiering_state)1963 void RecompileNativeModule(NativeModule* native_module,
1964                            TieringState tiering_state) {
1965   // Install a callback to notify us once background recompilation finished.
1966   auto recompilation_finished_semaphore = std::make_shared<base::Semaphore>(0);
1967   auto* compilation_state = Impl(native_module->compilation_state());
1968 
1969   class RecompilationFinishedCallback : public CompilationEventCallback {
1970    public:
1971     explicit RecompilationFinishedCallback(
1972         std::shared_ptr<base::Semaphore> recompilation_finished_semaphore)
1973         : recompilation_finished_semaphore_(
1974               std::move(recompilation_finished_semaphore)) {}
1975 
1976     void call(CompilationEvent event) override {
1977       DCHECK_NE(CompilationEvent::kFailedCompilation, event);
1978       if (event == CompilationEvent::kFinishedRecompilation) {
1979         recompilation_finished_semaphore_->Signal();
1980       }
1981     }
1982 
1983    private:
1984     std::shared_ptr<base::Semaphore> recompilation_finished_semaphore_;
1985   };
1986 
1987   // The callback captures a shared ptr to the semaphore.
1988   // Initialize the compilation units and kick off background compile tasks.
1989   compilation_state->InitializeRecompilation(
1990       tiering_state, std::make_unique<RecompilationFinishedCallback>(
1991                          recompilation_finished_semaphore));
1992 
1993   constexpr JobDelegate* kNoDelegate = nullptr;
1994   ExecuteCompilationUnits(compilation_state->native_module_weak(),
1995                           compilation_state->counters(), kNoDelegate,
1996                           kBaselineOnly);
1997   recompilation_finished_semaphore->Wait();
1998   DCHECK(!compilation_state->failed());
1999 }
2000 
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,int compilation_id)2001 AsyncCompileJob::AsyncCompileJob(
2002     Isolate* isolate, const WasmFeatures& enabled,
2003     std::unique_ptr<byte[]> bytes_copy, size_t length, Handle<Context> context,
2004     Handle<Context> incumbent_context, const char* api_method_name,
2005     std::shared_ptr<CompilationResultResolver> resolver, int compilation_id)
2006     : isolate_(isolate),
2007       api_method_name_(api_method_name),
2008       enabled_features_(enabled),
2009       dynamic_tiering_(isolate_->IsWasmDynamicTieringEnabled()
2010                            ? DynamicTiering::kEnabled
2011                            : DynamicTiering::kDisabled),
2012       wasm_lazy_compilation_(FLAG_wasm_lazy_compilation),
2013       start_time_(base::TimeTicks::Now()),
2014       bytes_copy_(std::move(bytes_copy)),
2015       wire_bytes_(bytes_copy_.get(), bytes_copy_.get() + length),
2016       resolver_(std::move(resolver)),
2017       compilation_id_(compilation_id) {
2018   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
2019                "wasm.AsyncCompileJob");
2020   CHECK(FLAG_wasm_async_compilation);
2021   CHECK(!FLAG_jitless);
2022   v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
2023   v8::Platform* platform = V8::GetCurrentPlatform();
2024   foreground_task_runner_ = platform->GetForegroundTaskRunner(v8_isolate);
2025   native_context_ =
2026       isolate->global_handles()->Create(context->native_context());
2027   incumbent_context_ = isolate->global_handles()->Create(*incumbent_context);
2028   DCHECK(native_context_->IsNativeContext());
2029   context_id_ = isolate->GetOrRegisterRecorderContextId(native_context_);
2030   metrics_event_.async = true;
2031 }
2032 
Start()2033 void AsyncCompileJob::Start() {
2034   DoAsync<DecodeModule>(isolate_->counters(),
2035                         isolate_->metrics_recorder());  // --
2036 }
2037 
Abort()2038 void AsyncCompileJob::Abort() {
2039   // Removing this job will trigger the destructor, which will cancel all
2040   // compilation.
2041   GetWasmEngine()->RemoveCompileJob(this);
2042 }
2043 
2044 class AsyncStreamingProcessor final : public StreamingProcessor {
2045  public:
2046   explicit AsyncStreamingProcessor(AsyncCompileJob* job,
2047                                    std::shared_ptr<Counters> counters,
2048                                    AccountingAllocator* allocator);
2049 
2050   ~AsyncStreamingProcessor() override;
2051 
2052   bool ProcessModuleHeader(base::Vector<const uint8_t> bytes,
2053                            uint32_t offset) override;
2054 
2055   bool ProcessSection(SectionCode section_code,
2056                       base::Vector<const uint8_t> bytes,
2057                       uint32_t offset) override;
2058 
2059   bool ProcessCodeSectionHeader(int num_functions,
2060                                 uint32_t functions_mismatch_error_offset,
2061                                 std::shared_ptr<WireBytesStorage>,
2062                                 int code_section_start,
2063                                 int code_section_length) override;
2064 
2065   bool ProcessFunctionBody(base::Vector<const uint8_t> bytes,
2066                            uint32_t offset) override;
2067 
2068   void OnFinishedChunk() override;
2069 
2070   void OnFinishedStream(base::OwnedVector<uint8_t> bytes) override;
2071 
2072   void OnError(const WasmError&) override;
2073 
2074   void OnAbort() override;
2075 
2076   bool Deserialize(base::Vector<const uint8_t> wire_bytes,
2077                    base::Vector<const uint8_t> module_bytes) override;
2078 
2079  private:
2080   // Finishes the AsyncCompileJob with an error.
2081   void FinishAsyncCompileJobWithError(const WasmError&);
2082 
2083   void CommitCompilationUnits();
2084 
2085   ModuleDecoder decoder_;
2086   AsyncCompileJob* job_;
2087   std::unique_ptr<CompilationUnitBuilder> compilation_unit_builder_;
2088   int num_functions_ = 0;
2089   bool prefix_cache_hit_ = false;
2090   bool before_code_section_ = true;
2091   std::shared_ptr<Counters> async_counters_;
2092   AccountingAllocator* allocator_;
2093 
2094   // Running hash of the wire bytes up to code section size, but excluding the
2095   // code section itself. Used by the {NativeModuleCache} to detect potential
2096   // duplicate modules.
2097   size_t prefix_hash_;
2098 };
2099 
CreateStreamingDecoder()2100 std::shared_ptr<StreamingDecoder> AsyncCompileJob::CreateStreamingDecoder() {
2101   DCHECK_NULL(stream_);
2102   stream_ = StreamingDecoder::CreateAsyncStreamingDecoder(
2103       std::make_unique<AsyncStreamingProcessor>(
2104           this, isolate_->async_counters(), isolate_->allocator()));
2105   return stream_;
2106 }
2107 
~AsyncCompileJob()2108 AsyncCompileJob::~AsyncCompileJob() {
2109   // Note: This destructor always runs on the foreground thread of the isolate.
2110   background_task_manager_.CancelAndWait();
2111   // If initial compilation did not finish yet we can abort it.
2112   if (native_module_) {
2113     Impl(native_module_->compilation_state())
2114         ->CancelCompilation(CompilationStateImpl::kCancelInitialCompilation);
2115   }
2116   // Tell the streaming decoder that the AsyncCompileJob is not available
2117   // anymore.
2118   // TODO(ahaas): Is this notification really necessary? Check
2119   // https://crbug.com/888170.
2120   if (stream_) stream_->NotifyCompilationEnded();
2121   CancelPendingForegroundTask();
2122   isolate_->global_handles()->Destroy(native_context_.location());
2123   isolate_->global_handles()->Destroy(incumbent_context_.location());
2124   if (!module_object_.is_null()) {
2125     isolate_->global_handles()->Destroy(module_object_.location());
2126   }
2127 }
2128 
CreateNativeModule(std::shared_ptr<const WasmModule> module,size_t code_size_estimate)2129 void AsyncCompileJob::CreateNativeModule(
2130     std::shared_ptr<const WasmModule> module, size_t code_size_estimate) {
2131   // Embedder usage count for declared shared memories.
2132   if (module->has_shared_memory) {
2133     isolate_->CountUsage(v8::Isolate::UseCounterFeature::kWasmSharedMemory);
2134   }
2135 
2136   // TODO(wasm): Improve efficiency of storing module wire bytes. Only store
2137   // relevant sections, not function bodies
2138 
2139   // Create the module object and populate with compiled functions and
2140   // information needed at instantiation time.
2141 
2142   native_module_ = GetWasmEngine()->NewNativeModule(
2143       isolate_, enabled_features_, std::move(module), code_size_estimate);
2144   native_module_->SetWireBytes({std::move(bytes_copy_), wire_bytes_.length()});
2145   native_module_->compilation_state()->set_compilation_id(compilation_id_);
2146 }
2147 
GetOrCreateNativeModule(std::shared_ptr<const WasmModule> module,size_t code_size_estimate)2148 bool AsyncCompileJob::GetOrCreateNativeModule(
2149     std::shared_ptr<const WasmModule> module, size_t code_size_estimate) {
2150   native_module_ = GetWasmEngine()->MaybeGetNativeModule(
2151       module->origin, wire_bytes_.module_bytes(), isolate_);
2152   if (native_module_ == nullptr) {
2153     CreateNativeModule(std::move(module), code_size_estimate);
2154     return false;
2155   }
2156   return true;
2157 }
2158 
PrepareRuntimeObjects()2159 void AsyncCompileJob::PrepareRuntimeObjects() {
2160   // Create heap objects for script and module bytes to be stored in the
2161   // module object. Asm.js is not compiled asynchronously.
2162   DCHECK(module_object_.is_null());
2163   auto source_url = stream_ ? stream_->url() : base::Vector<const char>();
2164   auto script =
2165       GetWasmEngine()->GetOrCreateScript(isolate_, native_module_, source_url);
2166   Handle<WasmModuleObject> module_object =
2167       WasmModuleObject::New(isolate_, native_module_, script);
2168 
2169   module_object_ = isolate_->global_handles()->Create(*module_object);
2170 }
2171 
2172 // This function assumes that it is executed in a HandleScope, and that a
2173 // context is set on the isolate.
FinishCompile(bool is_after_cache_hit)2174 void AsyncCompileJob::FinishCompile(bool is_after_cache_hit) {
2175   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
2176                "wasm.FinishAsyncCompile");
2177   bool is_after_deserialization = !module_object_.is_null();
2178   auto compilation_state = Impl(native_module_->compilation_state());
2179   if (!is_after_deserialization) {
2180     if (stream_) {
2181       stream_->NotifyNativeModuleCreated(native_module_);
2182     }
2183     PrepareRuntimeObjects();
2184   }
2185 
2186   // Measure duration of baseline compilation or deserialization from cache.
2187   if (base::TimeTicks::IsHighResolution()) {
2188     base::TimeDelta duration = base::TimeTicks::Now() - start_time_;
2189     int duration_usecs = static_cast<int>(duration.InMicroseconds());
2190     isolate_->counters()->wasm_streaming_finish_wasm_module_time()->AddSample(
2191         duration_usecs);
2192 
2193     if (is_after_cache_hit || is_after_deserialization) {
2194       v8::metrics::WasmModuleCompiled event{
2195           true,                                     // async
2196           true,                                     // streamed
2197           is_after_cache_hit,                       // cached
2198           is_after_deserialization,                 // deserialized
2199           wasm_lazy_compilation_,                   // lazy
2200           !compilation_state->failed(),             // success
2201           native_module_->turbofan_code_size(),     // code_size_in_bytes
2202           native_module_->liftoff_bailout_count(),  // liftoff_bailout_count
2203           duration.InMicroseconds(),                // wall_clock_duration_in_us
2204           static_cast<int64_t>(                     // cpu_time_duration_in_us
2205               native_module_->baseline_compilation_cpu_duration())};
2206       isolate_->metrics_recorder()->DelayMainThreadEvent(event, context_id_);
2207     }
2208   }
2209 
2210   DCHECK(!isolate_->context().is_null());
2211   // Finish the wasm script now and make it public to the debugger.
2212   Handle<Script> script(module_object_->script(), isolate_);
2213   const WasmModule* module = module_object_->module();
2214   if (script->type() == Script::TYPE_WASM &&
2215       module->debug_symbols.type == WasmDebugSymbols::Type::SourceMap &&
2216       !module->debug_symbols.external_url.is_empty()) {
2217     ModuleWireBytes wire_bytes(module_object_->native_module()->wire_bytes());
2218     MaybeHandle<String> src_map_str = isolate_->factory()->NewStringFromUtf8(
2219         wire_bytes.GetNameOrNull(module->debug_symbols.external_url),
2220         AllocationType::kOld);
2221     script->set_source_mapping_url(*src_map_str.ToHandleChecked());
2222   }
2223   {
2224     TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
2225                  "wasm.Debug.OnAfterCompile");
2226     isolate_->debug()->OnAfterCompile(script);
2227   }
2228 
2229   // TODO(bbudge) Allow deserialization without wrapper compilation, so we can
2230   // just compile wrappers here.
2231   if (!is_after_deserialization) {
2232     Handle<FixedArray> export_wrappers;
2233     if (is_after_cache_hit) {
2234       // TODO(thibaudm): Look into sharing wrappers.
2235       CompileJsToWasmWrappers(isolate_, module, &export_wrappers);
2236     } else {
2237       compilation_state->FinalizeJSToWasmWrappers(isolate_, module,
2238                                                   &export_wrappers);
2239     }
2240     module_object_->set_export_wrappers(*export_wrappers);
2241   }
2242   // We can only update the feature counts once the entire compile is done.
2243   compilation_state->PublishDetectedFeatures(isolate_);
2244 
2245   // We might need to recompile the module for debugging, if the debugger was
2246   // enabled while streaming compilation was running. Since handling this while
2247   // compiling via streaming is tricky, we just tier down now, before publishing
2248   // the module.
2249   if (native_module_->IsTieredDown()) native_module_->RecompileForTiering();
2250 
2251   // Finally, log all generated code (it does not matter if this happens
2252   // repeatedly in case the script is shared).
2253   native_module_->LogWasmCodes(isolate_, module_object_->script());
2254 
2255   FinishModule();
2256 }
2257 
DecodeFailed(const WasmError & error)2258 void AsyncCompileJob::DecodeFailed(const WasmError& error) {
2259   ErrorThrower thrower(isolate_, api_method_name_);
2260   thrower.CompileFailed(error);
2261   // {job} keeps the {this} pointer alive.
2262   std::shared_ptr<AsyncCompileJob> job =
2263       GetWasmEngine()->RemoveCompileJob(this);
2264   resolver_->OnCompilationFailed(thrower.Reify());
2265 }
2266 
AsyncCompileFailed()2267 void AsyncCompileJob::AsyncCompileFailed() {
2268   ErrorThrower thrower(isolate_, api_method_name_);
2269   DCHECK_EQ(native_module_->module()->origin, kWasmOrigin);
2270   const bool lazy_module = wasm_lazy_compilation_;
2271   ValidateSequentially(native_module_->module(), native_module_.get(),
2272                        isolate_->counters(), isolate_->allocator(), &thrower,
2273                        lazy_module);
2274   DCHECK(thrower.error());
2275   // {job} keeps the {this} pointer alive.
2276   std::shared_ptr<AsyncCompileJob> job =
2277       GetWasmEngine()->RemoveCompileJob(this);
2278   resolver_->OnCompilationFailed(thrower.Reify());
2279 }
2280 
AsyncCompileSucceeded(Handle<WasmModuleObject> result)2281 void AsyncCompileJob::AsyncCompileSucceeded(Handle<WasmModuleObject> result) {
2282   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
2283                "wasm.OnCompilationSucceeded");
2284   // We have to make sure that an "incumbent context" is available in case
2285   // the module's start function calls out to Blink.
2286   Local<v8::Context> backup_incumbent_context =
2287       Utils::ToLocal(incumbent_context_);
2288   v8::Context::BackupIncumbentScope incumbent(backup_incumbent_context);
2289   resolver_->OnCompilationSucceeded(result);
2290 }
2291 
2292 class AsyncCompileJob::CompilationStateCallback
2293     : public CompilationEventCallback {
2294  public:
CompilationStateCallback(AsyncCompileJob * job)2295   explicit CompilationStateCallback(AsyncCompileJob* job) : job_(job) {}
2296 
call(CompilationEvent event)2297   void call(CompilationEvent event) override {
2298     // This callback is only being called from a foreground task.
2299     switch (event) {
2300       case CompilationEvent::kFinishedExportWrappers:
2301         // Even if baseline compilation units finish first, we trigger the
2302         // "kFinishedExportWrappers" event first.
2303         DCHECK(!last_event_.has_value());
2304         break;
2305       case CompilationEvent::kFinishedBaselineCompilation:
2306         DCHECK_EQ(CompilationEvent::kFinishedExportWrappers, last_event_);
2307         if (job_->DecrementAndCheckFinisherCount()) {
2308           // Install the native module in the cache, or reuse a conflicting one.
2309           // If we get a conflicting module, wait until we are back in the
2310           // main thread to update {job_->native_module_} to avoid a data race.
2311           std::shared_ptr<NativeModule> native_module = job_->native_module_;
2312           bool cache_hit = !GetWasmEngine()->UpdateNativeModuleCache(
2313               false, &native_module, job_->isolate_);
2314           DCHECK_EQ(cache_hit, native_module != job_->native_module_);
2315           job_->DoSync<CompileFinished>(cache_hit ? std::move(native_module)
2316                                                   : nullptr);
2317         }
2318         break;
2319       case CompilationEvent::kFinishedCompilationChunk:
2320         DCHECK(CompilationEvent::kFinishedBaselineCompilation == last_event_ ||
2321                CompilationEvent::kFinishedCompilationChunk == last_event_);
2322         break;
2323       case CompilationEvent::kFinishedTopTierCompilation:
2324         DCHECK(CompilationEvent::kFinishedBaselineCompilation == last_event_);
2325         // At this point, the job will already be gone, thus do not access it
2326         // here.
2327         break;
2328       case CompilationEvent::kFailedCompilation:
2329         DCHECK(!last_event_.has_value() ||
2330                last_event_ == CompilationEvent::kFinishedExportWrappers);
2331         if (job_->DecrementAndCheckFinisherCount()) {
2332           // Don't update {job_->native_module_} to avoid data races with other
2333           // compilation threads. Use a copy of the shared pointer instead.
2334           std::shared_ptr<NativeModule> native_module = job_->native_module_;
2335           GetWasmEngine()->UpdateNativeModuleCache(true, &native_module,
2336                                                    job_->isolate_);
2337           job_->DoSync<CompileFailed>();
2338         }
2339         break;
2340       case CompilationEvent::kFinishedRecompilation:
2341         // This event can happen either before or after
2342         // {kFinishedTopTierCompilation}, hence don't remember this in
2343         // {last_event_}.
2344         return;
2345     }
2346 #ifdef DEBUG
2347     last_event_ = event;
2348 #endif
2349   }
2350 
2351  private:
2352   AsyncCompileJob* job_;
2353 #ifdef DEBUG
2354   // This will be modified by different threads, but they externally
2355   // synchronize, so no explicit synchronization (currently) needed here.
2356   base::Optional<CompilationEvent> last_event_;
2357 #endif
2358 };
2359 
2360 // A closure to run a compilation step (either as foreground or background
2361 // task) and schedule the next step(s), if any.
2362 class AsyncCompileJob::CompileStep {
2363  public:
2364   virtual ~CompileStep() = default;
2365 
Run(AsyncCompileJob * job,bool on_foreground)2366   void Run(AsyncCompileJob* job, bool on_foreground) {
2367     if (on_foreground) {
2368       HandleScope scope(job->isolate_);
2369       SaveAndSwitchContext saved_context(job->isolate_, *job->native_context_);
2370       RunInForeground(job);
2371     } else {
2372       RunInBackground(job);
2373     }
2374   }
2375 
RunInForeground(AsyncCompileJob *)2376   virtual void RunInForeground(AsyncCompileJob*) { UNREACHABLE(); }
RunInBackground(AsyncCompileJob *)2377   virtual void RunInBackground(AsyncCompileJob*) { UNREACHABLE(); }
2378 };
2379 
2380 class AsyncCompileJob::CompileTask : public CancelableTask {
2381  public:
CompileTask(AsyncCompileJob * job,bool on_foreground)2382   CompileTask(AsyncCompileJob* job, bool on_foreground)
2383       // We only manage the background tasks with the {CancelableTaskManager} of
2384       // the {AsyncCompileJob}. Foreground tasks are managed by the system's
2385       // {CancelableTaskManager}. Background tasks cannot spawn tasks managed by
2386       // their own task manager.
2387       : CancelableTask(on_foreground ? job->isolate_->cancelable_task_manager()
2388                                      : &job->background_task_manager_),
2389         job_(job),
2390         on_foreground_(on_foreground) {}
2391 
~CompileTask()2392   ~CompileTask() override {
2393     if (job_ != nullptr && on_foreground_) ResetPendingForegroundTask();
2394   }
2395 
RunInternal()2396   void RunInternal() final {
2397     if (!job_) return;
2398     if (on_foreground_) ResetPendingForegroundTask();
2399     job_->step_->Run(job_, on_foreground_);
2400     // After execution, reset {job_} such that we don't try to reset the pending
2401     // foreground task when the task is deleted.
2402     job_ = nullptr;
2403   }
2404 
Cancel()2405   void Cancel() {
2406     DCHECK_NOT_NULL(job_);
2407     job_ = nullptr;
2408   }
2409 
2410  private:
2411   // {job_} will be cleared to cancel a pending task.
2412   AsyncCompileJob* job_;
2413   bool on_foreground_;
2414 
ResetPendingForegroundTask() const2415   void ResetPendingForegroundTask() const {
2416     DCHECK_EQ(this, job_->pending_foreground_task_);
2417     job_->pending_foreground_task_ = nullptr;
2418   }
2419 };
2420 
StartForegroundTask()2421 void AsyncCompileJob::StartForegroundTask() {
2422   DCHECK_NULL(pending_foreground_task_);
2423 
2424   auto new_task = std::make_unique<CompileTask>(this, true);
2425   pending_foreground_task_ = new_task.get();
2426   foreground_task_runner_->PostTask(std::move(new_task));
2427 }
2428 
ExecuteForegroundTaskImmediately()2429 void AsyncCompileJob::ExecuteForegroundTaskImmediately() {
2430   DCHECK_NULL(pending_foreground_task_);
2431 
2432   auto new_task = std::make_unique<CompileTask>(this, true);
2433   pending_foreground_task_ = new_task.get();
2434   new_task->Run();
2435 }
2436 
CancelPendingForegroundTask()2437 void AsyncCompileJob::CancelPendingForegroundTask() {
2438   if (!pending_foreground_task_) return;
2439   pending_foreground_task_->Cancel();
2440   pending_foreground_task_ = nullptr;
2441 }
2442 
StartBackgroundTask()2443 void AsyncCompileJob::StartBackgroundTask() {
2444   auto task = std::make_unique<CompileTask>(this, false);
2445 
2446   // If --wasm-num-compilation-tasks=0 is passed, do only spawn foreground
2447   // tasks. This is used to make timing deterministic.
2448   if (FLAG_wasm_num_compilation_tasks > 0) {
2449     V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(task));
2450   } else {
2451     foreground_task_runner_->PostTask(std::move(task));
2452   }
2453 }
2454 
2455 template <typename Step,
2456           AsyncCompileJob::UseExistingForegroundTask use_existing_fg_task,
2457           typename... Args>
DoSync(Args &&...args)2458 void AsyncCompileJob::DoSync(Args&&... args) {
2459   NextStep<Step>(std::forward<Args>(args)...);
2460   if (use_existing_fg_task && pending_foreground_task_ != nullptr) return;
2461   StartForegroundTask();
2462 }
2463 
2464 template <typename Step, typename... Args>
DoImmediately(Args &&...args)2465 void AsyncCompileJob::DoImmediately(Args&&... args) {
2466   NextStep<Step>(std::forward<Args>(args)...);
2467   ExecuteForegroundTaskImmediately();
2468 }
2469 
2470 template <typename Step, typename... Args>
DoAsync(Args &&...args)2471 void AsyncCompileJob::DoAsync(Args&&... args) {
2472   NextStep<Step>(std::forward<Args>(args)...);
2473   StartBackgroundTask();
2474 }
2475 
2476 template <typename Step, typename... Args>
NextStep(Args &&...args)2477 void AsyncCompileJob::NextStep(Args&&... args) {
2478   step_.reset(new Step(std::forward<Args>(args)...));
2479 }
2480 
2481 //==========================================================================
2482 // Step 1: (async) Decode the module.
2483 //==========================================================================
2484 class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep {
2485  public:
DecodeModule(Counters * counters,std::shared_ptr<metrics::Recorder> metrics_recorder)2486   explicit DecodeModule(Counters* counters,
2487                         std::shared_ptr<metrics::Recorder> metrics_recorder)
2488       : counters_(counters), metrics_recorder_(std::move(metrics_recorder)) {}
2489 
RunInBackground(AsyncCompileJob * job)2490   void RunInBackground(AsyncCompileJob* job) override {
2491     ModuleResult result;
2492     {
2493       DisallowHandleAllocation no_handle;
2494       DisallowGarbageCollection no_gc;
2495       // Decode the module bytes.
2496       TRACE_COMPILE("(1) Decoding module...\n");
2497       TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
2498                    "wasm.DecodeModule");
2499       auto enabled_features = job->enabled_features_;
2500       result = DecodeWasmModule(
2501           enabled_features, job->wire_bytes_.start(), job->wire_bytes_.end(),
2502           false, kWasmOrigin, counters_, metrics_recorder_, job->context_id(),
2503           DecodingMethod::kAsync, GetWasmEngine()->allocator());
2504 
2505       // Validate lazy functions here if requested.
2506       if (!FLAG_wasm_lazy_validation && result.ok()) {
2507         const WasmModule* module = result.value().get();
2508         DCHECK_EQ(module->origin, kWasmOrigin);
2509         const bool lazy_module = job->wasm_lazy_compilation_;
2510         if (MayCompriseLazyFunctions(module, enabled_features, lazy_module)) {
2511           auto allocator = GetWasmEngine()->allocator();
2512           int start = module->num_imported_functions;
2513           int end = start + module->num_declared_functions;
2514 
2515           for (int func_index = start; func_index < end; func_index++) {
2516             const WasmFunction* func = &module->functions[func_index];
2517             base::Vector<const uint8_t> code =
2518                 job->wire_bytes_.GetFunctionBytes(func);
2519 
2520             CompileStrategy strategy = GetCompileStrategy(
2521                 module, enabled_features, func_index, lazy_module);
2522             bool validate_lazily_compiled_function =
2523                 strategy == CompileStrategy::kLazy ||
2524                 strategy == CompileStrategy::kLazyBaselineEagerTopTier;
2525             if (validate_lazily_compiled_function) {
2526               DecodeResult function_result =
2527                   ValidateSingleFunction(module, func_index, code, counters_,
2528                                          allocator, enabled_features);
2529               if (function_result.failed()) {
2530                 result = ModuleResult(function_result.error());
2531                 break;
2532               }
2533             }
2534           }
2535         }
2536       }
2537     }
2538     if (result.failed()) {
2539       // Decoding failure; reject the promise and clean up.
2540       job->DoSync<DecodeFail>(std::move(result).error());
2541     } else {
2542       // Decode passed.
2543       std::shared_ptr<WasmModule> module = std::move(result).value();
2544       const bool include_liftoff = FLAG_liftoff;
2545       size_t code_size_estimate =
2546           wasm::WasmCodeManager::EstimateNativeModuleCodeSize(
2547               module.get(), include_liftoff, job->dynamic_tiering_);
2548       job->DoSync<PrepareAndStartCompile>(std::move(module), true,
2549                                           code_size_estimate);
2550     }
2551   }
2552 
2553  private:
2554   Counters* const counters_;
2555   std::shared_ptr<metrics::Recorder> metrics_recorder_;
2556 };
2557 
2558 //==========================================================================
2559 // Step 1b: (sync) Fail decoding the module.
2560 //==========================================================================
2561 class AsyncCompileJob::DecodeFail : public CompileStep {
2562  public:
DecodeFail(WasmError error)2563   explicit DecodeFail(WasmError error) : error_(std::move(error)) {}
2564 
2565  private:
2566   WasmError error_;
2567 
RunInForeground(AsyncCompileJob * job)2568   void RunInForeground(AsyncCompileJob* job) override {
2569     TRACE_COMPILE("(1b) Decoding failed.\n");
2570     // {job_} is deleted in DecodeFailed, therefore the {return}.
2571     return job->DecodeFailed(error_);
2572   }
2573 };
2574 
2575 //==========================================================================
2576 // Step 2 (sync): Create heap-allocated data and start compile.
2577 //==========================================================================
2578 class AsyncCompileJob::PrepareAndStartCompile : public CompileStep {
2579  public:
PrepareAndStartCompile(std::shared_ptr<const WasmModule> module,bool start_compilation,size_t code_size_estimate)2580   PrepareAndStartCompile(std::shared_ptr<const WasmModule> module,
2581                          bool start_compilation, size_t code_size_estimate)
2582       : module_(std::move(module)),
2583         start_compilation_(start_compilation),
2584         code_size_estimate_(code_size_estimate) {}
2585 
2586  private:
RunInForeground(AsyncCompileJob * job)2587   void RunInForeground(AsyncCompileJob* job) override {
2588     TRACE_COMPILE("(2) Prepare and start compile...\n");
2589 
2590     const bool streaming = job->wire_bytes_.length() == 0;
2591     if (streaming) {
2592       // Streaming compilation already checked for cache hits.
2593       job->CreateNativeModule(module_, code_size_estimate_);
2594     } else if (job->GetOrCreateNativeModule(std::move(module_),
2595                                             code_size_estimate_)) {
2596       job->FinishCompile(true);
2597       return;
2598     }
2599 
2600     // Make sure all compilation tasks stopped running. Decoding (async step)
2601     // is done.
2602     job->background_task_manager_.CancelAndWait();
2603 
2604     CompilationStateImpl* compilation_state =
2605         Impl(job->native_module_->compilation_state());
2606     compilation_state->AddCallback(
2607         std::make_unique<CompilationStateCallback>(job));
2608     if (base::TimeTicks::IsHighResolution()) {
2609       auto compile_mode = job->stream_ == nullptr
2610                               ? CompilationTimeCallback::kAsync
2611                               : CompilationTimeCallback::kStreaming;
2612       compilation_state->AddCallback(std::make_unique<CompilationTimeCallback>(
2613           job->isolate_->async_counters(), job->isolate_->metrics_recorder(),
2614           job->context_id_, job->native_module_, compile_mode));
2615     }
2616 
2617     if (start_compilation_) {
2618       std::unique_ptr<CompilationUnitBuilder> builder =
2619           InitializeCompilation(job->isolate(), job->native_module_.get());
2620       compilation_state->InitializeCompilationUnits(std::move(builder));
2621       // We are in single-threaded mode, so there are no worker tasks that will
2622       // do the compilation. We call {WaitForCompilationEvent} here so that the
2623       // main thread paticipates and finishes the compilation.
2624       if (FLAG_wasm_num_compilation_tasks == 0) {
2625         compilation_state->WaitForCompilationEvent(
2626             CompilationEvent::kFinishedBaselineCompilation);
2627       }
2628     }
2629   }
2630 
2631   const std::shared_ptr<const WasmModule> module_;
2632   const bool start_compilation_;
2633   const size_t code_size_estimate_;
2634 };
2635 
2636 //==========================================================================
2637 // Step 3a (sync): Compilation failed.
2638 //==========================================================================
2639 class AsyncCompileJob::CompileFailed : public CompileStep {
2640  private:
RunInForeground(AsyncCompileJob * job)2641   void RunInForeground(AsyncCompileJob* job) override {
2642     TRACE_COMPILE("(3a) Compilation failed\n");
2643     DCHECK(job->native_module_->compilation_state()->failed());
2644 
2645     // {job_} is deleted in AsyncCompileFailed, therefore the {return}.
2646     return job->AsyncCompileFailed();
2647   }
2648 };
2649 
2650 namespace {
2651 class SampleTopTierCodeSizeCallback : public CompilationEventCallback {
2652  public:
SampleTopTierCodeSizeCallback(std::weak_ptr<NativeModule> native_module)2653   explicit SampleTopTierCodeSizeCallback(
2654       std::weak_ptr<NativeModule> native_module)
2655       : native_module_(std::move(native_module)) {}
2656 
call(CompilationEvent event)2657   void call(CompilationEvent event) override {
2658     if (event != CompilationEvent::kFinishedTopTierCompilation) return;
2659     if (std::shared_ptr<NativeModule> native_module = native_module_.lock()) {
2660       GetWasmEngine()->SampleTopTierCodeSizeInAllIsolates(native_module);
2661     }
2662   }
2663 
2664  private:
2665   std::weak_ptr<NativeModule> native_module_;
2666 };
2667 }  // namespace
2668 
2669 //==========================================================================
2670 // Step 3b (sync): Compilation finished.
2671 //==========================================================================
2672 class AsyncCompileJob::CompileFinished : public CompileStep {
2673  public:
CompileFinished(std::shared_ptr<NativeModule> cached_native_module)2674   explicit CompileFinished(std::shared_ptr<NativeModule> cached_native_module)
2675       : cached_native_module_(std::move(cached_native_module)) {}
2676 
2677  private:
RunInForeground(AsyncCompileJob * job)2678   void RunInForeground(AsyncCompileJob* job) override {
2679     TRACE_COMPILE("(3b) Compilation finished\n");
2680     if (cached_native_module_) {
2681       job->native_module_ = cached_native_module_;
2682     } else {
2683       DCHECK(!job->native_module_->compilation_state()->failed());
2684       // Sample the generated code size when baseline compilation finished.
2685       job->native_module_->SampleCodeSize(job->isolate_->counters(),
2686                                           NativeModule::kAfterBaseline);
2687       // Also, set a callback to sample the code size after top-tier compilation
2688       // finished. This callback will *not* keep the NativeModule alive.
2689       job->native_module_->compilation_state()->AddCallback(
2690           std::make_unique<SampleTopTierCodeSizeCallback>(job->native_module_));
2691     }
2692     // Then finalize and publish the generated module.
2693     job->FinishCompile(cached_native_module_ != nullptr);
2694   }
2695 
2696   std::shared_ptr<NativeModule> cached_native_module_;
2697 };
2698 
FinishModule()2699 void AsyncCompileJob::FinishModule() {
2700   TRACE_COMPILE("(4) Finish module...\n");
2701   AsyncCompileSucceeded(module_object_);
2702   GetWasmEngine()->RemoveCompileJob(this);
2703 }
2704 
AsyncStreamingProcessor(AsyncCompileJob * job,std::shared_ptr<Counters> async_counters,AccountingAllocator * allocator)2705 AsyncStreamingProcessor::AsyncStreamingProcessor(
2706     AsyncCompileJob* job, std::shared_ptr<Counters> async_counters,
2707     AccountingAllocator* allocator)
2708     : decoder_(job->enabled_features_),
2709       job_(job),
2710       compilation_unit_builder_(nullptr),
2711       async_counters_(async_counters),
2712       allocator_(allocator) {}
2713 
~AsyncStreamingProcessor()2714 AsyncStreamingProcessor::~AsyncStreamingProcessor() {
2715   if (job_->native_module_ && job_->native_module_->wire_bytes().empty()) {
2716     // Clean up the temporary cache entry.
2717     GetWasmEngine()->StreamingCompilationFailed(prefix_hash_);
2718   }
2719 }
2720 
FinishAsyncCompileJobWithError(const WasmError & error)2721 void AsyncStreamingProcessor::FinishAsyncCompileJobWithError(
2722     const WasmError& error) {
2723   DCHECK(error.has_error());
2724   // Make sure all background tasks stopped executing before we change the state
2725   // of the AsyncCompileJob to DecodeFail.
2726   job_->background_task_manager_.CancelAndWait();
2727 
2728   // Record event metrics.
2729   auto duration = base::TimeTicks::Now() - job_->start_time_;
2730   job_->metrics_event_.success = false;
2731   job_->metrics_event_.streamed = true;
2732   job_->metrics_event_.module_size_in_bytes = job_->wire_bytes_.length();
2733   job_->metrics_event_.function_count = num_functions_;
2734   job_->metrics_event_.wall_clock_duration_in_us = duration.InMicroseconds();
2735   job_->isolate_->metrics_recorder()->DelayMainThreadEvent(job_->metrics_event_,
2736                                                            job_->context_id_);
2737 
2738   // Check if there is already a CompiledModule, in which case we have to clean
2739   // up the CompilationStateImpl as well.
2740   if (job_->native_module_) {
2741     Impl(job_->native_module_->compilation_state())
2742         ->CancelCompilation(CompilationStateImpl::kCancelUnconditionally);
2743 
2744     job_->DoSync<AsyncCompileJob::DecodeFail,
2745                  AsyncCompileJob::kUseExistingForegroundTask>(error);
2746 
2747     // Clear the {compilation_unit_builder_} if it exists. This is needed
2748     // because there is a check in the destructor of the
2749     // {CompilationUnitBuilder} that it is empty.
2750     if (compilation_unit_builder_) compilation_unit_builder_->Clear();
2751   } else {
2752     job_->DoSync<AsyncCompileJob::DecodeFail>(error);
2753   }
2754 }
2755 
2756 // Process the module header.
ProcessModuleHeader(base::Vector<const uint8_t> bytes,uint32_t offset)2757 bool AsyncStreamingProcessor::ProcessModuleHeader(
2758     base::Vector<const uint8_t> bytes, uint32_t offset) {
2759   TRACE_STREAMING("Process module header...\n");
2760   decoder_.StartDecoding(job_->isolate()->counters(),
2761                          job_->isolate()->metrics_recorder(),
2762                          job_->context_id(), GetWasmEngine()->allocator());
2763   decoder_.DecodeModuleHeader(bytes, offset);
2764   if (!decoder_.ok()) {
2765     FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false).error());
2766     return false;
2767   }
2768   prefix_hash_ = NativeModuleCache::WireBytesHash(bytes);
2769   return true;
2770 }
2771 
2772 // Process all sections except for the code section.
ProcessSection(SectionCode section_code,base::Vector<const uint8_t> bytes,uint32_t offset)2773 bool AsyncStreamingProcessor::ProcessSection(SectionCode section_code,
2774                                              base::Vector<const uint8_t> bytes,
2775                                              uint32_t offset) {
2776   TRACE_STREAMING("Process section %d ...\n", section_code);
2777   if (compilation_unit_builder_) {
2778     // We reached a section after the code section, we do not need the
2779     // compilation_unit_builder_ anymore.
2780     CommitCompilationUnits();
2781     compilation_unit_builder_.reset();
2782   }
2783   if (before_code_section_) {
2784     // Combine section hashes until code section.
2785     prefix_hash_ = base::hash_combine(prefix_hash_,
2786                                       NativeModuleCache::WireBytesHash(bytes));
2787   }
2788   if (section_code == SectionCode::kUnknownSectionCode) {
2789     size_t bytes_consumed = ModuleDecoder::IdentifyUnknownSection(
2790         &decoder_, bytes, offset, &section_code);
2791     if (!decoder_.ok()) {
2792       FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false).error());
2793       return false;
2794     }
2795     if (section_code == SectionCode::kUnknownSectionCode) {
2796       // Skip unknown sections that we do not know how to handle.
2797       return true;
2798     }
2799     // Remove the unknown section tag from the payload bytes.
2800     offset += bytes_consumed;
2801     bytes = bytes.SubVector(bytes_consumed, bytes.size());
2802   }
2803   constexpr bool verify_functions = false;
2804   decoder_.DecodeSection(section_code, bytes, offset, verify_functions);
2805   if (!decoder_.ok()) {
2806     FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false).error());
2807     return false;
2808   }
2809   return true;
2810 }
2811 
2812 // Start the code section.
ProcessCodeSectionHeader(int num_functions,uint32_t functions_mismatch_error_offset,std::shared_ptr<WireBytesStorage> wire_bytes_storage,int code_section_start,int code_section_length)2813 bool AsyncStreamingProcessor::ProcessCodeSectionHeader(
2814     int num_functions, uint32_t functions_mismatch_error_offset,
2815     std::shared_ptr<WireBytesStorage> wire_bytes_storage,
2816     int code_section_start, int code_section_length) {
2817   DCHECK_LE(0, code_section_length);
2818   before_code_section_ = false;
2819   TRACE_STREAMING("Start the code section with %d functions...\n",
2820                   num_functions);
2821   decoder_.StartCodeSection();
2822   if (!decoder_.CheckFunctionsCount(static_cast<uint32_t>(num_functions),
2823                                     functions_mismatch_error_offset)) {
2824     FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false).error());
2825     return false;
2826   }
2827 
2828   decoder_.set_code_section(code_section_start,
2829                             static_cast<uint32_t>(code_section_length));
2830 
2831   prefix_hash_ = base::hash_combine(prefix_hash_,
2832                                     static_cast<uint32_t>(code_section_length));
2833   if (!GetWasmEngine()->GetStreamingCompilationOwnership(prefix_hash_)) {
2834     // Known prefix, wait until the end of the stream and check the cache.
2835     prefix_cache_hit_ = true;
2836     return true;
2837   }
2838 
2839   // Execute the PrepareAndStartCompile step immediately and not in a separate
2840   // task.
2841   int num_imported_functions =
2842       static_cast<int>(decoder_.module()->num_imported_functions);
2843   DCHECK_EQ(kWasmOrigin, decoder_.module()->origin);
2844   const bool include_liftoff = FLAG_liftoff;
2845   size_t code_size_estimate =
2846       wasm::WasmCodeManager::EstimateNativeModuleCodeSize(
2847           num_functions, num_imported_functions, code_section_length,
2848           include_liftoff, job_->dynamic_tiering_);
2849   job_->DoImmediately<AsyncCompileJob::PrepareAndStartCompile>(
2850       decoder_.shared_module(), false, code_size_estimate);
2851 
2852   auto* compilation_state = Impl(job_->native_module_->compilation_state());
2853   compilation_state->SetWireBytesStorage(std::move(wire_bytes_storage));
2854   DCHECK_EQ(job_->native_module_->module()->origin, kWasmOrigin);
2855 
2856   // Set outstanding_finishers_ to 2, because both the AsyncCompileJob and the
2857   // AsyncStreamingProcessor have to finish.
2858   job_->outstanding_finishers_.store(2);
2859   compilation_unit_builder_ =
2860       InitializeCompilation(job_->isolate(), job_->native_module_.get());
2861   return true;
2862 }
2863 
2864 // Process a function body.
ProcessFunctionBody(base::Vector<const uint8_t> bytes,uint32_t offset)2865 bool AsyncStreamingProcessor::ProcessFunctionBody(
2866     base::Vector<const uint8_t> bytes, uint32_t offset) {
2867   TRACE_STREAMING("Process function body %d ...\n", num_functions_);
2868 
2869   decoder_.DecodeFunctionBody(
2870       num_functions_, static_cast<uint32_t>(bytes.length()), offset, false);
2871 
2872   const WasmModule* module = decoder_.module();
2873   auto enabled_features = job_->enabled_features_;
2874   uint32_t func_index =
2875       num_functions_ + decoder_.module()->num_imported_functions;
2876   DCHECK_EQ(module->origin, kWasmOrigin);
2877   const bool lazy_module = job_->wasm_lazy_compilation_;
2878   CompileStrategy strategy =
2879       GetCompileStrategy(module, enabled_features, func_index, lazy_module);
2880   bool validate_lazily_compiled_function =
2881       !FLAG_wasm_lazy_validation &&
2882       (strategy == CompileStrategy::kLazy ||
2883        strategy == CompileStrategy::kLazyBaselineEagerTopTier);
2884   if (validate_lazily_compiled_function) {
2885     // The native module does not own the wire bytes until {SetWireBytes} is
2886     // called in {OnFinishedStream}. Validation must use {bytes} parameter.
2887     DecodeResult result =
2888         ValidateSingleFunction(module, func_index, bytes, async_counters_.get(),
2889                                allocator_, enabled_features);
2890 
2891     if (result.failed()) {
2892       FinishAsyncCompileJobWithError(result.error());
2893       return false;
2894     }
2895   }
2896 
2897   // Don't compile yet if we might have a cache hit.
2898   if (prefix_cache_hit_) {
2899     num_functions_++;
2900     return true;
2901   }
2902 
2903   auto* compilation_state = Impl(job_->native_module_->compilation_state());
2904   compilation_state->AddCompilationUnit(compilation_unit_builder_.get(),
2905                                         func_index);
2906   ++num_functions_;
2907 
2908   return true;
2909 }
2910 
CommitCompilationUnits()2911 void AsyncStreamingProcessor::CommitCompilationUnits() {
2912   DCHECK(compilation_unit_builder_);
2913   compilation_unit_builder_->Commit();
2914 }
2915 
OnFinishedChunk()2916 void AsyncStreamingProcessor::OnFinishedChunk() {
2917   TRACE_STREAMING("FinishChunk...\n");
2918   if (compilation_unit_builder_) CommitCompilationUnits();
2919 }
2920 
2921 // Finish the processing of the stream.
OnFinishedStream(base::OwnedVector<uint8_t> bytes)2922 void AsyncStreamingProcessor::OnFinishedStream(
2923     base::OwnedVector<uint8_t> bytes) {
2924   TRACE_STREAMING("Finish stream...\n");
2925   DCHECK_EQ(NativeModuleCache::PrefixHash(bytes.as_vector()), prefix_hash_);
2926   ModuleResult result = decoder_.FinishDecoding(false);
2927   if (result.failed()) {
2928     FinishAsyncCompileJobWithError(result.error());
2929     return;
2930   }
2931 
2932   job_->wire_bytes_ = ModuleWireBytes(bytes.as_vector());
2933   job_->bytes_copy_ = bytes.ReleaseData();
2934 
2935   // Record event metrics.
2936   auto duration = base::TimeTicks::Now() - job_->start_time_;
2937   job_->metrics_event_.success = true;
2938   job_->metrics_event_.streamed = true;
2939   job_->metrics_event_.module_size_in_bytes = job_->wire_bytes_.length();
2940   job_->metrics_event_.function_count = num_functions_;
2941   job_->metrics_event_.wall_clock_duration_in_us = duration.InMicroseconds();
2942   job_->isolate_->metrics_recorder()->DelayMainThreadEvent(job_->metrics_event_,
2943                                                            job_->context_id_);
2944 
2945   if (prefix_cache_hit_) {
2946     // Restart as an asynchronous, non-streaming compilation. Most likely
2947     // {PrepareAndStartCompile} will get the native module from the cache.
2948     const bool include_liftoff = FLAG_liftoff;
2949     size_t code_size_estimate =
2950         wasm::WasmCodeManager::EstimateNativeModuleCodeSize(
2951             result.value().get(), include_liftoff, job_->dynamic_tiering_);
2952     job_->DoSync<AsyncCompileJob::PrepareAndStartCompile>(
2953         std::move(result).value(), true, code_size_estimate);
2954     return;
2955   }
2956 
2957   // We have to open a HandleScope and prepare the Context for
2958   // CreateNativeModule, PrepareRuntimeObjects and FinishCompile as this is a
2959   // callback from the embedder.
2960   HandleScope scope(job_->isolate_);
2961   SaveAndSwitchContext saved_context(job_->isolate_, *job_->native_context_);
2962 
2963   // Record the size of the wire bytes. In synchronous and asynchronous
2964   // (non-streaming) compilation, this happens in {DecodeWasmModule}.
2965   auto* histogram = job_->isolate_->counters()->wasm_wasm_module_size_bytes();
2966   histogram->AddSample(job_->wire_bytes_.module_bytes().length());
2967 
2968   const bool has_code_section = job_->native_module_ != nullptr;
2969   bool cache_hit = false;
2970   if (!has_code_section) {
2971     // We are processing a WebAssembly module without code section. Create the
2972     // native module now (would otherwise happen in {PrepareAndStartCompile} or
2973     // {ProcessCodeSectionHeader}).
2974     constexpr size_t kCodeSizeEstimate = 0;
2975     cache_hit = job_->GetOrCreateNativeModule(std::move(result).value(),
2976                                               kCodeSizeEstimate);
2977   } else {
2978     job_->native_module_->SetWireBytes(
2979         {std::move(job_->bytes_copy_), job_->wire_bytes_.length()});
2980   }
2981   const bool needs_finish = job_->DecrementAndCheckFinisherCount();
2982   DCHECK_IMPLIES(!has_code_section, needs_finish);
2983   if (needs_finish) {
2984     const bool failed = job_->native_module_->compilation_state()->failed();
2985     if (!cache_hit) {
2986       cache_hit = !GetWasmEngine()->UpdateNativeModuleCache(
2987           failed, &job_->native_module_, job_->isolate_);
2988     }
2989     if (failed) {
2990       job_->AsyncCompileFailed();
2991     } else {
2992       job_->FinishCompile(cache_hit);
2993     }
2994   }
2995 }
2996 
2997 // Report an error detected in the StreamingDecoder.
OnError(const WasmError & error)2998 void AsyncStreamingProcessor::OnError(const WasmError& error) {
2999   TRACE_STREAMING("Stream error...\n");
3000   FinishAsyncCompileJobWithError(error);
3001 }
3002 
OnAbort()3003 void AsyncStreamingProcessor::OnAbort() {
3004   TRACE_STREAMING("Abort stream...\n");
3005   job_->Abort();
3006 }
3007 
Deserialize(base::Vector<const uint8_t> module_bytes,base::Vector<const uint8_t> wire_bytes)3008 bool AsyncStreamingProcessor::Deserialize(
3009     base::Vector<const uint8_t> module_bytes,
3010     base::Vector<const uint8_t> wire_bytes) {
3011   TRACE_EVENT0("v8.wasm", "wasm.Deserialize");
3012   TimedHistogramScope time_scope(
3013       job_->isolate()->counters()->wasm_deserialization_time(),
3014       job_->isolate());
3015   // DeserializeNativeModule and FinishCompile assume that they are executed in
3016   // a HandleScope, and that a context is set on the isolate.
3017   HandleScope scope(job_->isolate_);
3018   SaveAndSwitchContext saved_context(job_->isolate_, *job_->native_context_);
3019 
3020   MaybeHandle<WasmModuleObject> result = DeserializeNativeModule(
3021       job_->isolate_, module_bytes, wire_bytes, job_->stream_->url());
3022 
3023   if (result.is_null()) return false;
3024 
3025   job_->module_object_ =
3026       job_->isolate_->global_handles()->Create(*result.ToHandleChecked());
3027   job_->native_module_ = job_->module_object_->shared_native_module();
3028   job_->wire_bytes_ = ModuleWireBytes(job_->native_module_->wire_bytes());
3029   job_->FinishCompile(false);
3030   return true;
3031 }
3032 
CompilationStateImpl(const std::shared_ptr<NativeModule> & native_module,std::shared_ptr<Counters> async_counters,DynamicTiering dynamic_tiering)3033 CompilationStateImpl::CompilationStateImpl(
3034     const std::shared_ptr<NativeModule>& native_module,
3035     std::shared_ptr<Counters> async_counters, DynamicTiering dynamic_tiering)
3036     : native_module_(native_module.get()),
3037       native_module_weak_(std::move(native_module)),
3038       async_counters_(std::move(async_counters)),
3039       compilation_unit_queues_(native_module->num_functions()),
3040       dynamic_tiering_(dynamic_tiering) {}
3041 
InitCompileJob()3042 void CompilationStateImpl::InitCompileJob() {
3043   DCHECK_NULL(compile_job_);
3044   compile_job_ = V8::GetCurrentPlatform()->PostJob(
3045       TaskPriority::kUserVisible, std::make_unique<BackgroundCompileJob>(
3046                                       native_module_weak_, async_counters_));
3047 }
3048 
CancelCompilation(CompilationStateImpl::CancellationPolicy cancellation_policy)3049 void CompilationStateImpl::CancelCompilation(
3050     CompilationStateImpl::CancellationPolicy cancellation_policy) {
3051   base::MutexGuard callbacks_guard(&callbacks_mutex_);
3052 
3053   if (cancellation_policy == kCancelInitialCompilation &&
3054       finished_events_.contains(
3055           CompilationEvent::kFinishedBaselineCompilation)) {
3056     // Initial compilation already finished; cannot be cancelled.
3057     return;
3058   }
3059 
3060   // std::memory_order_relaxed is sufficient because no other state is
3061   // synchronized with |compile_cancelled_|.
3062   compile_cancelled_.store(true, std::memory_order_relaxed);
3063 
3064   // No more callbacks after abort.
3065   callbacks_.clear();
3066 }
3067 
cancelled() const3068 bool CompilationStateImpl::cancelled() const {
3069   return compile_cancelled_.load(std::memory_order_relaxed);
3070 }
3071 
SetupCompilationProgressForFunction(bool lazy_function,NativeModule * native_module,const WasmFeatures & enabled_features,int func_index)3072 uint8_t CompilationStateImpl::SetupCompilationProgressForFunction(
3073     bool lazy_function, NativeModule* native_module,
3074     const WasmFeatures& enabled_features, int func_index) {
3075   ExecutionTierPair requested_tiers =
3076       GetRequestedExecutionTiers(native_module, enabled_features, func_index);
3077   CompileStrategy strategy = GetCompileStrategy(
3078       native_module->module(), enabled_features, func_index, lazy_function);
3079 
3080   bool required_for_baseline = strategy == CompileStrategy::kEager;
3081   bool required_for_top_tier = strategy != CompileStrategy::kLazy;
3082   DCHECK_EQ(required_for_top_tier,
3083             strategy == CompileStrategy::kEager ||
3084                 strategy == CompileStrategy::kLazyBaselineEagerTopTier);
3085 
3086   // Count functions to complete baseline and top tier compilation.
3087   if (required_for_baseline) outstanding_baseline_units_++;
3088   if (required_for_top_tier) outstanding_top_tier_functions_++;
3089 
3090   // Initialize function's compilation progress.
3091   ExecutionTier required_baseline_tier = required_for_baseline
3092                                              ? requested_tiers.baseline_tier
3093                                              : ExecutionTier::kNone;
3094   ExecutionTier required_top_tier =
3095       required_for_top_tier ? requested_tiers.top_tier : ExecutionTier::kNone;
3096   uint8_t function_progress =
3097       ReachedTierField::encode(ExecutionTier::kNone) |
3098       RequiredBaselineTierField::encode(required_baseline_tier) |
3099       RequiredTopTierField::encode(required_top_tier);
3100 
3101   return function_progress;
3102 }
3103 
InitializeCompilationProgress(bool lazy_module,int num_import_wrappers,int num_export_wrappers)3104 void CompilationStateImpl::InitializeCompilationProgress(
3105     bool lazy_module, int num_import_wrappers, int num_export_wrappers) {
3106   DCHECK(!failed());
3107   auto enabled_features = native_module_->enabled_features();
3108   auto* module = native_module_->module();
3109 
3110   base::MutexGuard guard(&callbacks_mutex_);
3111   DCHECK_EQ(0, outstanding_baseline_units_);
3112   DCHECK_EQ(0, outstanding_export_wrappers_);
3113   DCHECK_EQ(0, outstanding_top_tier_functions_);
3114   compilation_progress_.reserve(module->num_declared_functions);
3115   int start = module->num_imported_functions;
3116   int end = start + module->num_declared_functions;
3117 
3118   const bool prefer_liftoff = native_module_->IsTieredDown();
3119   for (int func_index = start; func_index < end; func_index++) {
3120     if (prefer_liftoff) {
3121       constexpr uint8_t kLiftoffOnlyFunctionProgress =
3122           RequiredTopTierField::encode(ExecutionTier::kLiftoff) |
3123           RequiredBaselineTierField::encode(ExecutionTier::kLiftoff) |
3124           ReachedTierField::encode(ExecutionTier::kNone);
3125       compilation_progress_.push_back(kLiftoffOnlyFunctionProgress);
3126       outstanding_baseline_units_++;
3127       outstanding_top_tier_functions_++;
3128       continue;
3129     }
3130     uint8_t function_progress = SetupCompilationProgressForFunction(
3131         lazy_module, native_module_, enabled_features, func_index);
3132     compilation_progress_.push_back(function_progress);
3133   }
3134   DCHECK_IMPLIES(lazy_module, outstanding_baseline_units_ == 0);
3135   DCHECK_IMPLIES(lazy_module, outstanding_top_tier_functions_ == 0);
3136   DCHECK_LE(0, outstanding_baseline_units_);
3137   DCHECK_LE(outstanding_baseline_units_, outstanding_top_tier_functions_);
3138   outstanding_baseline_units_ += num_import_wrappers;
3139   outstanding_export_wrappers_ = num_export_wrappers;
3140 
3141   // Trigger callbacks if module needs no baseline or top tier compilation. This
3142   // can be the case for an empty or fully lazy module.
3143   TriggerCallbacks();
3144 }
3145 
AddCompilationUnitInternal(CompilationUnitBuilder * builder,int function_index,uint8_t function_progress)3146 uint8_t CompilationStateImpl::AddCompilationUnitInternal(
3147     CompilationUnitBuilder* builder, int function_index,
3148     uint8_t function_progress) {
3149   ExecutionTier required_baseline_tier =
3150       CompilationStateImpl::RequiredBaselineTierField::decode(
3151           function_progress);
3152   ExecutionTier required_top_tier =
3153       CompilationStateImpl::RequiredTopTierField::decode(function_progress);
3154   ExecutionTier reached_tier =
3155       CompilationStateImpl::ReachedTierField::decode(function_progress);
3156 
3157   if (FLAG_experimental_wasm_gc) {
3158     // The Turbofan optimizations we enable for WasmGC code can (for now)
3159     // take a very long time, so skip Turbofan compilation for super-large
3160     // functions.
3161     // Besides, module serialization currently requires that all functions
3162     // have been TF-compiled. By enabling this limit only for WasmGC, we
3163     // make sure that non-experimental modules can be serialize as usual.
3164     // TODO(jkummerow): This is a stop-gap solution to avoid excessive
3165     // compile times. We would like to replace this hard threshold with
3166     // a better solution (TBD) eventually.
3167     constexpr uint32_t kMaxWasmFunctionSizeForTurbofan = 500 * KB;
3168     uint32_t size = builder->module()->functions[function_index].code.length();
3169     if (size > kMaxWasmFunctionSizeForTurbofan) {
3170       required_baseline_tier = ExecutionTier::kLiftoff;
3171       if (required_top_tier == ExecutionTier::kTurbofan) {
3172         required_top_tier = ExecutionTier::kLiftoff;
3173         outstanding_top_tier_functions_--;
3174       }
3175     }
3176   }
3177 
3178   if (reached_tier < required_baseline_tier) {
3179     builder->AddBaselineUnit(function_index, required_baseline_tier);
3180   }
3181   if (reached_tier < required_top_tier &&
3182       required_baseline_tier != required_top_tier) {
3183     builder->AddTopTierUnit(function_index, required_top_tier);
3184   }
3185   return CompilationStateImpl::RequiredBaselineTierField::encode(
3186              required_baseline_tier) |
3187          CompilationStateImpl::RequiredTopTierField::encode(required_top_tier) |
3188          CompilationStateImpl::ReachedTierField::encode(reached_tier);
3189 }
3190 
InitializeCompilationUnits(std::unique_ptr<CompilationUnitBuilder> builder)3191 void CompilationStateImpl::InitializeCompilationUnits(
3192     std::unique_ptr<CompilationUnitBuilder> builder) {
3193   int offset = native_module_->module()->num_imported_functions;
3194   if (native_module_->IsTieredDown()) {
3195     for (size_t i = 0; i < compilation_progress_.size(); ++i) {
3196       int func_index = offset + static_cast<int>(i);
3197       builder->AddDebugUnit(func_index);
3198     }
3199   } else {
3200     base::MutexGuard guard(&callbacks_mutex_);
3201 
3202     for (size_t i = 0; i < compilation_progress_.size(); ++i) {
3203       uint8_t function_progress = compilation_progress_[i];
3204       int func_index = offset + static_cast<int>(i);
3205       compilation_progress_[i] = AddCompilationUnitInternal(
3206           builder.get(), func_index, function_progress);
3207     }
3208   }
3209   builder->Commit();
3210 }
3211 
AddCompilationUnit(CompilationUnitBuilder * builder,int func_index)3212 void CompilationStateImpl::AddCompilationUnit(CompilationUnitBuilder* builder,
3213                                               int func_index) {
3214   if (native_module_->IsTieredDown()) {
3215     builder->AddDebugUnit(func_index);
3216     return;
3217   }
3218   int offset = native_module_->module()->num_imported_functions;
3219   int progress_index = func_index - offset;
3220   uint8_t function_progress;
3221   {
3222     // TODO(ahaas): This lock may cause overhead. If so, we could get rid of the
3223     // lock as follows:
3224     // 1) Make compilation_progress_ an array of atomic<uint8_t>, and access it
3225     // lock-free.
3226     // 2) Have a copy of compilation_progress_ that we use for initialization.
3227     // 3) Just re-calculate the content of compilation_progress_.
3228     base::MutexGuard guard(&callbacks_mutex_);
3229     function_progress = compilation_progress_[progress_index];
3230   }
3231   uint8_t updated_function_progress =
3232       AddCompilationUnitInternal(builder, func_index, function_progress);
3233   if (updated_function_progress != function_progress) {
3234     // This should happen very rarely (only for super-large functions), so we're
3235     // not worried about overhead.
3236     base::MutexGuard guard(&callbacks_mutex_);
3237     compilation_progress_[progress_index] = updated_function_progress;
3238   }
3239 }
3240 
InitializeCompilationProgressAfterDeserialization(base::Vector<const int> lazy_functions,base::Vector<const int> liftoff_functions)3241 void CompilationStateImpl::InitializeCompilationProgressAfterDeserialization(
3242     base::Vector<const int> lazy_functions,
3243     base::Vector<const int> liftoff_functions) {
3244   TRACE_EVENT2("v8.wasm", "wasm.CompilationAfterDeserialization",
3245                "num_lazy_functions", lazy_functions.size(),
3246                "num_liftoff_functions", liftoff_functions.size());
3247   TimedHistogramScope lazy_compile_time_scope(
3248       counters()->wasm_compile_after_deserialize());
3249 
3250   auto* module = native_module_->module();
3251   auto enabled_features = native_module_->enabled_features();
3252   const bool lazy_module = IsLazyModule(module);
3253   base::Optional<CodeSpaceWriteScope> lazy_code_space_write_scope;
3254   if (lazy_module || !lazy_functions.empty()) {
3255     lazy_code_space_write_scope.emplace(native_module_);
3256   }
3257   {
3258     base::MutexGuard guard(&callbacks_mutex_);
3259     DCHECK(compilation_progress_.empty());
3260     constexpr uint8_t kProgressAfterTurbofanDeserialization =
3261         RequiredBaselineTierField::encode(ExecutionTier::kTurbofan) |
3262         RequiredTopTierField::encode(ExecutionTier::kTurbofan) |
3263         ReachedTierField::encode(ExecutionTier::kTurbofan);
3264     finished_events_.Add(CompilationEvent::kFinishedExportWrappers);
3265     if (liftoff_functions.empty() || lazy_module) {
3266       // We have to trigger the compilation events to finish compilation.
3267       // Typically the events get triggered when a CompilationUnit finishes, but
3268       // with lazy compilation there are no compilation units.
3269       // The {kFinishedBaselineCompilation} event is needed for module
3270       // compilation to finish.
3271       finished_events_.Add(CompilationEvent::kFinishedBaselineCompilation);
3272       if (liftoff_functions.empty() && lazy_functions.empty()) {
3273         // All functions exist now as TurboFan functions, so we can trigger the
3274         // {kFinishedTopTierCompilation} event.
3275         // The {kFinishedTopTierCompilation} event is needed for the C-API so
3276         // that {serialize()} works after {deserialize()}.
3277         finished_events_.Add(CompilationEvent::kFinishedTopTierCompilation);
3278       }
3279     }
3280     compilation_progress_.assign(module->num_declared_functions,
3281                                  kProgressAfterTurbofanDeserialization);
3282     for (auto func_index : lazy_functions) {
3283       native_module_->UseLazyStub(func_index);
3284 
3285       compilation_progress_[declared_function_index(module, func_index)] =
3286           SetupCompilationProgressForFunction(/*lazy_function =*/true,
3287                                               native_module_, enabled_features,
3288                                               func_index);
3289     }
3290     for (auto func_index : liftoff_functions) {
3291       if (lazy_module) {
3292         native_module_->UseLazyStub(func_index);
3293       }
3294       // Check that {func_index} is not contained in {lazy_functions}.
3295       DCHECK_EQ(
3296           compilation_progress_[declared_function_index(module, func_index)],
3297           kProgressAfterTurbofanDeserialization);
3298       compilation_progress_[declared_function_index(module, func_index)] =
3299           SetupCompilationProgressForFunction(lazy_module, native_module_,
3300                                               enabled_features, func_index);
3301     }
3302   }
3303   auto builder = std::make_unique<CompilationUnitBuilder>(native_module_);
3304   InitializeCompilationUnits(std::move(builder));
3305   WaitForCompilationEvent(CompilationEvent::kFinishedBaselineCompilation);
3306 }
3307 
InitializeRecompilation(TieringState new_tiering_state,std::unique_ptr<CompilationEventCallback> recompilation_finished_callback)3308 void CompilationStateImpl::InitializeRecompilation(
3309     TieringState new_tiering_state,
3310     std::unique_ptr<CompilationEventCallback> recompilation_finished_callback) {
3311   DCHECK(!failed());
3312 
3313   // Hold the mutex as long as possible, to synchronize between multiple
3314   // recompilations that are triggered at the same time (e.g. when the profiler
3315   // is disabled).
3316   base::Optional<base::MutexGuard> guard(&callbacks_mutex_);
3317 
3318   // As long as there are outstanding recompilation functions, take part in
3319   // compilation. This is to avoid recompiling for the same tier or for
3320   // different tiers concurrently. Note that the compilation unit queues can run
3321   // empty before {outstanding_recompilation_functions_} drops to zero. In this
3322   // case, we do not wait for the last running compilation threads to finish
3323   // their units, but just start our own recompilation already.
3324   while (outstanding_recompilation_functions_ > 0 &&
3325          compilation_unit_queues_.GetTotalSize() > 0) {
3326     guard.reset();
3327     constexpr JobDelegate* kNoDelegate = nullptr;
3328     ExecuteCompilationUnits(native_module_weak_, async_counters_.get(),
3329                             kNoDelegate, kBaselineOrTopTier);
3330     guard.emplace(&callbacks_mutex_);
3331   }
3332 
3333   // Information about compilation progress is shared between this class and the
3334   // NativeModule. Before updating information here, consult the NativeModule to
3335   // find all functions that need recompilation.
3336   // Since the current tiering state is updated on the NativeModule before
3337   // triggering recompilation, it's OK if the information is slightly outdated.
3338   // If we compile functions twice, the NativeModule will ignore all redundant
3339   // code (or code compiled for the wrong tier).
3340   std::vector<int> recompile_function_indexes =
3341       native_module_->FindFunctionsToRecompile(new_tiering_state);
3342 
3343   callbacks_.emplace_back(std::move(recompilation_finished_callback));
3344   tiering_state_ = new_tiering_state;
3345 
3346   // If compilation progress is not initialized yet, then compilation didn't
3347   // start yet, and new code will be kept tiered-down from the start. For
3348   // streaming compilation, there is a special path to tier down later, when
3349   // the module is complete. In any case, we don't need to recompile here.
3350   base::Optional<CompilationUnitBuilder> builder;
3351   if (compilation_progress_.size() > 0) {
3352     builder.emplace(native_module_);
3353     const WasmModule* module = native_module_->module();
3354     DCHECK_EQ(module->num_declared_functions, compilation_progress_.size());
3355     DCHECK_GE(module->num_declared_functions,
3356               recompile_function_indexes.size());
3357     outstanding_recompilation_functions_ =
3358         static_cast<int>(recompile_function_indexes.size());
3359     // Restart recompilation if another recompilation is already happening.
3360     for (auto& progress : compilation_progress_) {
3361       progress = MissingRecompilationField::update(progress, false);
3362     }
3363     auto new_tier = new_tiering_state == kTieredDown ? ExecutionTier::kLiftoff
3364                                                      : ExecutionTier::kTurbofan;
3365     int imported = module->num_imported_functions;
3366     // Generate necessary compilation units on the fly.
3367     for (int function_index : recompile_function_indexes) {
3368       DCHECK_LE(imported, function_index);
3369       int slot_index = function_index - imported;
3370       auto& progress = compilation_progress_[slot_index];
3371       progress = MissingRecompilationField::update(progress, true);
3372       builder->AddRecompilationUnit(function_index, new_tier);
3373     }
3374   }
3375 
3376   // Trigger callback if module needs no recompilation.
3377   if (outstanding_recompilation_functions_ == 0) {
3378     TriggerCallbacks(base::EnumSet<CompilationEvent>(
3379         {CompilationEvent::kFinishedRecompilation}));
3380   }
3381 
3382   if (builder.has_value()) {
3383     // Avoid holding lock while scheduling a compile job.
3384     guard.reset();
3385     builder->Commit();
3386   }
3387 }
3388 
AddCallback(std::unique_ptr<CompilationEventCallback> callback)3389 void CompilationStateImpl::AddCallback(
3390     std::unique_ptr<CompilationEventCallback> callback) {
3391   base::MutexGuard callbacks_guard(&callbacks_mutex_);
3392   // Immediately trigger events that already happened.
3393   for (auto event : {CompilationEvent::kFinishedExportWrappers,
3394                      CompilationEvent::kFinishedBaselineCompilation,
3395                      CompilationEvent::kFinishedTopTierCompilation,
3396                      CompilationEvent::kFailedCompilation}) {
3397     if (finished_events_.contains(event)) {
3398       callback->call(event);
3399     }
3400   }
3401   constexpr base::EnumSet<CompilationEvent> kFinalEvents{
3402       CompilationEvent::kFinishedTopTierCompilation,
3403       CompilationEvent::kFailedCompilation};
3404   if (!finished_events_.contains_any(kFinalEvents)) {
3405     callbacks_.emplace_back(std::move(callback));
3406   }
3407 }
3408 
CommitCompilationUnits(base::Vector<WasmCompilationUnit> baseline_units,base::Vector<WasmCompilationUnit> top_tier_units,base::Vector<std::shared_ptr<JSToWasmWrapperCompilationUnit>> js_to_wasm_wrapper_units)3409 void CompilationStateImpl::CommitCompilationUnits(
3410     base::Vector<WasmCompilationUnit> baseline_units,
3411     base::Vector<WasmCompilationUnit> top_tier_units,
3412     base::Vector<std::shared_ptr<JSToWasmWrapperCompilationUnit>>
3413         js_to_wasm_wrapper_units) {
3414   if (!js_to_wasm_wrapper_units.empty()) {
3415     // |js_to_wasm_wrapper_units_| will only be initialized once.
3416     DCHECK_EQ(0, outstanding_js_to_wasm_wrappers_.load());
3417     js_to_wasm_wrapper_units_.insert(js_to_wasm_wrapper_units_.end(),
3418                                      js_to_wasm_wrapper_units.begin(),
3419                                      js_to_wasm_wrapper_units.end());
3420     // Use release semantics such that updates to {js_to_wasm_wrapper_units_}
3421     // are available to other threads doing an acquire load.
3422     outstanding_js_to_wasm_wrappers_.store(js_to_wasm_wrapper_units.size(),
3423                                            std::memory_order_release);
3424   }
3425   if (!baseline_units.empty() || !top_tier_units.empty()) {
3426     compilation_unit_queues_.AddUnits(baseline_units, top_tier_units,
3427                                       native_module_->module());
3428   }
3429   compile_job_->NotifyConcurrencyIncrease();
3430 }
3431 
CommitTopTierCompilationUnit(WasmCompilationUnit unit)3432 void CompilationStateImpl::CommitTopTierCompilationUnit(
3433     WasmCompilationUnit unit) {
3434   CommitCompilationUnits({}, {&unit, 1}, {});
3435 }
3436 
AddTopTierPriorityCompilationUnit(WasmCompilationUnit unit,size_t priority)3437 void CompilationStateImpl::AddTopTierPriorityCompilationUnit(
3438     WasmCompilationUnit unit, size_t priority) {
3439   compilation_unit_queues_.AddTopTierPriorityUnit(unit, priority);
3440   {
3441     base::MutexGuard guard(&callbacks_mutex_);
3442     outstanding_top_tier_functions_++;
3443   }
3444   compile_job_->NotifyConcurrencyIncrease();
3445 }
3446 
3447 std::shared_ptr<JSToWasmWrapperCompilationUnit>
GetNextJSToWasmWrapperCompilationUnit()3448 CompilationStateImpl::GetNextJSToWasmWrapperCompilationUnit() {
3449   size_t outstanding_units =
3450       outstanding_js_to_wasm_wrappers_.load(std::memory_order_relaxed);
3451   // Use acquire semantics such that initialization of
3452   // {js_to_wasm_wrapper_units_} is available.
3453   while (outstanding_units &&
3454          !outstanding_js_to_wasm_wrappers_.compare_exchange_weak(
3455              outstanding_units, outstanding_units - 1,
3456              std::memory_order_acquire)) {
3457     // Retry with updated {outstanding_units}.
3458   }
3459   if (outstanding_units == 0) return nullptr;
3460   return js_to_wasm_wrapper_units_[outstanding_units - 1];
3461 }
3462 
FinalizeJSToWasmWrappers(Isolate * isolate,const WasmModule * module,Handle<FixedArray> * export_wrappers_out)3463 void CompilationStateImpl::FinalizeJSToWasmWrappers(
3464     Isolate* isolate, const WasmModule* module,
3465     Handle<FixedArray>* export_wrappers_out) {
3466   *export_wrappers_out = isolate->factory()->NewFixedArray(
3467       MaxNumExportWrappers(module), AllocationType::kOld);
3468   // TODO(6792): Wrappers below are allocated with {Factory::NewCode}. As an
3469   // optimization we create a code memory modification scope that avoids
3470   // changing the page permissions back-and-forth between RWX and RX, because
3471   // many such wrapper are allocated in sequence below.
3472   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
3473                "wasm.FinalizeJSToWasmWrappers", "wrappers",
3474                js_to_wasm_wrapper_units_.size());
3475   CodePageCollectionMemoryModificationScope modification_scope(isolate->heap());
3476   for (auto& unit : js_to_wasm_wrapper_units_) {
3477     DCHECK_EQ(isolate, unit->isolate());
3478     Handle<Code> code = unit->Finalize();
3479     int wrapper_index =
3480         GetExportWrapperIndex(module, unit->sig(), unit->is_import());
3481     (*export_wrappers_out)->set(wrapper_index, ToCodeT(*code));
3482     RecordStats(*code, isolate->counters());
3483   }
3484 }
3485 
GetQueueForCompileTask(int task_id)3486 CompilationUnitQueues::Queue* CompilationStateImpl::GetQueueForCompileTask(
3487     int task_id) {
3488   return compilation_unit_queues_.GetQueueForTask(task_id);
3489 }
3490 
3491 base::Optional<WasmCompilationUnit>
GetNextCompilationUnit(CompilationUnitQueues::Queue * queue,CompileBaselineOnly baseline_only)3492 CompilationStateImpl::GetNextCompilationUnit(
3493     CompilationUnitQueues::Queue* queue, CompileBaselineOnly baseline_only) {
3494   return compilation_unit_queues_.GetNextUnit(queue, baseline_only);
3495 }
3496 
OnFinishedUnits(base::Vector<WasmCode * > code_vector)3497 void CompilationStateImpl::OnFinishedUnits(
3498     base::Vector<WasmCode*> code_vector) {
3499   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
3500                "wasm.OnFinishedUnits", "units", code_vector.size());
3501 
3502   base::MutexGuard guard(&callbacks_mutex_);
3503 
3504   // In case of no outstanding compilation units we can return early.
3505   // This is especially important for lazy modules that were deserialized.
3506   // Compilation progress was not set up in these cases.
3507   if (outstanding_baseline_units_ == 0 && outstanding_export_wrappers_ == 0 &&
3508       outstanding_top_tier_functions_ == 0 &&
3509       outstanding_recompilation_functions_ == 0) {
3510     return;
3511   }
3512 
3513   // Assume an order of execution tiers that represents the quality of their
3514   // generated code.
3515   static_assert(ExecutionTier::kNone < ExecutionTier::kLiftoff &&
3516                     ExecutionTier::kLiftoff < ExecutionTier::kTurbofan,
3517                 "Assume an order on execution tiers");
3518 
3519   DCHECK_EQ(compilation_progress_.size(),
3520             native_module_->module()->num_declared_functions);
3521 
3522   base::EnumSet<CompilationEvent> triggered_events;
3523 
3524   for (size_t i = 0; i < code_vector.size(); i++) {
3525     WasmCode* code = code_vector[i];
3526     DCHECK_NOT_NULL(code);
3527     DCHECK_LT(code->index(), native_module_->num_functions());
3528 
3529     if (code->index() <
3530         static_cast<int>(native_module_->num_imported_functions())) {
3531       // Import wrapper.
3532       DCHECK_EQ(code->tier(), ExecutionTier::kTurbofan);
3533       outstanding_baseline_units_--;
3534     } else {
3535       // Function.
3536       DCHECK_NE(code->tier(), ExecutionTier::kNone);
3537 
3538       // Read function's compilation progress.
3539       // This view on the compilation progress may differ from the actually
3540       // compiled code. Any lazily compiled function does not contribute to the
3541       // compilation progress but may publish code to the code manager.
3542       int slot_index =
3543           declared_function_index(native_module_->module(), code->index());
3544       uint8_t function_progress = compilation_progress_[slot_index];
3545       ExecutionTier required_baseline_tier =
3546           RequiredBaselineTierField::decode(function_progress);
3547       ExecutionTier required_top_tier =
3548           RequiredTopTierField::decode(function_progress);
3549       ExecutionTier reached_tier = ReachedTierField::decode(function_progress);
3550 
3551       // Check whether required baseline or top tier are reached.
3552       if (reached_tier < required_baseline_tier &&
3553           required_baseline_tier <= code->tier()) {
3554         DCHECK_GT(outstanding_baseline_units_, 0);
3555         outstanding_baseline_units_--;
3556       }
3557       if (code->tier() == ExecutionTier::kTurbofan) {
3558         bytes_since_last_chunk_ += code->instructions().size();
3559       }
3560       if (reached_tier < required_top_tier &&
3561           required_top_tier <= code->tier()) {
3562         DCHECK_GT(outstanding_top_tier_functions_, 0);
3563         outstanding_top_tier_functions_--;
3564       }
3565 
3566       if (V8_UNLIKELY(MissingRecompilationField::decode(function_progress))) {
3567         DCHECK_LT(0, outstanding_recompilation_functions_);
3568         // If tiering up, accept any TurboFan code. For tiering down, look at
3569         // the {for_debugging} flag. The tier can be Liftoff or TurboFan and is
3570         // irrelevant here. In particular, we want to ignore any outstanding
3571         // non-debugging units.
3572         bool matches = tiering_state_ == kTieredDown
3573                            ? code->for_debugging()
3574                            : code->tier() == ExecutionTier::kTurbofan;
3575         if (matches) {
3576           outstanding_recompilation_functions_--;
3577           compilation_progress_[slot_index] = MissingRecompilationField::update(
3578               compilation_progress_[slot_index], false);
3579           if (outstanding_recompilation_functions_ == 0) {
3580             triggered_events.Add(CompilationEvent::kFinishedRecompilation);
3581           }
3582         }
3583       }
3584 
3585       // Update function's compilation progress.
3586       if (code->tier() > reached_tier) {
3587         compilation_progress_[slot_index] = ReachedTierField::update(
3588             compilation_progress_[slot_index], code->tier());
3589       }
3590       DCHECK_LE(0, outstanding_baseline_units_);
3591     }
3592   }
3593 
3594   TriggerCallbacks(triggered_events);
3595 }
3596 
OnFinishedJSToWasmWrapperUnits(int num)3597 void CompilationStateImpl::OnFinishedJSToWasmWrapperUnits(int num) {
3598   if (num == 0) return;
3599   base::MutexGuard guard(&callbacks_mutex_);
3600   DCHECK_GE(outstanding_export_wrappers_, num);
3601   outstanding_export_wrappers_ -= num;
3602   TriggerCallbacks();
3603 }
3604 
TriggerCallbacks(base::EnumSet<CompilationEvent> triggered_events)3605 void CompilationStateImpl::TriggerCallbacks(
3606     base::EnumSet<CompilationEvent> triggered_events) {
3607   DCHECK(!callbacks_mutex_.TryLock());
3608 
3609   if (outstanding_export_wrappers_ == 0) {
3610     triggered_events.Add(CompilationEvent::kFinishedExportWrappers);
3611     if (outstanding_baseline_units_ == 0) {
3612       triggered_events.Add(CompilationEvent::kFinishedBaselineCompilation);
3613       if (dynamic_tiering_ == DynamicTiering::kDisabled &&
3614           outstanding_top_tier_functions_ == 0) {
3615         triggered_events.Add(CompilationEvent::kFinishedTopTierCompilation);
3616       }
3617     }
3618   }
3619 
3620   if (dynamic_tiering_ == DynamicTiering::kEnabled &&
3621       static_cast<size_t>(FLAG_wasm_caching_threshold) <
3622           bytes_since_last_chunk_) {
3623     triggered_events.Add(CompilationEvent::kFinishedCompilationChunk);
3624     bytes_since_last_chunk_ = 0;
3625   }
3626   if (compile_failed_.load(std::memory_order_relaxed)) {
3627     // *Only* trigger the "failed" event.
3628     triggered_events =
3629         base::EnumSet<CompilationEvent>({CompilationEvent::kFailedCompilation});
3630   }
3631 
3632   if (triggered_events.empty()) return;
3633 
3634   // Don't trigger past events again.
3635   triggered_events -= finished_events_;
3636   // Recompilation can happen multiple times, thus do not store this. There can
3637   // also be multiple compilation chunks.
3638   finished_events_ |= triggered_events -
3639                       CompilationEvent::kFinishedRecompilation -
3640                       CompilationEvent::kFinishedCompilationChunk;
3641 
3642   for (auto event :
3643        {std::make_pair(CompilationEvent::kFailedCompilation,
3644                        "wasm.CompilationFailed"),
3645         std::make_pair(CompilationEvent::kFinishedExportWrappers,
3646                        "wasm.ExportWrappersFinished"),
3647         std::make_pair(CompilationEvent::kFinishedBaselineCompilation,
3648                        "wasm.BaselineFinished"),
3649         std::make_pair(CompilationEvent::kFinishedTopTierCompilation,
3650                        "wasm.TopTierFinished"),
3651         std::make_pair(CompilationEvent::kFinishedCompilationChunk,
3652                        "wasm.CompilationChunkFinished"),
3653         std::make_pair(CompilationEvent::kFinishedRecompilation,
3654                        "wasm.RecompilationFinished")}) {
3655     if (!triggered_events.contains(event.first)) continue;
3656     DCHECK_NE(compilation_id_, kInvalidCompilationID);
3657     TRACE_EVENT1("v8.wasm", event.second, "id", compilation_id_);
3658     for (auto& callback : callbacks_) {
3659       callback->call(event.first);
3660     }
3661   }
3662 
3663   if (outstanding_baseline_units_ == 0 && outstanding_export_wrappers_ == 0 &&
3664       outstanding_top_tier_functions_ == 0 &&
3665       outstanding_recompilation_functions_ == 0) {
3666     callbacks_.erase(
3667         std::remove_if(
3668             callbacks_.begin(), callbacks_.end(),
3669             [](std::unique_ptr<CompilationEventCallback>& event) {
3670               return event->release_after_final_event() ==
3671                      CompilationEventCallback::ReleaseAfterFinalEvent::kRelease;
3672             }),
3673         callbacks_.end());
3674   }
3675 }
3676 
OnCompilationStopped(WasmFeatures detected)3677 void CompilationStateImpl::OnCompilationStopped(WasmFeatures detected) {
3678   base::MutexGuard guard(&mutex_);
3679   detected_features_.Add(detected);
3680 }
3681 
PublishDetectedFeatures(Isolate * isolate)3682 void CompilationStateImpl::PublishDetectedFeatures(Isolate* isolate) {
3683   // Notifying the isolate of the feature counts must take place under
3684   // the mutex, because even if we have finished baseline compilation,
3685   // tiering compilations may still occur in the background.
3686   base::MutexGuard guard(&mutex_);
3687   UpdateFeatureUseCounts(isolate, detected_features_);
3688 }
3689 
PublishCompilationResults(std::vector<std::unique_ptr<WasmCode>> unpublished_code)3690 void CompilationStateImpl::PublishCompilationResults(
3691     std::vector<std::unique_ptr<WasmCode>> unpublished_code) {
3692   if (unpublished_code.empty()) return;
3693 
3694   // For import wrapper compilation units, add result to the cache.
3695   int num_imported_functions = native_module_->num_imported_functions();
3696   WasmImportWrapperCache* cache = native_module_->import_wrapper_cache();
3697   for (const auto& code : unpublished_code) {
3698     int func_index = code->index();
3699     DCHECK_LE(0, func_index);
3700     DCHECK_LT(func_index, native_module_->num_functions());
3701     if (func_index < num_imported_functions) {
3702       const FunctionSig* sig =
3703           native_module_->module()->functions[func_index].sig;
3704       WasmImportWrapperCache::CacheKey key(
3705           compiler::kDefaultImportCallKind, sig,
3706           static_cast<int>(sig->parameter_count()), kNoSuspend);
3707       // If two imported functions have the same key, only one of them should
3708       // have been added as a compilation unit. So it is always the first time
3709       // we compile a wrapper for this key here.
3710       DCHECK_NULL((*cache)[key]);
3711       (*cache)[key] = code.get();
3712       code->IncRef();
3713     }
3714   }
3715   PublishCode(base::VectorOf(unpublished_code));
3716 }
3717 
PublishCode(base::Vector<std::unique_ptr<WasmCode>> code)3718 void CompilationStateImpl::PublishCode(
3719     base::Vector<std::unique_ptr<WasmCode>> code) {
3720   WasmCodeRefScope code_ref_scope;
3721   std::vector<WasmCode*> published_code =
3722       native_module_->PublishCode(std::move(code));
3723   // Defer logging code in case wire bytes were not fully received yet.
3724   if (native_module_->HasWireBytes()) {
3725     GetWasmEngine()->LogCode(base::VectorOf(published_code));
3726   }
3727 
3728   OnFinishedUnits(base::VectorOf(std::move(published_code)));
3729 }
3730 
SchedulePublishCompilationResults(std::vector<std::unique_ptr<WasmCode>> unpublished_code)3731 void CompilationStateImpl::SchedulePublishCompilationResults(
3732     std::vector<std::unique_ptr<WasmCode>> unpublished_code) {
3733   {
3734     base::MutexGuard guard(&publish_mutex_);
3735     if (publisher_running_) {
3736       // Add new code to the queue and return.
3737       publish_queue_.reserve(publish_queue_.size() + unpublished_code.size());
3738       for (auto& c : unpublished_code) {
3739         publish_queue_.emplace_back(std::move(c));
3740       }
3741       return;
3742     }
3743     publisher_running_ = true;
3744   }
3745   CodeSpaceWriteScope code_space_write_scope(native_module_);
3746   while (true) {
3747     PublishCompilationResults(std::move(unpublished_code));
3748     unpublished_code.clear();
3749 
3750     // Keep publishing new code that came in.
3751     base::MutexGuard guard(&publish_mutex_);
3752     DCHECK(publisher_running_);
3753     if (publish_queue_.empty()) {
3754       publisher_running_ = false;
3755       return;
3756     }
3757     unpublished_code.swap(publish_queue_);
3758   }
3759 }
3760 
NumOutstandingCompilations() const3761 size_t CompilationStateImpl::NumOutstandingCompilations() const {
3762   size_t outstanding_wrappers =
3763       outstanding_js_to_wasm_wrappers_.load(std::memory_order_relaxed);
3764   size_t outstanding_functions = compilation_unit_queues_.GetTotalSize();
3765   return outstanding_wrappers + outstanding_functions;
3766 }
3767 
SetError()3768 void CompilationStateImpl::SetError() {
3769   compile_cancelled_.store(true, std::memory_order_relaxed);
3770   if (compile_failed_.exchange(true, std::memory_order_relaxed)) {
3771     return;  // Already failed before.
3772   }
3773 
3774   base::MutexGuard callbacks_guard(&callbacks_mutex_);
3775   TriggerCallbacks();
3776   callbacks_.clear();
3777 }
3778 
WaitForCompilationEvent(CompilationEvent expect_event)3779 void CompilationStateImpl::WaitForCompilationEvent(
3780     CompilationEvent expect_event) {
3781   class WaitForCompilationEventCallback : public CompilationEventCallback {
3782    public:
3783     WaitForCompilationEventCallback(std::shared_ptr<base::Semaphore> semaphore,
3784                                     std::shared_ptr<std::atomic<bool>> done,
3785                                     base::EnumSet<CompilationEvent> events)
3786         : semaphore_(std::move(semaphore)),
3787           done_(std::move(done)),
3788           events_(events) {}
3789 
3790     void call(CompilationEvent event) override {
3791       if (!events_.contains(event)) return;
3792       done_->store(true, std::memory_order_relaxed);
3793       semaphore_->Signal();
3794     }
3795 
3796    private:
3797     std::shared_ptr<base::Semaphore> semaphore_;
3798     std::shared_ptr<std::atomic<bool>> done_;
3799     base::EnumSet<CompilationEvent> events_;
3800   };
3801 
3802   auto semaphore = std::make_shared<base::Semaphore>(0);
3803   auto done = std::make_shared<std::atomic<bool>>(false);
3804   base::EnumSet<CompilationEvent> events{expect_event,
3805                                          CompilationEvent::kFailedCompilation};
3806   {
3807     base::MutexGuard callbacks_guard(&callbacks_mutex_);
3808     if (finished_events_.contains_any(events)) return;
3809     callbacks_.emplace_back(std::make_unique<WaitForCompilationEventCallback>(
3810         semaphore, done, events));
3811   }
3812 
3813   class WaitForEventDelegate final : public JobDelegate {
3814    public:
3815     explicit WaitForEventDelegate(std::shared_ptr<std::atomic<bool>> done)
3816         : done_(std::move(done)) {}
3817 
3818     bool ShouldYield() override {
3819       return done_->load(std::memory_order_relaxed);
3820     }
3821 
3822     bool IsJoiningThread() const override { return true; }
3823 
3824     void NotifyConcurrencyIncrease() override { UNIMPLEMENTED(); }
3825 
3826     uint8_t GetTaskId() override { return kMainTaskId; }
3827 
3828    private:
3829     std::shared_ptr<std::atomic<bool>> done_;
3830   };
3831 
3832   WaitForEventDelegate delegate{done};
3833   // Everything except for top-tier units will be processed with kBaselineOnly
3834   // (including wrappers). Hence we choose this for any event except
3835   // {kFinishedTopTierCompilation}.
3836   auto compile_tiers =
3837       expect_event == CompilationEvent::kFinishedTopTierCompilation
3838           ? kBaselineOrTopTier
3839           : kBaselineOnly;
3840   ExecuteCompilationUnits(native_module_weak_, async_counters_.get(), &delegate,
3841                           compile_tiers);
3842   semaphore->Wait();
3843 }
3844 
3845 namespace {
3846 using JSToWasmWrapperQueue =
3847     WrapperQueue<JSToWasmWrapperKey, base::hash<JSToWasmWrapperKey>>;
3848 using JSToWasmWrapperUnitMap =
3849     std::unordered_map<JSToWasmWrapperKey,
3850                        std::unique_ptr<JSToWasmWrapperCompilationUnit>,
3851                        base::hash<JSToWasmWrapperKey>>;
3852 
3853 class CompileJSToWasmWrapperJob final : public JobTask {
3854  public:
CompileJSToWasmWrapperJob(JSToWasmWrapperQueue * queue,JSToWasmWrapperUnitMap * compilation_units)3855   CompileJSToWasmWrapperJob(JSToWasmWrapperQueue* queue,
3856                             JSToWasmWrapperUnitMap* compilation_units)
3857       : queue_(queue),
3858         compilation_units_(compilation_units),
3859         outstanding_units_(queue->size()) {}
3860 
Run(JobDelegate * delegate)3861   void Run(JobDelegate* delegate) override {
3862     while (base::Optional<JSToWasmWrapperKey> key = queue_->pop()) {
3863       JSToWasmWrapperCompilationUnit* unit = (*compilation_units_)[*key].get();
3864       unit->Execute();
3865       outstanding_units_.fetch_sub(1, std::memory_order_relaxed);
3866       if (delegate && delegate->ShouldYield()) return;
3867     }
3868   }
3869 
GetMaxConcurrency(size_t) const3870   size_t GetMaxConcurrency(size_t /* worker_count */) const override {
3871     DCHECK_GE(FLAG_wasm_num_compilation_tasks, 1);
3872     // {outstanding_units_} includes the units that other workers are currently
3873     // working on, so we can safely ignore the {worker_count} and just return
3874     // the current number of outstanding units.
3875     return std::min(static_cast<size_t>(FLAG_wasm_num_compilation_tasks),
3876                     outstanding_units_.load(std::memory_order_relaxed));
3877   }
3878 
3879  private:
3880   JSToWasmWrapperQueue* const queue_;
3881   JSToWasmWrapperUnitMap* const compilation_units_;
3882   std::atomic<size_t> outstanding_units_;
3883 };
3884 }  // namespace
3885 
CompileJsToWasmWrappers(Isolate * isolate,const WasmModule * module,Handle<FixedArray> * export_wrappers_out)3886 void CompileJsToWasmWrappers(Isolate* isolate, const WasmModule* module,
3887                              Handle<FixedArray>* export_wrappers_out) {
3888   TRACE_EVENT0("v8.wasm", "wasm.CompileJsToWasmWrappers");
3889   *export_wrappers_out = isolate->factory()->NewFixedArray(
3890       MaxNumExportWrappers(module), AllocationType::kOld);
3891 
3892   JSToWasmWrapperQueue queue;
3893   JSToWasmWrapperUnitMap compilation_units;
3894   WasmFeatures enabled_features = WasmFeatures::FromIsolate(isolate);
3895 
3896   // Prepare compilation units in the main thread.
3897   for (auto exp : module->export_table) {
3898     if (exp.kind != kExternalFunction) continue;
3899     auto& function = module->functions[exp.index];
3900     JSToWasmWrapperKey key(function.imported, *function.sig);
3901     if (queue.insert(key)) {
3902       auto unit = std::make_unique<JSToWasmWrapperCompilationUnit>(
3903           isolate, function.sig, module, function.imported, enabled_features,
3904           JSToWasmWrapperCompilationUnit::kAllowGeneric);
3905       compilation_units.emplace(key, std::move(unit));
3906     }
3907   }
3908 
3909   {
3910     // This is nested inside the event above, so the name can be less
3911     // descriptive. It's mainly to log the number of wrappers.
3912     TRACE_EVENT1("v8.wasm", "wasm.JsToWasmWrapperCompilation", "num_wrappers",
3913                  compilation_units.size());
3914     auto job =
3915         std::make_unique<CompileJSToWasmWrapperJob>(&queue, &compilation_units);
3916     if (FLAG_wasm_num_compilation_tasks > 0) {
3917       auto job_handle = V8::GetCurrentPlatform()->PostJob(
3918           TaskPriority::kUserVisible, std::move(job));
3919 
3920       // Wait for completion, while contributing to the work.
3921       job_handle->Join();
3922     } else {
3923       job->Run(nullptr);
3924     }
3925   }
3926 
3927   // Finalize compilation jobs in the main thread.
3928   // TODO(6792): Wrappers below are allocated with {Factory::NewCode}. As an
3929   // optimization we create a code memory modification scope that avoids
3930   // changing the page permissions back-and-forth between RWX and RX, because
3931   // many such wrapper are allocated in sequence below.
3932   CodePageCollectionMemoryModificationScope modification_scope(isolate->heap());
3933   for (auto& pair : compilation_units) {
3934     JSToWasmWrapperKey key = pair.first;
3935     JSToWasmWrapperCompilationUnit* unit = pair.second.get();
3936     DCHECK_EQ(isolate, unit->isolate());
3937     Handle<Code> code = unit->Finalize();
3938     int wrapper_index = GetExportWrapperIndex(module, &key.second, key.first);
3939     (*export_wrappers_out)->set(wrapper_index, ToCodeT(*code));
3940     RecordStats(*code, isolate->counters());
3941   }
3942 }
3943 
CompileImportWrapper(NativeModule * native_module,Counters * counters,compiler::WasmImportCallKind kind,const FunctionSig * sig,int expected_arity,Suspend suspend,WasmImportWrapperCache::ModificationScope * cache_scope)3944 WasmCode* CompileImportWrapper(
3945     NativeModule* native_module, Counters* counters,
3946     compiler::WasmImportCallKind kind, const FunctionSig* sig,
3947     int expected_arity, Suspend suspend,
3948     WasmImportWrapperCache::ModificationScope* cache_scope) {
3949   // Entry should exist, so that we don't insert a new one and invalidate
3950   // other threads' iterators/references, but it should not have been compiled
3951   // yet.
3952   WasmImportWrapperCache::CacheKey key(kind, sig, expected_arity, suspend);
3953   DCHECK_NULL((*cache_scope)[key]);
3954   bool source_positions = is_asmjs_module(native_module->module());
3955   // Keep the {WasmCode} alive until we explicitly call {IncRef}.
3956   WasmCodeRefScope code_ref_scope;
3957   CompilationEnv env = native_module->CreateCompilationEnv();
3958   WasmCompilationResult result = compiler::CompileWasmImportCallWrapper(
3959       &env, kind, sig, source_positions, expected_arity, suspend);
3960   WasmCode* published_code;
3961   {
3962     CodeSpaceWriteScope code_space_write_scope(native_module);
3963     std::unique_ptr<WasmCode> wasm_code = native_module->AddCode(
3964         result.func_index, result.code_desc, result.frame_slot_count,
3965         result.tagged_parameter_slots,
3966         result.protected_instructions_data.as_vector(),
3967         result.source_positions.as_vector(), GetCodeKind(result),
3968         ExecutionTier::kNone, kNoDebugging);
3969     published_code = native_module->PublishCode(std::move(wasm_code));
3970   }
3971   (*cache_scope)[key] = published_code;
3972   published_code->IncRef();
3973   counters->wasm_generated_code_size()->Increment(
3974       published_code->instructions().length());
3975   counters->wasm_reloc_size()->Increment(published_code->reloc_info().length());
3976   return published_code;
3977 }
3978 
3979 }  // namespace wasm
3980 }  // namespace internal
3981 }  // namespace v8
3982 
3983 #undef TRACE_COMPILE
3984 #undef TRACE_STREAMING
3985 #undef TRACE_LAZY
3986