6fe15f6df9
This works around bugs in UndefinedBehaviorSanitizer and Clang. Bug: b/28638298 Change-Id: I6be595f9664516019d28017d24559583a1ae3a21 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/144354 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
1049 lines
36 KiB
C++
1049 lines
36 KiB
C++
// Copyright (c) 2013, 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 <memory>
|
|
#include <utility>
|
|
|
|
#include "vm/dart.h"
|
|
|
|
#include "vm/clustered_snapshot.h"
|
|
#include "vm/code_observers.h"
|
|
#include "vm/compiler/runtime_offsets_extracted.h"
|
|
#include "vm/compiler/runtime_offsets_list.h"
|
|
#include "vm/cpu.h"
|
|
#include "vm/dart_api_state.h"
|
|
#include "vm/dart_entry.h"
|
|
#include "vm/debugger.h"
|
|
#include "vm/flags.h"
|
|
#include "vm/handles.h"
|
|
#include "vm/heap/become.h"
|
|
#include "vm/heap/freelist.h"
|
|
#include "vm/heap/heap.h"
|
|
#include "vm/heap/pointer_block.h"
|
|
#include "vm/isolate.h"
|
|
#include "vm/isolate_reload.h"
|
|
#include "vm/kernel_isolate.h"
|
|
#include "vm/malloc_hooks.h"
|
|
#include "vm/message_handler.h"
|
|
#include "vm/metrics.h"
|
|
#include "vm/native_entry.h"
|
|
#include "vm/object.h"
|
|
#include "vm/object_id_ring.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/port.h"
|
|
#include "vm/profiler.h"
|
|
#include "vm/reverse_pc_lookup_cache.h"
|
|
#include "vm/service_isolate.h"
|
|
#include "vm/simulator.h"
|
|
#include "vm/snapshot.h"
|
|
#include "vm/stack_frame.h"
|
|
#include "vm/stub_code.h"
|
|
#include "vm/symbols.h"
|
|
#include "vm/thread_interrupter.h"
|
|
#include "vm/thread_pool.h"
|
|
#include "vm/timeline.h"
|
|
#include "vm/virtual_memory.h"
|
|
#include "vm/zone.h"
|
|
|
|
namespace dart {
|
|
|
|
DECLARE_FLAG(bool, print_class_table);
|
|
DEFINE_FLAG(bool, keep_code, false, "Keep deoptimized code for profiling.");
|
|
DEFINE_FLAG(bool, trace_shutdown, false, "Trace VM shutdown on stderr");
|
|
DECLARE_FLAG(bool, strong);
|
|
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
DEFINE_FLAG(bool, print_llvm_constant_pool, false, "Print LLVM constant pool");
|
|
#endif
|
|
|
|
Isolate* Dart::vm_isolate_ = NULL;
|
|
int64_t Dart::start_time_micros_ = 0;
|
|
ThreadPool* Dart::thread_pool_ = NULL;
|
|
DebugInfo* Dart::pprof_symbol_generator_ = NULL;
|
|
ReadOnlyHandles* Dart::predefined_handles_ = NULL;
|
|
Snapshot::Kind Dart::vm_snapshot_kind_ = Snapshot::kInvalid;
|
|
Dart_ThreadExitCallback Dart::thread_exit_callback_ = NULL;
|
|
Dart_FileOpenCallback Dart::file_open_callback_ = NULL;
|
|
Dart_FileReadCallback Dart::file_read_callback_ = NULL;
|
|
Dart_FileWriteCallback Dart::file_write_callback_ = NULL;
|
|
Dart_FileCloseCallback Dart::file_close_callback_ = NULL;
|
|
Dart_EntropySource Dart::entropy_source_callback_ = NULL;
|
|
bool Dart::non_nullable_flag_ = false;
|
|
|
|
// Structure for managing read-only global handles allocation used for
|
|
// creating global read-only handles that are pre created and initialized
|
|
// for use across all isolates. Having these global pre created handles
|
|
// stored in the vm isolate ensures that we don't constantly create and
|
|
// destroy handles for read-only objects referred in the VM code
|
|
// (e.g: symbols, null object, empty array etc.)
|
|
// The ReadOnlyHandles C++ Wrapper around VMHandles which is a ValueObject is
|
|
// to ensure that the handles area is not trashed by automatic running of C++
|
|
// static destructors when 'exit()" is called by any isolate. There might be
|
|
// other isolates running at the same time and trashing the handles area will
|
|
// have unintended consequences.
|
|
class ReadOnlyHandles {
|
|
public:
|
|
ReadOnlyHandles() {}
|
|
|
|
private:
|
|
VMHandles handles_;
|
|
LocalHandles api_handles_;
|
|
|
|
friend class Dart;
|
|
DISALLOW_COPY_AND_ASSIGN(ReadOnlyHandles);
|
|
};
|
|
|
|
static void CheckOffsets() {
|
|
#if !defined(IS_SIMARM_X64)
|
|
// These offsets are embedded in precompiled instructions. We need the
|
|
// compiler and the runtime to agree.
|
|
bool ok = true;
|
|
#define CHECK_OFFSET(expr, offset) \
|
|
if ((expr) != (offset)) { \
|
|
OS::PrintErr("%s got %" Pd ", %s expected %" Pd "\n", #expr, \
|
|
static_cast<intptr_t>(expr), #offset, \
|
|
static_cast<intptr_t>(offset)); \
|
|
ok = false; \
|
|
}
|
|
|
|
#define CHECK_FIELD(Class, Name) CHECK_OFFSET(Class::Name(), Class##_##Name)
|
|
#define CHECK_ARRAY(Class, Name) \
|
|
CHECK_OFFSET(Class::ArrayTraits::elements_start_offset(), \
|
|
Class##_elements_start_offset) \
|
|
CHECK_OFFSET(Class::ArrayTraits::kElementSize, Class##_element_size)
|
|
#define CHECK_ARRAY_STRUCTFIELD(Class, Name, ElementOffsetName, FieldOffset)
|
|
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
#define CHECK_SIZEOF(Class, Name, What) \
|
|
CHECK_OFFSET(sizeof(What), AOT_##Class##_##Name)
|
|
#else
|
|
#define CHECK_SIZEOF(Class, Name, What) \
|
|
CHECK_OFFSET(sizeof(What), Class##_##Name)
|
|
#endif
|
|
|
|
#define CHECK_RANGE(Class, Name, Type, First, Last, Filter)
|
|
#define CHECK_CONSTANT(Class, Name) CHECK_OFFSET(Class::Name, Class##_##Name)
|
|
|
|
OFFSETS_LIST(CHECK_FIELD, CHECK_ARRAY, CHECK_ARRAY_STRUCTFIELD, CHECK_SIZEOF,
|
|
CHECK_RANGE, CHECK_CONSTANT, NOT_IN_PRECOMPILED_RUNTIME)
|
|
|
|
if (!ok) {
|
|
FATAL(
|
|
"CheckOffsets failed. Try updating offsets by running "
|
|
"./tools/run_offsets_extractor.sh");
|
|
}
|
|
#undef CHECK_FIELD
|
|
#undef CHECK_ARRAY
|
|
#undef CHECK_ARRAY_STRUCTFIELD
|
|
#undef CHECK_SIZEOF
|
|
#undef CHECK_RANGE
|
|
#undef CHECK_CONSTANT
|
|
#undef CHECK_OFFSET
|
|
#endif // !defined(IS_SIMARM_X64)
|
|
}
|
|
|
|
char* Dart::Init(const uint8_t* vm_isolate_snapshot,
|
|
const uint8_t* instructions_snapshot,
|
|
Dart_IsolateGroupCreateCallback create_group,
|
|
Dart_InitializeIsolateCallback initialize_isolate,
|
|
Dart_IsolateShutdownCallback shutdown,
|
|
Dart_IsolateCleanupCallback cleanup,
|
|
Dart_IsolateGroupCleanupCallback cleanup_group,
|
|
Dart_ThreadExitCallback thread_exit,
|
|
Dart_FileOpenCallback file_open,
|
|
Dart_FileReadCallback file_read,
|
|
Dart_FileWriteCallback file_write,
|
|
Dart_FileCloseCallback file_close,
|
|
Dart_EntropySource entropy_source,
|
|
Dart_GetVMServiceAssetsArchive get_service_assets,
|
|
bool start_kernel_isolate,
|
|
Dart_CodeObserver* observer) {
|
|
CheckOffsets();
|
|
// TODO(iposva): Fix race condition here.
|
|
if (vm_isolate_ != NULL || !Flags::Initialized()) {
|
|
return strdup("VM already initialized or flags not initialized.");
|
|
}
|
|
|
|
if (FLAG_causal_async_stacks && FLAG_lazy_async_stacks) {
|
|
return strdup(
|
|
"To use --lazy-async-stacks, please disable --causal-async-stacks!");
|
|
}
|
|
|
|
const Snapshot* snapshot = nullptr;
|
|
if (vm_isolate_snapshot != nullptr) {
|
|
snapshot = Snapshot::SetupFromBuffer(vm_isolate_snapshot);
|
|
if (snapshot == nullptr) {
|
|
return strdup("Invalid vm isolate snapshot seen");
|
|
}
|
|
}
|
|
|
|
// We are initializing the VM. We will take the VM-global flags used
|
|
// during snapshot generation time also at runtime (this avoids the need
|
|
// for the embedder to pass the same flags used during snapshot generation
|
|
// also to the runtime).
|
|
if (snapshot != nullptr) {
|
|
char* error =
|
|
SnapshotHeaderReader::InitializeGlobalVMFlagsFromSnapshot(snapshot);
|
|
if (error != nullptr) {
|
|
return error;
|
|
}
|
|
}
|
|
|
|
FrameLayout::Init();
|
|
|
|
set_thread_exit_callback(thread_exit);
|
|
SetFileCallbacks(file_open, file_read, file_write, file_close);
|
|
set_entropy_source_callback(entropy_source);
|
|
OS::Init();
|
|
NOT_IN_PRODUCT(CodeObservers::Init());
|
|
if (observer != nullptr) {
|
|
NOT_IN_PRODUCT(CodeObservers::RegisterExternal(*observer));
|
|
}
|
|
start_time_micros_ = OS::GetCurrentMonotonicMicros();
|
|
VirtualMemory::Init();
|
|
OSThread::Init();
|
|
Zone::Init();
|
|
#if defined(SUPPORT_TIMELINE)
|
|
Timeline::Init();
|
|
TimelineBeginEndScope tbes(Timeline::GetVMStream(), "Dart::Init");
|
|
#endif
|
|
IsolateGroup::Init();
|
|
Isolate::InitVM();
|
|
PortMap::Init();
|
|
FreeListElement::Init();
|
|
ForwardingCorpse::Init();
|
|
Api::Init();
|
|
NativeSymbolResolver::Init();
|
|
NOT_IN_PRODUCT(Profiler::Init());
|
|
SemiSpace::Init();
|
|
NOT_IN_PRODUCT(Metric::Init());
|
|
StoreBuffer::Init();
|
|
MarkingStack::Init();
|
|
|
|
#if defined(USING_SIMULATOR)
|
|
Simulator::Init();
|
|
#endif
|
|
// Create the read-only handles area.
|
|
ASSERT(predefined_handles_ == NULL);
|
|
predefined_handles_ = new ReadOnlyHandles();
|
|
// Create the VM isolate and finish the VM initialization.
|
|
ASSERT(thread_pool_ == NULL);
|
|
thread_pool_ = new ThreadPool();
|
|
{
|
|
ASSERT(vm_isolate_ == NULL);
|
|
ASSERT(Flags::Initialized());
|
|
const bool is_vm_isolate = true;
|
|
|
|
// Cache value of "non-nullable" experimental flag.
|
|
set_non_nullable_flag(KernelIsolate::GetExperimentalFlag("non-nullable"));
|
|
|
|
// Setup default flags for the VM isolate.
|
|
Dart_IsolateFlags api_flags;
|
|
Isolate::FlagsInitialize(&api_flags);
|
|
|
|
// We make a fake [IsolateGroupSource] here, since the "vm-isolate" is not
|
|
// really an isolate itself - it acts more as a container for VM-global
|
|
// objects.
|
|
std::unique_ptr<IsolateGroupSource> source(
|
|
new IsolateGroupSource(nullptr, kVmIsolateName, vm_isolate_snapshot,
|
|
instructions_snapshot, nullptr, -1, api_flags));
|
|
// ObjectStore should be created later, after null objects are initialized.
|
|
auto group = new IsolateGroup(std::move(source), /*embedder_data=*/nullptr,
|
|
/*object_store=*/nullptr);
|
|
group->CreateHeap(/*is_vm_isolate=*/true,
|
|
/*is_service_or_kernel_isolate=*/false);
|
|
IsolateGroup::RegisterIsolateGroup(group);
|
|
vm_isolate_ =
|
|
Isolate::InitIsolate(kVmIsolateName, group, api_flags, is_vm_isolate);
|
|
group->set_initial_spawn_successful();
|
|
|
|
// Verify assumptions about executing in the VM isolate.
|
|
ASSERT(vm_isolate_ == Isolate::Current());
|
|
ASSERT(vm_isolate_ == Thread::Current()->isolate());
|
|
|
|
Thread* T = Thread::Current();
|
|
ASSERT(T != NULL);
|
|
StackZone zone(T);
|
|
HandleScope handle_scope(T);
|
|
Object::InitNullAndBool(vm_isolate_);
|
|
vm_isolate_->set_object_store(new ObjectStore());
|
|
vm_isolate_->isolate_object_store()->Init();
|
|
vm_isolate_->isolate_group_->object_store_ =
|
|
vm_isolate_->object_store_shared_ptr_;
|
|
TargetCPUFeatures::Init();
|
|
Object::Init(vm_isolate_);
|
|
ArgumentsDescriptor::Init();
|
|
ICData::Init();
|
|
SubtypeTestCache::Init();
|
|
if (vm_isolate_snapshot != NULL) {
|
|
#if defined(SUPPORT_TIMELINE)
|
|
TimelineBeginEndScope tbes(Timeline::GetVMStream(), "ReadVMSnapshot");
|
|
#endif
|
|
ASSERT(snapshot != nullptr);
|
|
vm_snapshot_kind_ = snapshot->kind();
|
|
|
|
if (Snapshot::IncludesCode(vm_snapshot_kind_)) {
|
|
if (vm_snapshot_kind_ == Snapshot::kFullAOT) {
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
return strdup("JIT runtime cannot run a precompiled snapshot");
|
|
#endif
|
|
}
|
|
if (instructions_snapshot == NULL) {
|
|
return strdup("Missing instructions snapshot");
|
|
}
|
|
} else if (Snapshot::IsFull(vm_snapshot_kind_)) {
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
return strdup("Precompiled runtime requires a precompiled snapshot");
|
|
#else
|
|
StubCode::Init();
|
|
Object::FinishInit(vm_isolate_);
|
|
// MallocHooks can't be initialized until StubCode has been since stack
|
|
// trace generation relies on stub methods that are generated in
|
|
// StubCode::Init().
|
|
// TODO(bkonyi) Split initialization for stack trace collection from the
|
|
// initialization for the actual malloc hooks to increase accuracy of
|
|
// memory consumption statistics.
|
|
MallocHooks::Init();
|
|
#endif
|
|
} else {
|
|
return strdup("Invalid vm isolate snapshot seen");
|
|
}
|
|
FullSnapshotReader reader(snapshot, instructions_snapshot, T);
|
|
const Error& error = Error::Handle(reader.ReadVMSnapshot());
|
|
if (!error.IsNull()) {
|
|
// Must copy before leaving the zone.
|
|
return strdup(error.ToErrorCString());
|
|
}
|
|
|
|
ReversePcLookupCache::BuildAndAttachToIsolateGroup(vm_isolate_->group());
|
|
|
|
Object::FinishInit(vm_isolate_);
|
|
#if defined(SUPPORT_TIMELINE)
|
|
if (tbes.enabled()) {
|
|
tbes.SetNumArguments(2);
|
|
tbes.FormatArgument(0, "snapshotSize", "%" Pd, snapshot->length());
|
|
tbes.FormatArgument(
|
|
1, "heapSize", "%" Pd64,
|
|
vm_isolate_->heap()->UsedInWords(Heap::kOld) * kWordSize);
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
if (FLAG_trace_isolates) {
|
|
OS::PrintErr("Size of vm isolate snapshot = %" Pd "\n",
|
|
snapshot->length());
|
|
vm_isolate_->heap()->PrintSizes();
|
|
MegamorphicCacheTable::PrintSizes(vm_isolate_);
|
|
intptr_t size;
|
|
intptr_t capacity;
|
|
Symbols::GetStats(vm_isolate_, &size, &capacity);
|
|
OS::PrintErr("VM Isolate: Number of symbols : %" Pd "\n", size);
|
|
OS::PrintErr("VM Isolate: Symbol table capacity : %" Pd "\n", capacity);
|
|
}
|
|
} else {
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
return strdup("Precompiled runtime requires a precompiled snapshot");
|
|
#else
|
|
vm_snapshot_kind_ = Snapshot::kNone;
|
|
StubCode::Init();
|
|
Object::FinishInit(vm_isolate_);
|
|
// MallocHooks can't be initialized until StubCode has been since stack
|
|
// trace generation relies on stub methods that are generated in
|
|
// StubCode::Init().
|
|
// TODO(bkonyi) Split initialization for stack trace collection from the
|
|
// initialization for the actual malloc hooks to increase accuracy of
|
|
// memory consumption statistics.
|
|
MallocHooks::Init();
|
|
Symbols::Init(vm_isolate_);
|
|
#endif
|
|
}
|
|
// We need to initialize the constants here for the vm isolate thread due to
|
|
// bootstrapping issues.
|
|
T->InitVMConstants();
|
|
#if defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_X64)
|
|
// Dart VM requires at least SSE2.
|
|
if (!TargetCPUFeatures::sse2_supported()) {
|
|
return strdup("SSE2 is required.");
|
|
}
|
|
#endif
|
|
{
|
|
#if defined(SUPPORT_TIMELINE)
|
|
TimelineBeginEndScope tbes(Timeline::GetVMStream(), "FinalizeVMIsolate");
|
|
#endif
|
|
Object::FinalizeVMIsolate(vm_isolate_);
|
|
}
|
|
#if defined(DEBUG)
|
|
vm_isolate_->heap()->Verify(kRequireMarked);
|
|
#endif
|
|
}
|
|
// Allocate the "persistent" scoped handles for the predefined API
|
|
// values (such as Dart_True, Dart_False and Dart_Null).
|
|
Api::InitHandles();
|
|
|
|
Thread::ExitIsolate(); // Unregister the VM isolate from this thread.
|
|
Isolate::SetCreateGroupCallback(create_group);
|
|
Isolate::SetInitializeCallback_(initialize_isolate);
|
|
Isolate::SetShutdownCallback(shutdown);
|
|
Isolate::SetCleanupCallback(cleanup);
|
|
Isolate::SetGroupCleanupCallback(cleanup_group);
|
|
|
|
#ifndef PRODUCT
|
|
const bool support_service = true;
|
|
Service::SetGetServiceAssetsCallback(get_service_assets);
|
|
#else
|
|
const bool support_service = false;
|
|
#endif
|
|
|
|
const bool is_dart2_aot_precompiler =
|
|
FLAG_precompiled_mode && !kDartPrecompiledRuntime;
|
|
|
|
if (!is_dart2_aot_precompiler &&
|
|
(support_service || !kDartPrecompiledRuntime)) {
|
|
ServiceIsolate::Run();
|
|
}
|
|
|
|
#ifndef DART_PRECOMPILED_RUNTIME
|
|
if (start_kernel_isolate) {
|
|
KernelIsolate::InitializeState();
|
|
}
|
|
#endif // DART_PRECOMPILED_RUNTIME
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void DumpAliveIsolates(intptr_t num_attempts,
|
|
bool only_aplication_isolates) {
|
|
IsolateGroup::ForEach([&](IsolateGroup* group) {
|
|
group->ForEachIsolate([&](Isolate* isolate) {
|
|
if (!only_aplication_isolates || !Isolate::IsVMInternalIsolate(isolate)) {
|
|
OS::PrintErr("Attempt:%" Pd " waiting for isolate %s to check in\n",
|
|
num_attempts, isolate->name());
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
static bool OnlyVmIsolateLeft() {
|
|
intptr_t count = 0;
|
|
bool found_vm_isolate = false;
|
|
IsolateGroup::ForEach([&](IsolateGroup* group) {
|
|
group->ForEachIsolate([&](Isolate* isolate) {
|
|
count++;
|
|
if (isolate == Dart::vm_isolate()) {
|
|
found_vm_isolate = true;
|
|
}
|
|
});
|
|
});
|
|
return count == 1 && found_vm_isolate;
|
|
}
|
|
|
|
// This waits until only the VM, service and kernel isolates are in the list.
|
|
void Dart::WaitForApplicationIsolateShutdown() {
|
|
ASSERT(!Isolate::creation_enabled_);
|
|
MonitorLocker ml(Isolate::isolate_creation_monitor_);
|
|
intptr_t num_attempts = 0;
|
|
while (Isolate::application_isolates_count_ > 0) {
|
|
Monitor::WaitResult retval = ml.Wait(1000);
|
|
if (retval == Monitor::kTimedOut) {
|
|
num_attempts += 1;
|
|
if (num_attempts > 10) {
|
|
DumpAliveIsolates(num_attempts, /*only_application_isolates=*/true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This waits until only the VM isolate remains in the list.
|
|
void Dart::WaitForIsolateShutdown() {
|
|
ASSERT(!Isolate::creation_enabled_);
|
|
MonitorLocker ml(Isolate::isolate_creation_monitor_);
|
|
intptr_t num_attempts = 0;
|
|
while (Isolate::total_isolates_count_ > 1) {
|
|
Monitor::WaitResult retval = ml.Wait(1000);
|
|
if (retval == Monitor::kTimedOut) {
|
|
num_attempts += 1;
|
|
if (num_attempts > 10) {
|
|
DumpAliveIsolates(num_attempts, /*only_application_isolates=*/false);
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(OnlyVmIsolateLeft());
|
|
}
|
|
|
|
char* Dart::Cleanup() {
|
|
ASSERT(Isolate::Current() == NULL);
|
|
if (vm_isolate_ == NULL) {
|
|
return strdup("VM already terminated.");
|
|
}
|
|
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Starting shutdown\n",
|
|
UptimeMillis());
|
|
}
|
|
|
|
#if !defined(PRODUCT)
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Shutting down profiling\n",
|
|
UptimeMillis());
|
|
}
|
|
Profiler::Cleanup();
|
|
#endif // !defined(PRODUCT)
|
|
|
|
NativeSymbolResolver::Cleanup();
|
|
|
|
// Disable the creation of new isolates.
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Disabling isolate creation\n",
|
|
UptimeMillis());
|
|
}
|
|
Isolate::DisableIsolateCreation();
|
|
|
|
// Send the OOB Kill message to all remaining application isolates.
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Killing all app isolates\n",
|
|
UptimeMillis());
|
|
}
|
|
Isolate::KillAllIsolates(Isolate::kInternalKillMsg);
|
|
|
|
// Wait for all isolates, but the service and the vm isolate to shut down.
|
|
// Only do that if there is a service isolate running.
|
|
if (ServiceIsolate::IsRunning() || KernelIsolate::IsRunning()) {
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Shutting down app isolates\n",
|
|
UptimeMillis());
|
|
}
|
|
WaitForApplicationIsolateShutdown();
|
|
}
|
|
|
|
// Shutdown the kernel isolate.
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Shutting down kernel isolate\n",
|
|
UptimeMillis());
|
|
}
|
|
KernelIsolate::Shutdown();
|
|
|
|
// Shutdown the service isolate.
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Shutting down service isolate\n",
|
|
UptimeMillis());
|
|
}
|
|
ServiceIsolate::Shutdown();
|
|
|
|
// Wait for the remaining isolate (service isolate) to shutdown
|
|
// before shutting down the thread pool.
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Waiting for isolate shutdown\n",
|
|
UptimeMillis());
|
|
}
|
|
WaitForIsolateShutdown();
|
|
|
|
#if !defined(PRODUCT)
|
|
{
|
|
// IMPORTANT: the code below enters VM isolate so that Metric::Cleanup could
|
|
// create a StackZone. We *must* wait for all other isolate to shutdown
|
|
// before entering VM isolate because code in the isolate initialization
|
|
// calls VerifyBootstrapClasses, which calls Heap::Verify which calls
|
|
// Scavenger::VisitObjects on the VM isolate's new space without taking
|
|
// any sort of locks: assuming that vm isolate is immutable and never
|
|
// entered by a mutator thread - which is in general true, but is violated
|
|
// by the code below.
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Entering vm isolate\n",
|
|
UptimeMillis());
|
|
}
|
|
bool result = Thread::EnterIsolate(vm_isolate_);
|
|
ASSERT(result);
|
|
Metric::Cleanup();
|
|
Thread::ExitIsolate();
|
|
}
|
|
#endif
|
|
|
|
// Shutdown the thread pool. On return, all thread pool threads have exited.
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Deleting thread pool\n",
|
|
UptimeMillis());
|
|
}
|
|
delete thread_pool_;
|
|
thread_pool_ = NULL;
|
|
|
|
Api::Cleanup();
|
|
delete predefined_handles_;
|
|
predefined_handles_ = NULL;
|
|
|
|
// Disable creation of any new OSThread structures which means no more new
|
|
// threads can do an EnterIsolate. This must come after isolate shutdown
|
|
// because new threads may need to be spawned to shutdown the isolates.
|
|
// This must come after deletion of the thread pool to avoid a race in which
|
|
// a thread spawned by the thread pool does not exit through the thread
|
|
// pool, messing up its bookkeeping.
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Disabling OS Thread creation\n",
|
|
UptimeMillis());
|
|
}
|
|
OSThread::DisableOSThreadCreation();
|
|
|
|
// Set the VM isolate as current isolate.
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Cleaning up vm isolate\n",
|
|
UptimeMillis());
|
|
}
|
|
bool result = Thread::EnterIsolate(vm_isolate_);
|
|
ASSERT(result);
|
|
|
|
ShutdownIsolate();
|
|
vm_isolate_ = NULL;
|
|
ASSERT(Isolate::IsolateListLength() == 0);
|
|
PortMap::Cleanup();
|
|
IsolateGroup::Cleanup();
|
|
ICData::Cleanup();
|
|
SubtypeTestCache::Cleanup();
|
|
ArgumentsDescriptor::Cleanup();
|
|
TargetCPUFeatures::Cleanup();
|
|
MarkingStack::Cleanup();
|
|
StoreBuffer::Cleanup();
|
|
Object::Cleanup();
|
|
SemiSpace::Cleanup();
|
|
StubCode::Cleanup();
|
|
#if defined(SUPPORT_TIMELINE)
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Shutting down timeline\n",
|
|
UptimeMillis());
|
|
}
|
|
Timeline::Cleanup();
|
|
#endif
|
|
Zone::Cleanup();
|
|
// Delete the current thread's TLS and set it's TLS to null.
|
|
// If it is the last thread then the destructor would call
|
|
// OSThread::Cleanup.
|
|
OSThread* os_thread = OSThread::Current();
|
|
OSThread::SetCurrent(NULL);
|
|
delete os_thread;
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Deleted os_thread\n",
|
|
UptimeMillis());
|
|
}
|
|
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Deleting code observers\n",
|
|
UptimeMillis());
|
|
}
|
|
NOT_IN_PRODUCT(CodeObservers::Cleanup());
|
|
OS::Cleanup();
|
|
if (FLAG_trace_shutdown) {
|
|
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Done\n", UptimeMillis());
|
|
}
|
|
MallocHooks::Cleanup();
|
|
Flags::Cleanup();
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
IsolateGroupReloadContext::SetFileModifiedCallback(NULL);
|
|
Service::SetEmbedderStreamCallbacks(NULL, NULL);
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
return NULL;
|
|
}
|
|
|
|
Isolate* Dart::CreateIsolate(const char* name_prefix,
|
|
const Dart_IsolateFlags& api_flags,
|
|
IsolateGroup* isolate_group) {
|
|
// Create a new isolate.
|
|
Isolate* isolate =
|
|
Isolate::InitIsolate(name_prefix, isolate_group, api_flags);
|
|
return isolate;
|
|
}
|
|
|
|
static bool IsSnapshotCompatible(Snapshot::Kind vm_kind,
|
|
Snapshot::Kind isolate_kind) {
|
|
if (vm_kind == isolate_kind) return true;
|
|
if (vm_kind == Snapshot::kFull && isolate_kind == Snapshot::kFullJIT)
|
|
return true;
|
|
return Snapshot::IsFull(isolate_kind);
|
|
}
|
|
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
static bool CloneIntoChildIsolateAOT(Thread* T,
|
|
Isolate* I,
|
|
IsolateGroup* source_isolate_group) {
|
|
// In AOT we speed up isolate spawning by copying donor's isolate structure.
|
|
Isolate* donor_isolate = source_isolate_group != nullptr
|
|
? source_isolate_group->FirstIsolate()
|
|
: nullptr;
|
|
if (donor_isolate == nullptr) {
|
|
return false;
|
|
}
|
|
I->isolate_object_store()->Init();
|
|
I->isolate_object_store()->PreallocateObjects();
|
|
// Initialize field_table with initial values.
|
|
I->set_field_table(T, donor_isolate->saved_initial_field_table()->Clone());
|
|
I->set_saved_initial_field_table(
|
|
donor_isolate->saved_initial_field_table_shareable());
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
ErrorPtr Dart::InitIsolateFromSnapshot(Thread* T,
|
|
Isolate* I,
|
|
const uint8_t* snapshot_data,
|
|
const uint8_t* snapshot_instructions,
|
|
const uint8_t* kernel_buffer,
|
|
intptr_t kernel_buffer_size) {
|
|
Error& error = Error::Handle(T->zone());
|
|
error = Object::Init(I, kernel_buffer, kernel_buffer_size);
|
|
if (!error.IsNull()) {
|
|
return error.raw();
|
|
}
|
|
if ((snapshot_data != NULL) && kernel_buffer == NULL) {
|
|
// Read the snapshot and setup the initial state.
|
|
#if defined(SUPPORT_TIMELINE)
|
|
TimelineBeginEndScope tbes(T, Timeline::GetIsolateStream(),
|
|
"ReadProgramSnapshot");
|
|
#endif // defined(SUPPORT_TIMELINE)
|
|
// TODO(turnidge): Remove once length is not part of the snapshot.
|
|
const Snapshot* snapshot = Snapshot::SetupFromBuffer(snapshot_data);
|
|
if (snapshot == NULL) {
|
|
const String& message = String::Handle(String::New("Invalid snapshot"));
|
|
return ApiError::New(message);
|
|
}
|
|
if (!IsSnapshotCompatible(vm_snapshot_kind_, snapshot->kind())) {
|
|
const String& message = String::Handle(String::NewFormatted(
|
|
"Incompatible snapshot kinds: vm '%s', isolate '%s'",
|
|
Snapshot::KindToCString(vm_snapshot_kind_),
|
|
Snapshot::KindToCString(snapshot->kind())));
|
|
return ApiError::New(message);
|
|
}
|
|
if (FLAG_trace_isolates) {
|
|
OS::PrintErr("Size of isolate snapshot = %" Pd "\n", snapshot->length());
|
|
}
|
|
FullSnapshotReader reader(snapshot, snapshot_instructions, T);
|
|
const Error& error = Error::Handle(reader.ReadProgramSnapshot());
|
|
if (!error.IsNull()) {
|
|
return error.raw();
|
|
}
|
|
|
|
ReversePcLookupCache::BuildAndAttachToIsolateGroup(I->group());
|
|
|
|
#if defined(SUPPORT_TIMELINE)
|
|
if (tbes.enabled()) {
|
|
tbes.SetNumArguments(2);
|
|
tbes.FormatArgument(0, "snapshotSize", "%" Pd, snapshot->length());
|
|
tbes.FormatArgument(1, "heapSize", "%" Pd64,
|
|
I->heap()->UsedInWords(Heap::kOld) * kWordSize);
|
|
}
|
|
#endif // defined(SUPPORT_TIMELINE)
|
|
if (FLAG_trace_isolates) {
|
|
I->heap()->PrintSizes();
|
|
MegamorphicCacheTable::PrintSizes(I);
|
|
}
|
|
} else {
|
|
if ((vm_snapshot_kind_ != Snapshot::kNone) && kernel_buffer == NULL) {
|
|
const String& message =
|
|
String::Handle(String::New("Missing isolate snapshot"));
|
|
return ApiError::New(message);
|
|
}
|
|
}
|
|
|
|
return Error::null();
|
|
}
|
|
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
static void PrintLLVMConstantPool(Thread* T, Isolate* I) {
|
|
StackZone printing_zone(T);
|
|
HandleScope printing_scope(T);
|
|
TextBuffer b(1000);
|
|
const auto& constants =
|
|
GrowableObjectArray::Handle(I->object_store()->llvm_constant_pool());
|
|
if (constants.IsNull()) {
|
|
b.AddString("No constant pool information in snapshot.\n\n");
|
|
} else {
|
|
auto const len = constants.Length();
|
|
b.Printf("Constant pool contents (length %" Pd "):\n", len);
|
|
auto& obj = Object::Handle();
|
|
for (intptr_t i = 0; i < len; i++) {
|
|
obj = constants.At(i);
|
|
b.Printf(" %5" Pd ": ", i);
|
|
if (obj.IsString()) {
|
|
b.AddChar('"');
|
|
b.AddEscapedString(obj.ToCString());
|
|
b.AddChar('"');
|
|
} else {
|
|
b.AddString(obj.ToCString());
|
|
}
|
|
b.AddChar('\n');
|
|
}
|
|
b.AddString("End of constant pool.\n\n");
|
|
}
|
|
const auto& functions =
|
|
GrowableObjectArray::Handle(I->object_store()->llvm_function_pool());
|
|
if (functions.IsNull()) {
|
|
b.AddString("No function pool information in snapshot.\n\n");
|
|
} else {
|
|
auto const len = functions.Length();
|
|
b.Printf("Function pool contents (length %" Pd "):\n", len);
|
|
auto& obj = Function::Handle();
|
|
for (intptr_t i = 0; i < len; i++) {
|
|
obj ^= functions.At(i);
|
|
ASSERT(!obj.IsNull());
|
|
b.Printf(" %5" Pd ": %s\n", i, obj.ToFullyQualifiedCString());
|
|
}
|
|
b.AddString("End of function pool.\n\n");
|
|
}
|
|
THR_Print("%s", b.buf());
|
|
}
|
|
#endif
|
|
|
|
ErrorPtr Dart::InitializeIsolate(const uint8_t* snapshot_data,
|
|
const uint8_t* snapshot_instructions,
|
|
const uint8_t* kernel_buffer,
|
|
intptr_t kernel_buffer_size,
|
|
IsolateGroup* source_isolate_group,
|
|
void* isolate_data) {
|
|
// Initialize the new isolate.
|
|
Thread* T = Thread::Current();
|
|
Isolate* I = T->isolate();
|
|
#if defined(SUPPORT_TIMELINE)
|
|
TimelineBeginEndScope tbes(T, Timeline::GetIsolateStream(),
|
|
"InitializeIsolate");
|
|
tbes.SetNumArguments(1);
|
|
tbes.CopyArgument(0, "isolateName", I->name());
|
|
#endif
|
|
ASSERT(I != NULL);
|
|
StackZone zone(T);
|
|
HandleScope handle_scope(T);
|
|
bool was_child_cloned_into_existing_isolate = false;
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
if (CloneIntoChildIsolateAOT(T, I, source_isolate_group)) {
|
|
was_child_cloned_into_existing_isolate = true;
|
|
} else {
|
|
#endif
|
|
const Error& error = Error::Handle(
|
|
InitIsolateFromSnapshot(T, I, snapshot_data, snapshot_instructions,
|
|
kernel_buffer, kernel_buffer_size));
|
|
if (!error.IsNull()) {
|
|
return error.raw();
|
|
}
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
}
|
|
#endif
|
|
|
|
Object::VerifyBuiltinVtables();
|
|
DEBUG_ONLY(I->heap()->Verify(kForbidMarked));
|
|
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
// AOT: The megamorphic miss function and code come from the snapshot.
|
|
ASSERT(I->object_store()->megamorphic_call_miss_code() != Code::null());
|
|
ASSERT(I->object_store()->build_method_extractor_code() != Code::null());
|
|
if (FLAG_print_llvm_constant_pool) {
|
|
PrintLLVMConstantPool(T, I);
|
|
}
|
|
#else
|
|
// JIT: The megamorphic call miss function and code come from the snapshot in
|
|
// JIT app snapshot, otherwise create them.
|
|
if (I->object_store()->megamorphic_call_miss_code() == Code::null()) {
|
|
MegamorphicCacheTable::InitMissHandler(I);
|
|
}
|
|
#if !defined(TARGET_ARCH_IA32)
|
|
if (I != Dart::vm_isolate()) {
|
|
I->object_store()->set_build_method_extractor_code(
|
|
Code::Handle(StubCode::GetBuildMethodExtractorStub(nullptr)));
|
|
}
|
|
#endif
|
|
#endif // defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
I->set_ic_miss_code(StubCode::SwitchableCallMiss());
|
|
|
|
if ((snapshot_data == NULL) || (kernel_buffer != NULL)) {
|
|
Error& error = Error::Handle();
|
|
error ^= I->object_store()->PreallocateObjects();
|
|
if (!error.IsNull()) {
|
|
return error.raw();
|
|
}
|
|
error ^= I->isolate_object_store()->PreallocateObjects();
|
|
if (!error.IsNull()) {
|
|
return error.raw();
|
|
}
|
|
}
|
|
|
|
if (!was_child_cloned_into_existing_isolate) {
|
|
I->heap()->InitGrowthControl();
|
|
}
|
|
I->set_init_callback_data(isolate_data);
|
|
if (FLAG_print_class_table) {
|
|
I->class_table()->Print();
|
|
}
|
|
#if !defined(PRODUCT)
|
|
ServiceIsolate::MaybeMakeServiceIsolate(I);
|
|
if (!ServiceIsolate::IsServiceIsolate(I) &&
|
|
!KernelIsolate::IsKernelIsolate(I)) {
|
|
I->message_handler()->set_should_pause_on_start(
|
|
FLAG_pause_isolates_on_start);
|
|
I->message_handler()->set_should_pause_on_exit(FLAG_pause_isolates_on_exit);
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
|
|
ServiceIsolate::SendIsolateStartupMessage();
|
|
#if !defined(PRODUCT)
|
|
I->debugger()->NotifyIsolateCreated();
|
|
#endif
|
|
|
|
// Create tag table.
|
|
I->set_tag_table(GrowableObjectArray::Handle(GrowableObjectArray::New()));
|
|
// Set up default UserTag.
|
|
const UserTag& default_tag = UserTag::Handle(UserTag::DefaultTag());
|
|
I->set_current_tag(default_tag);
|
|
|
|
if (FLAG_keep_code) {
|
|
I->set_deoptimized_code_array(
|
|
GrowableObjectArray::Handle(GrowableObjectArray::New()));
|
|
}
|
|
return Error::null();
|
|
}
|
|
|
|
const char* Dart::FeaturesString(Isolate* isolate,
|
|
bool is_vm_isolate,
|
|
Snapshot::Kind kind) {
|
|
TextBuffer buffer(64);
|
|
|
|
// Different fields are included for DEBUG/RELEASE/PRODUCT.
|
|
#if defined(DEBUG)
|
|
buffer.AddString("debug");
|
|
#elif defined(PRODUCT)
|
|
buffer.AddString("product");
|
|
#else
|
|
buffer.AddString("release");
|
|
#endif
|
|
|
|
#define ADD_FLAG(name, value) \
|
|
do { \
|
|
buffer.AddString(value ? (" " #name) : (" no-" #name)); \
|
|
} while (0);
|
|
#define ADD_P(name, T, DV, C) ADD_FLAG(name, FLAG_##name)
|
|
#define ADD_R(name, PV, T, DV, C) ADD_FLAG(name, FLAG_##name)
|
|
#define ADD_C(name, PCV, PV, T, DV, C) ADD_FLAG(name, FLAG_##name)
|
|
#define ADD_D(name, T, DV, C) ADD_FLAG(name, FLAG_##name)
|
|
|
|
#define ADD_ISOLATE_FLAG(name, isolate_flag, flag) \
|
|
do { \
|
|
const bool value = (isolate != NULL) ? isolate->name() : flag; \
|
|
ADD_FLAG(#name, value); \
|
|
} while (0);
|
|
|
|
if (Snapshot::IncludesCode(kind)) {
|
|
VM_GLOBAL_FLAG_LIST(ADD_P, ADD_R, ADD_C, ADD_D);
|
|
|
|
// enabling assertions affects deopt ids.
|
|
ADD_ISOLATE_FLAG(asserts, enable_asserts, FLAG_enable_asserts);
|
|
if (kind == Snapshot::kFullJIT) {
|
|
ADD_ISOLATE_FLAG(use_field_guards, use_field_guards,
|
|
FLAG_use_field_guards);
|
|
ADD_ISOLATE_FLAG(use_osr, use_osr, FLAG_use_osr);
|
|
}
|
|
#if !defined(PRODUCT)
|
|
buffer.AddString(FLAG_code_comments ? " code-comments"
|
|
: " no-code-comments");
|
|
#endif
|
|
|
|
// Generated code must match the host architecture and ABI.
|
|
#if defined(TARGET_ARCH_ARM)
|
|
#if defined(TARGET_OS_MACOS) || defined(TARGET_OS_MACOS_IOS)
|
|
buffer.AddString(" arm-ios");
|
|
#else
|
|
buffer.AddString(" arm-eabi");
|
|
#endif
|
|
buffer.AddString(TargetCPUFeatures::hardfp_supported() ? " hardfp"
|
|
: " softfp");
|
|
#elif defined(TARGET_ARCH_ARM64)
|
|
#if defined(TARGET_OS_FUCHSIA)
|
|
// See signal handler cheat in Assembler::EnterFrame.
|
|
buffer.AddString(" arm64-fuchsia");
|
|
#else
|
|
buffer.AddString(" arm64-sysv");
|
|
#endif
|
|
#elif defined(TARGET_ARCH_IA32)
|
|
buffer.AddString(" ia32");
|
|
#elif defined(TARGET_ARCH_X64)
|
|
#if defined(TARGET_OS_WINDOWS)
|
|
buffer.AddString(" x64-win");
|
|
#else
|
|
buffer.AddString(" x64-sysv");
|
|
#endif
|
|
|
|
#else
|
|
#error What architecture?
|
|
#endif
|
|
|
|
ADD_ISOLATE_FLAG(null_safety, null_safety, FLAG_null_safety);
|
|
}
|
|
|
|
if (Dart::non_nullable_flag()) {
|
|
buffer.AddString(" nnbd-experiment");
|
|
}
|
|
#undef ADD_ISOLATE_FLAG
|
|
#undef ADD_D
|
|
#undef ADD_C
|
|
#undef ADD_R
|
|
#undef ADD_P
|
|
#undef ADD_FLAG
|
|
|
|
return buffer.Steal();
|
|
}
|
|
|
|
void Dart::RunShutdownCallback() {
|
|
Thread* thread = Thread::Current();
|
|
ASSERT(thread->execution_state() == Thread::kThreadInVM);
|
|
Isolate* isolate = thread->isolate();
|
|
void* isolate_group_data = isolate->group()->embedder_data();
|
|
void* isolate_data = isolate->init_callback_data();
|
|
Dart_IsolateShutdownCallback callback = Isolate::ShutdownCallback();
|
|
if (callback != NULL) {
|
|
TransitionVMToNative transition(thread);
|
|
(callback)(isolate_group_data, isolate_data);
|
|
}
|
|
}
|
|
|
|
void Dart::ShutdownIsolate(Isolate* isolate) {
|
|
ASSERT(Isolate::Current() == NULL);
|
|
// We need to enter the isolate in order to shut it down.
|
|
bool result = Thread::EnterIsolate(isolate);
|
|
ASSERT(result);
|
|
ShutdownIsolate();
|
|
// Since the isolate is shutdown and deleted, there is no need to
|
|
// exit the isolate here.
|
|
ASSERT(Isolate::Current() == NULL);
|
|
}
|
|
|
|
void Dart::ShutdownIsolate() {
|
|
Isolate::Current()->Shutdown();
|
|
}
|
|
|
|
bool Dart::VmIsolateNameEquals(const char* name) {
|
|
ASSERT(name != NULL);
|
|
return (strcmp(name, kVmIsolateName) == 0);
|
|
}
|
|
|
|
int64_t Dart::UptimeMicros() {
|
|
return OS::GetCurrentMonotonicMicros() - Dart::start_time_micros_;
|
|
}
|
|
|
|
uword Dart::AllocateReadOnlyHandle() {
|
|
ASSERT(Isolate::Current() == Dart::vm_isolate());
|
|
ASSERT(predefined_handles_ != NULL);
|
|
return predefined_handles_->handles_.AllocateScopedHandle();
|
|
}
|
|
|
|
LocalHandle* Dart::AllocateReadOnlyApiHandle() {
|
|
ASSERT(Isolate::Current() == Dart::vm_isolate());
|
|
ASSERT(predefined_handles_ != NULL);
|
|
return predefined_handles_->api_handles_.AllocateHandle();
|
|
}
|
|
|
|
bool Dart::IsReadOnlyHandle(uword address) {
|
|
ASSERT(predefined_handles_ != NULL);
|
|
return predefined_handles_->handles_.IsValidScopedHandle(address);
|
|
}
|
|
|
|
bool Dart::IsReadOnlyApiHandle(Dart_Handle handle) {
|
|
ASSERT(predefined_handles_ != NULL);
|
|
return predefined_handles_->api_handles_.IsValidHandle(handle);
|
|
}
|
|
|
|
} // namespace dart
|