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