| // 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 Simulator; | |
| 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, | |
| Thread* suspended_thread = nullptr); | |
| 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_INCLUDE_SIMULATOR) | |
| Simulator* simulator() const { return simulator_; } | |
| void set_simulator(Simulator* value) { simulator_ = value; } | |
| #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_INCLUDE_SIMULATOR) | |
| Simulator* simulator_ = nullptr; | |
| #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_ |