diff options
Diffstat (limited to 'deps/v8/src/wasm/module-compiler.cc')
-rw-r--r-- | deps/v8/src/wasm/module-compiler.cc | 1557 |
1 files changed, 681 insertions, 876 deletions
diff --git a/deps/v8/src/wasm/module-compiler.cc b/deps/v8/src/wasm/module-compiler.cc index ab8b49027e..96abaa0a04 100644 --- a/deps/v8/src/wasm/module-compiler.cc +++ b/deps/v8/src/wasm/module-compiler.cc @@ -5,6 +5,7 @@ #include "src/wasm/module-compiler.h" #include <algorithm> +#include <memory> #include <queue> #include "src/api/api-inl.h" @@ -96,10 +97,7 @@ class V8_NODISCARD BackgroundCompileScope { std::shared_ptr<NativeModule> native_module_; }; -enum CompileBaselineOnly : bool { - kBaselineOnly = true, - kBaselineOrTopTier = false -}; +enum CompilationTier { kBaseline = 0, kTopTier = 1, kNumTiers = kTopTier + 1 }; // A set of work-stealing queues (vectors of units). Each background compile // task owns one of the queues and steals from all others once its own queue @@ -173,19 +171,15 @@ class CompilationUnitQueues { return queues_[task_id].get(); } - base::Optional<WasmCompilationUnit> GetNextUnit( - Queue* queue, CompileBaselineOnly baseline_only) { - // As long as any lower-tier units are outstanding we need to steal them - // before executing own higher-tier units. - int max_tier = baseline_only ? kBaseline : kTopTier; - for (int tier = GetLowestTierWithUnits(); tier <= max_tier; ++tier) { - if (auto unit = GetNextUnitOfTier(queue, tier)) { - size_t old_units_count = - num_units_[tier].fetch_sub(1, std::memory_order_relaxed); - DCHECK_LE(1, old_units_count); - USE(old_units_count); - return unit; - } + base::Optional<WasmCompilationUnit> GetNextUnit(Queue* queue, + CompilationTier tier) { + DCHECK_LT(tier, CompilationTier::kNumTiers); + if (auto unit = GetNextUnitOfTier(queue, tier)) { + size_t old_units_count = + num_units_[tier].fetch_sub(1, std::memory_order_relaxed); + DCHECK_LE(1, old_units_count); + USE(old_units_count); + return unit; } return {}; } @@ -210,8 +204,9 @@ class CompilationUnitQueues { base::MutexGuard guard(&queue->mutex); base::Optional<base::MutexGuard> big_units_guard; - for (auto pair : {std::make_pair(int{kBaseline}, baseline_units), - std::make_pair(int{kTopTier}, top_tier_units)}) { + for (auto pair : + {std::make_pair(CompilationTier::kBaseline, baseline_units), + std::make_pair(CompilationTier::kTopTier, top_tier_units)}) { int tier = pair.first; base::Vector<WasmCompilationUnit> units = pair.second; if (units.empty()) continue; @@ -256,26 +251,29 @@ class CompilationUnitQueues { queue->top_tier_priority_units.emplace(priority, unit); } num_priority_units_.fetch_add(1, std::memory_order_relaxed); - num_units_[kTopTier].fetch_add(1, std::memory_order_relaxed); + num_units_[CompilationTier::kTopTier].fetch_add(1, + std::memory_order_relaxed); } - // Get the current total number of units in all queues. This is only a + // Get the current number of units in the queue for |tier|. This is only a // momentary snapshot, it's not guaranteed that {GetNextUnit} returns a unit // if this method returns non-zero. - size_t GetTotalSize() const { - size_t total = 0; - for (auto& atomic_counter : num_units_) { - total += atomic_counter.load(std::memory_order_relaxed); + size_t GetSizeForTier(CompilationTier tier) const { + DCHECK_LT(tier, CompilationTier::kNumTiers); + return num_units_[tier].load(std::memory_order_relaxed); + } + + void AllowAnotherTopTierJob(uint32_t func_index) { + top_tier_compiled_[func_index].store(false, std::memory_order_relaxed); + } + + void AllowAnotherTopTierJobForAllFunctions() { + for (int i = 0; i < num_declared_functions_; i++) { + AllowAnotherTopTierJob(i); } - return total; } private: - // Store tier in int so we can easily loop over it: - static constexpr int kBaseline = 0; - static constexpr int kTopTier = 1; - static constexpr int kNumTiers = kTopTier + 1; - // Functions bigger than {kBigUnitsLimit} will be compiled first, in ascending // order of their function body size. static constexpr size_t kBigUnitsLimit = 4096; @@ -315,10 +313,10 @@ class CompilationUnitQueues { base::Mutex mutex; // Can be read concurrently to check whether any elements are in the queue. - std::atomic<bool> has_units[kNumTiers]; + std::atomic<bool> has_units[CompilationTier::kNumTiers]; // Protected by {mutex}: - std::priority_queue<BigUnit> units[kNumTiers]; + std::priority_queue<BigUnit> units[CompilationTier::kNumTiers]; }; struct QueueImpl : public Queue { @@ -334,7 +332,7 @@ class CompilationUnitQueues { base::Mutex mutex; // All fields below are protected by {mutex}. - std::vector<WasmCompilationUnit> units[kNumTiers]; + std::vector<WasmCompilationUnit> units[CompilationTier::kNumTiers]; std::priority_queue<TopTierPriorityUnit> top_tier_priority_units; int next_steal_task_id; }; @@ -344,19 +342,12 @@ class CompilationUnitQueues { return next == static_cast<int>(num_queues) ? 0 : next; } - int GetLowestTierWithUnits() const { - for (int tier = 0; tier < kNumTiers; ++tier) { - if (num_units_[tier].load(std::memory_order_relaxed) > 0) return tier; - } - return kNumTiers; - } - base::Optional<WasmCompilationUnit> GetNextUnitOfTier(Queue* public_queue, int tier) { QueueImpl* queue = static_cast<QueueImpl*>(public_queue); // First check whether there is a priority unit. Execute that first. - if (tier == kTopTier) { + if (tier == CompilationTier::kTopTier) { if (auto unit = GetTopTierPriorityUnit(queue)) { return unit; } @@ -430,7 +421,8 @@ class CompilationUnitQueues { true, std::memory_order_relaxed)) { return unit; } - num_units_[kTopTier].fetch_sub(1, std::memory_order_relaxed); + num_units_[CompilationTier::kTopTier].fetch_sub( + 1, std::memory_order_relaxed); } steal_task_id = queue->next_steal_task_id; } @@ -504,7 +496,8 @@ class CompilationUnitQueues { returned_unit = unit; break; } - num_units_[kTopTier].fetch_sub(1, std::memory_order_relaxed); + num_units_[CompilationTier::kTopTier].fetch_sub( + 1, std::memory_order_relaxed); } } base::MutexGuard guard(&queue->mutex); @@ -520,7 +513,7 @@ class CompilationUnitQueues { BigUnitsQueue big_units_queue_; - std::atomic<size_t> num_units_[kNumTiers]; + std::atomic<size_t> num_units_[CompilationTier::kNumTiers]; std::atomic<size_t> num_priority_units_{0}; std::unique_ptr<std::atomic<bool>[]> top_tier_compiled_; std::atomic<int> next_queue_to_add{0}; @@ -544,7 +537,12 @@ class CompilationStateImpl { std::shared_ptr<Counters> async_counters, DynamicTiering dynamic_tiering); ~CompilationStateImpl() { - if (compile_job_->IsValid()) compile_job_->CancelAndDetach(); + if (js_to_wasm_wrapper_job_->IsValid()) + js_to_wasm_wrapper_job_->CancelAndDetach(); + if (baseline_compile_job_->IsValid()) + baseline_compile_job_->CancelAndDetach(); + if (top_tier_compile_job_->IsValid()) + top_tier_compile_job_->CancelAndDetach(); } // Call right after the constructor, after the {compilation_state_} field in @@ -575,8 +573,6 @@ class CompilationStateImpl { int num_export_wrappers, ProfileInformation* pgo_info); - // Initialize the compilation progress after deserialization. This is needed - // for recompilation (e.g. for tier down) to work later. void InitializeCompilationProgressAfterDeserialization( base::Vector<const int> lazy_functions, base::Vector<const int> eager_functions); @@ -591,14 +587,6 @@ class CompilationStateImpl { // equivalent to {InitializeCompilationUnits}. void AddCompilationUnit(CompilationUnitBuilder* builder, int func_index); - // Initialize recompilation of the whole module: Setup compilation progress - // for recompilation and add the respective compilation units. The callback is - // called immediately if no recompilation is needed, or called later - // otherwise. - void InitializeRecompilation(TieringState new_tiering_state, - std::unique_ptr<CompilationEventCallback> - recompilation_finished_callback); - // Add the callback to be called on compilation events. Needs to be // set before {CommitCompilationUnits} is run to ensure that it receives all // events. The callback object must support being deleted from any thread. @@ -616,7 +604,7 @@ class CompilationStateImpl { CompilationUnitQueues::Queue* GetQueueForCompileTask(int task_id); base::Optional<WasmCompilationUnit> GetNextCompilationUnit( - CompilationUnitQueues::Queue*, CompileBaselineOnly); + CompilationUnitQueues::Queue*, CompilationTier tier); std::shared_ptr<JSToWasmWrapperCompilationUnit> GetNextJSToWasmWrapperCompilationUnit(); @@ -630,15 +618,21 @@ class CompilationStateImpl { void SchedulePublishCompilationResults( std::vector<std::unique_ptr<WasmCode>> unpublished_code); - size_t NumOutstandingCompilations() const; + size_t NumOutstandingExportWrappers() const; + size_t NumOutstandingCompilations(CompilationTier tier) const; void SetError(); void WaitForCompilationEvent(CompilationEvent event); - void SetHighPriority() { - // TODO(wasm): Keep a lower priority for TurboFan-only jobs. - compile_job_->UpdatePriority(TaskPriority::kUserBlocking); + void TierUpAllFunctions(); + + void AllowAnotherTopTierJob(uint32_t func_index) { + compilation_unit_queues_.AllowAnotherTopTierJob(func_index); + } + + void AllowAnotherTopTierJobForAllFunctions() { + compilation_unit_queues_.AllowAnotherTopTierJobForAllFunctions(); } bool failed() const { @@ -651,11 +645,6 @@ class CompilationStateImpl { outstanding_export_wrappers_ == 0; } - bool recompilation_finished() const { - base::MutexGuard guard(&callbacks_mutex_); - return outstanding_recompilation_functions_ == 0; - } - DynamicTiering dynamic_tiering() const { return dynamic_tiering_; } Counters* counters() const { return async_counters_.get(); } @@ -682,15 +671,14 @@ class CompilationStateImpl { } private: - // Returns the potentially-updated {function_progress}. - uint8_t AddCompilationUnitInternal(CompilationUnitBuilder* builder, - int function_index, - uint8_t function_progress); + void AddCompilationUnitInternal(CompilationUnitBuilder* builder, + int function_index, + uint8_t function_progress); // Trigger callbacks according to the internal counters below - // (outstanding_...), plus the given events. + // (outstanding_...). // Hold the {callbacks_mutex_} when calling this method. - void TriggerCallbacks(base::EnumSet<CompilationEvent> additional_events = {}); + void TriggerCallbacks(); void PublishCompilationResults( std::vector<std::unique_ptr<WasmCode>> unpublished_code); @@ -726,9 +714,11 @@ class CompilationStateImpl { // being accessed concurrently. mutable base::Mutex mutex_; - // The compile job handle, initialized right after construction of + // The compile job handles, initialized right after construction of // {CompilationStateImpl}. - std::unique_ptr<JobHandle> compile_job_; + std::unique_ptr<JobHandle> js_to_wasm_wrapper_job_; + std::unique_ptr<JobHandle> baseline_compile_job_; + std::unique_ptr<JobHandle> top_tier_compile_job_; // The compilation id to identify trace events linked to this compilation. static constexpr int kInvalidCompilationID = -1; @@ -770,9 +760,6 @@ class CompilationStateImpl { size_t bytes_since_last_chunk_ = 0; std::vector<uint8_t> compilation_progress_; - int outstanding_recompilation_functions_ = 0; - TieringState tiering_state_ = kTieredUp; - // End of fields protected by {callbacks_mutex_}. ////////////////////////////////////////////////////////////////////////////// @@ -785,7 +772,6 @@ class CompilationStateImpl { using RequiredBaselineTierField = base::BitField8<ExecutionTier, 0, 2>; using RequiredTopTierField = base::BitField8<ExecutionTier, 2, 2>; using ReachedTierField = base::BitField8<ExecutionTier, 4, 2>; - using MissingRecompilationField = base::BitField8<bool, 6, 1>; }; CompilationStateImpl* Impl(CompilationState* compilation_state) { @@ -805,7 +791,7 @@ bool BackgroundCompileScope::cancelled() const { Impl(native_module_->compilation_state())->cancelled(); } -void UpdateFeatureUseCounts(Isolate* isolate, const WasmFeatures& detected) { +void UpdateFeatureUseCounts(Isolate* isolate, WasmFeatures detected) { using Feature = v8::Isolate::UseCounterFeature; constexpr static std::pair<WasmFeature, Feature> kUseCounters[] = { {kFeature_reftypes, Feature::kWasmRefTypes}, @@ -853,7 +839,17 @@ void CompilationState::AddCallback( return Impl(this)->AddCallback(std::move(callback)); } -void CompilationState::SetHighPriority() { Impl(this)->SetHighPriority(); } +void CompilationState::TierUpAllFunctions() { + Impl(this)->TierUpAllFunctions(); +} + +void CompilationState::AllowAnotherTopTierJob(uint32_t func_index) { + Impl(this)->AllowAnotherTopTierJob(func_index); +} + +void CompilationState::AllowAnotherTopTierJobForAllFunctions() { + Impl(this)->AllowAnotherTopTierJobForAllFunctions(); +} void CompilationState::InitializeAfterDeserialization( base::Vector<const int> lazy_functions, @@ -868,10 +864,6 @@ bool CompilationState::baseline_compilation_finished() const { return Impl(this)->baseline_compilation_finished(); } -bool CompilationState::recompilation_finished() const { - return Impl(this)->recompilation_finished(); -} - void CompilationState::set_compilation_id(int compilation_id) { Impl(this)->set_compilation_id(compilation_id); } @@ -920,7 +912,7 @@ const WasmCompilationHint* GetCompilationHint(const WasmModule* module, } CompileStrategy GetCompileStrategy(const WasmModule* module, - const WasmFeatures& enabled_features, + WasmFeatures enabled_features, uint32_t func_index, bool lazy_module) { if (lazy_module) return CompileStrategy::kLazy; if (!enabled_features.has_compilation_hints()) { @@ -945,20 +937,24 @@ struct ExecutionTierPair { ExecutionTier top_tier; }; +// Pass the debug state as a separate parameter to avoid data races: the debug +// state may change between its use here and its use at the call site. To have +// a consistent view on the debug state, the caller reads the debug state once +// and then passes it to this function. ExecutionTierPair GetDefaultTiersPerModule(NativeModule* native_module, DynamicTiering dynamic_tiering, + DebugState is_in_debug_state, bool lazy_module) { const WasmModule* module = native_module->module(); if (is_asmjs_module(module)) { return {ExecutionTier::kTurbofan, ExecutionTier::kTurbofan}; } - // TODO(13224): Use lazy compilation for debug code. - if (native_module->IsTieredDown()) { - return {ExecutionTier::kLiftoff, ExecutionTier::kLiftoff}; - } if (lazy_module) { return {ExecutionTier::kNone, ExecutionTier::kNone}; } + if (is_in_debug_state) { + return {ExecutionTier::kLiftoff, ExecutionTier::kLiftoff}; + } ExecutionTier baseline_tier = v8_flags.liftoff ? ExecutionTier::kLiftoff : ExecutionTier::kTurbofan; bool eager_tier_up = !dynamic_tiering && v8_flags.wasm_tier_up; @@ -968,14 +964,17 @@ ExecutionTierPair GetDefaultTiersPerModule(NativeModule* native_module, } ExecutionTierPair GetLazyCompilationTiers(NativeModule* native_module, - uint32_t func_index) { + uint32_t func_index, + DebugState is_in_debug_state) { DynamicTiering dynamic_tiering = Impl(native_module->compilation_state())->dynamic_tiering(); // For lazy compilation, get the tiers we would use if lazy compilation is // disabled. constexpr bool kNotLazy = false; - ExecutionTierPair tiers = - GetDefaultTiersPerModule(native_module, dynamic_tiering, kNotLazy); + ExecutionTierPair tiers = GetDefaultTiersPerModule( + native_module, dynamic_tiering, is_in_debug_state, kNotLazy); + // If we are in debug mode, we ignore compilation hints. + if (is_in_debug_state) return tiers; // Check if compilation hints override default tiering behaviour. if (native_module->enabled_features().has_compilation_hints()) { @@ -1012,7 +1011,7 @@ class CompilationUnitBuilder { void AddImportUnit(uint32_t func_index) { DCHECK_GT(native_module_->module()->num_imported_functions, func_index); baseline_units_.emplace_back(func_index, ExecutionTier::kNone, - kNoDebugging); + kNotForDebugging); } void AddJSToWasmWrapperUnit( @@ -1021,35 +1020,22 @@ class CompilationUnitBuilder { } void AddBaselineUnit(int func_index, ExecutionTier tier) { - baseline_units_.emplace_back(func_index, tier, kNoDebugging); + baseline_units_.emplace_back(func_index, tier, kNotForDebugging); } void AddTopTierUnit(int func_index, ExecutionTier tier) { - tiering_units_.emplace_back(func_index, tier, kNoDebugging); + tiering_units_.emplace_back(func_index, tier, kNotForDebugging); } - void AddDebugUnit(int func_index) { - baseline_units_.emplace_back(func_index, ExecutionTier::kLiftoff, - kForDebugging); - } - - void AddRecompilationUnit(int func_index, ExecutionTier tier) { - // For recompilation, just treat all units like baseline units. - baseline_units_.emplace_back( - func_index, tier, - tier == ExecutionTier::kLiftoff ? kForDebugging : kNoDebugging); - } - - bool Commit() { + void Commit() { if (baseline_units_.empty() && tiering_units_.empty() && js_to_wasm_wrapper_units_.empty()) { - return false; + return; } compilation_state()->CommitCompilationUnits( base::VectorOf(baseline_units_), base::VectorOf(tiering_units_), base::VectorOf(js_to_wasm_wrapper_units_)); Clear(); - return true; } void Clear() { @@ -1072,38 +1058,20 @@ class CompilationUnitBuilder { js_to_wasm_wrapper_units_; }; -WasmError GetWasmErrorWithName(ModuleWireBytes wire_bytes, - const WasmFunction* func, - const WasmModule* module, WasmError error) { - WasmName name = wire_bytes.GetNameOrNull(func, module); - if (name.begin() == nullptr) { - return WasmError(error.offset(), "Compiling function #%d failed: %s", - func->func_index, error.message().c_str()); - } else { - TruncatedUserString<> truncated_name(name); - return WasmError(error.offset(), - "Compiling function #%d:\"%.*s\" failed: %s", - func->func_index, truncated_name.length(), - truncated_name.start(), error.message().c_str()); - } -} - -void SetCompileError(ErrorThrower* thrower, ModuleWireBytes wire_bytes, - const WasmFunction* func, const WasmModule* module, - WasmError error) { - thrower->CompileFailed(GetWasmErrorWithName(std::move(wire_bytes), func, - module, std::move(error))); -} - DecodeResult ValidateSingleFunction(const WasmModule* module, int func_index, base::Vector<const uint8_t> code, - AccountingAllocator* allocator, WasmFeatures enabled_features) { + // Sometimes functions get validated unpredictably in the background, for + // debugging or when inlining one function into another. We check here if that + // is the case, and exit early if so. + if (module->function_was_validated(func_index)) return {}; const WasmFunction* func = &module->functions[func_index]; FunctionBody body{func->sig, func->code.offset(), code.begin(), code.end()}; WasmFeatures detected_features; - return ValidateFunctionBody(allocator, enabled_features, module, - &detected_features, body); + DecodeResult result = + ValidateFunctionBody(enabled_features, module, &detected_features, body); + if (result.ok()) module->set_function_validated(func_index); + return result; } enum OnlyLazyFunctions : bool { @@ -1111,37 +1079,6 @@ enum OnlyLazyFunctions : bool { kOnlyLazyFunctions = true, }; -void ValidateSequentially( - const WasmModule* module, NativeModule* native_module, Counters* counters, - AccountingAllocator* allocator, ErrorThrower* thrower, - OnlyLazyFunctions only_lazy_functions = kAllFunctions) { - DCHECK(!thrower->error()); - uint32_t start = module->num_imported_functions; - uint32_t end = start + module->num_declared_functions; - auto enabled_features = native_module->enabled_features(); - bool lazy_module = v8_flags.wasm_lazy_compilation; - for (uint32_t func_index = start; func_index < end; func_index++) { - // Skip non-lazy functions if requested. - if (only_lazy_functions) { - CompileStrategy strategy = - GetCompileStrategy(module, enabled_features, func_index, lazy_module); - if (strategy != CompileStrategy::kLazy && - strategy != CompileStrategy::kLazyBaselineEagerTopTier) { - continue; - } - } - - ModuleWireBytes wire_bytes{native_module->wire_bytes()}; - const WasmFunction* func = &module->functions[func_index]; - base::Vector<const uint8_t> code = wire_bytes.GetFunctionBytes(func); - DecodeResult result = ValidateSingleFunction(module, func_index, code, - allocator, enabled_features); - if (result.failed()) { - SetCompileError(thrower, wire_bytes, func, module, result.error()); - } - } -} - bool IsLazyModule(const WasmModule* module) { return v8_flags.wasm_lazy_compilation || (v8_flags.asm_wasm_lazy_compilation && is_asmjs_module(module)); @@ -1187,18 +1124,17 @@ bool CompileLazy(Isolate* isolate, WasmInstanceObject instance, TRACE_LAZY("Compiling wasm-function#%d.\n", func_index); - base::ThreadTicks thread_ticks = base::ThreadTicks::IsSupported() - ? base::ThreadTicks::Now() - : base::ThreadTicks(); - CompilationStateImpl* compilation_state = Impl(native_module->compilation_state()); - ExecutionTierPair tiers = GetLazyCompilationTiers(native_module, func_index); + DebugState is_in_debug_state = native_module->IsInDebugState(); + ExecutionTierPair tiers = + GetLazyCompilationTiers(native_module, func_index, is_in_debug_state); DCHECK_LE(native_module->num_imported_functions(), func_index); DCHECK_LT(func_index, native_module->num_functions()); - WasmCompilationUnit baseline_unit{func_index, tiers.baseline_tier, - kNoDebugging}; + WasmCompilationUnit baseline_unit{ + func_index, tiers.baseline_tier, + is_in_debug_state ? kForDebugging : kNotForDebugging}; CompilationEnv env = native_module->CreateCompilationEnv(); // TODO(wasm): Use an assembler buffer cache for lazy compilation. AssemblerBufferCache* assembler_buffer_cache = nullptr; @@ -1207,11 +1143,6 @@ bool CompileLazy(Isolate* isolate, WasmInstanceObject instance, &env, compilation_state->GetWireBytesStorage().get(), counters, assembler_buffer_cache, &detected_features); compilation_state->OnCompilationStopped(detected_features); - if (!thread_ticks.IsNull()) { - native_module->UpdateCPUDuration( - (base::ThreadTicks::Now() - thread_ticks).InMicroseconds(), - tiers.baseline_tier); - } // During lazy compilation, we can only get compilation errors when // {--wasm-lazy-validation} is enabled. Otherwise, the module was fully @@ -1245,7 +1176,8 @@ bool CompileLazy(Isolate* isolate, WasmInstanceObject instance, if (GetCompileStrategy(module, native_module->enabled_features(), func_index, lazy_module) == CompileStrategy::kLazy && tiers.baseline_tier < tiers.top_tier) { - WasmCompilationUnit tiering_unit{func_index, tiers.top_tier, kNoDebugging}; + WasmCompilationUnit tiering_unit{func_index, tiers.top_tier, + kNotForDebugging}; compilation_state->CommitTopTierCompilationUnit(tiering_unit); } return true; @@ -1262,15 +1194,15 @@ void ThrowLazyCompilationError(Isolate* isolate, base::Vector<const uint8_t> code = compilation_state->GetWireBytesStorage()->GetCode(func->code); - WasmEngine* engine = GetWasmEngine(); auto enabled_features = native_module->enabled_features(); - DecodeResult decode_result = ValidateSingleFunction( - module, func_index, code, engine->allocator(), enabled_features); + DecodeResult decode_result = + ValidateSingleFunction(module, func_index, code, enabled_features); CHECK(decode_result.failed()); wasm::ErrorThrower thrower(isolate, nullptr); - SetCompileError(&thrower, ModuleWireBytes(native_module->wire_bytes()), func, - module, decode_result.error()); + thrower.CompileFailed(GetWasmErrorWithName(native_module->wire_bytes(), + func_index, module, + std::move(decode_result).error())); } class TransitiveTypeFeedbackProcessor { @@ -1321,7 +1253,9 @@ class TransitiveTypeFeedbackProcessor { DisallowGarbageCollection no_gc_scope_; WasmInstanceObject instance_; const WasmModule* const module_; - base::MutexGuard mutex_guard; + // TODO(jkummerow): Check if it makes a difference to apply any updates + // as a single batch at the end. + base::SharedMutexGuard<base::kExclusive> mutex_guard; std::unordered_map<uint32_t, FunctionTypeFeedback>& feedback_for_function_; std::set<int> queue_; }; @@ -1455,12 +1389,13 @@ void TriggerTierUp(WasmInstanceObject instance, int func_index) { CompilationStateImpl* compilation_state = Impl(native_module->compilation_state()); WasmCompilationUnit tiering_unit{func_index, ExecutionTier::kTurbofan, - kNoDebugging}; + kNotForDebugging}; const WasmModule* module = native_module->module(); int priority; { - base::MutexGuard mutex_guard(&module->type_feedback.mutex); + base::SharedMutexGuard<base::kExclusive> mutex_guard( + &module->type_feedback.mutex); int array_index = wasm::declared_function_index(instance.module(), func_index); instance.tiering_budget_array()[array_index] = v8_flags.wasm_tiering_budget; @@ -1477,7 +1412,7 @@ void TriggerTierUp(WasmInstanceObject instance, int func_index) { // Before adding the tier-up unit or increasing priority, do process type // feedback for best code generation. - if (v8_flags.wasm_speculative_inlining) { + if (native_module->enabled_features().has_inlining()) { // TODO(jkummerow): we could have collisions here if different instances // of the same module have collected different feedback. If that ever // becomes a problem, figure out a solution. @@ -1489,21 +1424,21 @@ void TriggerTierUp(WasmInstanceObject instance, int func_index) { void TierUpNowForTesting(Isolate* isolate, WasmInstanceObject instance, int func_index) { - if (v8_flags.wasm_speculative_inlining) { + NativeModule* native_module = instance.module_object().native_module(); + if (native_module->enabled_features().has_inlining()) { TransitiveTypeFeedbackProcessor::Process(instance, func_index); } - auto* native_module = instance.module_object().native_module(); - wasm::GetWasmEngine()->CompileFunction(isolate, native_module, func_index, + wasm::GetWasmEngine()->CompileFunction(isolate->counters(), native_module, + func_index, wasm::ExecutionTier::kTurbofan); CHECK(!native_module->compilation_state()->failed()); } namespace { -void RecordStats(CodeT codet, Counters* counters) { - if (codet.is_off_heap_trampoline()) return; - Code code = FromCodeT(codet); - counters->wasm_generated_code_size()->Increment(code.raw_body_size()); +void RecordStats(Code code, Counters* counters) { + if (!code.has_instruction_stream()) return; + counters->wasm_generated_code_size()->Increment(code.body_size()); counters->wasm_reloc_size()->Increment(code.relocation_info().length()); } @@ -1570,17 +1505,8 @@ constexpr uint8_t kMainTaskId = 0; // Run by the {BackgroundCompileJob} (on any thread). CompilationExecutionResult ExecuteCompilationUnits( std::weak_ptr<NativeModule> native_module, Counters* counters, - JobDelegate* delegate, CompileBaselineOnly baseline_only) { + JobDelegate* delegate, CompilationTier tier) { TRACE_EVENT0("v8.wasm", "wasm.ExecuteCompilationUnits"); - - // Execute JS to Wasm wrapper units first, so that they are ready to be - // finalized by the main thread when the kFinishedBaselineCompilation event is - // triggered. - if (ExecuteJSToWasmWrapperCompilationUnits(native_module, delegate) == - kYield) { - return kYield; - } - // These fields are initialized in a {BackgroundCompileScope} before // starting compilation. base::Optional<CompilationEnv> env; @@ -1596,10 +1522,6 @@ CompilationExecutionResult ExecuteCompilationUnits( WasmFeatures detected_features = WasmFeatures::None(); - base::ThreadTicks thread_ticks = base::ThreadTicks::IsSupported() - ? base::ThreadTicks::Now() - : base::ThreadTicks(); - // Preparation (synchronized): Initialize the fields above and get the first // compilation unit. { @@ -1609,8 +1531,8 @@ CompilationExecutionResult ExecuteCompilationUnits( wire_bytes = compile_scope.compilation_state()->GetWireBytesStorage(); module = compile_scope.native_module()->shared_module(); queue = compile_scope.compilation_state()->GetQueueForCompileTask(task_id); - unit = compile_scope.compilation_state()->GetNextCompilationUnit( - queue, baseline_only); + unit = + compile_scope.compilation_state()->GetNextCompilationUnit(queue, tier); if (!unit) return kNoMoreUnits; } TRACE_COMPILE("ExecuteCompilationUnits (task id %d)\n", task_id); @@ -1659,12 +1581,7 @@ CompilationExecutionResult ExecuteCompilationUnits( // Yield or get next unit. if (yield || !(unit = compile_scope.compilation_state()->GetNextCompilationUnit( - queue, baseline_only))) { - if (!thread_ticks.IsNull()) { - compile_scope.native_module()->UpdateCPUDuration( - (base::ThreadTicks::Now() - thread_ticks).InMicroseconds(), - current_tier); - } + queue, tier))) { std::vector<std::unique_ptr<WasmCode>> unpublished_code = compile_scope.native_module()->AddCompiledCode( base::VectorOf(std::move(results_to_publish))); @@ -1686,12 +1603,6 @@ CompilationExecutionResult ExecuteCompilationUnits( bool liftoff_finished = unit->tier() != current_tier && unit->tier() == ExecutionTier::kTurbofan; if (batch_full || liftoff_finished) { - if (!thread_ticks.IsNull()) { - base::ThreadTicks thread_ticks_now = base::ThreadTicks::Now(); - compile_scope.native_module()->UpdateCPUDuration( - (thread_ticks_now - thread_ticks).InMicroseconds(), current_tier); - thread_ticks = thread_ticks_now; - } std::vector<std::unique_ptr<WasmCode>> unpublished_code = compile_scope.native_module()->AddCompiledCode( base::VectorOf(std::move(results_to_publish))); @@ -1717,6 +1628,19 @@ int AddExportWrapperUnits(Isolate* isolate, NativeModule* native_module, uint32_t canonical_type_index = native_module->module() ->isorecursive_canonical_type_ids[function.sig_index]; + int wrapper_index = + GetExportWrapperIndex(canonical_type_index, function.imported); + if (wrapper_index < isolate->heap()->js_to_wasm_wrappers().length()) { + MaybeObject existing_wrapper = + isolate->heap()->js_to_wasm_wrappers().Get(wrapper_index); + if (existing_wrapper.IsStrongOrWeak() && + !existing_wrapper.GetHeapObject().IsUndefined()) { + // Skip wrapper compilation as the wrapper is already cached. + // Note that this does not guarantee that the wrapper is still cached + // at the moment at which the WasmInternalFunction is instantiated. + continue; + } + } JSToWasmWrapperKey key(function.imported, canonical_type_index); if (keys.insert(key).second) { auto unit = std::make_shared<JSToWasmWrapperCompilationUnit>( @@ -1740,15 +1664,12 @@ int AddImportWrapperUnits(NativeModule* native_module, for (int func_index = 0; func_index < num_imported_functions; func_index++) { const WasmFunction& function = native_module->module()->functions[func_index]; - if (!IsJSCompatibleSignature(function.sig, native_module->module(), - native_module->enabled_features())) { - continue; - } + if (!IsJSCompatibleSignature(function.sig)) continue; uint32_t canonical_type_index = native_module->module() ->isorecursive_canonical_type_ids[function.sig_index]; WasmImportWrapperCache::CacheKey key( - compiler::kDefaultImportCallKind, canonical_type_index, + kDefaultImportCallKind, canonical_type_index, static_cast<int>(function.sig->parameter_count()), kNoSuspend); auto it = keys.insert(key); if (it.second) { @@ -1761,32 +1682,9 @@ int AddImportWrapperUnits(NativeModule* native_module, return static_cast<int>(keys.size()); } -void InitializeLazyCompilation(NativeModule* native_module) { - const bool lazy_module = IsLazyModule(native_module->module()); - auto* module = native_module->module(); - - uint32_t start = module->num_imported_functions; - uint32_t end = start + module->num_declared_functions; - base::Optional<CodeSpaceWriteScope> lazy_code_space_write_scope; - for (uint32_t func_index = start; func_index < end; func_index++) { - CompileStrategy strategy = GetCompileStrategy( - module, native_module->enabled_features(), func_index, lazy_module); - if (strategy == CompileStrategy::kLazy || - strategy == CompileStrategy::kLazyBaselineEagerTopTier) { - // Open a single scope for all following calls to {UseLazyStub()}, instead - // of flipping page permissions for each {func_index} individually. - if (!lazy_code_space_write_scope.has_value()) { - lazy_code_space_write_scope.emplace(native_module); - } - native_module->UseLazyStub(func_index); - } - } -} - std::unique_ptr<CompilationUnitBuilder> InitializeCompilation( Isolate* isolate, NativeModule* native_module, ProfileInformation* pgo_info) { - InitializeLazyCompilation(native_module); CompilationStateImpl* compilation_state = Impl(native_module->compilation_state()); auto builder = std::make_unique<CompilationUnitBuilder>(native_module); @@ -1799,7 +1697,7 @@ std::unique_ptr<CompilationUnitBuilder> InitializeCompilation( } bool MayCompriseLazyFunctions(const WasmModule* module, - const WasmFeatures& enabled_features) { + WasmFeatures enabled_features) { if (IsLazyModule(module)) return true; if (enabled_features.has_compilation_hints()) return true; #ifdef ENABLE_SLOW_DCHECKS @@ -1854,9 +1752,7 @@ class CompilationTimeCallback : public CompilationEventCallback { true, // success native_module->liftoff_code_size(), // code_size_in_bytes native_module->liftoff_bailout_count(), // liftoff_bailout_count - duration.InMicroseconds(), // wall_clock_duration_in_us - static_cast<int64_t>( // cpu_time_duration_in_us - native_module->baseline_compilation_cpu_duration())}; + duration.InMicroseconds()}; // wall_clock_duration_in_us metrics_recorder_->DelayMainThreadEvent(event, context_id_); } if (compilation_event == CompilationEvent::kFailedCompilation) { @@ -1869,9 +1765,7 @@ class CompilationTimeCallback : public CompilationEventCallback { false, // success native_module->liftoff_code_size(), // code_size_in_bytes native_module->liftoff_bailout_count(), // liftoff_bailout_count - duration.InMicroseconds(), // wall_clock_duration_in_us - static_cast<int64_t>( // cpu_time_duration_in_us - native_module->baseline_compilation_cpu_duration())}; + duration.InMicroseconds()}; // wall_clock_duration_in_us metrics_recorder_->DelayMainThreadEvent(event, context_id_); } } @@ -1885,26 +1779,44 @@ class CompilationTimeCallback : public CompilationEventCallback { const CompileMode compile_mode_; }; +WasmError ValidateFunctions(const WasmModule* module, + base::Vector<const uint8_t> wire_bytes, + WasmFeatures enabled_features, + OnlyLazyFunctions only_lazy_functions) { + DCHECK_EQ(module->origin, kWasmOrigin); + if (only_lazy_functions && + !MayCompriseLazyFunctions(module, enabled_features)) { + return {}; + } + + std::function<bool(int)> filter; // Initially empty for "all functions". + if (only_lazy_functions) { + const bool is_lazy_module = IsLazyModule(module); + filter = [module, enabled_features, is_lazy_module](int func_index) { + CompileStrategy strategy = GetCompileStrategy(module, enabled_features, + func_index, is_lazy_module); + return strategy == CompileStrategy::kLazy || + strategy == CompileStrategy::kLazyBaselineEagerTopTier; + }; + } + // Call {ValidateFunctions} in the module decoder. + return ValidateFunctions(module, enabled_features, wire_bytes, filter); +} + +WasmError ValidateFunctions(const NativeModule& native_module, + OnlyLazyFunctions only_lazy_functions) { + return ValidateFunctions(native_module.module(), native_module.wire_bytes(), + native_module.enabled_features(), + only_lazy_functions); +} + void CompileNativeModule(Isolate* isolate, v8::metrics::Recorder::ContextId context_id, - ErrorThrower* thrower, const WasmModule* wasm_module, + ErrorThrower* thrower, std::shared_ptr<NativeModule> native_module, ProfileInformation* pgo_info) { CHECK(!v8_flags.jitless); - ModuleWireBytes wire_bytes(native_module->wire_bytes()); - if (!v8_flags.wasm_lazy_validation && wasm_module->origin == kWasmOrigin && - MayCompriseLazyFunctions(wasm_module, - native_module->enabled_features())) { - // Validate wasm modules for lazy compilation if requested. Never validate - // asm.js modules as these are valid by construction (additionally a CHECK - // will catch this during lazy compilation). - ValidateSequentially(wasm_module, native_module.get(), isolate->counters(), - isolate->allocator(), thrower, kOnlyLazyFunctions); - // On error: Return and leave the module in an unexecutable state. - if (thrower->error()) return; - } - - DCHECK_GE(kMaxInt, native_module->module()->num_declared_functions); + const WasmModule* module = native_module->module(); // The callback captures a shared ptr to the semaphore. auto* compilation_state = Impl(native_module->compilation_state()); @@ -1919,72 +1831,113 @@ void CompileNativeModule(Isolate* isolate, InitializeCompilation(isolate, native_module.get(), pgo_info); compilation_state->InitializeCompilationUnits(std::move(builder)); + // Validate wasm modules for lazy compilation if requested. Never validate + // asm.js modules as these are valid by construction (additionally a CHECK + // will catch this during lazy compilation). + if (!v8_flags.wasm_lazy_validation && module->origin == kWasmOrigin) { + DCHECK(!thrower->error()); + if (WasmError validation_error = + ValidateFunctions(*native_module, kOnlyLazyFunctions)) { + thrower->CompileFailed(std::move(validation_error)); + return; + } + } + compilation_state->WaitForCompilationEvent( CompilationEvent::kFinishedExportWrappers); - if (compilation_state->failed()) { - DCHECK_IMPLIES(IsLazyModule(wasm_module), !v8_flags.wasm_lazy_validation); - ValidateSequentially(wasm_module, native_module.get(), isolate->counters(), - isolate->allocator(), thrower); - CHECK(thrower->error()); - return; - } - - compilation_state->FinalizeJSToWasmWrappers(isolate, wasm_module); + if (!compilation_state->failed()) { + compilation_state->FinalizeJSToWasmWrappers(isolate, module); - compilation_state->WaitForCompilationEvent( - CompilationEvent::kFinishedBaselineCompilation); + compilation_state->WaitForCompilationEvent( + CompilationEvent::kFinishedBaselineCompilation); - compilation_state->PublishDetectedFeatures(isolate); + compilation_state->PublishDetectedFeatures(isolate); + } if (compilation_state->failed()) { - DCHECK_IMPLIES(IsLazyModule(wasm_module), !v8_flags.wasm_lazy_validation); - ValidateSequentially(wasm_module, native_module.get(), isolate->counters(), - isolate->allocator(), thrower); - CHECK(thrower->error()); + DCHECK_IMPLIES(IsLazyModule(module), !v8_flags.wasm_lazy_validation); + WasmError validation_error = + ValidateFunctions(*native_module, kAllFunctions); + CHECK(validation_error.has_error()); + thrower->CompileFailed(std::move(validation_error)); } } +class AsyncCompileJSToWasmWrapperJob final : public JobTask { + public: + explicit AsyncCompileJSToWasmWrapperJob( + std::weak_ptr<NativeModule> native_module) + : native_module_(std::move(native_module)), + engine_barrier_(GetWasmEngine()->GetBarrierForBackgroundCompile()) {} + + void Run(JobDelegate* delegate) override { + auto engine_scope = engine_barrier_->TryLock(); + if (!engine_scope) return; + ExecuteJSToWasmWrapperCompilationUnits(native_module_, delegate); + } + + size_t GetMaxConcurrency(size_t worker_count) const override { + BackgroundCompileScope compile_scope(native_module_); + if (compile_scope.cancelled()) return 0; + size_t flag_limit = static_cast<size_t>( + std::max(1, v8_flags.wasm_num_compilation_tasks.value())); + // NumOutstandingExportWrappers() does not reflect the units that running + // workers are processing, thus add the current worker count to that number. + return std::min( + flag_limit, + worker_count + + compile_scope.compilation_state()->NumOutstandingExportWrappers()); + } + + private: + std::weak_ptr<NativeModule> native_module_; + std::shared_ptr<OperationsBarrier> engine_barrier_; +}; + class BackgroundCompileJob final : public JobTask { public: explicit BackgroundCompileJob(std::weak_ptr<NativeModule> native_module, - std::shared_ptr<Counters> async_counters) + std::shared_ptr<Counters> async_counters, + CompilationTier tier) : native_module_(std::move(native_module)), engine_barrier_(GetWasmEngine()->GetBarrierForBackgroundCompile()), - async_counters_(std::move(async_counters)) {} + async_counters_(std::move(async_counters)), + tier_(tier) {} void Run(JobDelegate* delegate) override { auto engine_scope = engine_barrier_->TryLock(); if (!engine_scope) return; ExecuteCompilationUnits(native_module_, async_counters_.get(), delegate, - kBaselineOrTopTier); + tier_); } size_t GetMaxConcurrency(size_t worker_count) const override { BackgroundCompileScope compile_scope(native_module_); if (compile_scope.cancelled()) return 0; + size_t flag_limit = static_cast<size_t>( + std::max(1, v8_flags.wasm_num_compilation_tasks.value())); // NumOutstandingCompilations() does not reflect the units that running // workers are processing, thus add the current worker count to that number. - return std::min( - static_cast<size_t>(v8_flags.wasm_num_compilation_tasks), - worker_count + - compile_scope.compilation_state()->NumOutstandingCompilations()); + return std::min(flag_limit, + worker_count + compile_scope.compilation_state() + ->NumOutstandingCompilations(tier_)); } private: std::weak_ptr<NativeModule> native_module_; std::shared_ptr<OperationsBarrier> engine_barrier_; const std::shared_ptr<Counters> async_counters_; + const CompilationTier tier_; }; } // namespace std::shared_ptr<NativeModule> CompileToNativeModule( - Isolate* isolate, const WasmFeatures& enabled, ErrorThrower* thrower, - std::shared_ptr<const WasmModule> module, const ModuleWireBytes& wire_bytes, + Isolate* isolate, WasmFeatures enabled_features, ErrorThrower* thrower, + std::shared_ptr<const WasmModule> module, ModuleWireBytes wire_bytes, int compilation_id, v8::metrics::Recorder::ContextId context_id, ProfileInformation* pgo_info) { - const WasmModule* wasm_module = module.get(); WasmEngine* engine = GetWasmEngine(); base::OwnedVector<uint8_t> wire_bytes_copy = base::OwnedVector<uint8_t>::Of(wire_bytes.module_bytes()); @@ -1993,20 +1946,20 @@ std::shared_ptr<NativeModule> CompileToNativeModule( // bytes of the temporary key and the new key have the same base pointer and // we can skip the full bytes comparison. std::shared_ptr<NativeModule> native_module = engine->MaybeGetNativeModule( - wasm_module->origin, wire_bytes_copy.as_vector(), isolate); + module->origin, wire_bytes_copy.as_vector(), isolate); if (native_module) { - CompileJsToWasmWrappers(isolate, wasm_module); + CompileJsToWasmWrappers(isolate, module.get()); return native_module; } base::Optional<TimedHistogramScope> wasm_compile_module_time_scope; if (base::TimeTicks::IsHighResolution()) { wasm_compile_module_time_scope.emplace(SELECT_WASM_COUNTER( - isolate->counters(), wasm_module->origin, wasm_compile, module_time)); + isolate->counters(), module->origin, wasm_compile, module_time)); } // Embedder usage count for declared shared memories. - if (wasm_module->has_shared_memory) { + if (module->has_shared_memory) { isolate->CountUsage(v8::Isolate::UseCounterFeature::kWasmSharedMemory); } @@ -2017,22 +1970,27 @@ std::shared_ptr<NativeModule> CompileToNativeModule( wasm::WasmCodeManager::EstimateNativeModuleCodeSize( module.get(), include_liftoff, DynamicTiering{v8_flags.wasm_dynamic_tiering.value()}); - native_module = - engine->NewNativeModule(isolate, enabled, module, code_size_estimate); + native_module = engine->NewNativeModule(isolate, enabled_features, module, + code_size_estimate); native_module->SetWireBytes(std::move(wire_bytes_copy)); native_module->compilation_state()->set_compilation_id(compilation_id); - // Sync compilation is user blocking, so we increase the priority. - native_module->compilation_state()->SetHighPriority(); - CompileNativeModule(isolate, context_id, thrower, wasm_module, native_module, - pgo_info); - bool cache_hit = !engine->UpdateNativeModuleCache(thrower->error(), - &native_module, isolate); - if (thrower->error()) return {}; + CompileNativeModule(isolate, context_id, thrower, native_module, pgo_info); - if (cache_hit) { - CompileJsToWasmWrappers(isolate, wasm_module); - return native_module; + if (thrower->error()) { + engine->UpdateNativeModuleCache(true, std::move(native_module), isolate); + return {}; + } + + std::shared_ptr<NativeModule> cached_native_module = + engine->UpdateNativeModuleCache(false, native_module, isolate); + + if (cached_native_module != native_module) { + // Do not use {module} or {native_module} any more; use + // {cached_native_module} instead. + module.reset(); + native_module.reset(); + return cached_native_module; } // Ensure that the code objects are logged before returning. @@ -2041,56 +1999,18 @@ std::shared_ptr<NativeModule> CompileToNativeModule( return native_module; } -void RecompileNativeModule(NativeModule* native_module, - TieringState tiering_state) { - // Install a callback to notify us once background recompilation finished. - auto recompilation_finished_semaphore = std::make_shared<base::Semaphore>(0); - auto* compilation_state = Impl(native_module->compilation_state()); - - class RecompilationFinishedCallback : public CompilationEventCallback { - public: - explicit RecompilationFinishedCallback( - std::shared_ptr<base::Semaphore> recompilation_finished_semaphore) - : recompilation_finished_semaphore_( - std::move(recompilation_finished_semaphore)) {} - - void call(CompilationEvent event) override { - DCHECK_NE(CompilationEvent::kFailedCompilation, event); - if (event == CompilationEvent::kFinishedRecompilation) { - recompilation_finished_semaphore_->Signal(); - } - } - - private: - std::shared_ptr<base::Semaphore> recompilation_finished_semaphore_; - }; - - // The callback captures a shared ptr to the semaphore. - // Initialize the compilation units and kick off background compile tasks. - compilation_state->InitializeRecompilation( - tiering_state, std::make_unique<RecompilationFinishedCallback>( - recompilation_finished_semaphore)); - - constexpr JobDelegate* kNoDelegate = nullptr; - ExecuteCompilationUnits(compilation_state->native_module_weak(), - compilation_state->counters(), kNoDelegate, - kBaselineOnly); - recompilation_finished_semaphore->Wait(); - DCHECK(!compilation_state->failed()); -} - AsyncCompileJob::AsyncCompileJob( - Isolate* isolate, const WasmFeatures& enabled, - std::unique_ptr<byte[]> bytes_copy, size_t length, Handle<Context> context, + Isolate* isolate, WasmFeatures enabled_features, + base::OwnedVector<const uint8_t> bytes, Handle<Context> context, Handle<Context> incumbent_context, const char* api_method_name, std::shared_ptr<CompilationResultResolver> resolver, int compilation_id) : isolate_(isolate), api_method_name_(api_method_name), - enabled_features_(enabled), + enabled_features_(enabled_features), dynamic_tiering_(DynamicTiering{v8_flags.wasm_dynamic_tiering.value()}), start_time_(base::TimeTicks::Now()), - bytes_copy_(std::move(bytes_copy)), - wire_bytes_(bytes_copy_.get(), bytes_copy_.get() + length), + bytes_copy_(std::move(bytes)), + wire_bytes_(bytes_copy_.as_vector()), resolver_(std::move(resolver)), compilation_id_(compilation_id) { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"), @@ -2119,13 +2039,124 @@ void AsyncCompileJob::Abort() { GetWasmEngine()->RemoveCompileJob(this); } -class AsyncStreamingProcessor final : public StreamingProcessor { +// {ValidateFunctionsStreamingJobData} holds information that is shared between +// the {AsyncStreamingProcessor} and the {ValidateFunctionsStreamingJob}. It +// lives in the {AsyncStreamingProcessor} and is updated from both classes. +struct ValidateFunctionsStreamingJobData { + struct Unit { + // {func_index == -1} represents an "invalid" unit. + int func_index = -1; + base::Vector<const uint8_t> code; + + // Check whether the unit is valid. + operator bool() const { + DCHECK_LE(-1, func_index); + return func_index >= 0; + } + }; + + void Initialize(int num_declared_functions) { + DCHECK_NULL(units); + units = base::OwnedVector<Unit>::NewForOverwrite(num_declared_functions); + // Initially {next == end}. + next_available_unit.store(units.begin(), std::memory_order_relaxed); + end_of_available_units.store(units.begin(), std::memory_order_relaxed); + } + + void AddUnit(int declared_func_index, base::Vector<const uint8_t> code, + JobHandle* job_handle) { + DCHECK_NOT_NULL(units); + // Write new unit to {*end}, then increment {end}. There is only one thread + // adding new units, so no further synchronization needed. + Unit* ptr = end_of_available_units.load(std::memory_order_relaxed); + // Check invariant: {next <= end}. + DCHECK_LE(next_available_unit.load(std::memory_order_relaxed), ptr); + *ptr++ = {declared_func_index, code}; + // Use release semantics, so whoever loads this pointer (using acquire + // semantics) sees all our previous stores. + end_of_available_units.store(ptr, std::memory_order_release); + size_t total_units_added = ptr - units.begin(); + // Periodically notify concurrency increase. This has overhead, so avoid + // calling it too often. As long as threads are still running they will + // continue processing new units anyway, and if background threads validate + // faster than we can add units, then only notifying after increasingly long + // delays is the right thing to do to avoid too many small validation tasks. + // We notify on each power of two after 16 units, and every 16k units (just + // to have *some* upper limit and avoiding to pile up too many units). + // Additionally, notify after receiving the last unit of the module. + if ((total_units_added >= 16 && + base::bits::IsPowerOfTwo(total_units_added)) || + (total_units_added % (16 * 1024)) == 0 || ptr == units.end()) { + job_handle->NotifyConcurrencyIncrease(); + } + } + + size_t NumOutstandingUnits() const { + Unit* next = next_available_unit.load(std::memory_order_relaxed); + Unit* end = end_of_available_units.load(std::memory_order_relaxed); + DCHECK_LE(next, end); + return end - next; + } + + // Retrieve one unit to validate; returns an "invalid" unit if nothing is in + // the queue. + Unit GetUnit() { + // Use an acquire load to synchronize with the store in {AddUnit}. All units + // before this {end} are fully initialized and ready to execute. + Unit* end = end_of_available_units.load(std::memory_order_acquire); + Unit* next = next_available_unit.load(std::memory_order_relaxed); + while (next < end) { + if (next_available_unit.compare_exchange_weak( + next, next + 1, std::memory_order_relaxed)) { + return *next; + } + // Otherwise retry with updated {next} pointer. + } + return {}; + } + + base::OwnedVector<Unit> units; + std::atomic<Unit*> next_available_unit; + std::atomic<Unit*> end_of_available_units; + std::atomic<bool> found_error{false}; +}; + +class ValidateFunctionsStreamingJob final : public JobTask { public: - explicit AsyncStreamingProcessor(AsyncCompileJob* job, - std::shared_ptr<Counters> counters, - AccountingAllocator* allocator); + ValidateFunctionsStreamingJob(const WasmModule* module, + WasmFeatures enabled_features, + ValidateFunctionsStreamingJobData* data) + : module_(module), enabled_features_(enabled_features), data_(data) {} - ~AsyncStreamingProcessor() override; + void Run(JobDelegate* delegate) override { + TRACE_EVENT0("v8.wasm", "wasm.ValidateFunctionsStreaming"); + using Unit = ValidateFunctionsStreamingJobData::Unit; + while (Unit unit = data_->GetUnit()) { + DecodeResult result = ValidateSingleFunction( + module_, unit.func_index, unit.code, enabled_features_); + + if (result.failed()) { + data_->found_error.store(true, std::memory_order_relaxed); + break; + } + // After validating one function, check if we should yield. + if (delegate->ShouldYield()) break; + } + } + + size_t GetMaxConcurrency(size_t worker_count) const override { + return worker_count + data_->NumOutstandingUnits(); + } + + private: + const WasmModule* const module_; + const WasmFeatures enabled_features_; + ValidateFunctionsStreamingJobData* data_; +}; + +class AsyncStreamingProcessor final : public StreamingProcessor { + public: + explicit AsyncStreamingProcessor(AsyncCompileJob* job); bool ProcessModuleHeader(base::Vector<const uint8_t> bytes, uint32_t offset) override; @@ -2140,14 +2171,13 @@ class AsyncStreamingProcessor final : public StreamingProcessor { int code_section_start, int code_section_length) override; - void ProcessFunctionBody(base::Vector<const uint8_t> bytes, + bool ProcessFunctionBody(base::Vector<const uint8_t> bytes, uint32_t offset) override; void OnFinishedChunk() override; - void OnFinishedStream(base::OwnedVector<uint8_t> bytes) override; - - void OnError(const WasmError&) override; + void OnFinishedStream(base::OwnedVector<const uint8_t> bytes, + bool after_error) override; void OnAbort() override; @@ -2155,11 +2185,6 @@ class AsyncStreamingProcessor final : public StreamingProcessor { base::Vector<const uint8_t> module_bytes) override; private: - enum ErrorLocation { kErrorInFunction, kErrorInSection }; - // Finishes the AsyncCompileJob with an error. - void FinishAsyncCompileJobWithError( - const WasmError&, ErrorLocation error_location = kErrorInSection); - void CommitCompilationUnits(); ModuleDecoder decoder_; @@ -2168,20 +2193,19 @@ class AsyncStreamingProcessor final : public StreamingProcessor { int num_functions_ = 0; bool prefix_cache_hit_ = false; bool before_code_section_ = true; - std::shared_ptr<Counters> async_counters_; - AccountingAllocator* allocator_; + ValidateFunctionsStreamingJobData validate_functions_job_data_; + std::unique_ptr<JobHandle> validate_functions_job_handle_; // Running hash of the wire bytes up to code section size, but excluding the // code section itself. Used by the {NativeModuleCache} to detect potential // duplicate modules. - size_t prefix_hash_; + size_t prefix_hash_ = 0; }; std::shared_ptr<StreamingDecoder> AsyncCompileJob::CreateStreamingDecoder() { DCHECK_NULL(stream_); stream_ = StreamingDecoder::CreateAsyncStreamingDecoder( - std::make_unique<AsyncStreamingProcessor>( - this, isolate_->async_counters(), isolate_->allocator())); + std::make_unique<AsyncStreamingProcessor>(this)); return stream_; } @@ -2195,9 +2219,7 @@ AsyncCompileJob::~AsyncCompileJob() { } // Tell the streaming decoder that the AsyncCompileJob is not available // anymore. - // TODO(ahaas): Is this notification really necessary? Check - // https://crbug.com/888170. - if (stream_) stream_->NotifyCompilationEnded(); + if (stream_) stream_->NotifyCompilationDiscarded(); CancelPendingForegroundTask(); isolate_->global_handles()->Destroy(native_context_.location()); isolate_->global_handles()->Destroy(incumbent_context_.location()); @@ -2213,15 +2235,12 @@ void AsyncCompileJob::CreateNativeModule( isolate_->CountUsage(v8::Isolate::UseCounterFeature::kWasmSharedMemory); } - // TODO(wasm): Improve efficiency of storing module wire bytes. Only store - // relevant sections, not function bodies - // Create the module object and populate with compiled functions and // information needed at instantiation time. native_module_ = GetWasmEngine()->NewNativeModule( isolate_, enabled_features_, std::move(module), code_size_estimate); - native_module_->SetWireBytes({std::move(bytes_copy_), wire_bytes_.length()}); + native_module_->SetWireBytes(std::move(bytes_copy_)); native_module_->compilation_state()->set_compilation_id(compilation_id_); } @@ -2255,15 +2274,15 @@ void AsyncCompileJob::PrepareRuntimeObjects() { void AsyncCompileJob::FinishCompile(bool is_after_cache_hit) { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"), "wasm.FinishAsyncCompile"); + if (stream_) { + stream_->NotifyNativeModuleCreated(native_module_); + } bool is_after_deserialization = !module_object_.is_null(); - auto compilation_state = Impl(native_module_->compilation_state()); if (!is_after_deserialization) { - if (stream_) { - stream_->NotifyNativeModuleCreated(native_module_); - } PrepareRuntimeObjects(); } + auto compilation_state = Impl(native_module_->compilation_state()); // Measure duration of baseline compilation or deserialization from cache. if (base::TimeTicks::IsHighResolution()) { base::TimeDelta duration = base::TimeTicks::Now() - start_time_; @@ -2281,9 +2300,7 @@ void AsyncCompileJob::FinishCompile(bool is_after_cache_hit) { !compilation_state->failed(), // success native_module_->turbofan_code_size(), // code_size_in_bytes native_module_->liftoff_bailout_count(), // liftoff_bailout_count - duration.InMicroseconds(), // wall_clock_duration_in_us - static_cast<int64_t>( // cpu_time_duration_in_us - native_module_->baseline_compilation_cpu_duration())}; + duration.InMicroseconds()}; // wall_clock_duration_in_us isolate_->metrics_recorder()->DelayMainThreadEvent(event, context_id_); } } @@ -2321,51 +2338,37 @@ void AsyncCompileJob::FinishCompile(bool is_after_cache_hit) { // We can only update the feature counts once the entire compile is done. compilation_state->PublishDetectedFeatures(isolate_); - // We might need to recompile the module for debugging, if the debugger was - // enabled while streaming compilation was running. Since handling this while - // compiling via streaming is tricky, we just tier down now, before publishing - // the module. - if (native_module_->IsTieredDown()) native_module_->RecompileForTiering(); + // We might need debug code for the module, if the debugger was enabled while + // streaming compilation was running. Since handling this while compiling via + // streaming is tricky, we just remove all code which may have been generated, + // and compile debug code lazily. + if (native_module_->IsInDebugState()) { + native_module_->RemoveCompiledCode( + NativeModule::RemoveFilter::kRemoveNonDebugCode); + } // Finally, log all generated code (it does not matter if this happens // repeatedly in case the script is shared). native_module_->LogWasmCodes(isolate_, module_object_->script()); - FinishModule(); + FinishSuccessfully(); } -void AsyncCompileJob::DecodeFailed(const WasmError& error) { - ErrorThrower thrower(isolate_, api_method_name_); - thrower.CompileFailed(error); +void AsyncCompileJob::Failed() { // {job} keeps the {this} pointer alive. - std::shared_ptr<AsyncCompileJob> job = + std::unique_ptr<AsyncCompileJob> job = GetWasmEngine()->RemoveCompileJob(this); - resolver_->OnCompilationFailed(thrower.Reify()); -} -void AsyncCompileJob::AsyncCompileFailed() { + // Revalidate the whole module to produce a deterministic error message. + constexpr bool kValidate = true; + ModuleResult result = DecodeWasmModule( + enabled_features_, wire_bytes_.module_bytes(), kValidate, kWasmOrigin); + CHECK(result.failed()); ErrorThrower thrower(isolate_, api_method_name_); - DCHECK_EQ(native_module_->module()->origin, kWasmOrigin); - ValidateSequentially(native_module_->module(), native_module_.get(), - isolate_->counters(), isolate_->allocator(), &thrower); - DCHECK(thrower.error()); - // {job} keeps the {this} pointer alive. - std::shared_ptr<AsyncCompileJob> job = - GetWasmEngine()->RemoveCompileJob(this); + thrower.CompileFailed(std::move(result).error()); resolver_->OnCompilationFailed(thrower.Reify()); } -void AsyncCompileJob::AsyncCompileSucceeded(Handle<WasmModuleObject> result) { - TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"), - "wasm.OnCompilationSucceeded"); - // We have to make sure that an "incumbent context" is available in case - // the module's start function calls out to Blink. - Local<v8::Context> backup_incumbent_context = - Utils::ToLocal(incumbent_context_); - v8::Context::BackupIncumbentScope incumbent(backup_incumbent_context); - resolver_->OnCompilationSucceeded(result); -} - class AsyncCompileJob::CompilationStateCallback : public CompilationEventCallback { public: @@ -2385,12 +2388,14 @@ class AsyncCompileJob::CompilationStateCallback // Install the native module in the cache, or reuse a conflicting one. // If we get a conflicting module, wait until we are back in the // main thread to update {job_->native_module_} to avoid a data race. - std::shared_ptr<NativeModule> native_module = job_->native_module_; - bool cache_hit = !GetWasmEngine()->UpdateNativeModuleCache( - false, &native_module, job_->isolate_); - DCHECK_EQ(cache_hit, native_module != job_->native_module_); - job_->DoSync<CompileFinished>(cache_hit ? std::move(native_module) - : nullptr); + std::shared_ptr<NativeModule> cached_native_module = + GetWasmEngine()->UpdateNativeModuleCache( + false, job_->native_module_, job_->isolate_); + if (cached_native_module == job_->native_module_) { + // There was no cached module. + cached_native_module = nullptr; + } + job_->DoSync<FinishCompilation>(std::move(cached_native_module)); } break; case CompilationEvent::kFinishedCompilationChunk: @@ -2403,16 +2408,11 @@ class AsyncCompileJob::CompilationStateCallback if (job_->DecrementAndCheckFinisherCount(kCompilation)) { // Don't update {job_->native_module_} to avoid data races with other // compilation threads. Use a copy of the shared pointer instead. - std::shared_ptr<NativeModule> native_module = job_->native_module_; - GetWasmEngine()->UpdateNativeModuleCache(true, &native_module, + GetWasmEngine()->UpdateNativeModuleCache(true, job_->native_module_, job_->isolate_); - job_->DoSync<CompileFailed>(); + job_->DoSync<Fail>(); } break; - case CompilationEvent::kFinishedRecompilation: - // This event can happen out of order, hence don't remember this in - // {last_event_}. - return; } #ifdef DEBUG last_event_ = event; @@ -2549,38 +2549,6 @@ void AsyncCompileJob::NextStep(Args&&... args) { step_.reset(new Step(std::forward<Args>(args)...)); } -WasmError ValidateLazilyCompiledFunctions(const WasmModule* module, - ModuleWireBytes wire_bytes, - WasmFeatures enabled_features) { - if (v8_flags.wasm_lazy_validation) return {}; - if (!MayCompriseLazyFunctions(module, enabled_features)) return {}; - - auto allocator = GetWasmEngine()->allocator(); - - // TODO(clemensb): Parallelize this. - const bool is_lazy_module = IsLazyModule(module); - for (const WasmFunction& function : module->declared_functions()) { - if (module->function_was_validated(function.func_index)) continue; - base::Vector<const uint8_t> code = wire_bytes.GetFunctionBytes(&function); - - CompileStrategy strategy = GetCompileStrategy( - module, enabled_features, function.func_index, is_lazy_module); - if (strategy != CompileStrategy::kLazy && - strategy != CompileStrategy::kLazyBaselineEagerTopTier) { - continue; - } - DecodeResult function_result = ValidateSingleFunction( - module, function.func_index, code, allocator, enabled_features); - if (function_result.failed()) { - WasmError error = std::move(function_result).error(); - return GetWasmErrorWithName(wire_bytes, &function, module, - std::move(error)); - } - module->set_function_validated(function.func_index); - } - return {}; -} - //========================================================================== // Step 1: (async) Decode the module. //========================================================================== @@ -2600,24 +2568,23 @@ class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"), "wasm.DecodeModule"); auto enabled_features = job->enabled_features_; - result = DecodeWasmModule( - enabled_features, job->wire_bytes_.start(), job->wire_bytes_.end(), - false, kWasmOrigin, counters_, metrics_recorder_, job->context_id(), - DecodingMethod::kAsync, GetWasmEngine()->allocator()); + result = + DecodeWasmModule(enabled_features, job->wire_bytes_.module_bytes(), + false, kWasmOrigin, counters_, metrics_recorder_, + job->context_id(), DecodingMethod::kAsync); // Validate lazy functions here if requested. - if (result.ok()) { + if (result.ok() && !v8_flags.wasm_lazy_validation) { const WasmModule* module = result.value().get(); - WasmError validation_error = ValidateLazilyCompiledFunctions( - module, job->wire_bytes_, job->enabled_features_); - if (validation_error.has_error()) { + if (WasmError validation_error = + ValidateFunctions(module, job->wire_bytes_.module_bytes(), + job->enabled_features_, kOnlyLazyFunctions)) result = ModuleResult{std::move(validation_error)}; - } } } if (result.failed()) { // Decoding failure; reject the promise and clean up. - job->DoSync<DecodeFail>(std::move(result).error()); + job->DoSync<Fail>(); } else { // Decode passed. std::shared_ptr<WasmModule> module = std::move(result).value(); @@ -2636,24 +2603,7 @@ class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep { }; //========================================================================== -// Step 1b: (sync) Fail decoding the module. -//========================================================================== -class AsyncCompileJob::DecodeFail : public CompileStep { - public: - explicit DecodeFail(WasmError error) : error_(std::move(error)) {} - - private: - WasmError error_; - - void RunInForeground(AsyncCompileJob* job) override { - TRACE_COMPILE("(1b) Decoding failed.\n"); - // {job_} is deleted in DecodeFailed, therefore the {return}. - return job->DecodeFailed(error_); - } -}; - -//========================================================================== -// Step 2 (sync): Create heap-allocated data and start compile. +// Step 2 (sync): Create heap-allocated data and start compilation. //========================================================================== class AsyncCompileJob::PrepareAndStartCompile : public CompileStep { public: @@ -2685,13 +2635,10 @@ class AsyncCompileJob::PrepareAndStartCompile : public CompileStep { // Note that we only need to validate lazily compiled functions, others // will be validated during eager compilation. DCHECK(start_compilation_); - if (ValidateLazilyCompiledFunctions( - module_.get(), ModuleWireBytes{job->native_module_->wire_bytes()}, - job->native_module_->enabled_features()) + if (!v8_flags.wasm_lazy_validation && + ValidateFunctions(*job->native_module_, kOnlyLazyFunctions) .has_error()) { - // TODO(clemensb): Use the error message instead of re-validation in - // {AsyncCompileFailed}. - job->AsyncCompileFailed(); + job->Failed(); return; } } @@ -2735,37 +2682,18 @@ class AsyncCompileJob::PrepareAndStartCompile : public CompileStep { }; //========================================================================== -// Step 3a (sync): Compilation failed. -//========================================================================== -class AsyncCompileJob::CompileFailed : public CompileStep { - private: - void RunInForeground(AsyncCompileJob* job) override { - TRACE_COMPILE("(3a) Compilation failed\n"); - DCHECK(job->native_module_->compilation_state()->failed()); - - // {job_} is deleted in AsyncCompileFailed, therefore the {return}. - return job->AsyncCompileFailed(); - } -}; - -//========================================================================== -// Step 3b (sync): Compilation finished. +// Step 3 (sync): Compilation finished. //========================================================================== -class AsyncCompileJob::CompileFinished : public CompileStep { +class AsyncCompileJob::FinishCompilation : public CompileStep { public: - explicit CompileFinished(std::shared_ptr<NativeModule> cached_native_module) + explicit FinishCompilation(std::shared_ptr<NativeModule> cached_native_module) : cached_native_module_(std::move(cached_native_module)) {} private: void RunInForeground(AsyncCompileJob* job) override { - TRACE_COMPILE("(3b) Compilation finished\n"); + TRACE_COMPILE("(3) Compilation finished\n"); if (cached_native_module_) { job->native_module_ = cached_native_module_; - } else { - DCHECK(!job->native_module_->compilation_state()->failed()); - // Sample the generated code size when baseline compilation finished. - job->native_module_->SampleCodeSize(job->isolate_->counters(), - NativeModule::kAfterBaseline); } // Then finalize and publish the generated module. job->FinishCompile(cached_native_module_ != nullptr); @@ -2774,80 +2702,44 @@ class AsyncCompileJob::CompileFinished : public CompileStep { std::shared_ptr<NativeModule> cached_native_module_; }; -void AsyncCompileJob::FinishModule() { +//========================================================================== +// Step 4 (sync): Decoding or compilation failed. +//========================================================================== +class AsyncCompileJob::Fail : public CompileStep { + private: + void RunInForeground(AsyncCompileJob* job) override { + TRACE_COMPILE("(4) Async compilation failed.\n"); + // {job_} is deleted in {Failed}, therefore the {return}. + return job->Failed(); + } +}; + +void AsyncCompileJob::FinishSuccessfully() { TRACE_COMPILE("(4) Finish module...\n"); - AsyncCompileSucceeded(module_object_); + { + TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"), + "wasm.OnCompilationSucceeded"); + // We have to make sure that an "incumbent context" is available in case + // the module's start function calls out to Blink. + Local<v8::Context> backup_incumbent_context = + Utils::ToLocal(incumbent_context_); + v8::Context::BackupIncumbentScope incumbent(backup_incumbent_context); + resolver_->OnCompilationSucceeded(module_object_); + } GetWasmEngine()->RemoveCompileJob(this); } -AsyncStreamingProcessor::AsyncStreamingProcessor( - AsyncCompileJob* job, std::shared_ptr<Counters> async_counters, - AccountingAllocator* allocator) +AsyncStreamingProcessor::AsyncStreamingProcessor(AsyncCompileJob* job) : decoder_(job->enabled_features_), job_(job), - compilation_unit_builder_(nullptr), - async_counters_(async_counters), - allocator_(allocator) {} - -AsyncStreamingProcessor::~AsyncStreamingProcessor() { - if (job_->native_module_ && job_->native_module_->wire_bytes().empty()) { - // Clean up the temporary cache entry. - GetWasmEngine()->StreamingCompilationFailed(prefix_hash_); - } -} - -void AsyncStreamingProcessor::FinishAsyncCompileJobWithError( - const WasmError& error, ErrorLocation error_location) { - DCHECK(error.has_error()); - // Make sure all background tasks stopped executing before we change the state - // of the AsyncCompileJob to DecodeFail. - job_->background_task_manager_.CancelAndWait(); - - // Record event metrics. - auto duration = base::TimeTicks::Now() - job_->start_time_; - job_->metrics_event_.success = false; - job_->metrics_event_.streamed = true; - job_->metrics_event_.module_size_in_bytes = job_->wire_bytes_.length(); - job_->metrics_event_.function_count = num_functions_; - job_->metrics_event_.wall_clock_duration_in_us = duration.InMicroseconds(); - job_->isolate_->metrics_recorder()->DelayMainThreadEvent(job_->metrics_event_, - job_->context_id_); - - // Check if there is already a CompiledModule, in which case we have to clean - // up the CompilationStateImpl as well. - if (job_->native_module_) { - CompilationStateImpl* impl = - Impl(job_->native_module_->compilation_state()); - - if (error_location == kErrorInFunction) { - impl->SetError(); - } - impl->CancelCompilation(CompilationStateImpl::kCancelUnconditionally); - if (error_location == kErrorInSection) { - job_->DoSync<AsyncCompileJob::DecodeFail, - AsyncCompileJob::kUseExistingForegroundTask>(error); - } - // Clear the {compilation_unit_builder_} if it exists. This is needed - // because there is a check in the destructor of the - // {CompilationUnitBuilder} that it is empty. - if (compilation_unit_builder_) compilation_unit_builder_->Clear(); - } else { - job_->DoSync<AsyncCompileJob::DecodeFail>(error); - } -} + compilation_unit_builder_(nullptr) {} // Process the module header. bool AsyncStreamingProcessor::ProcessModuleHeader( base::Vector<const uint8_t> bytes, uint32_t offset) { TRACE_STREAMING("Process module header...\n"); - decoder_.StartDecoding(job_->isolate()->counters(), - job_->isolate()->metrics_recorder(), - job_->context_id(), GetWasmEngine()->allocator()); decoder_.DecodeModuleHeader(bytes, offset); - if (!decoder_.ok()) { - FinishAsyncCompileJobWithError(decoder_.FinishDecoding().error()); - return false; - } + if (!decoder_.ok()) return false; prefix_hash_ = GetWireBytesHash(bytes); return true; } @@ -2870,10 +2762,7 @@ bool AsyncStreamingProcessor::ProcessSection(SectionCode section_code, if (section_code == SectionCode::kUnknownSectionCode) { size_t bytes_consumed = ModuleDecoder::IdentifyUnknownSection( &decoder_, bytes, offset, §ion_code); - if (!decoder_.ok()) { - FinishAsyncCompileJobWithError(decoder_.FinishDecoding().error()); - return false; - } + if (!decoder_.ok()) return false; if (section_code == SectionCode::kUnknownSectionCode) { // Skip unknown sections that we do not know how to handle. return true; @@ -2883,11 +2772,7 @@ bool AsyncStreamingProcessor::ProcessSection(SectionCode section_code, bytes = bytes.SubVector(bytes_consumed, bytes.size()); } decoder_.DecodeSection(section_code, bytes, offset); - if (!decoder_.ok()) { - FinishAsyncCompileJobWithError(decoder_.FinishDecoding().error()); - return false; - } - return true; + return decoder_.ok(); } // Start the code section. @@ -2903,7 +2788,6 @@ bool AsyncStreamingProcessor::ProcessCodeSectionHeader( static_cast<uint32_t>(code_section_length)); if (!decoder_.CheckFunctionsCount(static_cast<uint32_t>(num_functions), functions_mismatch_error_offset)) { - FinishAsyncCompileJobWithError(decoder_.FinishDecoding().error()); return false; } @@ -2944,7 +2828,7 @@ bool AsyncStreamingProcessor::ProcessCodeSectionHeader( } // Process a function body. -void AsyncStreamingProcessor::ProcessFunctionBody( +bool AsyncStreamingProcessor::ProcessFunctionBody( base::Vector<const uint8_t> bytes, uint32_t offset) { TRACE_STREAMING("Process function body %d ...\n", num_functions_); uint32_t func_index = @@ -2957,15 +2841,7 @@ void AsyncStreamingProcessor::ProcessFunctionBody( if (prefix_cache_hit_) { // Don't compile yet if we might have a cache hit. - return; - } - - // Bail out after the {prefix_cache_hit_}, because if {prefix_cache_hit_} is - // true, the native module does not exist. - if (job_->native_module_->compilation_state()->failed()) { - // There has already been an error, there is no need to do any more - // validation or compiling. - return; + return true; } const WasmModule* module = decoder_.module(); @@ -2979,20 +2855,24 @@ void AsyncStreamingProcessor::ProcessFunctionBody( (strategy == CompileStrategy::kLazy || strategy == CompileStrategy::kLazyBaselineEagerTopTier); if (validate_lazily_compiled_function) { - // The native module does not own the wire bytes until {SetWireBytes} is - // called in {OnFinishedStream}. Validation must use {bytes} parameter. - DecodeResult result = ValidateSingleFunction(module, func_index, bytes, - allocator_, enabled_features); - - if (result.failed()) { - FinishAsyncCompileJobWithError(result.error(), kErrorInFunction); - return; + // {bytes} is part of a section buffer owned by the streaming decoder. The + // streaming decoder is held alive by the {AsyncCompileJob}, so we can just + // use the {bytes} vector as long as the {AsyncCompileJob} is still running. + if (!validate_functions_job_handle_) { + validate_functions_job_data_.Initialize(module->num_declared_functions); + validate_functions_job_handle_ = V8::GetCurrentPlatform()->CreateJob( + TaskPriority::kUserVisible, + std::make_unique<ValidateFunctionsStreamingJob>( + module, enabled_features, &validate_functions_job_data_)); } + validate_functions_job_data_.AddUnit(func_index, bytes, + validate_functions_job_handle_.get()); } auto* compilation_state = Impl(job_->native_module_->compilation_state()); compilation_state->AddCompilationUnit(compilation_unit_builder_.get(), func_index); + return true; } void AsyncStreamingProcessor::CommitCompilationUnits() { @@ -3007,21 +2887,27 @@ void AsyncStreamingProcessor::OnFinishedChunk() { // Finish the processing of the stream. void AsyncStreamingProcessor::OnFinishedStream( - base::OwnedVector<uint8_t> bytes) { + base::OwnedVector<const uint8_t> bytes, bool after_error) { TRACE_STREAMING("Finish stream...\n"); - DCHECK_EQ(NativeModuleCache::PrefixHash(bytes.as_vector()), prefix_hash_); - ModuleResult result = decoder_.FinishDecoding(); - if (result.failed()) { - FinishAsyncCompileJobWithError(result.error()); - return; + ModuleResult module_result = decoder_.FinishDecoding(); + if (module_result.failed()) after_error = true; + + if (validate_functions_job_handle_) { + // Wait for background validation to finish, then check if a validation + // error was found. + // TODO(13447): Do not block here; register validation as another finisher + // instead. + validate_functions_job_handle_->Join(); + validate_functions_job_handle_.reset(); + if (validate_functions_job_data_.found_error) after_error = true; } job_->wire_bytes_ = ModuleWireBytes(bytes.as_vector()); - job_->bytes_copy_ = bytes.ReleaseData(); + job_->bytes_copy_ = std::move(bytes); // Record event metrics. auto duration = base::TimeTicks::Now() - job_->start_time_; - job_->metrics_event_.success = true; + job_->metrics_event_.success = !after_error; job_->metrics_event_.streamed = true; job_->metrics_event_.module_size_in_bytes = job_->wire_bytes_.length(); job_->metrics_event_.function_count = num_functions_; @@ -3029,15 +2915,39 @@ void AsyncStreamingProcessor::OnFinishedStream( job_->isolate_->metrics_recorder()->DelayMainThreadEvent(job_->metrics_event_, job_->context_id_); + if (after_error) { + if (job_->native_module_ && job_->native_module_->wire_bytes().empty()) { + // Clean up the temporary cache entry. + GetWasmEngine()->StreamingCompilationFailed(prefix_hash_); + } + // Calling {Failed} will invalidate the {AsyncCompileJob} and delete {this}. + job_->Failed(); + return; + } + + std::shared_ptr<WasmModule> module = std::move(module_result).value(); + + // At this point we identified the module as valid (except maybe for function + // bodies, if lazy validation is enabled). + // This DCHECK could be considered slow, but it only happens once per async + // module compilation, and we only re-decode the module structure, without + // validation function bodies. Overall this does not add a lot of overhead. + DCHECK(DecodeWasmModule(job_->enabled_features_, + job_->bytes_copy_.as_vector(), + /* validate functions */ false, kWasmOrigin) + .ok()); + + DCHECK_EQ(NativeModuleCache::PrefixHash(job_->wire_bytes_.module_bytes()), + prefix_hash_); if (prefix_cache_hit_) { // Restart as an asynchronous, non-streaming compilation. Most likely // {PrepareAndStartCompile} will get the native module from the cache. const bool include_liftoff = v8_flags.liftoff; size_t code_size_estimate = wasm::WasmCodeManager::EstimateNativeModuleCodeSize( - result.value().get(), include_liftoff, job_->dynamic_tiering_); + module.get(), include_liftoff, job_->dynamic_tiering_); job_->DoSync<AsyncCompileJob::PrepareAndStartCompile>( - std::move(result).value(), true, code_size_estimate); + std::move(module), true, code_size_estimate); return; } @@ -3047,10 +2957,15 @@ void AsyncStreamingProcessor::OnFinishedStream( HandleScope scope(job_->isolate_); SaveAndSwitchContext saved_context(job_->isolate_, *job_->native_context_); - // Record the size of the wire bytes. In synchronous and asynchronous - // (non-streaming) compilation, this happens in {DecodeWasmModule}. - auto* histogram = job_->isolate_->counters()->wasm_wasm_module_size_bytes(); - histogram->AddSample(job_->wire_bytes_.module_bytes().length()); + // Record the size of the wire bytes and the number of functions. In + // synchronous and asynchronous (non-streaming) compilation, this happens in + // {DecodeWasmModule}. + auto* module_size_histogram = + job_->isolate_->counters()->wasm_wasm_module_size_bytes(); + module_size_histogram->AddSample(job_->wire_bytes_.module_bytes().length()); + auto* num_functions_histogram = + job_->isolate_->counters()->wasm_functions_per_wasm_module(); + num_functions_histogram->AddSample(static_cast<int>(num_functions_)); const bool has_code_section = job_->native_module_ != nullptr; bool cache_hit = false; @@ -3059,11 +2974,10 @@ void AsyncStreamingProcessor::OnFinishedStream( // native module now (would otherwise happen in {PrepareAndStartCompile} or // {ProcessCodeSectionHeader}). constexpr size_t kCodeSizeEstimate = 0; - cache_hit = job_->GetOrCreateNativeModule(std::move(result).value(), - kCodeSizeEstimate); + cache_hit = + job_->GetOrCreateNativeModule(std::move(module), kCodeSizeEstimate); } else { - job_->native_module_->SetWireBytes( - {std::move(job_->bytes_copy_), job_->wire_bytes_.length()}); + job_->native_module_->SetWireBytes(std::move(job_->bytes_copy_)); } const bool needs_finish = job_->DecrementAndCheckFinisherCount(AsyncCompileJob::kStreamingDecoder); @@ -3071,25 +2985,32 @@ void AsyncStreamingProcessor::OnFinishedStream( if (needs_finish) { const bool failed = job_->native_module_->compilation_state()->failed(); if (!cache_hit) { - cache_hit = !GetWasmEngine()->UpdateNativeModuleCache( - failed, &job_->native_module_, job_->isolate_); + auto* prev_native_module = job_->native_module_.get(); + job_->native_module_ = GetWasmEngine()->UpdateNativeModuleCache( + failed, std::move(job_->native_module_), job_->isolate_); + cache_hit = prev_native_module != job_->native_module_.get(); } + // We finally call {Failed} or {FinishCompile}, which will invalidate the + // {AsyncCompileJob} and delete {this}. if (failed) { - job_->AsyncCompileFailed(); + job_->Failed(); } else { job_->FinishCompile(cache_hit); } } } -// Report an error detected in the StreamingDecoder. -void AsyncStreamingProcessor::OnError(const WasmError& error) { - TRACE_STREAMING("Stream error...\n"); - FinishAsyncCompileJobWithError(error); -} - void AsyncStreamingProcessor::OnAbort() { TRACE_STREAMING("Abort stream...\n"); + if (validate_functions_job_handle_) { + validate_functions_job_handle_->Cancel(); + validate_functions_job_handle_.reset(); + } + if (job_->native_module_ && job_->native_module_->wire_bytes().empty()) { + // Clean up the temporary cache entry. + GetWasmEngine()->StreamingCompilationFailed(prefix_hash_); + } + // {Abort} invalidates the {AsyncCompileJob}, which in turn deletes {this}. job_->Abort(); } @@ -3117,6 +3038,7 @@ bool AsyncStreamingProcessor::Deserialize( job_->isolate_->global_handles()->Create(*result.ToHandleChecked()); job_->native_module_ = job_->module_object_->shared_native_module(); job_->wire_bytes_ = ModuleWireBytes(job_->native_module_->wire_bytes()); + // Calling {FinishCompile} deletes the {AsyncCompileJob} and {this}. job_->FinishCompile(false); return true; } @@ -3131,12 +3053,22 @@ CompilationStateImpl::CompilationStateImpl( dynamic_tiering_(dynamic_tiering) {} void CompilationStateImpl::InitCompileJob() { - DCHECK_NULL(compile_job_); + DCHECK_NULL(js_to_wasm_wrapper_job_); + DCHECK_NULL(baseline_compile_job_); + DCHECK_NULL(top_tier_compile_job_); // Create the job, but don't spawn workers yet. This will happen on // {NotifyConcurrencyIncrease}. - compile_job_ = V8::GetCurrentPlatform()->CreateJob( - TaskPriority::kUserVisible, std::make_unique<BackgroundCompileJob>( - native_module_weak_, async_counters_)); + js_to_wasm_wrapper_job_ = V8::GetCurrentPlatform()->CreateJob( + TaskPriority::kUserBlocking, + std::make_unique<AsyncCompileJSToWasmWrapperJob>(native_module_weak_)); + baseline_compile_job_ = V8::GetCurrentPlatform()->CreateJob( + TaskPriority::kUserVisible, + std::make_unique<BackgroundCompileJob>( + native_module_weak_, async_counters_, CompilationTier::kBaseline)); + top_tier_compile_job_ = V8::GetCurrentPlatform()->CreateJob( + TaskPriority::kUserVisible, + std::make_unique<BackgroundCompileJob>( + native_module_weak_, async_counters_, CompilationTier::kTopTier)); } void CompilationStateImpl::CancelCompilation( @@ -3256,7 +3188,8 @@ void CompilationStateImpl::InitializeCompilationProgress( // Compute the default compilation progress for all functions, and set it. const ExecutionTierPair default_tiers = GetDefaultTiersPerModule( - native_module_, dynamic_tiering_, IsLazyModule(module)); + native_module_, dynamic_tiering_, native_module_->IsInDebugState(), + IsLazyModule(module)); const uint8_t default_progress = RequiredBaselineTierField::encode(default_tiers.baseline_tier) | RequiredTopTierField::encode(default_tiers.top_tier) | @@ -3289,7 +3222,7 @@ void CompilationStateImpl::InitializeCompilationProgress( TriggerCallbacks(); } -uint8_t CompilationStateImpl::AddCompilationUnitInternal( +void CompilationStateImpl::AddCompilationUnitInternal( CompilationUnitBuilder* builder, int function_index, uint8_t function_progress) { ExecutionTier required_baseline_tier = @@ -3300,26 +3233,6 @@ uint8_t CompilationStateImpl::AddCompilationUnitInternal( ExecutionTier reached_tier = CompilationStateImpl::ReachedTierField::decode(function_progress); - if (v8_flags.experimental_wasm_gc && !v8_flags.wasm_lazy_compilation) { - // The Turbofan optimizations we enable for WasmGC code can (for now) - // take a very long time, so skip Turbofan compilation for super-large - // functions. - // Besides, module serialization currently requires that all functions - // have been TF-compiled. By enabling this limit only for WasmGC, we - // make sure that non-experimental modules can be serialize as usual. - // TODO(jkummerow): This is a stop-gap solution to avoid excessive - // compile times. We would like to replace this hard threshold with - // a better solution (TBD) eventually. - constexpr uint32_t kMaxWasmFunctionSizeForTurbofan = 500 * KB; - uint32_t size = builder->module()->functions[function_index].code.length(); - if (size > kMaxWasmFunctionSizeForTurbofan) { - required_baseline_tier = ExecutionTier::kLiftoff; - if (required_top_tier == ExecutionTier::kTurbofan) { - required_top_tier = ExecutionTier::kLiftoff; - } - } - } - if (reached_tier < required_baseline_tier) { builder->AddBaselineUnit(function_index, required_baseline_tier); } @@ -3327,28 +3240,18 @@ uint8_t CompilationStateImpl::AddCompilationUnitInternal( required_baseline_tier != required_top_tier) { builder->AddTopTierUnit(function_index, required_top_tier); } - return CompilationStateImpl::RequiredBaselineTierField::encode( - required_baseline_tier) | - CompilationStateImpl::RequiredTopTierField::encode(required_top_tier) | - CompilationStateImpl::ReachedTierField::encode(reached_tier); } void CompilationStateImpl::InitializeCompilationUnits( std::unique_ptr<CompilationUnitBuilder> builder) { int offset = native_module_->module()->num_imported_functions; - if (native_module_->IsTieredDown()) { - for (size_t i = 0; i < compilation_progress_.size(); ++i) { - int func_index = offset + static_cast<int>(i); - builder->AddDebugUnit(func_index); - } - } else { + { base::MutexGuard guard(&callbacks_mutex_); - for (size_t i = 0; i < compilation_progress_.size(); ++i) { + for (size_t i = 0, e = compilation_progress_.size(); i < e; ++i) { uint8_t function_progress = compilation_progress_[i]; int func_index = offset + static_cast<int>(i); - compilation_progress_[i] = AddCompilationUnitInternal( - builder.get(), func_index, function_progress); + AddCompilationUnitInternal(builder.get(), func_index, function_progress); } } builder->Commit(); @@ -3356,10 +3259,6 @@ void CompilationStateImpl::InitializeCompilationUnits( void CompilationStateImpl::AddCompilationUnit(CompilationUnitBuilder* builder, int func_index) { - if (native_module_->IsTieredDown()) { - builder->AddDebugUnit(func_index); - return; - } int offset = native_module_->module()->num_imported_functions; int progress_index = func_index - offset; uint8_t function_progress; @@ -3373,14 +3272,7 @@ void CompilationStateImpl::AddCompilationUnit(CompilationUnitBuilder* builder, base::MutexGuard guard(&callbacks_mutex_); function_progress = compilation_progress_[progress_index]; } - uint8_t updated_function_progress = - AddCompilationUnitInternal(builder, func_index, function_progress); - if (updated_function_progress != function_progress) { - // This should happen very rarely (only for super-large functions), so we're - // not worried about overhead. - base::MutexGuard guard(&callbacks_mutex_); - compilation_progress_[progress_index] = updated_function_progress; - } + AddCompilationUnitInternal(builder, func_index, function_progress); } void CompilationStateImpl::InitializeCompilationProgressAfterDeserialization( @@ -3419,8 +3311,6 @@ void CompilationStateImpl::InitializeCompilationProgressAfterDeserialization( RequiredTopTierField::encode(ExecutionTier::kNone) | ReachedTierField::encode(ExecutionTier::kNone); for (auto func_index : lazy_functions) { - native_module_->UseLazyStub(func_index); - compilation_progress_[declared_function_index(module, func_index)] = kProgressForLazyFunctions; } @@ -3428,7 +3318,8 @@ void CompilationStateImpl::InitializeCompilationProgressAfterDeserialization( // Update compilation state for eagerly compiled functions. constexpr bool kNotLazy = false; ExecutionTierPair default_tiers = - GetDefaultTiersPerModule(native_module_, dynamic_tiering_, kNotLazy); + GetDefaultTiersPerModule(native_module_, dynamic_tiering_, + native_module_->IsInDebugState(), kNotLazy); uint8_t progress_for_eager_functions = RequiredBaselineTierField::encode(default_tiers.baseline_tier) | RequiredTopTierField::encode(default_tiers.top_tier) | @@ -3448,7 +3339,7 @@ void CompilationStateImpl::InitializeCompilationProgressAfterDeserialization( // that as finished already. Baseline compilation is done if we do not have // any Liftoff functions to compile. finished_events_.Add(CompilationEvent::kFinishedExportWrappers); - if (eager_functions.empty()) { + if (eager_functions.empty() || v8_flags.wasm_lazy_compilation) { finished_events_.Add(CompilationEvent::kFinishedBaselineCompilation); } } @@ -3457,87 +3348,6 @@ void CompilationStateImpl::InitializeCompilationProgressAfterDeserialization( WaitForCompilationEvent(CompilationEvent::kFinishedBaselineCompilation); } -void CompilationStateImpl::InitializeRecompilation( - TieringState new_tiering_state, - std::unique_ptr<CompilationEventCallback> recompilation_finished_callback) { - DCHECK(!failed()); - - // Hold the mutex as long as possible, to synchronize between multiple - // recompilations that are triggered at the same time (e.g. when the profiler - // is disabled). - base::Optional<base::MutexGuard> guard(&callbacks_mutex_); - - // As long as there are outstanding recompilation functions, take part in - // compilation. This is to avoid recompiling for the same tier or for - // different tiers concurrently. Note that the compilation unit queues can run - // empty before {outstanding_recompilation_functions_} drops to zero. In this - // case, we do not wait for the last running compilation threads to finish - // their units, but just start our own recompilation already. - while (outstanding_recompilation_functions_ > 0 && - compilation_unit_queues_.GetTotalSize() > 0) { - guard.reset(); - constexpr JobDelegate* kNoDelegate = nullptr; - ExecuteCompilationUnits(native_module_weak_, async_counters_.get(), - kNoDelegate, kBaselineOrTopTier); - guard.emplace(&callbacks_mutex_); - } - - // Information about compilation progress is shared between this class and the - // NativeModule. Before updating information here, consult the NativeModule to - // find all functions that need recompilation. - // Since the current tiering state is updated on the NativeModule before - // triggering recompilation, it's OK if the information is slightly outdated. - // If we compile functions twice, the NativeModule will ignore all redundant - // code (or code compiled for the wrong tier). - std::vector<int> recompile_function_indexes = - native_module_->FindFunctionsToRecompile(new_tiering_state); - - callbacks_.emplace_back(std::move(recompilation_finished_callback)); - tiering_state_ = new_tiering_state; - - // If compilation progress is not initialized yet, then compilation didn't - // start yet, and new code will be kept tiered-down from the start. For - // streaming compilation, there is a special path to tier down later, when - // the module is complete. In any case, we don't need to recompile here. - base::Optional<CompilationUnitBuilder> builder; - if (compilation_progress_.size() > 0) { - builder.emplace(native_module_); - const WasmModule* module = native_module_->module(); - DCHECK_EQ(module->num_declared_functions, compilation_progress_.size()); - DCHECK_GE(module->num_declared_functions, - recompile_function_indexes.size()); - outstanding_recompilation_functions_ = - static_cast<int>(recompile_function_indexes.size()); - // Restart recompilation if another recompilation is already happening. - for (auto& progress : compilation_progress_) { - progress = MissingRecompilationField::update(progress, false); - } - auto new_tier = new_tiering_state == kTieredDown ? ExecutionTier::kLiftoff - : ExecutionTier::kTurbofan; - int imported = module->num_imported_functions; - // Generate necessary compilation units on the fly. - for (int function_index : recompile_function_indexes) { - DCHECK_LE(imported, function_index); - int slot_index = function_index - imported; - auto& progress = compilation_progress_[slot_index]; - progress = MissingRecompilationField::update(progress, true); - builder->AddRecompilationUnit(function_index, new_tier); - } - } - - // Trigger callback if module needs no recompilation. - if (outstanding_recompilation_functions_ == 0) { - TriggerCallbacks(base::EnumSet<CompilationEvent>( - {CompilationEvent::kFinishedRecompilation})); - } - - if (builder.has_value()) { - // Avoid holding lock while scheduling a compile job. - guard.reset(); - builder->Commit(); - } -} - void CompilationStateImpl::AddCallback( std::unique_ptr<CompilationEventCallback> callback) { base::MutexGuard callbacks_guard(&callbacks_mutex_); @@ -3571,13 +3381,22 @@ void CompilationStateImpl::CommitCompilationUnits( // are available to other threads doing an acquire load. outstanding_js_to_wasm_wrappers_.store(js_to_wasm_wrapper_units.size(), std::memory_order_release); + DCHECK(js_to_wasm_wrapper_job_->IsValid()); + js_to_wasm_wrapper_job_->NotifyConcurrencyIncrease(); } if (!baseline_units.empty() || !top_tier_units.empty()) { compilation_unit_queues_.AddUnits(baseline_units, top_tier_units, native_module_->module()); } ResetPKUPermissionsForThreadSpawning pku_reset_scope; - compile_job_->NotifyConcurrencyIncrease(); + if (!baseline_units.empty()) { + DCHECK(baseline_compile_job_->IsValid()); + baseline_compile_job_->NotifyConcurrencyIncrease(); + } + if (!top_tier_units.empty()) { + DCHECK(top_tier_compile_job_->IsValid()); + top_tier_compile_job_->NotifyConcurrencyIncrease(); + } } void CompilationStateImpl::CommitTopTierCompilationUnit( @@ -3592,7 +3411,7 @@ void CompilationStateImpl::AddTopTierPriorityCompilationUnit( // {NotifyConcurrencyIncrease} can spawn new threads which could inherit PKU // permissions (which would be a security issue). DCHECK(!CodeSpaceWriteScope::IsInScope()); - compile_job_->NotifyConcurrencyIncrease(); + top_tier_compile_job_->NotifyConcurrencyIncrease(); } std::shared_ptr<JSToWasmWrapperCompilationUnit> @@ -3626,12 +3445,18 @@ void CompilationStateImpl::FinalizeJSToWasmWrappers(Isolate* isolate, CodePageCollectionMemoryModificationScope modification_scope(isolate->heap()); for (auto& unit : js_to_wasm_wrapper_units_) { DCHECK_EQ(isolate, unit->isolate()); - Handle<CodeT> code = unit->Finalize(); + // Note: The code is either the compiled signature-specific wrapper or the + // generic wrapper built-in. + Handle<Code> code = unit->Finalize(); uint32_t index = GetExportWrapperIndex(unit->canonical_sig_index(), unit->is_import()); isolate->heap()->js_to_wasm_wrappers().Set(index, MaybeObject::FromObject(*code)); - RecordStats(*code, isolate->counters()); + if (!code->is_builtin()) { + // Do not increase code stats for non-jitted wrappers. + RecordStats(*code, isolate->counters()); + isolate->counters()->wasm_compiled_export_wrapper()->Increment(1); + } } } @@ -3642,8 +3467,8 @@ CompilationUnitQueues::Queue* CompilationStateImpl::GetQueueForCompileTask( base::Optional<WasmCompilationUnit> CompilationStateImpl::GetNextCompilationUnit( - CompilationUnitQueues::Queue* queue, CompileBaselineOnly baseline_only) { - return compilation_unit_queues_.GetNextUnit(queue, baseline_only); + CompilationUnitQueues::Queue* queue, CompilationTier tier) { + return compilation_unit_queues_.GetNextUnit(queue, tier); } void CompilationStateImpl::OnFinishedUnits( @@ -3662,8 +3487,6 @@ void CompilationStateImpl::OnFinishedUnits( DCHECK_EQ(compilation_progress_.size(), native_module_->module()->num_declared_functions); - base::EnumSet<CompilationEvent> triggered_events; - for (size_t i = 0; i < code_vector.size(); i++) { WasmCode* code = code_vector[i]; DCHECK_NOT_NULL(code); @@ -3699,25 +3522,6 @@ void CompilationStateImpl::OnFinishedUnits( bytes_since_last_chunk_ += code->instructions().size(); } - if (V8_UNLIKELY(MissingRecompilationField::decode(function_progress))) { - DCHECK_LT(0, outstanding_recompilation_functions_); - // If tiering up, accept any TurboFan code. For tiering down, look at - // the {for_debugging} flag. The tier can be Liftoff or TurboFan and is - // irrelevant here. In particular, we want to ignore any outstanding - // non-debugging units. - bool matches = tiering_state_ == kTieredDown - ? code->for_debugging() - : code->tier() == ExecutionTier::kTurbofan; - if (matches) { - outstanding_recompilation_functions_--; - compilation_progress_[slot_index] = MissingRecompilationField::update( - compilation_progress_[slot_index], false); - if (outstanding_recompilation_functions_ == 0) { - triggered_events.Add(CompilationEvent::kFinishedRecompilation); - } - } - } - // Update function's compilation progress. if (code->tier() > reached_tier) { compilation_progress_[slot_index] = ReachedTierField::update( @@ -3727,7 +3531,7 @@ void CompilationStateImpl::OnFinishedUnits( } } - TriggerCallbacks(triggered_events); + TriggerCallbacks(); } void CompilationStateImpl::OnFinishedJSToWasmWrapperUnits(int num) { @@ -3738,10 +3542,10 @@ void CompilationStateImpl::OnFinishedJSToWasmWrapperUnits(int num) { TriggerCallbacks(); } -void CompilationStateImpl::TriggerCallbacks( - base::EnumSet<CompilationEvent> triggered_events) { +void CompilationStateImpl::TriggerCallbacks() { DCHECK(!callbacks_mutex_.TryLock()); + base::EnumSet<CompilationEvent> triggered_events; if (outstanding_export_wrappers_ == 0) { triggered_events.Add(CompilationEvent::kFinishedExportWrappers); if (outstanding_baseline_units_ == 0) { @@ -3767,11 +3571,9 @@ void CompilationStateImpl::TriggerCallbacks( // Don't trigger past events again. triggered_events -= finished_events_; - // Recompilation can happen multiple times, thus do not store this. There can - // also be multiple compilation chunks. - finished_events_ |= triggered_events - - CompilationEvent::kFinishedRecompilation - - CompilationEvent::kFinishedCompilationChunk; + // There can be multiple compilation chunks, thus do not store this. + finished_events_ |= + triggered_events - CompilationEvent::kFinishedCompilationChunk; for (auto event : {std::make_pair(CompilationEvent::kFailedCompilation, @@ -3781,9 +3583,7 @@ void CompilationStateImpl::TriggerCallbacks( std::make_pair(CompilationEvent::kFinishedBaselineCompilation, "wasm.BaselineFinished"), std::make_pair(CompilationEvent::kFinishedCompilationChunk, - "wasm.CompilationChunkFinished"), - std::make_pair(CompilationEvent::kFinishedRecompilation, - "wasm.RecompilationFinished")}) { + "wasm.CompilationChunkFinished")}) { if (!triggered_events.contains(event.first)) continue; DCHECK_NE(compilation_id_, kInvalidCompilationID); TRACE_EVENT1("v8.wasm", event.second, "id", compilation_id_); @@ -3792,8 +3592,7 @@ void CompilationStateImpl::TriggerCallbacks( } } - if (outstanding_baseline_units_ == 0 && outstanding_export_wrappers_ == 0 && - outstanding_recompilation_functions_ == 0) { + if (outstanding_baseline_units_ == 0 && outstanding_export_wrappers_ == 0) { auto new_end = std::remove_if( callbacks_.begin(), callbacks_.end(), [](const auto& callback) { return callback->release_after_final_event(); @@ -3833,7 +3632,7 @@ void CompilationStateImpl::PublishCompilationResults( native_module_->module() ->isorecursive_canonical_type_ids[function.sig_index]; WasmImportWrapperCache::CacheKey key( - compiler::kDefaultImportCallKind, canonical_type_index, + kDefaultImportCallKind, canonical_type_index, static_cast<int>(function.sig->parameter_count()), kNoSuspend); // If two imported functions have the same key, only one of them should // have been added as a compilation unit. So it is always the first time @@ -3889,11 +3688,13 @@ void CompilationStateImpl::SchedulePublishCompilationResults( } } -size_t CompilationStateImpl::NumOutstandingCompilations() const { - size_t outstanding_wrappers = - outstanding_js_to_wasm_wrappers_.load(std::memory_order_relaxed); - size_t outstanding_functions = compilation_unit_queues_.GetTotalSize(); - return outstanding_wrappers + outstanding_functions; +size_t CompilationStateImpl::NumOutstandingExportWrappers() const { + return outstanding_js_to_wasm_wrappers_.load(std::memory_order_relaxed); +} + +size_t CompilationStateImpl::NumOutstandingCompilations( + CompilationTier tier) const { + return compilation_unit_queues_.GetSizeForTier(tier); } void CompilationStateImpl::SetError() { @@ -3909,61 +3710,62 @@ void CompilationStateImpl::SetError() { void CompilationStateImpl::WaitForCompilationEvent( CompilationEvent expect_event) { - class WaitForCompilationEventCallback : public CompilationEventCallback { - public: - WaitForCompilationEventCallback(std::shared_ptr<base::Semaphore> semaphore, - std::shared_ptr<std::atomic<bool>> done, - base::EnumSet<CompilationEvent> events) - : semaphore_(std::move(semaphore)), - done_(std::move(done)), - events_(events) {} - - void call(CompilationEvent event) override { - if (!events_.contains(event)) return; - done_->store(true, std::memory_order_relaxed); - semaphore_->Signal(); - } - - private: - std::shared_ptr<base::Semaphore> semaphore_; - std::shared_ptr<std::atomic<bool>> done_; - base::EnumSet<CompilationEvent> events_; - }; - - auto semaphore = std::make_shared<base::Semaphore>(0); - auto done = std::make_shared<std::atomic<bool>>(false); + switch (expect_event) { + case CompilationEvent::kFinishedExportWrappers: + break; + case CompilationEvent::kFinishedBaselineCompilation: + if (baseline_compile_job_->IsValid()) baseline_compile_job_->Join(); + break; + default: + // Waiting on other CompilationEvent doesn't make sense. + UNREACHABLE(); + } + if (js_to_wasm_wrapper_job_->IsValid()) js_to_wasm_wrapper_job_->Join(); +#ifdef DEBUG base::EnumSet<CompilationEvent> events{expect_event, CompilationEvent::kFailedCompilation}; - { - base::MutexGuard callbacks_guard(&callbacks_mutex_); - if (finished_events_.contains_any(events)) return; - callbacks_.emplace_back(std::make_unique<WaitForCompilationEventCallback>( - semaphore, done, events)); - } - - class WaitForEventDelegate final : public JobDelegate { - public: - explicit WaitForEventDelegate(std::shared_ptr<std::atomic<bool>> done) - : done_(std::move(done)) {} + base::MutexGuard guard(&callbacks_mutex_); + DCHECK(finished_events_.contains_any(events)); +#endif +} - bool ShouldYield() override { - return done_->load(std::memory_order_relaxed); +void CompilationStateImpl::TierUpAllFunctions() { + const WasmModule* module = native_module_->module(); + uint32_t num_wasm_functions = module->num_declared_functions; + WasmCodeRefScope code_ref_scope; + CompilationUnitBuilder builder(native_module_); + for (uint32_t i = 0; i < num_wasm_functions; ++i) { + int func_index = module->num_imported_functions + i; + WasmCode* code = native_module_->GetCode(func_index); + if (!code || !code->is_turbofan()) { + builder.AddTopTierUnit(func_index, ExecutionTier::kTurbofan); } + } + builder.Commit(); + // Join the compilation, until no compilation units are left anymore. + class DummyDelegate final : public JobDelegate { + bool ShouldYield() override { return false; } bool IsJoiningThread() const override { return true; } - void NotifyConcurrencyIncrease() override { UNIMPLEMENTED(); } - uint8_t GetTaskId() override { return kMainTaskId; } - - private: - std::shared_ptr<std::atomic<bool>> done_; }; - WaitForEventDelegate delegate{done}; + DummyDelegate delegate; ExecuteCompilationUnits(native_module_weak_, async_counters_.get(), &delegate, - kBaselineOnly); - semaphore->Wait(); + CompilationTier::kTopTier); + + // We cannot wait for other compilation threads to finish, so we explicitly + // compile all functions which are not yet available as TurboFan code. + for (uint32_t i = 0; i < num_wasm_functions; ++i) { + uint32_t func_index = module->num_imported_functions + i; + WasmCode* code = native_module_->GetCode(func_index); + if (!code || !code->is_turbofan()) { + wasm::GetWasmEngine()->CompileFunction(async_counters_.get(), + native_module_, func_index, + wasm::ExecutionTier::kTurbofan); + } + } } namespace { @@ -4028,7 +3830,7 @@ void CompileJsToWasmWrappers(Isolate* isolate, const WasmModule* module) { module->isorecursive_canonical_type_ids[function.sig_index]; int wrapper_index = GetExportWrapperIndex(canonical_type_index, function.imported); - auto existing_wrapper = + MaybeObject existing_wrapper = isolate->heap()->js_to_wasm_wrappers().Get(wrapper_index); if (existing_wrapper.IsStrongOrWeak() && !existing_wrapper.GetHeapObject().IsUndefined()) { @@ -4073,19 +3875,22 @@ void CompileJsToWasmWrappers(Isolate* isolate, const WasmModule* module) { JSToWasmWrapperKey key = pair.first; JSToWasmWrapperCompilationUnit* unit = pair.second.get(); DCHECK_EQ(isolate, unit->isolate()); - Handle<CodeT> code = unit->Finalize(); + Handle<Code> code = unit->Finalize(); int wrapper_index = GetExportWrapperIndex(key.second, key.first); isolate->heap()->js_to_wasm_wrappers().Set( wrapper_index, HeapObjectReference::Strong(*code)); - RecordStats(*code, isolate->counters()); + if (!code->is_builtin()) { + // Do not increase code stats for non-jitted wrappers. + RecordStats(*code, isolate->counters()); + isolate->counters()->wasm_compiled_export_wrapper()->Increment(1); + } } } WasmCode* CompileImportWrapper( - NativeModule* native_module, Counters* counters, - compiler::WasmImportCallKind kind, const FunctionSig* sig, - uint32_t canonical_type_index, int expected_arity, Suspend suspend, - WasmImportWrapperCache::ModificationScope* cache_scope) { + NativeModule* native_module, Counters* counters, ImportCallKind kind, + const FunctionSig* sig, uint32_t canonical_type_index, int expected_arity, + Suspend suspend, WasmImportWrapperCache::ModificationScope* cache_scope) { // Entry should exist, so that we don't insert a new one and invalidate // other threads' iterators/references, but it should not have been compiled // yet. @@ -4106,7 +3911,7 @@ WasmCode* CompileImportWrapper( result.tagged_parameter_slots, result.protected_instructions_data.as_vector(), result.source_positions.as_vector(), GetCodeKind(result), - ExecutionTier::kNone, kNoDebugging); + ExecutionTier::kNone, kNotForDebugging); published_code = native_module->PublishCode(std::move(wasm_code)); } (*cache_scope)[key] = published_code; |