v8 maglev 编译过程中的数据竞争
编译线程与 GC 线程
maglev 在 compile 期间对于整个构图以及图优化阶段加上了
UnparkedScopeIfOnBackground unparked_scope(local_isolate->heap()); 以处理跟 GC 线程的数据竞争
// static
bool MaglevCompiler::Compile(LocalIsolate* local_isolate,
MaglevCompilationInfo* compilation_info) {
std::optional<MaglevGraphLabellerScope> graph_labeller_scope;
compiler::CurrentHeapBrokerScope current_broker(compilation_info->broker());
Graph* graph = Graph::New(compilation_info);
if (V8_UNLIKELY(ALWAYS_MAGLEV_GRAPH_LABELLER_BOOL ||
compilation_info->is_tracing_enabled() ||
compilation_info->collect_source_positions())) {
compilation_info->set_graph_labeller(new MaglevGraphLabeller());
graph_labeller_scope.emplace(compilation_info->graph_labeller());
}
{
UnparkedScopeIfOnBackground unparked_scope(local_isolate->heap());
if (V8_UNLIKELY(v8_flags.maglev_print_bytecode &&
compilation_info->is_tracing_enabled())) {
MaglevCompilationUnit* top_level_unit =
compilation_info->toplevel_compilation_unit();
std::cout << "Compiling " << Brief(*compilation_info->toplevel_function())
<< " with Maglev\n";
BytecodeArray::Disassemble(top_level_unit->bytecode().object(),
std::cout);
if (v8_flags.maglev_print_feedback) {
Print(*top_level_unit->feedback().object(), std::cout);
}
}
// ...................
}
UnparkedScopeIfOnBackground 做了什么?
构造函数中针对 background 线程创建 optional 的 scope,主线程跳过。对于后台编译的情况,调用 UnparkedScope 的构造函数,构造函数中调用 local_heap_->Unpark();
// Scope that explicitly unparks a background thread, allowing access to the
// heap and the creation of handles. It has no effect on the main thread.
class V8_NODISCARD UnparkedScopeIfOnBackground {
public:
explicit UnparkedScopeIfOnBackground(LocalIsolate* local_isolate)
: UnparkedScopeIfOnBackground(local_isolate->heap()) {}
explicit UnparkedScopeIfOnBackground(LocalHeap* local_heap) {
if (!local_heap->is_main_thread()) scope_.emplace(local_heap);
}
private:
std::optional<UnparkedScope> scope_;
};
Unpark 将线程的状态切换到 Running 态,指示当前线程允许访问堆。如果当前是 Parked 状态,快速路径直接切换状态到 Running,否则走 slowpath。
void Unpark() {
DCHECK(AllowSafepoints::IsAllowed());
ThreadState expected = ThreadState::Parked();
if (!state_.CompareExchangeWeak(expected, ThreadState::Running())) {
UnparkSlowPath();
}
}
什么时候不是 Parked 状态?
ThreadState 是一个复合值,比如正在 GC 会有 SafepointRequested 标记,主线程需要执行 GC 会有 CollectionRequested 标记(只在主线程)。这个时候不是纯 Parked 状态,走 slowpath,先等待 GC 请求执行。
while 循环,exptected 是 ThreadState::Parked() 的时候切换到 ThreadState::Running() 并返回。is_main_thread 先不关注,子线程调用 SleepInUnpark。这之中会等待 GC 结束,结束后对于每个 local_heap 把 SafepointRequested 标记移除掉,在下一次循环进去的时候 if 条件满足退出。当前的编译线程被切换到 ThreadState::Running() 态。
void LocalHeap::UnparkSlowPath() {
while (true) {
ThreadState current_state = ThreadState::Parked();
if (state_.CompareExchangeStrong(current_state, ThreadState::Running()))
return;
// CAS above failed, so state is Parked with some additional flag.
DCHECK(current_state.IsParked());
if (is_main_thread()) {
DCHECK(current_state.IsSafepointRequested() ||
current_state.IsCollectionRequested());
if (current_state.IsSafepointRequested()) {
SleepInUnpark();
continue;
}
if (current_state.IsCollectionRequested()) {
DCHECK(!current_state.IsSafepointRequested());
if (!state_.CompareExchangeStrong(current_state,
current_state.SetRunning()))
continue;
if (!heap()->ignore_local_gc_requests()) {
heap_->CollectGarbageForBackground(this);
}
return;
}
} else {
DCHECK(current_state.IsSafepointRequested());
DCHECK(!current_state.IsCollectionRequested());
SleepInUnpark();
}
}
}
后续触发 GC 的时候会等待其他线程进入 safepoint,本质上是把自己设置成 parked 状态
对于编译线程,BuildBody 中的 for 循环每次处理字节码的时候会插一个 safepoint 点,决定要不要把自己设置成 parked 状态(对于编译线程,检查 safepointrequested)。另一个插入 safepoint 的点在处理 looppeeling 的时候。
void MaglevGraphBuilder::BuildBody() {
int position = 0;
while (!source_position_iterator_.done() &&
source_position_iterator_.code_offset() < entrypoint_) {
position = source_position_iterator_.source_position().ScriptOffset();
source_position_iterator_.Advance();
}
reducer_.SetSourcePosition(position, inlining_id_);
for (iterator_.SetOffset(entrypoint_); !iterator_.done();
iterator_.Advance()) {
local_isolate_->heap()->Safepoint();
if (V8_UNLIKELY(
loop_headers_to_peel_.Contains(iterator_.current_offset()))) {
PeelLoop();
DCHECK_EQ(iterator_.current_bytecode(), interpreter::Bytecode::kJumpLoop);
continue;
}
if (VisitSingleBytecode().IsDoneWithAbort()) {
MarkBytecodeDead();
}
}
DCHECK_EQ(loop_effects_stack_.size(),
is_inline() && caller_details_->loop_effects ? 1 : 0);
}
总体来说,maglev 的编译线程跟 GC 线程是互斥关系,通过插入 safepoint 保证跟 GC 线程的数据竞争。
编译线程与主线程
编译线程通过 MapUpdaterGuardIfNeeded 和 BoilerplateMigrationGuardIfNeeded 保证少数情况下与主线程互斥
两个互斥锁继承自 RecursiveMutexGuardIfNeeded,一个递归可重入锁(一些读 map 的过程有递归)。
// Locks {mutex} through the duration of this scope iff it is the first
// occurrence. This is done to have a recursive shared lock on {mutex}.
class V8_NODISCARD RecursiveMutexGuardIfNeeded {
protected:
V8_INLINE RecursiveMutexGuardIfNeeded(LocalIsolate* local_isolate,
base::Mutex* mutex,
int* mutex_depth_address);
~RecursiveMutexGuardIfNeeded() {
DCHECK_GE((*mutex_depth_address_), 1);
(*mutex_depth_address_)--;
DCHECK_EQ(initial_mutex_depth_, (*mutex_depth_address_));
}
private:
int* const mutex_depth_address_;
const int initial_mutex_depth_;
ParkedMutexGuardIf mutex_guard_;
};
其构造函数如下,调用 mutex_guard_ 上锁,只有锁深度为 0,也就是第一次申请锁的时候会上锁
V8_INLINE
JSHeapBroker::RecursiveMutexGuardIfNeeded::RecursiveMutexGuardIfNeeded(
LocalIsolate* local_isolate, base::Mutex* mutex, int* mutex_depth_address)
: mutex_depth_address_(mutex_depth_address),
initial_mutex_depth_(*mutex_depth_address_),
mutex_guard_(local_isolate, mutex, initial_mutex_depth_ == 0) {
(*mutex_depth_address_)++;
}
BoilerplateMigrationGuardIfNeeded
从 isolate 中拿到 boilerplate_migration_access_,一个互斥锁,并加锁。
V8_INLINE JSHeapBroker::BoilerplateMigrationGuardIfNeeded::
BoilerplateMigrationGuardIfNeeded(JSHeapBroker* broker)
: RecursiveMutexGuardIfNeeded(
broker -> local_isolate_or_isolate(),
broker->isolate()->boilerplate_migration_access(),
&broker->boilerplate_migration_mutex_depth_) {}
} // namespace v8::internal::compiler
两个使用点,跟 literal 对象创建时读取 Boilerplate (模板)有关,保护 Boilerplate 的 map。按照注释,Boilerplate 保存的属性不会修改,但是 map 可能会发生修改,map 中记录的一些东西可能会更新。主线程在做修改的时候也会获取锁。
Turbofan js-create-lowering 过程中优化 literal 对象的创建
TryAllocateFastLiteral
Maglev VisitCreateObjectLiteral 与 VisitCreateArrayLiteral 过程中读取模板优化 literlal 对象创建
TryReadBoilerplateForFastLiteral
MapUpdaterGuardIfNeeded
读 map 相关内容的时候与主线程在更新 map 互斥
编译线程获取锁
maglev / turbofan 调用 GetPropertyAccessInfo 获取对象访问信息,优化 load 字节码的时候用到。会遍历 map 整理信息,整个过程通过 MapUpdaterGuardIfNeeded 保护
ComputePropertyAccessInfo
处理跟主线程在做 slacktracking 的时序关系,保证读到稳态的 map,比如 size,unused 一致性(都在 tracking 前或者都在 trcking 后)
InstanceSizeWithMinSlack
使用 MapUpdaterGuardIfNeeded 保护整个 map 的序列化过程
MapData
在 ReadFeedbackForPropertyAccess 的时候处理 elementaccess,防止主线程同步修改 elementkind
ProcessFeedbackMapsForElementAccess
主线程加锁
主线程更新 map 时加锁
Handle<Map> MapUpdater::Update() {
base::MutexGuard mutex_guard(isolate_->map_updater_access());
return UpdateImpl();
}
主线程做 stacktracking 优化的时候加锁
// static
void MapUpdater::CompleteInobjectSlackTracking(Isolate* isolate,
Tagged<Map> initial_map) {
// Has to be an initial map.
CHECK(IsUndefined(initial_map->GetBackPointer(), isolate));
const int slack = initial_map->ComputeMinObjectSlack(isolate);
DCHECK_GE(slack, 0);
TransitionsAccessor transitions(isolate, initial_map);
TransitionsAccessor::TraverseCallback callback;
if (slack != 0) {
// Resize the initial map and all maps in its transition tree.
callback = [slack](Tagged<Map> map) {
#ifdef DEBUG
int old_visitor_id = Map::GetVisitorId(map);
int new_unused = map->UnusedPropertyFields() - slack;
#endif
map->set_instance_size(map->InstanceSizeFromSlack(slack));
map->set_construction_counter(Map::kNoSlackTracking);
DCHECK_EQ(old_visitor_id, Map::GetVisitorId(map));
DCHECK_EQ(new_unused, map->UnusedPropertyFields());
};
} else {
// Stop slack tracking for this map.
callback = [&](Tagged<Map> map) {
map->set_construction_counter(Map::kNoSlackTracking);
DCHECK(!TransitionsAccessor(isolate, map).HasSideStepTransitions());
};
}
{
// The map_updater_access lock is taken here to guarantee atomicity of all
// related map changes (instead of guaranteeing only atomicity of each
// single map change). This is needed e.g. by InstancesNeedsRewriting,
// which expects certain relations between maps to hold.
//
// Note: Avoid locking the full_transition_array_access lock inside this
// call to TraverseTransitionTree to prevent dependencies between the two
// locks.
base::MutexGuard guard(isolate->map_updater_access());
transitions.TraverseTransitionTree(callback);
}
}
加锁,保护对于属性字段类型的修改
ReconfigureToDataField