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