Commit 660938b7 authored by Clemens Backes's avatar Clemens Backes Committed by V8 LUCI CQ

[wasm] Avoid taking a mutex during Liftoff compilation

The mutex is there to protect against concurrent compilation of the same
function. It can be avoided by accumulating the vector of call targets
locally in the LiftoffCompiler, and only transferring it to the stored
type feedback once at the end (under the mutex).

Also, choose slightly better data structures: base::OwnedVector uses
less memory that std::vector (because it never over-reserves), and
std::unordered_map is more memory efficient and faster to lookup that
{std::map}.

R=jkummerow@chromium.org

Bug: v8:13209
Change-Id: I7aa82560a83cbac5c019effc7fd38c9b1495a42c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3840294
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#82602}
parent df59f217
...@@ -558,9 +558,9 @@ class LiftoffCompiler { ...@@ -558,9 +558,9 @@ class LiftoffCompiler {
} }
int GetFeedbackVectorSlots() const { int GetFeedbackVectorSlots() const {
// The number of instructions is capped by max function size. // The number of call instructions is capped by max function size.
static_assert(kV8MaxWasmFunctionSize < std::numeric_limits<int>::max() / 2); static_assert(kV8MaxWasmFunctionSize < std::numeric_limits<int>::max() / 2);
return static_cast<int>(num_call_instructions_) * 2; return static_cast<int>(encountered_call_instructions_.size()) * 2;
} }
void unsupported(FullDecoder* decoder, LiftoffBailoutReason reason, void unsupported(FullDecoder* decoder, LiftoffBailoutReason reason,
...@@ -1058,6 +1058,22 @@ class LiftoffCompiler { ...@@ -1058,6 +1058,22 @@ class LiftoffCompiler {
// The previous calls may have also generated a bailout. // The previous calls may have also generated a bailout.
DidAssemblerBailout(decoder); DidAssemblerBailout(decoder);
DCHECK_EQ(num_exceptions_, 0); DCHECK_EQ(num_exceptions_, 0);
if (FLAG_wasm_speculative_inlining &&
!encountered_call_instructions_.empty()) {
// Update the call targets stored in the WasmModule.
TypeFeedbackStorage& type_feedback = env_->module->type_feedback;
base::MutexGuard mutex_guard(&type_feedback.mutex);
base::OwnedVector<uint32_t>& call_targets =
type_feedback.feedback_for_function[func_index_].call_targets;
if (call_targets.empty()) {
call_targets =
base::OwnedVector<uint32_t>::Of(encountered_call_instructions_);
} else {
DCHECK_EQ(call_targets.as_vector(),
base::VectorOf(encountered_call_instructions_));
}
}
} }
void OnFirstError(FullDecoder* decoder) { void OnFirstError(FullDecoder* decoder) {
...@@ -6986,12 +7002,9 @@ class LiftoffCompiler { ...@@ -6986,12 +7002,9 @@ class LiftoffCompiler {
// One slot would be enough for call_direct, but would make index // One slot would be enough for call_direct, but would make index
// computations much more complicated. // computations much more complicated.
uintptr_t vector_slot = num_call_instructions_ * 2; size_t vector_slot = encountered_call_instructions_.size() * 2;
if (FLAG_wasm_speculative_inlining) { if (FLAG_wasm_speculative_inlining) {
base::MutexGuard mutex_guard(&decoder->module_->type_feedback.mutex); encountered_call_instructions_.push_back(imm.index);
decoder->module_->type_feedback.feedback_for_function[func_index_]
.call_targets.push_back(imm.index);
num_call_instructions_++;
} }
if (imm.index < env_->module->num_imported_functions) { if (imm.index < env_->module->num_imported_functions) {
...@@ -7234,13 +7247,9 @@ class LiftoffCompiler { ...@@ -7234,13 +7247,9 @@ class LiftoffCompiler {
__ Fill(vector, liftoff::kFeedbackVectorOffset, kPointerKind); __ Fill(vector, liftoff::kFeedbackVectorOffset, kPointerKind);
LiftoffAssembler::VarState vector_var(kPointerKind, vector, 0); LiftoffAssembler::VarState vector_var(kPointerKind, vector, 0);
LiftoffRegister index = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); LiftoffRegister index = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
uintptr_t vector_slot = num_call_instructions_ * 2; size_t vector_slot = encountered_call_instructions_.size() * 2;
{ encountered_call_instructions_.push_back(
base::MutexGuard mutex_guard(&decoder->module_->type_feedback.mutex); FunctionTypeFeedback::kNonDirectCall);
decoder->module_->type_feedback.feedback_for_function[func_index_]
.call_targets.push_back(FunctionTypeFeedback::kNonDirectCall);
}
num_call_instructions_++;
__ LoadConstant(index, WasmValue::ForUintPtr(vector_slot)); __ LoadConstant(index, WasmValue::ForUintPtr(vector_slot));
LiftoffAssembler::VarState index_var(kIntPtrKind, index, 0); LiftoffAssembler::VarState index_var(kIntPtrKind, index, 0);
...@@ -7574,10 +7583,11 @@ class LiftoffCompiler { ...@@ -7574,10 +7583,11 @@ class LiftoffCompiler {
// Current number of exception refs on the stack. // Current number of exception refs on the stack.
int num_exceptions_ = 0; int num_exceptions_ = 0;
// Number of feedback-collecting call instructions encountered. While // Updated during compilation on every "call" or "call_ref" instruction.
// compiling, also index of the next such instruction. Used for indexing type // Holds the call target, or {FunctionTypeFeedback::kNonDirectCall} for
// feedback. // "call_ref".
uintptr_t num_call_instructions_ = 0; // After compilation, this is transferred into {WasmModule::type_feedback}.
std::vector<uint32_t> encountered_call_instructions_;
int32_t* max_steps_; int32_t* max_steps_;
int32_t* nondeterminism_; int32_t* nondeterminism_;
......
...@@ -1285,8 +1285,8 @@ class TransitiveTypeFeedbackProcessor { ...@@ -1285,8 +1285,8 @@ class TransitiveTypeFeedbackProcessor {
TransitiveTypeFeedbackProcessor(WasmInstanceObject instance, int func_index) TransitiveTypeFeedbackProcessor(WasmInstanceObject instance, int func_index)
: instance_(instance), : instance_(instance),
module_(instance.module()), module_(instance.module()),
mutex_guard(&module_->type_feedback.mutex),
feedback_for_function_(module_->type_feedback.feedback_for_function) { feedback_for_function_(module_->type_feedback.feedback_for_function) {
base::MutexGuard mutex_guard(&module_->type_feedback.mutex);
queue_.insert(func_index); queue_.insert(func_index);
while (!queue_.empty()) { while (!queue_.empty()) {
auto next = queue_.cbegin(); auto next = queue_.cbegin();
...@@ -1319,8 +1319,9 @@ class TransitiveTypeFeedbackProcessor { ...@@ -1319,8 +1319,9 @@ class TransitiveTypeFeedbackProcessor {
DisallowGarbageCollection no_gc_scope_; DisallowGarbageCollection no_gc_scope_;
WasmInstanceObject instance_; WasmInstanceObject instance_;
const WasmModule* const module_; const WasmModule* const module_;
std::map<uint32_t, FunctionTypeFeedback>& feedback_for_function_; base::MutexGuard mutex_guard;
std::unordered_set<int> queue_; std::unordered_map<uint32_t, FunctionTypeFeedback>& feedback_for_function_;
std::set<int> queue_;
}; };
class FeedbackMaker { class FeedbackMaker {
...@@ -1407,8 +1408,9 @@ void TransitiveTypeFeedbackProcessor::Process(int func_index) { ...@@ -1407,8 +1408,9 @@ void TransitiveTypeFeedbackProcessor::Process(int func_index) {
Object maybe_feedback = instance_.feedback_vectors().get(which_vector); Object maybe_feedback = instance_.feedback_vectors().get(which_vector);
if (!maybe_feedback.IsFixedArray()) return; if (!maybe_feedback.IsFixedArray()) return;
FixedArray feedback = FixedArray::cast(maybe_feedback); FixedArray feedback = FixedArray::cast(maybe_feedback);
const std::vector<uint32_t>& call_direct_targets = base::Vector<uint32_t> call_direct_targets =
module_->type_feedback.feedback_for_function[func_index].call_targets; module_->type_feedback.feedback_for_function[func_index]
.call_targets.as_vector();
FeedbackMaker fm(instance_, func_index, feedback.length() / 2); FeedbackMaker fm(instance_, func_index, feedback.length() / 2);
for (int i = 0; i < feedback.length(); i += 2) { for (int i = 0; i < feedback.length(); i += 2) {
Object value = feedback.get(i); Object value = feedback.get(i);
......
...@@ -467,7 +467,7 @@ struct FunctionTypeFeedback { ...@@ -467,7 +467,7 @@ struct FunctionTypeFeedback {
// {call_targets} has one entry per "call" and "call_ref" in the function. // {call_targets} has one entry per "call" and "call_ref" in the function.
// For "call", it holds the index of the called function, for "call_ref" the // For "call", it holds the index of the called function, for "call_ref" the
// value will be {kNonDirectCall}. // value will be {kNonDirectCall}.
std::vector<uint32_t> call_targets; base::OwnedVector<uint32_t> call_targets;
// {tierup_priority} is updated and used when triggering tier-up. // {tierup_priority} is updated and used when triggering tier-up.
// TODO(clemensb): This does not belong here; find a better place. // TODO(clemensb): This does not belong here; find a better place.
...@@ -477,7 +477,7 @@ struct FunctionTypeFeedback { ...@@ -477,7 +477,7 @@ struct FunctionTypeFeedback {
}; };
struct TypeFeedbackStorage { struct TypeFeedbackStorage {
std::map<uint32_t, FunctionTypeFeedback> feedback_for_function; std::unordered_map<uint32_t, FunctionTypeFeedback> feedback_for_function;
// Accesses to {feedback_for_function} are guarded by this mutex. // Accesses to {feedback_for_function} are guarded by this mutex.
base::Mutex mutex; base::Mutex mutex;
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment