blob: f121ddc1ae86f59424545e4e8500605513bb1e79 [file] [log] [blame] [edit]
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#ifndef RUNTIME_VM_HEAP_SAFEPOINT_H_
#define RUNTIME_VM_HEAP_SAFEPOINT_H_
#include "vm/globals.h"
#include "vm/isolate.h"
#include "vm/lockers.h"
#include "vm/thread.h"
#include "vm/thread_registry.h"
#include "vm/thread_stack_resource.h"
namespace dart {
class ThreadBarrier;
// A stack based scope that can be used to perform an operation after getting
// all threads to a safepoint. At the end of the operation all the threads are
// resumed.
class SafepointOperationScope : public ThreadStackResource {
protected:
SafepointOperationScope(Thread* T, SafepointLevel level);
~SafepointOperationScope();
private:
SafepointLevel level_;
DISALLOW_COPY_AND_ASSIGN(SafepointOperationScope);
};
// Gets all mutators to a safepoint where GC is allowed.
class GcSafepointOperationScope : public SafepointOperationScope {
public:
explicit GcSafepointOperationScope(Thread* T)
: SafepointOperationScope(T, SafepointLevel::kGC) {}
~GcSafepointOperationScope() {}
private:
DISALLOW_COPY_AND_ASSIGN(GcSafepointOperationScope);
};
// Gets all mutators to a safepoint where GC and Deopt is allowed.
class DeoptSafepointOperationScope : public SafepointOperationScope {
public:
explicit DeoptSafepointOperationScope(Thread* T)
: SafepointOperationScope(T, SafepointLevel::kGCAndDeopt) {}
~DeoptSafepointOperationScope() {}
private:
DISALLOW_COPY_AND_ASSIGN(DeoptSafepointOperationScope);
};
// Gets all mutators to a safepoint where GC, Deopt and Reload is allowed.
class ReloadSafepointOperationScope : public SafepointOperationScope {
public:
explicit ReloadSafepointOperationScope(Thread* T)
: SafepointOperationScope(T, SafepointLevel::kGCAndDeoptAndReload) {}
~ReloadSafepointOperationScope() {}
private:
DISALLOW_COPY_AND_ASSIGN(ReloadSafepointOperationScope);
};
// A stack based scope that can be used to perform an operation after getting
// all threads to a safepoint. At the end of the operation all the threads are
// resumed. Allocations in the scope will force heap growth.
class ForceGrowthSafepointOperationScope : public ThreadStackResource {
public:
ForceGrowthSafepointOperationScope(Thread* T, SafepointLevel level);
~ForceGrowthSafepointOperationScope();
private:
SafepointLevel level_;
bool current_growth_controller_state_;
DISALLOW_COPY_AND_ASSIGN(ForceGrowthSafepointOperationScope);
};
// Subclasses of SafepointTask are able to run on thread blocked at a safepoint.
class SafepointTask : public ThreadPool::Task,
public IntrusiveDListEntry<SafepointTask> {
protected:
SafepointTask(IsolateGroup* isolate_group,
ThreadBarrier* barrier,
Thread::TaskKind kind)
: isolate_group_(isolate_group), barrier_(barrier), kind_(kind) {}
public:
virtual ~SafepointTask();
void Run();
void RunBlockedAtSafepoint();
void RunMain();
virtual void RunEnteredIsolateGroup() = 0;
protected:
IsolateGroup* isolate_group_;
ThreadBarrier* barrier_;
Thread::TaskKind kind_;
private:
DISALLOW_COPY_AND_ASSIGN(SafepointTask);
};
// Implements handling of safepoint operations for all threads in an
// IsolateGroup.
class SafepointHandler {
public:
explicit SafepointHandler(IsolateGroup* I);
~SafepointHandler();
void EnterSafepointUsingLock(Thread* T);
void ExitSafepointUsingLock(Thread* T);
void BlockForSafepoint(Thread* T);
// The innermost safepoint operation this thread owns
//
// Returns `SafepointLevel::kNone` if the current thread doesn't own any
// safepoint. Otherwise returns the innermost safepoint level of the current
// thread.
//
// * Will return SafepointLevel::kDeoptAndGC for
//
// DeoptSafepointOperationScope sp;
//
// * Will return SafepointLevel::kGC for
//
// DeoptSafepointOperationScope sp1;
// GcSafepointOperationScope sp2;
//
SafepointLevel InnermostSafepointOperation(
const Thread* current_thread) const;
bool AnySafepointInProgressLocked() {
for (intptr_t level = 0; level < SafepointLevel::kNumLevels; ++level) {
if (handlers_[level]->SafepointInProgress()) {
return true;
}
}
return false;
}
void RunTasks(IntrusiveDList<SafepointTask>* tasks);
private:
class LevelHandler {
public:
LevelHandler(IsolateGroup* isolate_group, SafepointLevel level)
: isolate_group_(isolate_group), level_(level) {}
bool SafepointInProgress() const {
ASSERT(threads_lock()->IsOwnedByCurrentThread());
ASSERT((operation_count_ > 0) == (owner_ != nullptr));
return ((operation_count_ > 0) && (owner_ != nullptr));
}
void SetSafepointInProgress(Thread* T) {
ASSERT(threads_lock()->IsOwnedByCurrentThread());
ASSERT(owner_ == nullptr);
ASSERT(operation_count_ == 0);
operation_count_ = 1;
owner_ = T;
}
void ResetSafepointInProgress(Thread* T) {
ASSERT(threads_lock()->IsOwnedByCurrentThread());
ASSERT(owner_ == T);
ASSERT(operation_count_ == 1);
ASSERT(num_threads_not_parked_ == 0);
operation_count_ = 0;
owner_ = nullptr;
}
void NotifyWeAreParked(Thread* T);
IsolateGroup* isolate_group() const { return isolate_group_; }
Monitor* threads_lock() const {
return isolate_group_->thread_registry()->threads_lock();
}
private:
friend class SafepointHandler;
// Helper methods for [SafepointThreads]
void NotifyThreadsToGetToSafepointLevel(
Thread* T,
MallocGrowableArray<Dart_Port>* oob_isolates);
void WaitUntilThreadsReachedSafepointLevel();
// Helper methods for [ResumeThreads]
void NotifyThreadsToContinue(Thread* T);
IsolateGroup* isolate_group_;
SafepointLevel level_;
// Monitor used by thread initiating a safepoint operation to track threads
// not at a safepoint and wait for these threads to reach a safepoint.
Monitor parked_lock_;
// If a safepoint operation is currently in progress, this field contains
// the thread that initiated the safepoint operation, otherwise it is
// nullptr.
std::atomic<Thread*> owner_ = nullptr;
// The number of nested safepoint operations currently held.
std::atomic<int32_t> operation_count_ = 0;
// Count the number of threads the currently in-progress safepoint operation
// is waiting for to check-in.
int32_t num_threads_not_parked_ = 0;
};
void SafepointThreads(Thread* T, SafepointLevel level);
void ResumeThreads(Thread* T, SafepointLevel level);
// Helper methods for [SafepointThreads]
void AssertWeOwnLowerLevelSafepoints(Thread* T, SafepointLevel level);
void AssertWeDoNotOwnLowerLevelSafepoints(Thread* T, SafepointLevel level);
void AcquireLowerLevelSafepoints(Thread* T, SafepointLevel level);
// Helper methods for [ResumeThreads]
void ReleaseLowerLevelSafepoints(Thread* T, SafepointLevel level);
void EnterSafepointLocked(Thread* T, MonitorLocker* tl, SafepointLevel level);
void ExitSafepointLocked(Thread* T, MonitorLocker* tl, SafepointLevel level);
IsolateGroup* isolate_group() const { return isolate_group_; }
Monitor* threads_lock() const {
return isolate_group_->thread_registry()->threads_lock();
}
IsolateGroup* isolate_group_;
LevelHandler* handlers_[SafepointLevel::kNumLevels];
IntrusiveDList<SafepointTask> tasks_;
friend class Isolate;
friend class IsolateGroup;
friend class SafepointOperationScope;
friend class ForceGrowthSafepointOperationScope;
friend class HeapIterationScope;
};
/*
* Set of StackResource classes to track thread execution state transitions:
*
* kThreadInGenerated transitioning to
* ==> kThreadInVM:
* - set_execution_state(kThreadInVM).
* - block if safepoint is requested.
* ==> kThreadInNative:
* - set_execution_state(kThreadInNative).
* - EnterSafepoint().
* ==> kThreadInBlockedState:
* - Invalid transition
*
* kThreadInVM transitioning to
* ==> kThreadInGenerated
* - set_execution_state(kThreadInGenerated).
* ==> kThreadInNative
* - set_execution_state(kThreadInNative).
* - EnterSafepoint.
* ==> kThreadInBlockedState
* - set_execution_state(kThreadInBlockedState).
* - EnterSafepoint.
*
* kThreadInNative transitioning to
* ==> kThreadInGenerated
* - ExitSafepoint.
* - set_execution_state(kThreadInGenerated).
* ==> kThreadInVM
* - ExitSafepoint.
* - set_execution_state(kThreadInVM).
* ==> kThreadInBlocked
* - Invalid transition.
*
* kThreadInBlocked transitioning to
* ==> kThreadInVM
* - ExitSafepoint.
* - set_execution_state(kThreadInVM).
* ==> kThreadInNative
* - Invalid transition.
* ==> kThreadInGenerated
* - Invalid transition.
*/
class TransitionSafepointState : public ThreadStackResource {
public:
explicit TransitionSafepointState(Thread* T) : ThreadStackResource(T) {}
~TransitionSafepointState() {}
SafepointHandler* handler() const {
ASSERT(thread()->isolate() != nullptr);
ASSERT(thread()->isolate()->safepoint_handler() != nullptr);
return thread()->isolate()->safepoint_handler();
}
private:
DISALLOW_COPY_AND_ASSIGN(TransitionSafepointState);
};
// TransitionGeneratedToVM is used to transition the safepoint state of a
// thread from "running generated code" to "running vm code" and ensures
// that the state is reverted back to "running generated code" when
// exiting the scope/frame.
class TransitionGeneratedToVM : public TransitionSafepointState {
public:
explicit TransitionGeneratedToVM(Thread* T) : TransitionSafepointState(T) {
ASSERT(T == Thread::Current());
ASSERT(T->execution_state() == Thread::kThreadInGenerated);
T->set_execution_state(Thread::kThreadInVM);
// Fast check to see if a safepoint is requested or not.
// We do the more expensive operation of blocking the thread
// only if a safepoint is requested.
if (T->IsSafepointRequested()) {
T->BlockForSafepoint();
}
}
~TransitionGeneratedToVM() {
ASSERT(thread()->execution_state() == Thread::kThreadInVM);
thread()->set_execution_state(Thread::kThreadInGenerated);
}
private:
DISALLOW_COPY_AND_ASSIGN(TransitionGeneratedToVM);
};
// TransitionGeneratedToNative is used to transition the safepoint state of a
// thread from "running generated code" to "running native code" and ensures
// that the state is reverted back to "running generated code" when
// exiting the scope/frame.
class TransitionGeneratedToNative : public TransitionSafepointState {
public:
explicit TransitionGeneratedToNative(Thread* T)
: TransitionSafepointState(T) {
// Native code is considered to be at a safepoint and so we mark it
// accordingly.
ASSERT(T->execution_state() == Thread::kThreadInGenerated);
T->set_execution_state(Thread::kThreadInNative);
T->EnterSafepoint();
}
~TransitionGeneratedToNative() {
// We are returning to generated code and so we are not at a safepoint
// anymore.
ASSERT(thread()->execution_state() == Thread::kThreadInNative);
thread()->ExitSafepoint();
thread()->set_execution_state(Thread::kThreadInGenerated);
}
private:
DISALLOW_COPY_AND_ASSIGN(TransitionGeneratedToNative);
};
// TransitionVMToBlocked is used to transition the safepoint state of a
// thread from "running vm code" to "blocked on a monitor" and ensures
// that the state is reverted back to "running vm code" when
// exiting the scope/frame.
class TransitionVMToBlocked : public TransitionSafepointState {
public:
explicit TransitionVMToBlocked(Thread* T) : TransitionSafepointState(T) {
ASSERT(T->CanAcquireSafepointLocks());
// A thread blocked on a monitor is considered to be at a safepoint.
ASSERT(T->execution_state() == Thread::kThreadInVM);
T->set_execution_state(Thread::kThreadInBlockedState);
T->EnterSafepoint();
}
~TransitionVMToBlocked() {
// We are returning to vm code and so we are not at a safepoint anymore.
ASSERT(thread()->execution_state() == Thread::kThreadInBlockedState);
thread()->ExitSafepoint();
thread()->set_execution_state(Thread::kThreadInVM);
}
private:
DISALLOW_COPY_AND_ASSIGN(TransitionVMToBlocked);
};
// TransitionVMToNative is used to transition the safepoint state of a
// thread from "running vm code" to "running native code" and ensures
// that the state is reverted back to "running vm code" when
// exiting the scope/frame.
class TransitionVMToNative : public TransitionSafepointState {
public:
explicit TransitionVMToNative(Thread* T) : TransitionSafepointState(T) {
// A thread running native code is considered to be at a safepoint.
ASSERT(T->execution_state() == Thread::kThreadInVM);
T->set_execution_state(Thread::kThreadInNative);
T->EnterSafepoint();
}
~TransitionVMToNative() {
// We are returning to vm code and so we are not at a safepoint anymore.
ASSERT(thread()->execution_state() == Thread::kThreadInNative);
thread()->ExitSafepoint();
thread()->set_execution_state(Thread::kThreadInVM);
}
private:
DISALLOW_COPY_AND_ASSIGN(TransitionVMToNative);
};
// TransitionVMToGenerated is used to transition the safepoint state of a
// thread from "running vm code" to "running generated code" and ensures
// that the state is reverted back to "running vm code" when
// exiting the scope/frame.
class TransitionVMToGenerated : public TransitionSafepointState {
public:
explicit TransitionVMToGenerated(Thread* T) : TransitionSafepointState(T) {
ASSERT(T == Thread::Current());
ASSERT(T->execution_state() == Thread::kThreadInVM);
T->set_execution_state(Thread::kThreadInGenerated);
}
~TransitionVMToGenerated() {
ASSERT(thread()->execution_state() == Thread::kThreadInGenerated);
thread()->set_execution_state(Thread::kThreadInVM);
// Fast check to see if a safepoint is requested or not.
if (thread()->IsSafepointRequested()) {
thread()->BlockForSafepoint();
}
}
private:
DISALLOW_COPY_AND_ASSIGN(TransitionVMToGenerated);
};
// TransitionNativeToVM is used to transition the safepoint state of a
// thread from "running native code" to "running vm code" and ensures
// that the state is reverted back to "running native code" when
// exiting the scope/frame.
class TransitionNativeToVM : public TransitionSafepointState {
public:
explicit TransitionNativeToVM(Thread* T) : TransitionSafepointState(T) {
// We are about to execute vm code and so we are not at a safepoint anymore.
ASSERT(T->execution_state() == Thread::kThreadInNative);
if (T->no_callback_scope_depth() == 0) {
T->ExitSafepoint();
}
T->set_execution_state(Thread::kThreadInVM);
}
~TransitionNativeToVM() {
// We are returning to native code and so we are at a safepoint.
ASSERT(thread()->execution_state() == Thread::kThreadInVM);
thread()->set_execution_state(Thread::kThreadInNative);
if (thread()->no_callback_scope_depth() == 0) {
thread()->EnterSafepoint();
}
}
private:
DISALLOW_COPY_AND_ASSIGN(TransitionNativeToVM);
};
// TransitionToGenerated is used to transition the safepoint state of a
// thread from "running vm code" or "running native code" to
// "running generated code" and ensures that the state is reverted back
// to "running vm code" or "running native code" when exiting the
// scope/frame.
class TransitionToGenerated : public TransitionSafepointState {
public:
explicit TransitionToGenerated(Thread* T)
: TransitionSafepointState(T), execution_state_(T->execution_state()) {
ASSERT(T == Thread::Current());
ASSERT((execution_state_ == Thread::kThreadInVM) ||
(execution_state_ == Thread::kThreadInNative));
if (execution_state_ == Thread::kThreadInNative) {
T->ExitSafepoint();
}
T->set_execution_state(Thread::kThreadInGenerated);
}
~TransitionToGenerated() {
ASSERT(thread()->execution_state() == Thread::kThreadInGenerated);
if (execution_state_ == Thread::kThreadInNative) {
thread()->set_execution_state(Thread::kThreadInNative);
thread()->EnterSafepoint();
} else {
ASSERT(execution_state_ == Thread::kThreadInVM);
thread()->set_execution_state(Thread::kThreadInVM);
}
}
private:
uint32_t execution_state_;
DISALLOW_COPY_AND_ASSIGN(TransitionToGenerated);
};
// TransitionToVM is used to transition the safepoint state of a
// thread from "running native code" to "running vm code"
// and ensures that the state is reverted back to "running native code"
// when exiting the scope/frame.
// This transition helper is mainly used in the error path of the
// Dart API implementations where we sometimes do not have an explicit
// transition set up.
class TransitionToVM : public TransitionSafepointState {
public:
explicit TransitionToVM(Thread* T)
: TransitionSafepointState(T), execution_state_(T->execution_state()) {
ASSERT(T == Thread::Current());
ASSERT((execution_state_ == Thread::kThreadInVM) ||
(execution_state_ == Thread::kThreadInNative));
if (execution_state_ == Thread::kThreadInNative) {
T->ExitSafepoint();
T->set_execution_state(Thread::kThreadInVM);
}
ASSERT(T->execution_state() == Thread::kThreadInVM);
}
~TransitionToVM() {
ASSERT(thread()->execution_state() == Thread::kThreadInVM);
if (execution_state_ == Thread::kThreadInNative) {
thread()->set_execution_state(Thread::kThreadInNative);
thread()->EnterSafepoint();
}
}
private:
uint32_t execution_state_;
DISALLOW_COPY_AND_ASSIGN(TransitionToVM);
};
// TransitionToNative is used to transition the safepoint state of a
// thread from "running VM code" to "running native code"
// and ensures that the state is reverted back to the initial state
// when exiting the scope/frame.
class TransitionToNative : public TransitionSafepointState {
public:
explicit TransitionToNative(Thread* T)
: TransitionSafepointState(T), execution_state_(T->execution_state()) {
ASSERT(T == Thread::Current());
ASSERT((execution_state_ == Thread::kThreadInVM) ||
(execution_state_ == Thread::kThreadInNative));
if (execution_state_ == Thread::kThreadInVM) {
T->set_execution_state(Thread::kThreadInNative);
T->EnterSafepoint();
}
ASSERT(T->execution_state() == Thread::kThreadInNative);
}
~TransitionToNative() {
ASSERT(thread()->execution_state() == Thread::kThreadInNative);
if (execution_state_ == Thread::kThreadInVM) {
thread()->ExitSafepoint();
thread()->set_execution_state(Thread::kThreadInVM);
}
}
private:
uint32_t execution_state_;
DISALLOW_COPY_AND_ASSIGN(TransitionToNative);
};
} // namespace dart
#endif // RUNTIME_VM_HEAP_SAFEPOINT_H_