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, §ion_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