Files
sdk/runtime/vm/thread.h
T
Ryan Macnak d36adbacaf [vm] Remove the VM isolate.
The former contents of the VM isolate are now included into each isolate group. This makes each isolate group's heap independent, and in particular allows each heap to be allocated to a separate pointer cage (not done in this CL).

The duplicated stubs that allowed PC relative calls are removed, since the originals can now be the target of PC relative calls.

The bootstrapping needing to load an AppJIT or AppAOT snapshot is reduced to allocating the oddballs. The code is entirely dropped in the AOT runtime, but the JIT runtime still has it to allow for flags to affect the compilation of the stub code. Further refactoring might be able to remove this for the JIT runtime too, with only gen_snapshot knowing how to bootstrap.

Class serialization no longer distinguishes predefined classes.

The page containing null is marked as never-evacuate. null, false and true must not move because the compiler relies on their low bits having certain patterns for some optimizations. (Previously, the entire VM isolate heap never moved.)

Compaction is disabled for IA32. Due to register pressure, some stub calls must not use a scratch register and embed the address of Code.

The page containing the call-through-safepoint stub is frozen when running with --write-protect-code and the stub is created at runtime (instead of loaded from an AppJIT or AppAOT snapshot). This stub must remain executable even during a safepoint, as a foreign call might during return during a safepoint and only block after the stub directs it to the runtime.

The snapshot symbols are renamed to kDartSnapshotData and kDartSnapshotText. There is no need to distinguish the VM isolate's snapshot, and snaphots are per isolate group not per isolate. Aliases with the old names are added to ease migration.

Some global flags that were automatically set based on the VM isolate's snapshot are now isolate group flags and automatically set by the isolate group's snapshot.

TEST=ci
Change-Id: Iee82016057d609112e9b021d178fc3d4d18b5044
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500621
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Tess Strickland <sstrickl@google.com>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
2026-05-18 11:35:03 -07:00

1969 lines
73 KiB
C++

// Copyright (c) 2015, 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_THREAD_H_
#define RUNTIME_VM_THREAD_H_
#if defined(SHOULD_NOT_INCLUDE_RUNTIME)
#error "Should not include runtime"
#endif
#include <setjmp.h>
#include <memory>
#include <utility>
#include "include/dart_api.h"
#include "platform/assert.h"
#include "platform/atomic.h"
#include "platform/safe_stack.h"
#include "platform/thread_sanitizer.h"
#include "vm/bitfield.h"
#include "vm/compiler/runtime_api.h"
#include "vm/constants.h"
#include "vm/globals.h"
#include "vm/handles.h"
#include "vm/heap/pointer_block.h"
#include "vm/heap/sampler.h"
#include "vm/intrusive_dlist.h"
#include "vm/os_thread.h"
#include "vm/pending_deopts.h"
#include "vm/random.h"
#include "vm/runtime_entry_list.h"
#include "vm/tags.h"
#include "vm/thread_stack_resource.h"
#include "vm/thread_state.h"
#include "vm/virtual_memory.h"
namespace dart {
class AbstractType;
class ApiLocalScope;
class Array;
class Bytecode;
class CompilerState;
class CompilerTimings;
class Class;
class Code;
class DeoptContext;
class Error;
class ExceptionHandlers;
class Field;
class FieldTable;
class Function;
class GrowableObjectArray;
class HandleScope;
class Heap;
class HierarchyInfo;
class Instance;
class Interpreter;
class Isolate;
class IsolateGroup;
class Library;
class LocalHandle;
class Object;
class OSThread;
class JSONObject;
class NoActiveIsolateScope;
class PcDescriptors;
class RuntimeEntry;
class Smi;
class StackResource;
class StackTrace;
class StreamInfo;
class String;
class TimelineStream;
class TypeArguments;
class TypeParameter;
class TypeUsageInfo;
class WeakTable;
class Zone;
namespace bytecode {
class BytecodeLoader;
}
namespace compiler {
namespace target {
class Thread;
} // namespace target
} // namespace compiler
#define REUSABLE_HANDLE_LIST(V) \
V(AbstractType) \
V(Array) \
V(Class) \
V(Code) \
V(Bytecode) \
V(Error) \
V(ExceptionHandlers) \
V(Field) \
V(Function) \
V(GrowableObjectArray) \
V(Instance) \
V(Library) \
V(LoadingUnit) \
V(Object) \
V(PcDescriptors) \
V(Smi) \
V(String) \
V(TypeParameters) \
V(TypeArguments) \
V(TypeParameter) \
V(WeakArray)
#define CACHED_VM_STUBS_LIST(V) \
V(CodePtr, fix_callers_target_code_, StubCode::FixCallersTarget().ptr(), \
nullptr) \
V(CodePtr, fix_allocation_stub_code_, \
StubCode::FixAllocationStubTarget().ptr(), nullptr) \
V(CodePtr, invoke_dart_code_stub_, StubCode::InvokeDartCode().ptr(), \
nullptr) \
V(CodePtr, invoke_dart_code_from_bytecode_stub_, \
StubCode::InvokeDartCodeFromBytecode().ptr(), nullptr) \
V(CodePtr, call_to_runtime_stub_, StubCode::CallToRuntime().ptr(), nullptr) \
V(CodePtr, late_initialization_error_shared_without_fpu_regs_stub_, \
StubCode::LateInitializationErrorSharedWithoutFPURegs().ptr(), nullptr) \
V(CodePtr, late_initialization_error_shared_with_fpu_regs_stub_, \
StubCode::LateInitializationErrorSharedWithFPURegs().ptr(), nullptr) \
V(CodePtr, null_error_shared_without_fpu_regs_stub_, \
StubCode::NullErrorSharedWithoutFPURegs().ptr(), nullptr) \
V(CodePtr, null_error_shared_with_fpu_regs_stub_, \
StubCode::NullErrorSharedWithFPURegs().ptr(), nullptr) \
V(CodePtr, null_arg_error_shared_without_fpu_regs_stub_, \
StubCode::NullArgErrorSharedWithoutFPURegs().ptr(), nullptr) \
V(CodePtr, null_arg_error_shared_with_fpu_regs_stub_, \
StubCode::NullArgErrorSharedWithFPURegs().ptr(), nullptr) \
V(CodePtr, null_cast_error_shared_without_fpu_regs_stub_, \
StubCode::NullCastErrorSharedWithoutFPURegs().ptr(), nullptr) \
V(CodePtr, null_cast_error_shared_with_fpu_regs_stub_, \
StubCode::NullCastErrorSharedWithFPURegs().ptr(), nullptr) \
V(CodePtr, range_error_shared_without_fpu_regs_stub_, \
StubCode::RangeErrorSharedWithoutFPURegs().ptr(), nullptr) \
V(CodePtr, range_error_shared_with_fpu_regs_stub_, \
StubCode::RangeErrorSharedWithFPURegs().ptr(), nullptr) \
V(CodePtr, write_error_shared_without_fpu_regs_stub_, \
StubCode::WriteErrorSharedWithoutFPURegs().ptr(), nullptr) \
V(CodePtr, write_error_shared_with_fpu_regs_stub_, \
StubCode::WriteErrorSharedWithFPURegs().ptr(), nullptr) \
V(CodePtr, field_access_error_shared_without_fpu_regs_stub_, \
StubCode::FieldAccessErrorSharedWithoutFPURegs().ptr(), nullptr) \
V(CodePtr, field_access_error_shared_with_fpu_regs_stub_, \
StubCode::FieldAccessErrorSharedWithFPURegs().ptr(), nullptr) \
V(CodePtr, allocate_mint_with_fpu_regs_stub_, \
StubCode::AllocateMintSharedWithFPURegs().ptr(), nullptr) \
V(CodePtr, allocate_mint_without_fpu_regs_stub_, \
StubCode::AllocateMintSharedWithoutFPURegs().ptr(), nullptr) \
V(CodePtr, async_exception_handler_stub_, \
StubCode::AsyncExceptionHandler().ptr(), nullptr) \
V(CodePtr, resume_stub_, StubCode::Resume().ptr(), nullptr) \
V(CodePtr, return_async_stub_, StubCode::ReturnAsync().ptr(), nullptr) \
V(CodePtr, return_async_not_future_stub_, \
StubCode::ReturnAsyncNotFuture().ptr(), nullptr) \
V(CodePtr, return_async_star_stub_, StubCode::ReturnAsyncStar().ptr(), \
nullptr) \
V(CodePtr, stack_overflow_shared_without_fpu_regs_stub_, \
StubCode::StackOverflowSharedWithoutFPURegs().ptr(), nullptr) \
V(CodePtr, stack_overflow_shared_with_fpu_regs_stub_, \
StubCode::StackOverflowSharedWithFPURegs().ptr(), nullptr) \
V(CodePtr, switchable_call_miss_stub_, StubCode::SwitchableCallMiss().ptr(), \
nullptr) \
V(CodePtr, throw_stub_, StubCode::Throw().ptr(), nullptr) \
V(CodePtr, re_throw_stub_, StubCode::Throw().ptr(), nullptr) \
V(CodePtr, optimize_stub_, StubCode::OptimizeFunction().ptr(), nullptr) \
V(CodePtr, deoptimize_stub_, StubCode::Deoptimize().ptr(), nullptr) \
V(CodePtr, lazy_deopt_from_return_stub_, \
StubCode::DeoptimizeLazyFromReturn().ptr(), nullptr) \
V(CodePtr, lazy_deopt_from_throw_stub_, \
StubCode::DeoptimizeLazyFromThrow().ptr(), nullptr) \
V(CodePtr, slow_type_test_stub_, StubCode::SlowTypeTest().ptr(), nullptr) \
V(CodePtr, lazy_specialize_type_test_stub_, \
StubCode::LazySpecializeTypeTest().ptr(), nullptr) \
V(CodePtr, enter_safepoint_stub_, StubCode::EnterSafepoint().ptr(), nullptr) \
V(CodePtr, exit_safepoint_stub_, StubCode::ExitSafepoint().ptr(), nullptr) \
V(CodePtr, call_native_through_safepoint_stub_, \
StubCode::CallNativeThroughSafepoint().ptr(), nullptr)
#define CACHED_NON_VM_STUB_LIST(V) \
V(ObjectPtr, object_null_, Object::null(), nullptr) \
V(SentinelPtr, object_sentinel_, Object::sentinel().ptr(), nullptr) \
V(BoolPtr, bool_true_, Object::bool_true().ptr(), nullptr) \
V(BoolPtr, bool_false_, Object::bool_false().ptr(), nullptr) \
V(ArrayPtr, empty_array_, Object::empty_array().ptr(), nullptr) \
V(TypeArgumentsPtr, empty_type_arguments_, \
Object::empty_type_arguments().ptr(), nullptr) \
V(TypePtr, dynamic_type_, Type::dynamic_type().ptr(), nullptr)
// List of VM-global objects/addresses cached in each Thread object.
// Important: constant false must immediately follow constant true.
#define CACHED_VM_OBJECTS_LIST(V) \
CACHED_NON_VM_STUB_LIST(V) \
CACHED_VM_STUBS_LIST(V)
#define CACHED_FUNCTION_ENTRY_POINTS_LIST(V) \
V(suspend_state_init_async) \
V(suspend_state_await) \
V(suspend_state_await_with_type_check) \
V(suspend_state_return_async) \
V(suspend_state_return_async_not_future) \
V(suspend_state_init_async_star) \
V(suspend_state_yield_async_star) \
V(suspend_state_return_async_star) \
V(suspend_state_init_sync_star) \
V(suspend_state_suspend_sync_star_at_start) \
V(suspend_state_handle_exception)
// This assertion marks places which assume that boolean false immediate
// follows bool true in the CACHED_VM_OBJECTS_LIST
#define ASSERT_BOOL_FALSE_FOLLOWS_BOOL_TRUE() \
ASSERT((Thread::bool_true_offset() + kWordSize) == \
Thread::bool_false_offset());
#define CACHED_VM_STUBS_ADDRESSES_LIST(V) \
V(uword, write_barrier_entry_point_, StubCode::WriteBarrier().EntryPoint(), \
0) \
V(uword, array_write_barrier_entry_point_, \
StubCode::ArrayWriteBarrier().EntryPoint(), 0) \
V(uword, call_to_runtime_entry_point_, \
StubCode::CallToRuntime().EntryPoint(), 0) \
V(uword, allocate_mint_with_fpu_regs_entry_point_, \
StubCode::AllocateMintSharedWithFPURegs().EntryPoint(), 0) \
V(uword, allocate_mint_without_fpu_regs_entry_point_, \
StubCode::AllocateMintSharedWithoutFPURegs().EntryPoint(), 0) \
V(uword, allocate_object_entry_point_, \
StubCode::AllocateObject().EntryPoint(), 0) \
V(uword, allocate_object_parameterized_entry_point_, \
StubCode::AllocateObjectParameterized().EntryPoint(), 0) \
V(uword, allocate_object_slow_entry_point_, \
StubCode::AllocateObjectSlow().EntryPoint(), 0) \
V(uword, stack_overflow_shared_without_fpu_regs_entry_point_, \
StubCode::StackOverflowSharedWithoutFPURegs().EntryPoint(), 0) \
V(uword, stack_overflow_shared_with_fpu_regs_entry_point_, \
StubCode::StackOverflowSharedWithFPURegs().EntryPoint(), 0) \
V(uword, megamorphic_call_checked_entry_, \
StubCode::MegamorphicCall().EntryPoint(), 0) \
V(uword, switchable_call_miss_entry_, \
StubCode::SwitchableCallMiss().EntryPoint(), 0) \
V(uword, optimize_entry_, StubCode::OptimizeFunction().EntryPoint(), 0) \
V(uword, deoptimize_entry_, StubCode::Deoptimize().EntryPoint(), 0) \
V(uword, call_native_through_safepoint_entry_point_, \
StubCode::CallNativeThroughSafepoint().EntryPoint(), 0) \
V(uword, jump_to_frame_entry_point_, StubCode::JumpToFrame().EntryPoint(), \
0) \
V(uword, slow_type_test_entry_point_, StubCode::SlowTypeTest().EntryPoint(), \
0) \
V(uword, resume_interpreter_adjusted_entry_point_, \
StubCode::ResumeInterpreter().EntryPoint() + \
SuspendStubABI::kResumePcDistance, \
0)
#define CACHED_ADDRESSES_LIST(V) \
CACHED_VM_STUBS_ADDRESSES_LIST(V) \
V(uword, bootstrap_native_wrapper_entry_point_, \
NativeEntry::BootstrapNativeCallWrapperEntry(), 0) \
V(uword, no_scope_native_wrapper_entry_point_, \
NativeEntry::NoScopeNativeCallWrapperEntry(), 0) \
V(uword, auto_scope_native_wrapper_entry_point_, \
NativeEntry::AutoScopeNativeCallWrapperEntry(), 0) \
V(uword, interpret_call_entry_point_, RuntimeEntry::InterpretCallEntry(), 0) \
V(StringPtr*, predefined_symbols_address_, Symbols::PredefinedAddress(), \
nullptr) \
V(uword, double_nan_address_, reinterpret_cast<uword>(&double_nan_constant), \
0) \
V(uword, double_negate_address_, \
reinterpret_cast<uword>(&double_negate_constant), 0) \
V(uword, double_abs_address_, reinterpret_cast<uword>(&double_abs_constant), \
0) \
V(uword, float_not_address_, reinterpret_cast<uword>(&float_not_constant), \
0) \
V(uword, float_negate_address_, \
reinterpret_cast<uword>(&float_negate_constant), 0) \
V(uword, float_absolute_address_, \
reinterpret_cast<uword>(&float_absolute_constant), 0) \
V(uword, float_zerow_address_, \
reinterpret_cast<uword>(&float_zerow_constant), 0)
#define CACHED_CONSTANTS_LIST(V) \
CACHED_VM_OBJECTS_LIST(V) \
CACHED_ADDRESSES_LIST(V)
enum class ValidationPolicy {
kValidateFrames = 0,
kDontValidateFrames = 1,
};
enum class RuntimeCallDeoptAbility {
// There was no leaf call or a leaf call that can cause deoptimization
// after-call.
kCanLazyDeopt,
// There was a leaf call and the VM cannot cause deoptimize after-call.
kCannotLazyDeopt,
};
// The safepoint level a thread is on or a safepoint operation is requested for
//
// The higher the number the stronger the guarantees:
// * the time-to-safepoint latency increases with level
// * the frequency of hitting possible safe points decreases with level
enum SafepointLevel {
// Safe to GC
kGC,
// Safe to GC as well as Deopt.
kGCAndDeopt,
// Safe to GC, Deopt as well as Reload.
kGCAndDeoptAndReload,
// Number of levels.
kNumLevels,
// No safepoint.
kNoSafepoint,
};
// Accessed from generated code.
struct TsanUtils {
// Used to allow unwinding runtime C frames using longjmp() when throwing
// exceptions. This allows triggering the normal TSAN shadow stack unwinding
// implementation.
// -> See https://dartbug.com/47472#issuecomment-948235479 for details.
#if defined(USING_THREAD_SANITIZER)
void* setjmp_function = reinterpret_cast<void*>(&DART_SETJMP);
#else
// MSVC (on Windows) is not happy with getting address of purely intrinsic.
void* setjmp_function = nullptr;
#endif
jmp_buf* setjmp_buffer = nullptr;
uword exception_pc = 0;
uword exception_sp = 0;
uword exception_fp = 0;
static intptr_t setjmp_function_offset() {
return OFFSET_OF(TsanUtils, setjmp_function);
}
static intptr_t setjmp_buffer_offset() {
return OFFSET_OF(TsanUtils, setjmp_buffer);
}
static intptr_t exception_pc_offset() {
return OFFSET_OF(TsanUtils, exception_pc);
}
static intptr_t exception_sp_offset() {
return OFFSET_OF(TsanUtils, exception_sp);
}
static intptr_t exception_fp_offset() {
return OFFSET_OF(TsanUtils, exception_fp);
}
};
// A VM thread; may be executing Dart code or performing helper tasks like
// garbage collection or compilation. The Thread structure associated with
// a thread is allocated by ThreadRegistry::GetFromFreelistLocked either
// before entering an isolate or entering an isolate group, and destroyed
// automatically when the underlying OS thread exits.
class Thread : public ThreadState, public IntrusiveDListEntry<Thread> {
public:
// The kind of task this thread is performing. Sampled by the profiler.
enum TaskKind {
kUnknownTask = 0,
kMutatorTask,
kCompilerTask,
kMarkerTask,
kSweeperTask,
kCompactorTask,
kScavengerTask,
kSampleBlockTask,
kIncrementalCompactorTask,
kSpawnTask,
kIsolateGroupBoundCallbackTask,
};
~Thread();
// The currently executing thread, or nullptr if not yet initialized.
static Thread* Current() {
return static_cast<Thread*>(OSThread::CurrentVMThread());
}
// Whether there's any active state on the [thread] that needs to be preserved
// across `Thread::ExitIsolate()` and `Thread::EnterIsolate()`.
bool HasActiveState();
void AssertNonMutatorInvariants();
void AssertDartMutatorInvariants();
void AssertNonDartMutatorInvariants();
void AssertEmptyStackInvariants();
void AssertEmptyThreadInvariants();
// Makes the current thread enter 'isolate'.
static void EnterIsolate(Isolate* isolate);
// Makes the current thread exit its isolate.
static void ExitIsolate(bool isolate_shutdown = false);
static void EnterIsolateGroupAsHelper(IsolateGroup* isolate_group,
TaskKind kind,
bool bypass_safepoint);
static void ExitIsolateGroupAsHelper(bool bypass_safepoint);
static void EnterIsolateGroupAsNonMutator(IsolateGroup* isolate_group,
TaskKind kind);
static void ExitIsolateGroupAsNonMutator();
static void EnterIsolateGroupAsMutator(IsolateGroup* isolate_group,
bool bypass_safepoint);
static void ExitIsolateGroupAsMutator(bool bypass_safepoint);
// Empties the store buffer block into the isolate.
void ReleaseStoreBuffer();
void AcquireMarkingStack();
void ReleaseMarkingStack();
void SetStackLimit(uword value);
void ClearStackLimit();
// The stack limit used by stack overflow checks in generated code. Either the
// true OS thread's stack limit minus some headroom, or a special value to
// trigger interrupts.
uword stack_limit_address() const {
return reinterpret_cast<uword>(&stack_limit_);
}
static intptr_t stack_limit_offset() {
return OFFSET_OF(Thread, stack_limit_);
}
// The true stack limit for this OS thread minus some headroom. Used on ARM64
// to keep CSP/R31 signal-handler safe while Dart uses R15 as its stack
// pointer.
static intptr_t saved_stack_limit_offset() {
return OFFSET_OF(Thread, saved_stack_limit_);
}
uword saved_stack_limit() const { return saved_stack_limit_; }
#if defined(USING_SAFE_STACK)
// The SafeStack pointer during the top-most DartEntry. Needs to be restored
// on Dart throw like longjmp would.
uword saved_safestack() const { return saved_safestack_; }
void set_saved_safestack(uword ssp) { saved_safestack_ = ssp; }
#endif
// The ShadowCallStack pointer during the top-most entry frame. Needs to be
// restored on Dart throw like longjmp would.
uword saved_shadow_call_stack() const { return saved_shadow_call_stack_; }
void set_saved_shadow_call_stack(uword ssp) {
saved_shadow_call_stack_ = ssp;
}
static uword saved_shadow_call_stack_offset() {
return OFFSET_OF(Thread, saved_shadow_call_stack_);
}
// Stack overflow flags
enum {
kOsrRequest = 0x1, // Current stack overflow caused by OSR request.
};
uword write_barrier_mask() const { return write_barrier_mask_; }
uword heap_base() const {
#if defined(DART_COMPRESSED_POINTERS)
return heap_base_;
#else
return 0;
#endif
}
static intptr_t write_barrier_mask_offset() {
return OFFSET_OF(Thread, write_barrier_mask_);
}
#if defined(DART_COMPRESSED_POINTERS)
static intptr_t heap_base_offset() { return OFFSET_OF(Thread, heap_base_); }
#endif
static intptr_t stack_overflow_flags_offset() {
return OFFSET_OF(Thread, stack_overflow_flags_);
}
uint32_t IncrementAndGetStackOverflowCount() {
return ++stack_overflow_count_;
}
uint32_t IncrementAndGetRuntimeCallCount() { return ++runtime_call_count_; }
static uword stack_overflow_shared_stub_entry_point_offset(bool fpu_regs) {
return fpu_regs
? stack_overflow_shared_with_fpu_regs_entry_point_offset()
: stack_overflow_shared_without_fpu_regs_entry_point_offset();
}
static intptr_t safepoint_state_offset() {
return OFFSET_OF(Thread, safepoint_state_);
}
// Tag state is maintained on transitions.
enum {
// Always true in generated state.
kDidNotExit = 0,
// The VM exited the generated state through FFI.
// This can be true in both native and VM state.
kExitThroughFfi = 1,
// The VM exited the generated state through a runtime call.
// This can be true in both native and VM state.
kExitThroughRuntimeCall = 2,
};
uword exit_through_ffi() { return exit_through_ffi_; }
static intptr_t exit_through_ffi_offset() {
return OFFSET_OF(Thread, exit_through_ffi_);
}
TaskKind task_kind() const {
return task_kind_.load(std::memory_order_acquire);
}
void set_task_kind(TaskKind kind) {
task_kind_.store(kind, std::memory_order_release);
}
// Retrieves and clears the stack overflow flags. These are set by
// the generated code before the slow path runtime routine for a
// stack overflow is called.
uword GetAndClearStackOverflowFlags();
// Interrupt bits.
enum {
kVMInterrupt = 0x1, // Internal VM checks: safepoints, store buffers, etc.
kMessageInterrupt = 0x2, // An interrupt to process an out of band message.
kInterruptsMask = (kVMInterrupt | kMessageInterrupt),
};
void ScheduleInterrupts(uword interrupt_bits);
ErrorPtr HandleInterrupts();
ErrorPtr HandleInterrupts(uword interrupt_bits);
uword GetAndClearInterrupts();
bool HasScheduledInterrupts() const {
return (stack_limit_.load() & kInterruptsMask) != 0;
}
// Monitor corresponding to this thread.
Monitor* thread_lock() const { return &thread_lock_; }
// The reusable api local scope for this thread.
ApiLocalScope* api_reusable_scope() const { return api_reusable_scope_; }
void set_api_reusable_scope(ApiLocalScope* value) {
ASSERT(value == nullptr || api_reusable_scope_ == nullptr);
api_reusable_scope_ = value;
}
// The api local scope for this thread, this where all local handles
// are allocated.
ApiLocalScope* api_top_scope() const { return api_top_scope_; }
void set_api_top_scope(ApiLocalScope* value) { api_top_scope_ = value; }
static intptr_t api_top_scope_offset() {
return OFFSET_OF(Thread, api_top_scope_);
}
void EnterApiScope();
void ExitApiScope();
static intptr_t double_truncate_round_supported_offset() {
return OFFSET_OF(Thread, double_truncate_round_supported_);
}
static intptr_t tsan_utils_offset() { return OFFSET_OF(Thread, tsan_utils_); }
#if defined(USING_THREAD_SANITIZER)
uword exit_through_ffi() const { return exit_through_ffi_; }
TsanUtils* tsan_utils() const { return tsan_utils_; }
#endif // defined(USING_THREAD_SANITIZER)
// The isolate that this thread is operating on, or nullptr if none.
Isolate* isolate() const { return isolate_; }
static intptr_t isolate_offset() { return OFFSET_OF(Thread, isolate_); }
static intptr_t isolate_group_offset() {
return OFFSET_OF(Thread, isolate_group_);
}
// The isolate group that this thread is operating on, or nullptr if none.
IsolateGroup* isolate_group() const { return isolate_group_; }
static intptr_t field_table_values_offset() {
return OFFSET_OF(Thread, field_table_values_);
}
static intptr_t shared_field_table_values_offset() {
return OFFSET_OF(Thread, shared_field_table_values_);
}
bool IsDartMutatorThread() const { return task_kind_ == kMutatorTask; }
bool HasDartMutatorStack() const {
// The thread with dart mutator task might be temporarily
// occupied by a gc task.
return IsDartMutatorThread()
// mutator thread with isolate
|| scheduled_dart_mutator_isolate_ != nullptr
// mutator thread without isolate
|| top_exit_frame_info_ != 0;
}
// Returns the dart mutator [Isolate] this thread belongs to or nullptr.
//
// `isolate()` in comparison can return
// - `nullptr` for dart mutators (e.g. if the mutator runs under
// [NoActiveIsolateScope])
// - an incorrect isolate (e.g. if [ActiveIsolateScope] is used to seemingly
// enter another isolate)
Isolate* scheduled_dart_mutator_isolate() const {
return scheduled_dart_mutator_isolate_;
}
#if defined(DEBUG)
bool IsInsideCompiler() const { return inside_compiler_; }
#endif
// Offset of Dart TimelineStream object.
static intptr_t dart_stream_offset() {
return OFFSET_OF(Thread, dart_stream_);
}
// Offset of the Dart VM Service Extension StreamInfo object.
static intptr_t service_extension_stream_offset() {
return OFFSET_OF(Thread, service_extension_stream_);
}
// Is |this| executing Dart code?
bool IsExecutingDartCode() const;
// Has |this| exited Dart code?
bool HasExitedDartCode() const;
bool HasCompilerState() const { return compiler_state_ != nullptr; }
CompilerState& compiler_state() {
ASSERT(HasCompilerState());
return *compiler_state_;
}
HierarchyInfo* hierarchy_info() const {
ASSERT(isolate_group_ != nullptr);
return hierarchy_info_;
}
void set_hierarchy_info(HierarchyInfo* value) {
ASSERT(isolate_group_ != nullptr);
ASSERT((hierarchy_info_ == nullptr && value != nullptr) ||
(hierarchy_info_ != nullptr && value == nullptr));
hierarchy_info_ = value;
}
TypeUsageInfo* type_usage_info() const {
ASSERT(isolate_group_ != nullptr);
return type_usage_info_;
}
void set_type_usage_info(TypeUsageInfo* value) {
ASSERT(isolate_group_ != nullptr);
ASSERT((type_usage_info_ == nullptr && value != nullptr) ||
(type_usage_info_ != nullptr && value == nullptr));
type_usage_info_ = value;
}
CompilerTimings* compiler_timings() const { return compiler_timings_; }
void set_compiler_timings(CompilerTimings* stats) {
compiler_timings_ = stats;
}
int32_t no_callback_scope_depth() const { return no_callback_scope_depth_; }
void IncrementNoCallbackScopeDepth() {
ASSERT(no_callback_scope_depth_ < INT_MAX);
no_callback_scope_depth_ += 1;
}
void DecrementNoCallbackScopeDepth() {
ASSERT(no_callback_scope_depth_ > 0);
no_callback_scope_depth_ -= 1;
}
bool force_growth() const { return force_growth_scope_depth_ != 0; }
void IncrementForceGrowthScopeDepth() {
ASSERT(force_growth_scope_depth_ < INT_MAX);
force_growth_scope_depth_ += 1;
}
void DecrementForceGrowthScopeDepth() {
ASSERT(force_growth_scope_depth_ > 0);
force_growth_scope_depth_ -= 1;
}
bool is_unwind_in_progress() const { return is_unwind_in_progress_; }
void StartUnwindError() {
is_unwind_in_progress_ = true;
SetUnwindErrorInProgress(true);
}
#if defined(DEBUG)
void EnterCompiler() {
ASSERT(!IsInsideCompiler());
inside_compiler_ = true;
}
void LeaveCompiler() {
ASSERT(IsInsideCompiler());
inside_compiler_ = false;
}
#endif
void StoreBufferAddObject(ObjectPtr obj);
void StoreBufferAddObjectGC(ObjectPtr obj);
#if defined(TESTING)
bool StoreBufferContains(ObjectPtr obj) const {
return store_buffer_block_->Contains(obj);
}
#endif
void StoreBufferBlockProcess(StoreBuffer::ThresholdPolicy policy);
void StoreBufferReleaseGC();
void StoreBufferAcquireGC();
static intptr_t store_buffer_block_offset() {
return OFFSET_OF(Thread, store_buffer_block_);
}
bool is_marking() const { return old_marking_stack_block_ != nullptr; }
void MarkingStackAddObject(ObjectPtr obj);
void OldMarkingStackAddObject(ObjectPtr obj);
void NewMarkingStackAddObject(ObjectPtr obj);
void DeferredMarkingStackAddObject(ObjectPtr obj);
void OldMarkingStackBlockProcess();
void NewMarkingStackBlockProcess();
void DeferredMarkingStackBlockProcess();
static intptr_t old_marking_stack_block_offset() {
return OFFSET_OF(Thread, old_marking_stack_block_);
}
static intptr_t new_marking_stack_block_offset() {
return OFFSET_OF(Thread, new_marking_stack_block_);
}
uword top_exit_frame_info() const { return top_exit_frame_info_; }
void set_top_exit_frame_info(uword top_exit_frame_info) {
top_exit_frame_info_ = top_exit_frame_info;
}
static intptr_t top_exit_frame_info_offset() {
return OFFSET_OF(Thread, top_exit_frame_info_);
}
Heap* heap() const;
// The TLAB memory boundaries.
//
// When the heap sampling profiler is enabled, we use the TLAB boundary to
// trigger slow path allocations so we can take a sample. This means that
// true_end() >= end(), where true_end() is the actual end address of the
// TLAB and end() is the chosen sampling boundary for the thread.
//
// When the heap sampling profiler is disabled, true_end() == end().
uword top() const { return top_.load(std::memory_order_relaxed); }
uword end() const { return end_; }
uword true_end() const { return true_end_; }
void set_top(uword top) { top_.store(top, std::memory_order_relaxed); }
void set_end(uword end) { end_ = end; }
void set_true_end(uword true_end) { true_end_ = true_end; }
static intptr_t top_offset() { return OFFSET_OF(Thread, top_); }
static intptr_t end_offset() { return OFFSET_OF(Thread, end_); }
int32_t no_safepoint_scope_depth() const {
#if defined(DEBUG)
return no_safepoint_scope_depth_;
#else
return 0;
#endif
}
void IncrementNoSafepointScopeDepth() {
#if defined(DEBUG)
ASSERT(no_safepoint_scope_depth_ < INT_MAX);
no_safepoint_scope_depth_ += 1;
#endif
}
void DecrementNoSafepointScopeDepth() {
#if defined(DEBUG)
ASSERT(no_safepoint_scope_depth_ > 0);
no_safepoint_scope_depth_ -= 1;
#endif
}
bool IsInNoReloadScope() const { return no_reload_scope_depth_ > 0; }
bool IsInStoppedMutatorsScope() const {
return stopped_mutators_scope_depth_ > 0;
}
#define DEFINE_OFFSET_METHOD(type_name, member_name, expr, default_init_value) \
static intptr_t member_name##offset() { \
return OFFSET_OF(Thread, member_name); \
}
CACHED_CONSTANTS_LIST(DEFINE_OFFSET_METHOD)
#undef DEFINE_OFFSET_METHOD
static intptr_t write_barrier_wrappers_thread_offset(Register reg) {
ASSERT((kDartAvailableCpuRegs & (1 << reg)) != 0);
intptr_t index = 0;
for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
if ((kDartAvailableCpuRegs & (1 << i)) == 0) continue;
if (i == reg) break;
++index;
}
return OFFSET_OF(Thread, write_barrier_wrappers_entry_points_) +
index * sizeof(uword);
}
static intptr_t WriteBarrierWrappersOffsetForRegister(Register reg) {
intptr_t index = 0;
for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
if ((kDartAvailableCpuRegs & (1 << i)) == 0) continue;
if (i == reg) {
return index * kStoreBufferWrapperSize;
}
++index;
}
UNREACHABLE();
return 0;
}
#define DEFINE_OFFSET_METHOD(name) \
static intptr_t name##_entry_point_offset() { \
return OFFSET_OF(Thread, name##_entry_point_); \
}
RUNTIME_ENTRY_LIST(DEFINE_OFFSET_METHOD)
#undef DEFINE_OFFSET_METHOD
#define DEFINE_OFFSET_METHOD(returntype, name, ...) \
static intptr_t name##_entry_point_offset() { \
return OFFSET_OF(Thread, name##_entry_point_); \
}
LEAF_RUNTIME_ENTRY_LIST(DEFINE_OFFSET_METHOD)
#undef DEFINE_OFFSET_METHOD
ObjectPoolPtr global_object_pool() const { return global_object_pool_; }
void set_global_object_pool(ObjectPoolPtr raw_value) {
global_object_pool_ = raw_value;
}
const uword* dispatch_table_array() const { return dispatch_table_array_; }
void set_dispatch_table_array(const uword* array) {
dispatch_table_array_ = array;
}
static bool CanLoadFromThread(const Object& object);
static intptr_t OffsetFromThread(const Object& object);
static bool ObjectAtOffset(intptr_t offset, Object* object);
static intptr_t OffsetFromThread(const RuntimeEntry* runtime_entry);
#define DEFINE_OFFSET_METHOD(name) \
static intptr_t name##_entry_point_offset() { \
return OFFSET_OF(Thread, name##_entry_point_); \
}
CACHED_FUNCTION_ENTRY_POINTS_LIST(DEFINE_OFFSET_METHOD)
#undef DEFINE_OFFSET_METHOD
#if defined(DEBUG)
// For asserts only. Has false positives when running with a simulator or
// SafeStack.
bool TopErrorHandlerIsSetJump() const;
bool TopErrorHandlerIsExitFrame() const;
#endif
uword vm_tag() const { return vm_tag_; }
void set_vm_tag(uword tag) { vm_tag_ = tag; }
static intptr_t vm_tag_offset() { return OFFSET_OF(Thread, vm_tag_); }
int64_t unboxed_int64_runtime_arg() const {
return unboxed_runtime_arg_.int64_storage[0];
}
void set_unboxed_int64_runtime_arg(int64_t value) {
unboxed_runtime_arg_.int64_storage[0] = value;
}
int64_t unboxed_int64_runtime_second_arg() const {
return unboxed_runtime_arg_.int64_storage[1];
}
void set_unboxed_int64_runtime_second_arg(int64_t value) {
unboxed_runtime_arg_.int64_storage[1] = value;
}
double unboxed_double_runtime_arg() const {
return unboxed_runtime_arg_.double_storage[0];
}
void set_unboxed_double_runtime_arg(double value) {
unboxed_runtime_arg_.double_storage[0] = value;
}
simd128_value_t unboxed_simd128_runtime_arg() const {
return unboxed_runtime_arg_;
}
void set_unboxed_simd128_runtime_arg(simd128_value_t value) {
unboxed_runtime_arg_ = value;
}
static intptr_t unboxed_runtime_arg_offset() {
return OFFSET_OF(Thread, unboxed_runtime_arg_);
}
static intptr_t global_object_pool_offset() {
return OFFSET_OF(Thread, global_object_pool_);
}
static intptr_t dispatch_table_array_offset() {
return OFFSET_OF(Thread, dispatch_table_array_);
}
ObjectPtr active_exception() const { return active_exception_; }
void set_active_exception(const Object& value);
void set_active_exception(LocalHandle* value);
static intptr_t active_exception_offset() {
return OFFSET_OF(Thread, active_exception_);
}
ObjectPtr active_stacktrace() const { return active_stacktrace_; }
void set_active_stacktrace(const Object& value);
static intptr_t active_stacktrace_offset() {
return OFFSET_OF(Thread, active_stacktrace_);
}
uword resume_pc() const { return resume_pc_; }
void set_resume_pc(uword value) { resume_pc_ = value; }
static uword resume_pc_offset() { return OFFSET_OF(Thread, resume_pc_); }
ErrorPtr sticky_error() const;
void set_sticky_error(const Error& value);
void ClearStickyError();
DART_WARN_UNUSED_RESULT ErrorPtr StealStickyError();
#if defined(DEBUG)
#define REUSABLE_HANDLE_SCOPE_ACCESSORS(object) \
void set_reusable_##object##_handle_scope_active(bool value) { \
reusable_##object##_handle_scope_active_ = value; \
} \
bool reusable_##object##_handle_scope_active() const { \
return reusable_##object##_handle_scope_active_; \
}
REUSABLE_HANDLE_LIST(REUSABLE_HANDLE_SCOPE_ACCESSORS)
#undef REUSABLE_HANDLE_SCOPE_ACCESSORS
bool IsAnyReusableHandleScopeActive() const {
#define IS_REUSABLE_HANDLE_SCOPE_ACTIVE(object) \
if (reusable_##object##_handle_scope_active_) { \
return true; \
}
REUSABLE_HANDLE_LIST(IS_REUSABLE_HANDLE_SCOPE_ACTIVE)
return false;
#undef IS_REUSABLE_HANDLE_SCOPE_ACTIVE
}
#endif // defined(DEBUG)
void ClearReusableHandles();
#define REUSABLE_HANDLE(object) \
object& object##Handle() const { \
ASSERT(object##_handle_ != nullptr); \
return *object##_handle_; \
}
REUSABLE_HANDLE_LIST(REUSABLE_HANDLE)
#undef REUSABLE_HANDLE
static bool IsAtSafepoint(SafepointLevel level, uword state) {
const uword mask = AtSafepointBits(level);
return (state & mask) == mask;
}
// Whether the current thread is owning any safepoint level.
bool IsAtSafepoint() const {
// Owning a higher level safepoint implies owning the lower levels as well.
return IsAtSafepoint(SafepointLevel::kGC);
}
bool IsAtSafepoint(SafepointLevel level) const {
return IsAtSafepoint(level, safepoint_state_.load());
}
void SetAtSafepoint(bool value, SafepointLevel level) {
ASSERT(thread_lock()->IsOwnedByCurrentThread());
ASSERT(level <= current_safepoint_level());
if (value) {
safepoint_state_ |= AtSafepointBits(level);
} else {
safepoint_state_ &= ~AtSafepointBits(level);
}
}
bool IsSafepointRequestedLocked(SafepointLevel level) const {
ASSERT(thread_lock()->IsOwnedByCurrentThread());
return IsSafepointRequested(level);
}
bool IsSafepointRequested() const {
return IsSafepointRequested(current_safepoint_level());
}
bool IsSafepointRequested(SafepointLevel level) const {
const uword state = safepoint_state_.load();
for (intptr_t i = level; i >= 0; --i) {
if (IsSafepointLevelRequested(state, static_cast<SafepointLevel>(i)))
return true;
}
return false;
}
bool IsSafepointLevelRequestedLocked(SafepointLevel level) const {
ASSERT(thread_lock()->IsOwnedByCurrentThread());
if (level > current_safepoint_level()) return false;
const uword state = safepoint_state_.load();
return IsSafepointLevelRequested(state, level);
}
static bool IsSafepointLevelRequested(uword state, SafepointLevel level) {
switch (level) {
case SafepointLevel::kGC:
return SafepointRequestedField::decode(state);
case SafepointLevel::kGCAndDeopt:
return DeoptSafepointRequestedField::decode(state);
case SafepointLevel::kGCAndDeoptAndReload:
return ReloadSafepointRequestedField::decode(state);
default:
UNREACHABLE();
}
}
void BlockForSafepoint();
uword SetSafepointRequested(SafepointLevel level, bool value) {
ASSERT(thread_lock()->IsOwnedByCurrentThread());
uword mask = 0;
switch (level) {
case SafepointLevel::kGC:
mask = SafepointRequestedField::mask_in_place();
break;
case SafepointLevel::kGCAndDeopt:
mask = DeoptSafepointRequestedField::mask_in_place();
break;
case SafepointLevel::kGCAndDeoptAndReload:
mask = ReloadSafepointRequestedField::mask_in_place();
break;
default:
UNREACHABLE();
}
if (value) {
// acquire pulls from the release in TryEnterSafepoint.
return safepoint_state_.fetch_or(mask, std::memory_order_acquire);
} else {
// release pushes to the acquire in TryExitSafepoint.
return safepoint_state_.fetch_and(~mask, std::memory_order_release);
}
}
static bool IsBlockedForSafepoint(uword state) {
return BlockedForSafepointField::decode(state);
}
bool IsBlockedForSafepoint() const {
return BlockedForSafepointField::decode(safepoint_state_);
}
// Normally execution state is only accessed for the current thread.
NO_SANITIZE_THREAD
bool IsBlockedForSafepointCrossThreadForTesting() const {
return BlockedForSafepointField::decode(safepoint_state_);
}
void SetBlockedForSafepoint(bool value) {
ASSERT(thread_lock()->IsOwnedByCurrentThread());
const uword mask = BlockedForSafepointField::mask_in_place();
if (value) {
safepoint_state_.fetch_or(mask);
} else {
safepoint_state_.fetch_and(~mask);
}
}
bool BypassSafepoints() const {
return BypassSafepointsField::decode(safepoint_state_);
}
static uword SetBypassSafepoints(bool value, uword state) {
return BypassSafepointsField::update(value, state);
}
bool UnwindErrorInProgress() const {
return UnwindErrorInProgressField::decode(safepoint_state_);
}
void SetUnwindErrorInProgress(bool value) {
const uword mask = UnwindErrorInProgressField::mask_in_place();
if (value) {
safepoint_state_.fetch_or(mask);
} else {
safepoint_state_.fetch_and(~mask);
}
}
void SetNoReloadScope(bool value) {
const uword mask = NoReloadScopeField::mask_in_place();
if (value) {
safepoint_state_.fetch_or(mask);
} else {
safepoint_state_.fetch_and(~mask);
}
}
bool OwnsGCSafepoint() const;
bool OwnsReloadSafepoint() const;
bool OwnsDeoptSafepoint() const;
bool OwnsSafepoint() const;
bool CanAcquireSafepointLocks() const;
uword safepoint_state() { return safepoint_state_; }
enum ExecutionState {
kThreadInVM = 0,
kThreadInGenerated,
kThreadInNative,
kThreadInBlockedState,
kThreadInReloadableBlockedState
};
ExecutionState execution_state() const {
return static_cast<ExecutionState>(execution_state_);
}
void set_execution_state(ExecutionState state) {
execution_state_ = static_cast<uword>(state);
}
static intptr_t execution_state_offset() {
return OFFSET_OF(Thread, execution_state_);
}
virtual bool MayAllocateHandles() {
return (execution_state() == kThreadInVM) ||
(execution_state() == kThreadInGenerated);
}
static uword native_safepoint_state_unacquired() {
return AtSafepointField::encode(false) |
AtDeoptSafepointField::encode(false) |
#if !defined(PRODUCT)
AtReloadSafepointField::encode(false) |
#endif
ActiveMutatorStealableField::encode(false) |
NoReloadScopeField::encode(false);
}
static uword native_safepoint_state_acquired() {
return AtSafepointField::encode(true) |
AtDeoptSafepointField::encode(true) |
#if !defined(PRODUCT)
AtReloadSafepointField::encode(true) |
#endif
ActiveMutatorStealableField::encode(true) |
NoReloadScopeField::encode(false);
}
bool TryStealActiveMutator() {
uword old_state = safepoint_state_.load();
if (!ActiveMutatorStealableField::decode(old_state)) return false;
uword new_state = old_state;
new_state = ActiveMutatorStealableField::update(false, new_state);
new_state = ActiveMutatorStolenField::update(true, new_state);
return safepoint_state_.compare_exchange_strong(old_state, new_state,
std::memory_order_relaxed);
}
bool TryEnterSafepointToNative() {
uword old_state = native_safepoint_state_unacquired();
uword new_state = native_safepoint_state_acquired();
return safepoint_state_.compare_exchange_strong(old_state, new_state,
std::memory_order_release);
}
void EnterSafepointToNative() {
ASSERT(no_safepoint_scope_depth() == 0);
// First try a fast update of the thread state to indicate it is at a
// safepoint.
if (!TryEnterSafepointToNative()) {
// Fast update failed which means we could potentially be in the middle
// of a safepoint operation.
EnterSafepointUsingLock();
if (!NoReloadScopeField::decode(safepoint_state_)) {
safepoint_state_.fetch_or(ActiveMutatorStealableField::encode(true));
}
}
}
bool TryEnterSafepoint() {
uword old_state = 0;
uword new_state = AtSafepointBits(current_safepoint_level());
return safepoint_state_.compare_exchange_strong(old_state, new_state,
std::memory_order_release);
}
void EnterSafepoint() {
ASSERT(no_safepoint_scope_depth() == 0);
// First try a fast update of the thread state to indicate it is at a
// safepoint.
if (!TryEnterSafepoint()) {
// Fast update failed which means we could potentially be in the middle
// of a safepoint operation.
EnterSafepointUsingLock();
}
}
bool TryExitSafepoint() {
uword old_state = AtSafepointBits(current_safepoint_level());
uword new_state = 0;
return safepoint_state_.compare_exchange_strong(old_state, new_state,
std::memory_order_acquire);
}
void ExitSafepoint() {
ASSERT(!ActiveMutatorStealableField::decode(safepoint_state_));
ASSERT(!ActiveMutatorStolenField::decode(safepoint_state_));
// First try a fast update of the thread state to indicate it is not at a
// safepoint anymore.
if (!TryExitSafepoint()) {
// Fast update failed which means we could potentially be in the middle
// of a safepoint operation.
ExitSafepointUsingLock();
}
#ifndef PRODUCT
// Exit frame must have remained valid for the whole duration of the
// safepoint. Do some quick checks to validate that.
if (FLAG_use_slow_path) {
ValidateExitFrameState();
}
#endif
}
bool TryExitSafepointFromNative() {
uword old_state = native_safepoint_state_acquired();
uword new_state = native_safepoint_state_unacquired();
return safepoint_state_.compare_exchange_strong(old_state, new_state,
std::memory_order_acquire);
}
void ExitSafepointFromNative() {
if (!TryExitSafepointFromNative()) {
ExitSafepointUsingLock();
uword old_state = safepoint_state_.fetch_and(
~(ActiveMutatorStealableField::encode(true) |
ActiveMutatorStolenField::encode(true)));
if (ActiveMutatorStolenField::decode(old_state)) {
set_execution_state(Thread::kThreadInVM);
allow_reload_scope_depth_++;
HandleStolen();
allow_reload_scope_depth_--;
}
}
ASSERT(!ActiveMutatorStealableField::decode(safepoint_state_));
ASSERT(!ActiveMutatorStolenField::decode(safepoint_state_));
#ifndef PRODUCT
// Exit frame must have remained valid for the whole duration of the
// safepoint. Do some quick checks to validate that.
if (FLAG_use_slow_path) {
ValidateExitFrameState();
}
#endif
}
void HandleStolen();
#ifndef PRODUCT
void ValidateExitFrameState();
#endif
void CheckForSafepoint() {
// If we are in a runtime call that doesn't support lazy deopt, we will only
// respond to gc safepointing requests.
ASSERT(no_safepoint_scope_depth() == 0);
if (IsSafepointRequested()) {
bool stolen = ActiveMutatorStolenField::decode(safepoint_state_.load());
ASSERT(!stolen);
BlockForSafepoint();
}
}
Thread* next() const { return next_; }
// Visit all object pointers.
void VisitObjectPointers(ObjectPointerVisitor* visitor,
ValidationPolicy validate_frames);
void RememberLiveTemporaries();
void DeferredMarkLiveTemporaries();
bool IsValidHandle(Dart_Handle object) const;
bool IsValidLocalHandle(Dart_Handle object) const;
intptr_t CountLocalHandles() const;
int ZoneSizeInBytes() const;
void UnwindScopes(uword stack_marker);
void InitVMConstants();
void FixInitiallyNullFields();
int64_t GetNextTaskId() { return next_task_id_++; }
static intptr_t next_task_id_offset() {
return OFFSET_OF(Thread, next_task_id_);
}
Random* random() { return &random_; }
static intptr_t random_offset() { return OFFSET_OF(Thread, random_); }
#ifndef PRODUCT
void PrintJSON(JSONStream* stream) const;
#endif
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
HeapProfileSampler& heap_sampler() { return heap_sampler_; }
#endif
PendingDeopts& pending_deopts() { return pending_deopts_; }
SafepointLevel current_safepoint_level() const {
if (runtime_call_deopt_ability_ ==
RuntimeCallDeoptAbility::kCannotLazyDeopt) {
return SafepointLevel::kGC;
}
#if defined(PRODUCT)
return SafepointLevel::kGCAndDeopt;
#else
if (no_reload_scope_depth_ > 0) {
return SafepointLevel::kGCAndDeopt;
}
if (execution_state_ == kThreadInNative ||
execution_state_ == kThreadInReloadableBlockedState) {
return SafepointLevel::kGCAndDeoptAndReload;
}
if (allow_reload_scope_depth_ <= 0) {
return SafepointLevel::kGCAndDeopt;
}
return SafepointLevel::kGCAndDeoptAndReload;
#endif
}
#if defined(DART_DYNAMIC_MODULES)
Interpreter* interpreter() const { return interpreter_; }
void set_interpreter(Interpreter* value) { interpreter_ = value; }
bytecode::BytecodeLoader* bytecode_loader() const { return bytecode_loader_; }
void set_bytecode_loader(bytecode::BytecodeLoader* value) {
bytecode_loader_ = value;
}
#endif
void set_single_step(bool value) { single_step_ = value; }
bool single_step() const { return single_step_; }
static intptr_t single_step_offset() {
return OFFSET_OF(Thread, single_step_);
}
bool IsDeoptimizing() const { return deopt_context_ != nullptr; }
DeoptContext* deopt_context() const { return deopt_context_; }
void set_deopt_context(DeoptContext* value) {
ASSERT(value == nullptr || deopt_context_ == nullptr);
deopt_context_ = value;
}
// The weak table used in the snapshot writer for the purpose of fast message
// sending.
WeakTable* forward_table_new() { return forward_table_new_.get(); }
void set_forward_table_new(WeakTable* table);
WeakTable* forward_table_old() { return forward_table_old_.get(); }
void set_forward_table_old(WeakTable* table);
MallocGrowableArray<ObjectPtr>* pointers_to_verify_at_exit() {
return &pointers_to_verify_at_exit_;
}
uword user_tag() const { return user_tag_; }
static intptr_t user_tag_offset() { return OFFSET_OF(Thread, user_tag_); }
static intptr_t current_tag_offset() {
return OFFSET_OF(Thread, current_tag_);
}
static intptr_t default_tag_offset() {
return OFFSET_OF(Thread, default_tag_);
}
UserTagPtr current_tag() const { return current_tag_; }
void set_current_tag(const UserTag& tag);
UserTagPtr default_tag() const { return default_tag_; }
void set_default_tag(const UserTag& tag);
void set_user_tag(uword tag) { user_tag_ = tag; }
ArrayPtr thread_locals() const { return thread_locals_; }
void set_thread_locals(const Array& thread_locals);
static intptr_t thread_locals_offset() {
return OFFSET_OF(Thread, thread_locals_);
}
private:
template <class T>
T* AllocateReusableHandle();
enum class RestoreWriteBarrierInvariantOp {
kAddToRememberedSet,
kAddToDeferredMarkingStack
};
friend class RestoreWriteBarrierInvariantVisitor;
void RestoreWriteBarrierInvariant(RestoreWriteBarrierInvariantOp op);
// Set the current compiler state and return the previous compiler state.
CompilerState* SetCompilerState(CompilerState* state) {
CompilerState* previous = compiler_state_;
compiler_state_ = state;
return previous;
}
// Accessed from generated code.
// ** This block of fields must come first! **
// For AOT cross-compilation, we rely on these members having the same offsets
// in SIMARM(IA32) and ARM, and the same offsets in SIMARM64(X64) and ARM64.
// We use only word-sized fields to avoid differences in struct packing on the
// different architectures. See also CheckOffsets in dart.cc.
volatile RelaxedAtomic<uword> stack_limit_ = 0;
uword write_barrier_mask_;
#if defined(DART_COMPRESSED_POINTERS)
uword heap_base_ = 0;
#endif
std::atomic<uword> top_ = 0;
uword end_ = 0;
const uword* dispatch_table_array_ = nullptr;
ObjectPtr* field_table_values_ = nullptr;
ObjectPtr* shared_field_table_values_ = nullptr;
// Offsets up to this point can all fit in a byte on X64. All of the above
// fields are very abundantly accessed from code. Thus, keeping them first
// is important for code size (although code size on X64 is not a priority).
// State that is cached in the TLS for fast access in generated code.
#define DECLARE_MEMBERS(type_name, member_name, expr, default_init_value) \
type_name member_name;
CACHED_CONSTANTS_LIST(DECLARE_MEMBERS)
#undef DECLARE_MEMBERS
#define DECLARE_MEMBERS(name) uword name##_entry_point_;
RUNTIME_ENTRY_LIST(DECLARE_MEMBERS)
#undef DECLARE_MEMBERS
uword write_barrier_wrappers_entry_points_[kNumberOfDartAvailableCpuRegs];
#define DECLARE_MEMBERS(name) uword name##_entry_point_ = 0;
CACHED_FUNCTION_ENTRY_POINTS_LIST(DECLARE_MEMBERS)
#undef DECLARE_MEMBERS
Isolate* isolate_ = nullptr;
IsolateGroup* isolate_group_ = nullptr;
uword saved_stack_limit_ = OSThread::kInvalidStackLimit;
// The mutator uses this to indicate it wants to OSR (by
// setting [Thread::kOsrRequest]) before going to runtime which will see this
// bit.
uword stack_overflow_flags_ = 0;
uword volatile top_exit_frame_info_ = 0;
StoreBufferBlock* store_buffer_block_ = nullptr;
MarkingStackBlock* old_marking_stack_block_ = nullptr;
MarkingStackBlock* new_marking_stack_block_ = nullptr;
MarkingStackBlock* deferred_marking_stack_block_ = nullptr;
uword volatile vm_tag_ = 0;
// JumpToExceptionHandler state:
ObjectPtr active_exception_;
ObjectPtr active_stacktrace_;
ObjectPoolPtr global_object_pool_;
uword resume_pc_ = 0;
uword saved_shadow_call_stack_ = 0;
/*
* The execution state for a thread.
*
* Potential execution states a thread could be in:
* kThreadInGenerated - The thread is running jitted dart/stub code.
* kThreadInVM - The thread is running VM code.
* kThreadInNative - The thread is running native code.
* kThreadInBlockedState - The thread is blocked waiting for a resource.
*
* Warning: Execution state doesn't imply the safepoint state. It's possible
* to be in [kThreadInNative] and still not be at-safepoint (e.g. due to a
* pending Dart_TypedDataAcquire() that increases no-callback-scope)
*/
uword execution_state_ = kThreadInNative;
/*
* Stores
*
* - whether the thread is at a safepoint (current thread sets these)
* [AtSafepointField]
* [AtDeoptSafepointField]
* [AtReloadSafepointField]
*
* - whether the thread is requested to safepoint (other thread sets these)
* [SafepointRequestedField]
* [DeoptSafepointRequestedField]
* [ReloadSafepointRequestedField]
*
* - whether the thread is blocked due to safepoint request and needs to
* be resumed after safepoint is done (current thread sets this)
* [BlockedForSafepointField]
*
* - whether the thread should be ignored for safepointing purposes
* [BypassSafepointsField]
*
* - whether the isolate running this thread has triggered an unwind error,
* which requires enforced exit on a transition from native back to
* generated.
* [UnwindErrorInProgressField]
*
* - whether a NoReloadScope is active (current thread sets these), this
* causes transitions to native/FFI to take the slow path instead of
* entering a reload safepoint
* [NoReloadScopeField]
*/
std::atomic<uword> safepoint_state_ = 0;
uword exit_through_ffi_ = 0;
#define DECLARE_MEMBERS(returntype, name, ...) uword name##_entry_point_;
LEAF_RUNTIME_ENTRY_LIST(DECLARE_MEMBERS)
#undef DECLARE_MEMBERS
ApiLocalScope* api_top_scope_ = nullptr;
uint8_t double_truncate_round_supported_;
// Memory locations dedicated for passing unboxed int64 and double
// values from generated code to runtime.
// TODO(dartbug.com/33549): Clean this up when unboxed values
// could be passed as arguments.
ALIGN8 simd128_value_t unboxed_runtime_arg_;
ALIGN8 int64_t next_task_id_;
ALIGN8 Random random_;
TsanUtils* tsan_utils_ = nullptr;
bool single_step_ = false;
uword user_tag_ = 0;
UserTagPtr current_tag_;
UserTagPtr default_tag_;
TimelineStream* const dart_stream_;
StreamInfo* const service_extension_stream_;
ArrayPtr thread_locals_ = nullptr;
// ---- End accessed from generated code. ----
// The layout of Thread object up to this point should not depend
// on DART_PRECOMPILED_RUNTIME, as it is accessed from generated code.
// The code is generated without DART_PRECOMPILED_RUNTIME, but used with
// DART_PRECOMPILED_RUNTIME.
uword true_end_ = 0;
mutable Monitor thread_lock_;
ApiLocalScope* api_reusable_scope_ = nullptr;
std::atomic<TaskKind> task_kind_ = kUnknownTask;
int32_t no_callback_scope_depth_ = 0;
int32_t force_growth_scope_depth_ = 0;
int32_t no_reload_scope_depth_ = 0;
int32_t allow_reload_scope_depth_ = 0;
int32_t stopped_mutators_scope_depth_ = 0;
#if defined(DEBUG)
int32_t no_safepoint_scope_depth_ = 0;
#endif
uint32_t stack_overflow_count_ = 0;
uint32_t runtime_call_count_ = 0;
VMHandles reusable_handles_;
// Deoptimization of stack frames.
RuntimeCallDeoptAbility runtime_call_deopt_ability_ =
RuntimeCallDeoptAbility::kCanLazyDeopt;
PendingDeopts pending_deopts_;
// Compiler state:
CompilerState* compiler_state_ = nullptr;
HierarchyInfo* hierarchy_info_ = nullptr;
TypeUsageInfo* type_usage_info_ = nullptr;
NoActiveIsolateScope* no_active_isolate_scope_ = nullptr;
CompilerTimings* compiler_timings_ = nullptr;
ErrorPtr sticky_error_;
ObjectPtr* field_table_values() const { return field_table_values_; }
ObjectPtr* shared_field_table_values() const {
return shared_field_table_values_;
}
// Reusable handles support.
#define REUSABLE_HANDLE_FIELDS(object) object* object##_handle_;
REUSABLE_HANDLE_LIST(REUSABLE_HANDLE_FIELDS)
#undef REUSABLE_HANDLE_FIELDS
#if defined(DEBUG)
#define REUSABLE_HANDLE_SCOPE_VARIABLE(object) \
bool reusable_##object##_handle_scope_active_;
REUSABLE_HANDLE_LIST(REUSABLE_HANDLE_SCOPE_VARIABLE);
#undef REUSABLE_HANDLE_SCOPE_VARIABLE
#endif // defined(DEBUG)
using AtSafepointField = BitField<uword, bool>;
using SafepointRequestedField =
BitField<uword, bool, AtSafepointField::kNextBit>;
using AtDeoptSafepointField =
BitField<uword, bool, SafepointRequestedField::kNextBit>;
using DeoptSafepointRequestedField =
BitField<uword, bool, AtDeoptSafepointField::kNextBit>;
using AtReloadSafepointField =
BitField<uword, bool, DeoptSafepointRequestedField::kNextBit>;
using ReloadSafepointRequestedField =
BitField<uword, bool, AtReloadSafepointField::kNextBit>;
using ActiveMutatorStealableField =
BitField<uword, bool, ReloadSafepointRequestedField::kNextBit>;
using ActiveMutatorStolenField =
BitField<uword, bool, ActiveMutatorStealableField::kNextBit>;
using BlockedForSafepointField =
BitField<uword, bool, ActiveMutatorStolenField::kNextBit>;
using BypassSafepointsField =
BitField<uword, bool, BlockedForSafepointField::kNextBit>;
using UnwindErrorInProgressField =
BitField<uword, bool, BypassSafepointsField::kNextBit>;
using NoReloadScopeField =
BitField<uword, bool, UnwindErrorInProgressField::kNextBit>;
static uword AtSafepointBits(SafepointLevel level) {
switch (level) {
case SafepointLevel::kGC:
return AtSafepointField::encode(true);
case SafepointLevel::kGCAndDeopt:
return AtSafepointField::encode(true) |
AtDeoptSafepointField::encode(true);
case SafepointLevel::kGCAndDeoptAndReload:
return AtSafepointField::encode(true) |
AtDeoptSafepointField::encode(true) |
AtReloadSafepointField::encode(true);
default:
UNREACHABLE();
}
}
#if defined(USING_SAFE_STACK)
uword saved_safestack_ = 0;
#endif
Thread* next_; // Used to chain the thread structures in an isolate.
Isolate* scheduled_dart_mutator_isolate_ = nullptr;
bool is_unwind_in_progress_ = false;
#if defined(DEBUG)
bool inside_compiler_ = false;
#endif
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
HeapProfileSampler heap_sampler_;
#endif
#if defined(DART_DYNAMIC_MODULES)
Interpreter* interpreter_ = nullptr;
bytecode::BytecodeLoader* bytecode_loader_ = nullptr;
#endif
DeoptContext* deopt_context_ = nullptr;
// Used during message sending of messages between isolates.
std::unique_ptr<WeakTable> forward_table_new_;
std::unique_ptr<WeakTable> forward_table_old_;
MallocGrowableArray<ObjectPtr> pointers_to_verify_at_exit_;
explicit Thread(bool is_bootstrapping);
void StoreBufferRelease(
StoreBuffer::ThresholdPolicy policy = StoreBuffer::kCheckThreshold);
void StoreBufferAcquire();
void OldMarkingStackRelease();
void OldMarkingStackAcquire();
void NewMarkingStackRelease();
void NewMarkingStackAcquire();
void DeferredMarkingStackRelease();
void DeferredMarkingStackAcquire();
void AcquireMarkingStacks();
void ReleaseMarkingStacks();
void FlushMarkingStacks();
void set_safepoint_state(uint32_t value) { safepoint_state_ = value; }
void EnterSafepointUsingLock();
void ExitSafepointUsingLock();
void SetupStateLocked(TaskKind kind);
void ResetStateLocked();
void SetupMutatorState();
void ResetMutatorState();
void SetupDartMutatorState(Isolate* isolate);
void SetupDartMutatorStateDependingOnSnapshot(IsolateGroup* group);
void ResetDartMutatorState();
static void SuspendDartMutatorThreadInternal(Thread* thread,
VMTag::VMTagId tag);
static void ResumeDartMutatorThreadInternal(Thread* thread);
static void SuspendThreadInternal(Thread* thread, VMTag::VMTagId tag);
static void ResumeThreadInternal(Thread* thread);
// Adds a new active thread to thread registry while associating it
// with the given isolate (group).
//
// All existing safepoint operations are waited for before adding the thread
// to the thread registry.
//
// => Anyone who iterates the active threads will first have to get us to
// safepoint (but can access `Thread::isolate()`).
static Thread* AddActiveThread(IsolateGroup* group,
Isolate* isolate,
TaskKind task_kind,
bool bypass_safepoint);
// Releases an active thread from the thread registry.
//
// Thread needs to be at-safepoint.
static void FreeActiveThread(Thread* thread,
Isolate* isolate,
bool bypass_safepoint);
static void SetCurrent(Thread* current) { OSThread::SetCurrentTLS(current); }
#define REUSABLE_FRIEND_DECLARATION(name) \
friend class Reusable##name##HandleScope;
REUSABLE_HANDLE_LIST(REUSABLE_FRIEND_DECLARATION)
#undef REUSABLE_FRIEND_DECLARATION
friend class ApiZone;
friend class ActiveIsolateScope;
friend class Interpreter;
friend class InterruptChecker;
friend class Isolate;
friend class IsolateGroup;
friend class NoActiveIsolateScope;
friend class NoReloadScope;
friend class RawReloadParticipationScope;
friend class Simulator;
friend class StackZone;
friend class StoppedMutatorsScope;
friend class ThreadRegistry;
friend class CompilerState;
friend class compiler::target::Thread;
friend class FieldTable;
friend class RuntimeCallDeoptScope;
friend class Dart; // Calls SetupCachedEntryPoints after snapshot reading
friend class
TransitionGeneratedToVM; // IsSafepointRequested/BlockForSafepoint
friend class
TransitionVMToGenerated; // IsSafepointRequested/BlockForSafepoint
friend class MonitorLocker; // ExitSafepointUsingLock
friend Isolate* CreateWithinExistingIsolateGroup(IsolateGroup*,
const char*,
char**);
DISALLOW_COPY_AND_ASSIGN(Thread);
};
class RuntimeCallDeoptScope : public StackResource {
public:
RuntimeCallDeoptScope(Thread* thread, RuntimeCallDeoptAbility kind)
: StackResource(thread) {
// We cannot have nested calls into the VM without deopt support.
ASSERT(thread->runtime_call_deopt_ability_ ==
RuntimeCallDeoptAbility::kCanLazyDeopt);
thread->runtime_call_deopt_ability_ = kind;
}
virtual ~RuntimeCallDeoptScope() {
thread()->runtime_call_deopt_ability_ =
RuntimeCallDeoptAbility::kCanLazyDeopt;
}
private:
Thread* thread() {
return reinterpret_cast<Thread*>(StackResource::thread());
}
};
#if defined(DART_HOST_OS_WINDOWS)
// Clears the state of the current thread and frees the allocation.
void WindowsThreadCleanUp();
#endif
#if defined(DART_INCLUDE_PROFILER)
// Disable thread interrupts.
class DisableThreadInterruptsScope : public StackResource {
public:
explicit DisableThreadInterruptsScope(Thread* thread);
~DisableThreadInterruptsScope();
};
#else
class DisableThreadInterruptsScope : public StackResource {
public:
explicit DisableThreadInterruptsScope(Thread* thread)
: StackResource(thread) {}
~DisableThreadInterruptsScope() {}
};
#endif // defined(DART_INCLUDE_PROFILER)
// Within a NoSafepointScope, the thread must not reach any safepoint. Used
// around code that manipulates raw object pointers directly without handles.
#if defined(DEBUG)
class NoSafepointScope : public ThreadStackResource {
public:
explicit NoSafepointScope(Thread* thread = nullptr)
: ThreadStackResource(thread != nullptr ? thread : Thread::Current()) {
this->thread()->IncrementNoSafepointScopeDepth();
}
~NoSafepointScope() { thread()->DecrementNoSafepointScopeDepth(); }
private:
DISALLOW_COPY_AND_ASSIGN(NoSafepointScope);
};
#else // defined(DEBUG)
class NoSafepointScope : public ValueObject {
public:
explicit NoSafepointScope(Thread* thread = nullptr) {}
private:
DISALLOW_COPY_AND_ASSIGN(NoSafepointScope);
};
#endif // defined(DEBUG)
// Disables initiating a reload operation as well as participating in another
// threads reload operation.
//
// Reload triggered by a mutator thread happens by sending all other mutator
// threads (that are running) OOB messages to check into a safepoint. The thread
// initiating the reload operation will block until all mutators are at a reload
// safepoint.
//
// When running under this scope, the processing of those OOB messages will
// ignore reload safepoint checkin requests. Yet we'll have to ensure that the
// dropped message is still acted upon.
//
// => To solve this we make the [~NoReloadScope] destructor resend a new reload
// OOB request to itself (the [~NoReloadScope] destructor is not necessarily at
// well-defined place where reload can happen - those places will explicitly
// opt-in via [ReloadParticipationScope]).
//
class NoReloadScope : public ThreadStackResource {
public:
explicit NoReloadScope(Thread* thread);
~NoReloadScope();
private:
DISALLOW_COPY_AND_ASSIGN(NoReloadScope);
};
// Allows triggering reload safepoint operations as well as participating in
// reload operations (at safepoint checks).
//
// By-default safepoint checkins will not participate in reload operations, as
// reload has to happen at very well-defined places. This scope is intended
// for those places where we explicitly want to allow safepoint checkins to
// participate in reload operations (triggered by other threads).
//
// If there is any [NoReloadScope] active we will still disable the safepoint
// checkins to participate in reload.
//
// We also require the thread inititating a reload operation to explicitly
// opt-in via this scope.
class RawReloadParticipationScope {
public:
explicit RawReloadParticipationScope(Thread* thread) : thread_(thread) {
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
thread->allow_reload_scope_depth_++;
ASSERT(thread->allow_reload_scope_depth_ >= 0);
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
}
~RawReloadParticipationScope() {
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
thread_->allow_reload_scope_depth_ -= 1;
ASSERT(thread_->allow_reload_scope_depth_ >= 0);
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
}
private:
Thread* thread_;
DISALLOW_COPY_AND_ASSIGN(RawReloadParticipationScope);
};
using ReloadParticipationScope =
AsThreadStackResource<RawReloadParticipationScope>;
class StoppedMutatorsScope : public ThreadStackResource {
public:
explicit StoppedMutatorsScope(Thread* thread) : ThreadStackResource(thread) {
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
thread->stopped_mutators_scope_depth_++;
ASSERT(thread->stopped_mutators_scope_depth_ >= 0);
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
}
~StoppedMutatorsScope() {
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
thread()->stopped_mutators_scope_depth_ -= 1;
ASSERT(thread()->stopped_mutators_scope_depth_ >= 0);
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
}
private:
DISALLOW_COPY_AND_ASSIGN(StoppedMutatorsScope);
};
// Within a EnterCompilerScope, the thread must operate on cloned fields.
#if defined(DEBUG)
class EnterCompilerScope : public ThreadStackResource {
public:
explicit EnterCompilerScope(Thread* thread = nullptr)
: ThreadStackResource(thread != nullptr ? thread : Thread::Current()) {
previously_is_inside_compiler_ = this->thread()->IsInsideCompiler();
if (!previously_is_inside_compiler_) {
this->thread()->EnterCompiler();
}
}
~EnterCompilerScope() {
if (!previously_is_inside_compiler_) {
thread()->LeaveCompiler();
}
}
private:
bool previously_is_inside_compiler_;
DISALLOW_COPY_AND_ASSIGN(EnterCompilerScope);
};
#else // defined(DEBUG)
class EnterCompilerScope : public ValueObject {
public:
explicit EnterCompilerScope(Thread* thread = nullptr) {}
private:
DISALLOW_COPY_AND_ASSIGN(EnterCompilerScope);
};
#endif // defined(DEBUG)
// Within a LeaveCompilerScope, the thread must operate on cloned fields.
#if defined(DEBUG)
class LeaveCompilerScope : public ThreadStackResource {
public:
explicit LeaveCompilerScope(Thread* thread = nullptr)
: ThreadStackResource(thread != nullptr ? thread : Thread::Current()) {
previously_is_inside_compiler_ = this->thread()->IsInsideCompiler();
if (previously_is_inside_compiler_) {
this->thread()->LeaveCompiler();
}
}
~LeaveCompilerScope() {
if (previously_is_inside_compiler_) {
thread()->EnterCompiler();
}
}
private:
bool previously_is_inside_compiler_;
DISALLOW_COPY_AND_ASSIGN(LeaveCompilerScope);
};
#else // defined(DEBUG)
class LeaveCompilerScope : public ValueObject {
public:
explicit LeaveCompilerScope(Thread* thread = nullptr) {}
private:
DISALLOW_COPY_AND_ASSIGN(LeaveCompilerScope);
};
#endif // defined(DEBUG)
} // namespace dart
#endif // RUNTIME_VM_THREAD_H_