| // 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. |
| |
| #include "vm/thread.h" |
| |
| #include "vm/cpu.h" |
| #include "vm/dart_api_state.h" |
| #include "vm/growable_array.h" |
| #include "vm/heap/safepoint.h" |
| #include "vm/isolate.h" |
| #include "vm/json_stream.h" |
| #include "vm/lockers.h" |
| #include "vm/log.h" |
| #include "vm/message_handler.h" |
| #include "vm/native_entry.h" |
| #include "vm/object.h" |
| #include "vm/object_store.h" |
| #include "vm/os_thread.h" |
| #include "vm/profiler.h" |
| #include "vm/runtime_entry.h" |
| #include "vm/service.h" |
| #include "vm/stub_code.h" |
| #include "vm/symbols.h" |
| #include "vm/thread_interrupter.h" |
| #include "vm/thread_registry.h" |
| #include "vm/timeline.h" |
| #include "vm/zone.h" |
| |
| #if !defined(DART_PRECOMPILED_RUNTIME) |
| #include "vm/ffi_callback_trampolines.h" |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) |
| |
| namespace dart { |
| |
| #if !defined(PRODUCT) |
| DECLARE_FLAG(bool, trace_service); |
| DECLARE_FLAG(bool, trace_service_verbose); |
| #endif // !defined(PRODUCT) |
| |
| Thread::~Thread() { |
| // We should cleanly exit any isolate before destruction. |
| ASSERT(isolate_ == nullptr); |
| ASSERT(store_buffer_block_ == nullptr); |
| ASSERT(marking_stack_block_ == nullptr); |
| // There should be no top api scopes at this point. |
| ASSERT(api_top_scope() == nullptr); |
| // Delete the resusable api scope if there is one. |
| if (api_reusable_scope_ != nullptr) { |
| delete api_reusable_scope_; |
| api_reusable_scope_ = nullptr; |
| } |
| |
| DO_IF_TSAN(delete tsan_utils_); |
| } |
| |
| #if defined(DEBUG) |
| #define REUSABLE_HANDLE_SCOPE_INIT(object) \ |
| reusable_##object##_handle_scope_active_(false), |
| #else |
| #define REUSABLE_HANDLE_SCOPE_INIT(object) |
| #endif // defined(DEBUG) |
| |
| #define REUSABLE_HANDLE_INITIALIZERS(object) object##_handle_(nullptr), |
| |
| Thread::Thread(bool is_vm_isolate) |
| : ThreadState(false), |
| stack_limit_(0), |
| write_barrier_mask_(UntaggedObject::kGenerationalBarrierMask), |
| heap_base_(0), |
| isolate_(nullptr), |
| dispatch_table_array_(nullptr), |
| saved_stack_limit_(0), |
| stack_overflow_flags_(0), |
| heap_(nullptr), |
| top_exit_frame_info_(0), |
| store_buffer_block_(nullptr), |
| marking_stack_block_(nullptr), |
| vm_tag_(0), |
| unboxed_int64_runtime_arg_(0), |
| unboxed_double_runtime_arg_(0.0), |
| active_exception_(Object::null()), |
| active_stacktrace_(Object::null()), |
| global_object_pool_(ObjectPool::null()), |
| resume_pc_(0), |
| execution_state_(kThreadInNative), |
| safepoint_state_(0), |
| ffi_callback_code_(GrowableObjectArray::null()), |
| ffi_callback_stack_return_(TypedData::null()), |
| api_top_scope_(nullptr), |
| double_truncate_round_supported_( |
| TargetCPUFeatures::double_truncate_round_supported() ? 1 : 0), |
| tsan_utils_(DO_IF_TSAN(new TsanUtils()) DO_IF_NOT_TSAN(nullptr)), |
| task_kind_(kUnknownTask), |
| dart_stream_(nullptr), |
| service_extension_stream_(nullptr), |
| thread_lock_(), |
| api_reusable_scope_(nullptr), |
| no_callback_scope_depth_(0), |
| #if defined(DEBUG) |
| no_safepoint_scope_depth_(0), |
| #endif |
| reusable_handles_(), |
| stack_overflow_count_(0), |
| hierarchy_info_(nullptr), |
| type_usage_info_(nullptr), |
| sticky_error_(Error::null()), |
| REUSABLE_HANDLE_LIST(REUSABLE_HANDLE_INITIALIZERS) |
| REUSABLE_HANDLE_LIST(REUSABLE_HANDLE_SCOPE_INIT) |
| #if defined(USING_SAFE_STACK) |
| saved_safestack_limit_(0), |
| #endif |
| next_(nullptr) { |
| #if defined(SUPPORT_TIMELINE) |
| dart_stream_ = Timeline::GetDartStream(); |
| ASSERT(dart_stream_ != nullptr); |
| #endif |
| #ifndef PRODUCT |
| service_extension_stream_ = &Service::extension_stream; |
| ASSERT(service_extension_stream_ != nullptr); |
| #endif |
| #define DEFAULT_INIT(type_name, member_name, init_expr, default_init_value) \ |
| member_name = default_init_value; |
| CACHED_CONSTANTS_LIST(DEFAULT_INIT) |
| #undef DEFAULT_INIT |
| |
| #if !defined(TARGET_ARCH_IA32) |
| for (intptr_t i = 0; i < kNumberOfDartAvailableCpuRegs; ++i) { |
| write_barrier_wrappers_entry_points_[i] = 0; |
| } |
| #endif |
| |
| #define DEFAULT_INIT(name) name##_entry_point_ = 0; |
| RUNTIME_ENTRY_LIST(DEFAULT_INIT) |
| #undef DEFAULT_INIT |
| |
| #define DEFAULT_INIT(returntype, name, ...) name##_entry_point_ = 0; |
| LEAF_RUNTIME_ENTRY_LIST(DEFAULT_INIT) |
| #undef DEFAULT_INIT |
| |
| // We cannot initialize the VM constants here for the vm isolate thread |
| // due to boot strapping issues. |
| if (!is_vm_isolate) { |
| InitVMConstants(); |
| } |
| |
| #if defined(DART_HOST_OS_FUCHSIA) |
| next_task_id_ = trace_generate_nonce(); |
| #else |
| next_task_id_ = Random::GlobalNextUInt64(); |
| #endif |
| } |
| |
| static const double double_nan_constant = NAN; |
| |
| static const struct ALIGN16 { |
| uint64_t a; |
| uint64_t b; |
| } double_negate_constant = {0x8000000000000000ULL, 0x8000000000000000ULL}; |
| |
| static const struct ALIGN16 { |
| uint64_t a; |
| uint64_t b; |
| } double_abs_constant = {0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL}; |
| |
| static const struct ALIGN16 { |
| uint32_t a; |
| uint32_t b; |
| uint32_t c; |
| uint32_t d; |
| } float_not_constant = {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}; |
| |
| static const struct ALIGN16 { |
| uint32_t a; |
| uint32_t b; |
| uint32_t c; |
| uint32_t d; |
| } float_negate_constant = {0x80000000, 0x80000000, 0x80000000, 0x80000000}; |
| |
| static const struct ALIGN16 { |
| uint32_t a; |
| uint32_t b; |
| uint32_t c; |
| uint32_t d; |
| } float_absolute_constant = {0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF}; |
| |
| static const struct ALIGN16 { |
| uint32_t a; |
| uint32_t b; |
| uint32_t c; |
| uint32_t d; |
| } float_zerow_constant = {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000}; |
| |
| void Thread::InitVMConstants() { |
| heap_base_ = Object::null()->heap_base(); |
| |
| #define ASSERT_VM_HEAP(type_name, member_name, init_expr, default_init_value) \ |
| ASSERT((init_expr)->IsOldObject()); |
| CACHED_VM_OBJECTS_LIST(ASSERT_VM_HEAP) |
| #undef ASSERT_VM_HEAP |
| |
| #define INIT_VALUE(type_name, member_name, init_expr, default_init_value) \ |
| ASSERT(member_name == default_init_value); \ |
| member_name = (init_expr); |
| CACHED_CONSTANTS_LIST(INIT_VALUE) |
| #undef INIT_VALUE |
| |
| #if !defined(TARGET_ARCH_IA32) |
| for (intptr_t i = 0; i < kNumberOfDartAvailableCpuRegs; ++i) { |
| write_barrier_wrappers_entry_points_[i] = |
| StubCode::WriteBarrierWrappers().EntryPoint() + |
| i * kStoreBufferWrapperSize; |
| } |
| #endif |
| |
| #define INIT_VALUE(name) \ |
| ASSERT(name##_entry_point_ == 0); \ |
| name##_entry_point_ = k##name##RuntimeEntry.GetEntryPoint(); |
| RUNTIME_ENTRY_LIST(INIT_VALUE) |
| #undef INIT_VALUE |
| |
| #define INIT_VALUE(returntype, name, ...) \ |
| ASSERT(name##_entry_point_ == 0); \ |
| name##_entry_point_ = k##name##RuntimeEntry.GetEntryPoint(); |
| LEAF_RUNTIME_ENTRY_LIST(INIT_VALUE) |
| #undef INIT_VALUE |
| |
| // Setup the thread specific reusable handles. |
| #define REUSABLE_HANDLE_ALLOCATION(object) \ |
| this->object##_handle_ = this->AllocateReusableHandle<object>(); |
| REUSABLE_HANDLE_LIST(REUSABLE_HANDLE_ALLOCATION) |
| #undef REUSABLE_HANDLE_ALLOCATION |
| } |
| |
| void Thread::set_active_exception(const Object& value) { |
| active_exception_ = value.ptr(); |
| } |
| |
| void Thread::set_active_stacktrace(const Object& value) { |
| active_stacktrace_ = value.ptr(); |
| } |
| |
| ErrorPtr Thread::sticky_error() const { |
| return sticky_error_; |
| } |
| |
| void Thread::set_sticky_error(const Error& value) { |
| ASSERT(!value.IsNull()); |
| sticky_error_ = value.ptr(); |
| } |
| |
| void Thread::ClearStickyError() { |
| sticky_error_ = Error::null(); |
| } |
| |
| ErrorPtr Thread::StealStickyError() { |
| NoSafepointScope no_safepoint; |
| ErrorPtr return_value = sticky_error_; |
| sticky_error_ = Error::null(); |
| return return_value; |
| } |
| |
| const char* Thread::TaskKindToCString(TaskKind kind) { |
| switch (kind) { |
| case kUnknownTask: |
| return "kUnknownTask"; |
| case kMutatorTask: |
| return "kMutatorTask"; |
| case kCompilerTask: |
| return "kCompilerTask"; |
| case kSweeperTask: |
| return "kSweeperTask"; |
| case kMarkerTask: |
| return "kMarkerTask"; |
| default: |
| UNREACHABLE(); |
| return ""; |
| } |
| } |
| |
| bool Thread::EnterIsolate(Isolate* isolate, bool is_nested_reenter) { |
| const bool kIsMutatorThread = true; |
| const bool kBypassSafepoint = false; |
| |
| is_nested_reenter = is_nested_reenter || |
| (isolate->mutator_thread() != nullptr && |
| isolate->mutator_thread()->top_exit_frame_info() != 0); |
| |
| Thread* thread = isolate->ScheduleThread(kIsMutatorThread, is_nested_reenter, |
| kBypassSafepoint); |
| if (thread != nullptr) { |
| ASSERT(thread->store_buffer_block_ == nullptr); |
| ASSERT(thread->isolate() == isolate); |
| ASSERT(thread->isolate_group() == isolate->group()); |
| thread->FinishEntering(kMutatorTask); |
| return true; |
| } |
| return false; |
| } |
| |
| void Thread::ExitIsolate(bool is_nested_exit) { |
| Thread* thread = Thread::Current(); |
| ASSERT(thread != nullptr); |
| ASSERT(thread->IsMutatorThread()); |
| ASSERT(thread->isolate() != nullptr); |
| ASSERT(thread->isolate_group() != nullptr); |
| DEBUG_ASSERT(!thread->IsAnyReusableHandleScopeActive()); |
| |
| thread->PrepareLeaving(); |
| |
| Isolate* isolate = thread->isolate(); |
| thread->set_vm_tag(isolate->is_runnable() ? VMTag::kIdleTagId |
| : VMTag::kLoadWaitTagId); |
| const bool kIsMutatorThread = true; |
| const bool kBypassSafepoint = false; |
| is_nested_exit = |
| is_nested_exit || (isolate->mutator_thread() != nullptr && |
| isolate->mutator_thread()->top_exit_frame_info() != 0); |
| isolate->UnscheduleThread(thread, kIsMutatorThread, is_nested_exit, |
| kBypassSafepoint); |
| } |
| |
| bool Thread::EnterIsolateAsHelper(Isolate* isolate, |
| TaskKind kind, |
| bool bypass_safepoint) { |
| ASSERT(kind != kMutatorTask); |
| const bool kIsMutatorThread = false; |
| const bool kIsNestedReenter = false; |
| Thread* thread = isolate->ScheduleThread(kIsMutatorThread, kIsNestedReenter, |
| bypass_safepoint); |
| if (thread != nullptr) { |
| ASSERT(!thread->IsMutatorThread()); |
| ASSERT(thread->isolate() == isolate); |
| ASSERT(thread->isolate_group() == isolate->group()); |
| thread->FinishEntering(kind); |
| return true; |
| } |
| return false; |
| } |
| |
| void Thread::ExitIsolateAsHelper(bool bypass_safepoint) { |
| Thread* thread = Thread::Current(); |
| ASSERT(thread != nullptr); |
| ASSERT(!thread->IsMutatorThread()); |
| ASSERT(thread->isolate() != nullptr); |
| ASSERT(thread->isolate_group() != nullptr); |
| |
| thread->PrepareLeaving(); |
| |
| Isolate* isolate = thread->isolate(); |
| ASSERT(isolate != nullptr); |
| const bool kIsMutatorThread = false; |
| const bool kIsNestedExit = false; |
| isolate->UnscheduleThread(thread, kIsMutatorThread, kIsNestedExit, |
| bypass_safepoint); |
| } |
| |
| bool Thread::EnterIsolateGroupAsHelper(IsolateGroup* isolate_group, |
| TaskKind kind, |
| bool bypass_safepoint) { |
| ASSERT(kind != kMutatorTask); |
| Thread* thread = isolate_group->ScheduleThread(bypass_safepoint); |
| if (thread != nullptr) { |
| ASSERT(!thread->IsMutatorThread()); |
| ASSERT(thread->isolate() == nullptr); |
| ASSERT(thread->isolate_group() == isolate_group); |
| thread->FinishEntering(kind); |
| return true; |
| } |
| return false; |
| } |
| |
| void Thread::ExitIsolateGroupAsHelper(bool bypass_safepoint) { |
| Thread* thread = Thread::Current(); |
| ASSERT(thread != nullptr); |
| ASSERT(!thread->IsMutatorThread()); |
| ASSERT(thread->isolate() == nullptr); |
| ASSERT(thread->isolate_group() != nullptr); |
| |
| thread->PrepareLeaving(); |
| |
| const bool kIsMutatorThread = false; |
| thread->isolate_group()->UnscheduleThread(thread, kIsMutatorThread, |
| bypass_safepoint); |
| } |
| |
| void Thread::ReleaseStoreBuffer() { |
| ASSERT(IsAtSafepoint()); |
| if (store_buffer_block_ == nullptr || store_buffer_block_->IsEmpty()) { |
| return; // Nothing to release. |
| } |
| // Prevent scheduling another GC by ignoring the threshold. |
| StoreBufferRelease(StoreBuffer::kIgnoreThreshold); |
| // Make sure to get an *empty* block; the isolate needs all entries |
| // at GC time. |
| // TODO(koda): Replace with an epilogue (PrepareAfterGC) that acquires. |
| store_buffer_block_ = isolate_group()->store_buffer()->PopEmptyBlock(); |
| } |
| |
| void Thread::SetStackLimit(uword limit) { |
| // The thread setting the stack limit is not necessarily the thread which |
| // the stack limit is being set on. |
| MonitorLocker ml(&thread_lock_); |
| if (!HasScheduledInterrupts()) { |
| // No interrupt pending, set stack_limit_ too. |
| stack_limit_.store(limit); |
| } |
| saved_stack_limit_ = limit; |
| } |
| |
| void Thread::ClearStackLimit() { |
| SetStackLimit(~static_cast<uword>(0)); |
| } |
| |
| static bool IsInterruptLimit(uword limit) { |
| return (limit & ~Thread::kInterruptsMask) == |
| (kInterruptStackLimit & ~Thread::kInterruptsMask); |
| } |
| |
| void Thread::ScheduleInterrupts(uword interrupt_bits) { |
| ASSERT((interrupt_bits & ~kInterruptsMask) == 0); // Must fit in mask. |
| |
| uword old_limit = stack_limit_.load(); |
| uword new_limit; |
| do { |
| if (IsInterruptLimit(old_limit)) { |
| new_limit = old_limit | interrupt_bits; |
| } else { |
| new_limit = (kInterruptStackLimit & ~kInterruptsMask) | interrupt_bits; |
| } |
| } while (!stack_limit_.compare_exchange_weak(old_limit, new_limit)); |
| } |
| |
| uword Thread::GetAndClearInterrupts() { |
| uword interrupt_bits = 0; |
| uword old_limit = stack_limit_.load(); |
| uword new_limit = saved_stack_limit_; |
| do { |
| if (IsInterruptLimit(old_limit)) { |
| interrupt_bits = interrupt_bits | (old_limit & kInterruptsMask); |
| } else { |
| return interrupt_bits; |
| } |
| } while (!stack_limit_.compare_exchange_weak(old_limit, new_limit)); |
| |
| return interrupt_bits; |
| } |
| |
| ErrorPtr Thread::HandleInterrupts() { |
| uword interrupt_bits = GetAndClearInterrupts(); |
| if ((interrupt_bits & kVMInterrupt) != 0) { |
| CheckForSafepoint(); |
| if (isolate_group()->store_buffer()->Overflowed()) { |
| // Evacuate: If the popular store buffer targets are copied instead of |
| // promoted, the store buffer won't shrink and a second scavenge will |
| // occur that does promote them. |
| heap()->CollectGarbage(this, GCType::kEvacuate, GCReason::kStoreBuffer); |
| } |
| |
| #if !defined(PRODUCT) |
| // Don't block system isolates to process CPU samples to avoid blocking |
| // them during critical tasks (e.g., initial compilation). |
| if (!Isolate::IsSystemIsolate(isolate())) { |
| // Processes completed SampleBlocks and sends CPU sample events over the |
| // service protocol when applicable. |
| SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer(); |
| if (sample_buffer != nullptr && sample_buffer->process_blocks()) { |
| sample_buffer->ProcessCompletedBlocks(); |
| } |
| } |
| #endif // !defined(PRODUCT) |
| } |
| if ((interrupt_bits & kMessageInterrupt) != 0) { |
| MessageHandler::MessageStatus status = |
| isolate()->message_handler()->HandleOOBMessages(); |
| if (status != MessageHandler::kOK) { |
| // False result from HandleOOBMessages signals that the isolate should |
| // be terminating. |
| if (FLAG_trace_isolates) { |
| OS::PrintErr( |
| "[!] Terminating isolate due to OOB message:\n" |
| "\tisolate: %s\n", |
| isolate()->name()); |
| } |
| return StealStickyError(); |
| } |
| } |
| return Error::null(); |
| } |
| |
| uword Thread::GetAndClearStackOverflowFlags() { |
| uword stack_overflow_flags = stack_overflow_flags_; |
| stack_overflow_flags_ = 0; |
| return stack_overflow_flags; |
| } |
| |
| void Thread::StoreBufferBlockProcess(StoreBuffer::ThresholdPolicy policy) { |
| StoreBufferRelease(policy); |
| StoreBufferAcquire(); |
| } |
| |
| void Thread::StoreBufferAddObject(ObjectPtr obj) { |
| ASSERT(this == Thread::Current()); |
| store_buffer_block_->Push(obj); |
| if (store_buffer_block_->IsFull()) { |
| StoreBufferBlockProcess(StoreBuffer::kCheckThreshold); |
| } |
| } |
| |
| void Thread::StoreBufferAddObjectGC(ObjectPtr obj) { |
| store_buffer_block_->Push(obj); |
| if (store_buffer_block_->IsFull()) { |
| StoreBufferBlockProcess(StoreBuffer::kIgnoreThreshold); |
| } |
| } |
| |
| void Thread::StoreBufferRelease(StoreBuffer::ThresholdPolicy policy) { |
| StoreBufferBlock* block = store_buffer_block_; |
| store_buffer_block_ = nullptr; |
| isolate_group()->store_buffer()->PushBlock(block, policy); |
| } |
| |
| void Thread::StoreBufferAcquire() { |
| store_buffer_block_ = isolate_group()->store_buffer()->PopNonFullBlock(); |
| } |
| |
| void Thread::MarkingStackBlockProcess() { |
| MarkingStackRelease(); |
| MarkingStackAcquire(); |
| } |
| |
| void Thread::DeferredMarkingStackBlockProcess() { |
| DeferredMarkingStackRelease(); |
| DeferredMarkingStackAcquire(); |
| } |
| |
| void Thread::MarkingStackAddObject(ObjectPtr obj) { |
| marking_stack_block_->Push(obj); |
| if (marking_stack_block_->IsFull()) { |
| MarkingStackBlockProcess(); |
| } |
| } |
| |
| void Thread::DeferredMarkingStackAddObject(ObjectPtr obj) { |
| deferred_marking_stack_block_->Push(obj); |
| if (deferred_marking_stack_block_->IsFull()) { |
| DeferredMarkingStackBlockProcess(); |
| } |
| } |
| |
| void Thread::MarkingStackRelease() { |
| MarkingStackBlock* block = marking_stack_block_; |
| marking_stack_block_ = nullptr; |
| write_barrier_mask_ = UntaggedObject::kGenerationalBarrierMask; |
| isolate_group()->marking_stack()->PushBlock(block); |
| } |
| |
| void Thread::MarkingStackAcquire() { |
| marking_stack_block_ = isolate_group()->marking_stack()->PopEmptyBlock(); |
| write_barrier_mask_ = UntaggedObject::kGenerationalBarrierMask | |
| UntaggedObject::kIncrementalBarrierMask; |
| } |
| |
| void Thread::DeferredMarkingStackRelease() { |
| MarkingStackBlock* block = deferred_marking_stack_block_; |
| deferred_marking_stack_block_ = nullptr; |
| isolate_group()->deferred_marking_stack()->PushBlock(block); |
| } |
| |
| void Thread::DeferredMarkingStackAcquire() { |
| deferred_marking_stack_block_ = |
| isolate_group()->deferred_marking_stack()->PopEmptyBlock(); |
| } |
| |
| bool Thread::CanCollectGarbage() const { |
| // We grow the heap instead of triggering a garbage collection when a |
| // thread is at a safepoint in the following situations : |
| // - background compiler thread finalizing and installing code |
| // - disassembly of the generated code is done after compilation |
| // So essentially we state that garbage collection is possible only |
| // when we are not at a safepoint. |
| return !IsAtSafepoint(); |
| } |
| |
| bool Thread::IsExecutingDartCode() const { |
| return (top_exit_frame_info() == 0) && VMTag::IsDartTag(vm_tag()); |
| } |
| |
| bool Thread::HasExitedDartCode() const { |
| return (top_exit_frame_info() != 0) && !VMTag::IsDartTag(vm_tag()); |
| } |
| |
| template <class C> |
| C* Thread::AllocateReusableHandle() { |
| C* handle = reinterpret_cast<C*>(reusable_handles_.AllocateScopedHandle()); |
| C::initializeHandle(handle, C::null()); |
| return handle; |
| } |
| |
| void Thread::ClearReusableHandles() { |
| #define CLEAR_REUSABLE_HANDLE(object) *object##_handle_ = object::null(); |
| REUSABLE_HANDLE_LIST(CLEAR_REUSABLE_HANDLE) |
| #undef CLEAR_REUSABLE_HANDLE |
| } |
| |
| void Thread::VisitObjectPointers(ObjectPointerVisitor* visitor, |
| ValidationPolicy validation_policy) { |
| ASSERT(visitor != nullptr); |
| |
| if (zone() != nullptr) { |
| zone()->VisitObjectPointers(visitor); |
| } |
| |
| // Visit objects in thread specific handles area. |
| reusable_handles_.VisitObjectPointers(visitor); |
| |
| visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&global_object_pool_)); |
| visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&active_exception_)); |
| visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&active_stacktrace_)); |
| visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&sticky_error_)); |
| visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&ffi_callback_code_)); |
| visitor->VisitPointer( |
| reinterpret_cast<ObjectPtr*>(&ffi_callback_stack_return_)); |
| |
| // Visit the api local scope as it has all the api local handles. |
| ApiLocalScope* scope = api_top_scope_; |
| while (scope != nullptr) { |
| scope->local_handles()->VisitObjectPointers(visitor); |
| scope = scope->previous(); |
| } |
| |
| // Only the mutator thread can run Dart code. |
| if (IsMutatorThread()) { |
| // The MarkTask, which calls this method, can run on a different thread. We |
| // therefore assume the mutator is at a safepoint and we can iterate its |
| // stack. |
| // TODO(vm-team): It would be beneficial to be able to ask the mutator |
| // thread whether it is in fact blocked at the moment (at a "safepoint") so |
| // we can safely iterate its stack. |
| // |
| // Unfortunately we cannot use `this->IsAtSafepoint()` here because that |
| // will return `false` even though the mutator thread is waiting for mark |
| // tasks (which iterate its stack) to finish. |
| const StackFrameIterator::CrossThreadPolicy cross_thread_policy = |
| StackFrameIterator::kAllowCrossThreadIteration; |
| |
| // Iterate over all the stack frames and visit objects on the stack. |
| StackFrameIterator frames_iterator(top_exit_frame_info(), validation_policy, |
| this, cross_thread_policy); |
| StackFrame* frame = frames_iterator.NextFrame(); |
| while (frame != nullptr) { |
| frame->VisitObjectPointers(visitor); |
| frame = frames_iterator.NextFrame(); |
| } |
| } else { |
| // We are not on the mutator thread. |
| RELEASE_ASSERT(top_exit_frame_info() == 0); |
| } |
| } |
| |
| class RestoreWriteBarrierInvariantVisitor : public ObjectPointerVisitor { |
| public: |
| RestoreWriteBarrierInvariantVisitor(IsolateGroup* group, |
| Thread* thread, |
| Thread::RestoreWriteBarrierInvariantOp op) |
| : ObjectPointerVisitor(group), |
| thread_(thread), |
| current_(Thread::Current()), |
| op_(op) {} |
| |
| void VisitPointers(ObjectPtr* first, ObjectPtr* last) { |
| for (; first != last + 1; first++) { |
| ObjectPtr obj = *first; |
| // Stores into new-space objects don't need a write barrier. |
| if (obj->IsSmiOrNewObject()) continue; |
| |
| // To avoid adding too much work into the remembered set, skip large |
| // arrays. Write barrier elimination will not remove the barrier |
| // if we can trigger GC between array allocation and store. |
| if (obj->GetClassId() == kArrayCid) { |
| const auto length = Smi::Value(Array::RawCast(obj)->untag()->length()); |
| if (length > Array::kMaxLengthForWriteBarrierElimination) { |
| continue; |
| } |
| } |
| |
| // Dart code won't store into VM-internal objects except Contexts and |
| // UnhandledExceptions. This assumption is checked by an assertion in |
| // WriteBarrierElimination::UpdateVectorForBlock. |
| if (!obj->IsDartInstance() && !obj->IsContext() && |
| !obj->IsUnhandledException()) |
| continue; |
| |
| // Dart code won't store into canonical instances. |
| if (obj->untag()->IsCanonical()) continue; |
| |
| // Objects in the VM isolate heap are immutable and won't be |
| // stored into. Check this condition last because there's no bit |
| // in the header for it. |
| if (obj->untag()->InVMIsolateHeap()) continue; |
| |
| switch (op_) { |
| case Thread::RestoreWriteBarrierInvariantOp::kAddToRememberedSet: |
| obj->untag()->EnsureInRememberedSet(current_); |
| if (current_->is_marking()) { |
| current_->DeferredMarkingStackAddObject(obj); |
| } |
| break; |
| case Thread::RestoreWriteBarrierInvariantOp::kAddToDeferredMarkingStack: |
| // Re-scan obj when finalizing marking. |
| current_->DeferredMarkingStackAddObject(obj); |
| break; |
| } |
| } |
| } |
| |
| void VisitCompressedPointers(uword heap_base, |
| CompressedObjectPtr* first, |
| CompressedObjectPtr* last) { |
| UNREACHABLE(); // Stack slots are not compressed. |
| } |
| |
| private: |
| Thread* const thread_; |
| Thread* const current_; |
| Thread::RestoreWriteBarrierInvariantOp op_; |
| }; |
| |
| // Write barrier elimination assumes that all live temporaries will be |
| // in the remembered set after a scavenge triggered by a non-Dart-call |
| // instruction (see Instruction::CanCallDart()), and additionally they will be |
| // in the deferred marking stack if concurrent marking started. Specifically, |
| // this includes any instruction which will always create an exit frame |
| // below the current frame before any other Dart frames. |
| // |
| // Therefore, to support this assumption, we scan the stack after a scavenge |
| // or when concurrent marking begins and add all live temporaries in |
| // Dart frames preceding an exit frame to the store buffer or deferred |
| // marking stack. |
| void Thread::RestoreWriteBarrierInvariant(RestoreWriteBarrierInvariantOp op) { |
| ASSERT(IsAtSafepoint()); |
| ASSERT(IsMutatorThread()); |
| |
| const StackFrameIterator::CrossThreadPolicy cross_thread_policy = |
| StackFrameIterator::kAllowCrossThreadIteration; |
| StackFrameIterator frames_iterator(top_exit_frame_info(), |
| ValidationPolicy::kDontValidateFrames, |
| this, cross_thread_policy); |
| RestoreWriteBarrierInvariantVisitor visitor(isolate_group(), this, op); |
| ObjectStore* object_store = isolate_group()->object_store(); |
| bool scan_next_dart_frame = false; |
| for (StackFrame* frame = frames_iterator.NextFrame(); frame != nullptr; |
| frame = frames_iterator.NextFrame()) { |
| if (frame->IsExitFrame()) { |
| scan_next_dart_frame = true; |
| } else if (frame->IsEntryFrame()) { |
| /* Continue searching. */ |
| } else if (frame->IsStubFrame()) { |
| const uword pc = frame->pc(); |
| if (Code::ContainsInstructionAt( |
| object_store->init_late_static_field_stub(), pc) || |
| Code::ContainsInstructionAt( |
| object_store->init_late_final_static_field_stub(), pc) || |
| Code::ContainsInstructionAt( |
| object_store->init_late_instance_field_stub(), pc) || |
| Code::ContainsInstructionAt( |
| object_store->init_late_final_instance_field_stub(), pc)) { |
| scan_next_dart_frame = true; |
| } |
| } else { |
| ASSERT(frame->IsDartFrame(/*validate=*/false)); |
| if (scan_next_dart_frame) { |
| frame->VisitObjectPointers(&visitor); |
| } |
| scan_next_dart_frame = false; |
| } |
| } |
| } |
| |
| void Thread::DeferredMarkLiveTemporaries() { |
| RestoreWriteBarrierInvariant( |
| RestoreWriteBarrierInvariantOp::kAddToDeferredMarkingStack); |
| } |
| |
| void Thread::RememberLiveTemporaries() { |
| RestoreWriteBarrierInvariant( |
| RestoreWriteBarrierInvariantOp::kAddToRememberedSet); |
| } |
| |
| bool Thread::CanLoadFromThread(const Object& object) { |
| // In order to allow us to use assembler helper routines with non-[Code] |
| // objects *before* stubs are initialized, we only loop ver the stubs if the |
| // [object] is in fact a [Code] object. |
| if (object.IsCode()) { |
| #define CHECK_OBJECT(type_name, member_name, expr, default_init_value) \ |
| if (object.ptr() == expr) { \ |
| return true; \ |
| } |
| CACHED_VM_STUBS_LIST(CHECK_OBJECT) |
| #undef CHECK_OBJECT |
| } |
| |
| // For non [Code] objects we check if the object equals to any of the cached |
| // non-stub entries. |
| #define CHECK_OBJECT(type_name, member_name, expr, default_init_value) \ |
| if (object.ptr() == expr) { \ |
| return true; \ |
| } |
| CACHED_NON_VM_STUB_LIST(CHECK_OBJECT) |
| #undef CHECK_OBJECT |
| return false; |
| } |
| |
| intptr_t Thread::OffsetFromThread(const Object& object) { |
| // In order to allow us to use assembler helper routines with non-[Code] |
| // objects *before* stubs are initialized, we only loop ver the stubs if the |
| // [object] is in fact a [Code] object. |
| if (object.IsCode()) { |
| #define COMPUTE_OFFSET(type_name, member_name, expr, default_init_value) \ |
| ASSERT((expr)->untag()->InVMIsolateHeap()); \ |
| if (object.ptr() == expr) { \ |
| return Thread::member_name##offset(); \ |
| } |
| CACHED_VM_STUBS_LIST(COMPUTE_OFFSET) |
| #undef COMPUTE_OFFSET |
| } |
| |
| // For non [Code] objects we check if the object equals to any of the cached |
| // non-stub entries. |
| #define COMPUTE_OFFSET(type_name, member_name, expr, default_init_value) \ |
| if (object.ptr() == expr) { \ |
| return Thread::member_name##offset(); \ |
| } |
| CACHED_NON_VM_STUB_LIST(COMPUTE_OFFSET) |
| #undef COMPUTE_OFFSET |
| |
| UNREACHABLE(); |
| return -1; |
| } |
| |
| bool Thread::ObjectAtOffset(intptr_t offset, Object* object) { |
| if (Isolate::Current() == Dart::vm_isolate()) { |
| // --disassemble-stubs runs before all the references through |
| // thread have targets |
| return false; |
| } |
| |
| #define COMPUTE_OFFSET(type_name, member_name, expr, default_init_value) \ |
| if (Thread::member_name##offset() == offset) { \ |
| *object = expr; \ |
| return true; \ |
| } |
| CACHED_VM_OBJECTS_LIST(COMPUTE_OFFSET) |
| #undef COMPUTE_OFFSET |
| return false; |
| } |
| |
| intptr_t Thread::OffsetFromThread(const RuntimeEntry* runtime_entry) { |
| #define COMPUTE_OFFSET(name) \ |
| if (runtime_entry->function() == k##name##RuntimeEntry.function()) { \ |
| return Thread::name##_entry_point_offset(); \ |
| } |
| RUNTIME_ENTRY_LIST(COMPUTE_OFFSET) |
| #undef COMPUTE_OFFSET |
| |
| #define COMPUTE_OFFSET(returntype, name, ...) \ |
| if (runtime_entry->function() == k##name##RuntimeEntry.function()) { \ |
| return Thread::name##_entry_point_offset(); \ |
| } |
| LEAF_RUNTIME_ENTRY_LIST(COMPUTE_OFFSET) |
| #undef COMPUTE_OFFSET |
| |
| UNREACHABLE(); |
| return -1; |
| } |
| |
| #if defined(DEBUG) |
| bool Thread::TopErrorHandlerIsSetJump() const { |
| if (long_jump_base() == nullptr) return false; |
| if (top_exit_frame_info_ == 0) return true; |
| #if defined(USING_SIMULATOR) || defined(USING_SAFE_STACK) |
| // False positives: simulator stack and native stack are unordered. |
| return true; |
| #else |
| return reinterpret_cast<uword>(long_jump_base()) < top_exit_frame_info_; |
| #endif |
| } |
| |
| bool Thread::TopErrorHandlerIsExitFrame() const { |
| if (top_exit_frame_info_ == 0) return false; |
| if (long_jump_base() == nullptr) return true; |
| #if defined(USING_SIMULATOR) || defined(USING_SAFE_STACK) |
| // False positives: simulator stack and native stack are unordered. |
| return true; |
| #else |
| return top_exit_frame_info_ < reinterpret_cast<uword>(long_jump_base()); |
| #endif |
| } |
| #endif // defined(DEBUG) |
| |
| bool Thread::IsValidHandle(Dart_Handle object) const { |
| return IsValidLocalHandle(object) || IsValidZoneHandle(object) || |
| IsValidScopedHandle(object); |
| } |
| |
| bool Thread::IsValidLocalHandle(Dart_Handle object) const { |
| ApiLocalScope* scope = api_top_scope_; |
| while (scope != nullptr) { |
| if (scope->local_handles()->IsValidHandle(object)) { |
| return true; |
| } |
| scope = scope->previous(); |
| } |
| return false; |
| } |
| |
| intptr_t Thread::CountLocalHandles() const { |
| intptr_t total = 0; |
| ApiLocalScope* scope = api_top_scope_; |
| while (scope != nullptr) { |
| total += scope->local_handles()->CountHandles(); |
| scope = scope->previous(); |
| } |
| return total; |
| } |
| |
| int Thread::ZoneSizeInBytes() const { |
| int total = 0; |
| ApiLocalScope* scope = api_top_scope_; |
| while (scope != nullptr) { |
| total += scope->zone()->SizeInBytes(); |
| scope = scope->previous(); |
| } |
| return total; |
| } |
| |
| void Thread::EnterApiScope() { |
| ASSERT(MayAllocateHandles()); |
| ApiLocalScope* new_scope = api_reusable_scope(); |
| if (new_scope == nullptr) { |
| new_scope = new ApiLocalScope(api_top_scope(), top_exit_frame_info()); |
| ASSERT(new_scope != nullptr); |
| } else { |
| new_scope->Reinit(this, api_top_scope(), top_exit_frame_info()); |
| set_api_reusable_scope(nullptr); |
| } |
| set_api_top_scope(new_scope); // New scope is now the top scope. |
| } |
| |
| void Thread::ExitApiScope() { |
| ASSERT(MayAllocateHandles()); |
| ApiLocalScope* scope = api_top_scope(); |
| ApiLocalScope* reusable_scope = api_reusable_scope(); |
| set_api_top_scope(scope->previous()); // Reset top scope to previous. |
| if (reusable_scope == nullptr) { |
| scope->Reset(this); // Reset the old scope which we just exited. |
| set_api_reusable_scope(scope); |
| } else { |
| ASSERT(reusable_scope != scope); |
| delete scope; |
| } |
| } |
| |
| void Thread::UnwindScopes(uword stack_marker) { |
| // Unwind all scopes using the same stack_marker, i.e. all scopes allocated |
| // under the same top_exit_frame_info. |
| ApiLocalScope* scope = api_top_scope_; |
| while (scope != nullptr && scope->stack_marker() != 0 && |
| scope->stack_marker() == stack_marker) { |
| api_top_scope_ = scope->previous(); |
| delete scope; |
| scope = api_top_scope_; |
| } |
| } |
| |
| void Thread::EnterSafepointUsingLock() { |
| isolate_group()->safepoint_handler()->EnterSafepointUsingLock(this); |
| } |
| |
| void Thread::ExitSafepointUsingLock() { |
| isolate_group()->safepoint_handler()->ExitSafepointUsingLock(this); |
| } |
| |
| void Thread::BlockForSafepoint() { |
| isolate_group()->safepoint_handler()->BlockForSafepoint(this); |
| } |
| |
| void Thread::FinishEntering(TaskKind kind) { |
| ASSERT(store_buffer_block_ == nullptr); |
| |
| task_kind_ = kind; |
| if (isolate_group()->marking_stack() != nullptr) { |
| // Concurrent mark in progress. Enable barrier for this thread. |
| MarkingStackAcquire(); |
| DeferredMarkingStackAcquire(); |
| } |
| |
| // TODO(koda): Use StoreBufferAcquire once we properly flush |
| // before Scavenge. |
| if (kind == kMutatorTask) { |
| StoreBufferAcquire(); |
| } else { |
| store_buffer_block_ = isolate_group()->store_buffer()->PopEmptyBlock(); |
| } |
| } |
| |
| void Thread::PrepareLeaving() { |
| ASSERT(store_buffer_block_ != nullptr); |
| ASSERT(execution_state() == Thread::kThreadInVM); |
| |
| task_kind_ = kUnknownTask; |
| if (is_marking()) { |
| MarkingStackRelease(); |
| DeferredMarkingStackRelease(); |
| } |
| StoreBufferRelease(); |
| } |
| |
| DisableThreadInterruptsScope::DisableThreadInterruptsScope(Thread* thread) |
| : StackResource(thread) { |
| if (thread != nullptr) { |
| OSThread* os_thread = thread->os_thread(); |
| ASSERT(os_thread != nullptr); |
| os_thread->DisableThreadInterrupts(); |
| } |
| } |
| |
| DisableThreadInterruptsScope::~DisableThreadInterruptsScope() { |
| if (thread() != nullptr) { |
| OSThread* os_thread = thread()->os_thread(); |
| ASSERT(os_thread != nullptr); |
| os_thread->EnableThreadInterrupts(); |
| } |
| } |
| |
| const intptr_t kInitialCallbackIdsReserved = 16; |
| int32_t Thread::AllocateFfiCallbackId() { |
| Zone* Z = Thread::Current()->zone(); |
| if (ffi_callback_code_ == GrowableObjectArray::null()) { |
| ffi_callback_code_ = GrowableObjectArray::New(kInitialCallbackIdsReserved); |
| } |
| const auto& array = GrowableObjectArray::Handle(Z, ffi_callback_code_); |
| array.Add(Code::Handle(Z, Code::null())); |
| const int32_t id = array.Length() - 1; |
| |
| // Allocate a native callback trampoline if necessary. |
| #if !defined(DART_PRECOMPILED_RUNTIME) |
| if (NativeCallbackTrampolines::Enabled()) { |
| auto* const tramps = isolate()->native_callback_trampolines(); |
| ASSERT(tramps->next_callback_id() == id); |
| tramps->AllocateTrampoline(); |
| } |
| #endif |
| |
| return id; |
| } |
| |
| void Thread::SetFfiCallbackCode(int32_t callback_id, const Code& code) { |
| Zone* Z = Thread::Current()->zone(); |
| |
| /// In AOT the callback ID might have been allocated during compilation but |
| /// 'ffi_callback_code_' is initialized to empty again when the program |
| /// starts. Therefore we may need to initialize or expand it to accomodate |
| /// the callback ID. |
| |
| if (ffi_callback_code_ == GrowableObjectArray::null()) { |
| ffi_callback_code_ = GrowableObjectArray::New(kInitialCallbackIdsReserved); |
| } |
| |
| const auto& array = GrowableObjectArray::Handle(Z, ffi_callback_code_); |
| |
| if (callback_id >= array.Length()) { |
| const int32_t capacity = array.Capacity(); |
| if (callback_id >= capacity) { |
| // Ensure both that we grow enough and an exponential growth strategy. |
| const int32_t new_capacity = |
| Utils::Maximum(callback_id + 1, capacity * 2); |
| array.Grow(new_capacity); |
| } |
| array.SetLength(callback_id + 1); |
| } |
| |
| array.SetAt(callback_id, code); |
| } |
| |
| void Thread::SetFfiCallbackStackReturn(int32_t callback_id, |
| intptr_t stack_return_delta) { |
| #if defined(TARGET_ARCH_IA32) |
| #else |
| UNREACHABLE(); |
| #endif |
| |
| Zone* Z = Thread::Current()->zone(); |
| |
| /// In AOT the callback ID might have been allocated during compilation but |
| /// 'ffi_callback_code_' is initialized to empty again when the program |
| /// starts. Therefore we may need to initialize or expand it to accomodate |
| /// the callback ID. |
| |
| if (ffi_callback_stack_return_ == TypedData::null()) { |
| ffi_callback_stack_return_ = TypedData::New( |
| kTypedDataInt8ArrayCid, kInitialCallbackIdsReserved, Heap::kOld); |
| } |
| |
| auto& array = TypedData::Handle(Z, ffi_callback_stack_return_); |
| |
| if (callback_id >= array.Length()) { |
| const int32_t capacity = array.Length(); |
| if (callback_id >= capacity) { |
| // Ensure both that we grow enough and an exponential growth strategy. |
| const int32_t new_capacity = |
| Utils::Maximum(callback_id + 1, capacity * 2); |
| const auto& new_array = TypedData::Handle( |
| Z, TypedData::New(kTypedDataUint8ArrayCid, new_capacity, Heap::kOld)); |
| for (intptr_t i = 0; i < capacity; i++) { |
| new_array.SetUint8(i, array.GetUint8(i)); |
| } |
| array ^= new_array.ptr(); |
| ffi_callback_stack_return_ = new_array.ptr(); |
| } |
| } |
| |
| ASSERT(callback_id < array.Length()); |
| array.SetUint8(callback_id, stack_return_delta); |
| } |
| |
| void Thread::VerifyCallbackIsolate(int32_t callback_id, uword entry) { |
| NoSafepointScope _; |
| |
| const GrowableObjectArrayPtr array = ffi_callback_code_; |
| if (array == GrowableObjectArray::null()) { |
| FATAL("Cannot invoke callback on incorrect isolate."); |
| } |
| |
| const SmiPtr length_smi = GrowableObjectArray::NoSafepointLength(array); |
| const intptr_t length = Smi::Value(length_smi); |
| |
| if (callback_id < 0 || callback_id >= length) { |
| FATAL("Cannot invoke callback on incorrect isolate."); |
| } |
| |
| if (entry != 0) { |
| CompressedObjectPtr* const code_array = |
| Array::DataOf(GrowableObjectArray::NoSafepointData(array)); |
| // RawCast allocates handles in ASSERTs. |
| const CodePtr code = static_cast<CodePtr>( |
| code_array[callback_id].Decompress(array.heap_base())); |
| if (!Code::ContainsInstructionAt(code, entry)) { |
| FATAL("Cannot invoke callback on incorrect isolate."); |
| } |
| } |
| } |
| |
| } // namespace dart |