e151a81108
Original CL in patchset 1. Split-off https://dart-review.googlesource.com/c/sdk/+/238341 And pulled in fix https://dart-review.googlesource.com/c/sdk/+/238582 (Should merge cleanly when this lands later.) This CL implements the `Finalizer` in the GC. The GC is specially aware of two types of objects for the purposes of running finalizers. 1) `FinalizerEntry` 2) `Finalizer` (`FinalizerBase`, `_FinalizerImpl`) A `FinalizerEntry` contains the `value`, the optional `detach` key, and the `token`, and a reference to the `finalizer`. An entry only holds on weakly to the value, detach key, and finalizer. (Similar to how `WeakReference` only holds on weakly to target). A `Finalizer` contains all entries, a list of entries of which the value is collected, and a reference to the isolate. When a the value of an entry is GCed, the enry is added over to the collected list. If any entry is moved to the collected list, a message is sent that invokes the finalizer to call the callback on all entries in that list. When a finalizer is detached by the user, the entry token is set to the entry itself and is removed from the all entries set. This ensures that if the entry was already moved to the collected list, the finalizer is not executed. To speed up detaching, we use a weak map from detach keys to list of entries. This ensures entries can be GCed. Both the scavenger and marker tasks process finalizer entries in parallel. Parallel tasks use an atomic exchange on the head of the collected entries list, ensuring no entries get lost. The mutator thread is guaranteed to be stopped when processing entries. This ensures that we do not need barriers for moving entries into the finalizers collected list. Dart reads and replaces the collected entries list also with an atomic exchange, ensuring the GC doesn't run in between a load/store. When a finalizer gets posted a message to process finalized objects, it is being kept alive by the message. An alternative design would be to pre-allocate a `WeakReference` in the finalizer pointing to the finalizer, and send that itself. This would be at the cost of an extra object. Send and exit is not supported in this CL, support will be added in a follow up CL. Trying to send will throw. Bug: https://github.com/dart-lang/sdk/issues/47777 TEST=runtime/tests/vm/dart/finalizer/* TEST=runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart TEST=runtime/vm/object_test.cc Change-Id: Ibdfeadc16d5d69ade50aae5b9f794284c4c4dbab Cq-Include-Trybots: luci.dart.try:vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-reload-linux-debug-x64-try,vm-ffi-android-debug-arm64c-try,dart-sdk-mac-arm64-try,vm-kernel-mac-release-arm64-try,pkg-mac-release-arm64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try,vm-kernel-win-debug-x64c-try,vm-kernel-win-debug-x64-try,vm-kernel-precomp-win-debug-x64c-try,vm-kernel-nnbd-win-release-ia32-try,vm-ffi-android-debug-arm-try,vm-precomp-ffi-qemu-linux-release-arm-try,vm-kernel-mac-debug-x64-try,vm-kernel-nnbd-mac-debug-x64-try,vm-kernel-nnbd-linux-debug-ia32-try,benchmark-linux-try,flutter-analyze-try,flutter-frontend-try,pkg-linux-debug-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-gcc-linux-try,vm-kernel-optcounter-threshold-linux-release-x64-try,vm-kernel-precomp-linux-debug-simarm_x64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-debug-x64c-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/238086 Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com>
1278 lines
47 KiB
C++
1278 lines
47 KiB
C++
// Copyright (c) 2012, 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 "include/dart_native_api.h"
|
|
#include "platform/assert.h"
|
|
#include "platform/unicode.h"
|
|
#include "vm/bootstrap_natives.h"
|
|
#include "vm/class_finalizer.h"
|
|
#include "vm/dart.h"
|
|
#include "vm/dart_api_impl.h"
|
|
#include "vm/dart_api_message.h"
|
|
#include "vm/dart_entry.h"
|
|
#include "vm/exceptions.h"
|
|
#include "vm/hash_table.h"
|
|
#include "vm/lockers.h"
|
|
#include "vm/longjump.h"
|
|
#include "vm/message_handler.h"
|
|
#include "vm/message_snapshot.h"
|
|
#include "vm/object.h"
|
|
#include "vm/object_graph_copy.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/port.h"
|
|
#include "vm/resolver.h"
|
|
#include "vm/service.h"
|
|
#include "vm/snapshot.h"
|
|
#include "vm/symbols.h"
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_NATIVE_ENTRY(CapabilityImpl_factory, 0, 1) {
|
|
ASSERT(
|
|
TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull());
|
|
uint64_t id = isolate->random()->NextUInt64();
|
|
return Capability::New(id);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(CapabilityImpl_equals, 0, 2) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Capability, recv, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Capability, other, arguments->NativeArgAt(1));
|
|
return (recv.Id() == other.Id()) ? Bool::True().ptr() : Bool::False().ptr();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(CapabilityImpl_get_hashcode, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Capability, cap, arguments->NativeArgAt(0));
|
|
int64_t id = cap.Id();
|
|
int32_t hi = static_cast<int32_t>(id >> 32);
|
|
int32_t lo = static_cast<int32_t>(id);
|
|
int32_t hash = (hi ^ lo) & kSmiMax;
|
|
return Smi::New(hash);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(RawReceivePortImpl_factory, 0, 2) {
|
|
ASSERT(
|
|
TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull());
|
|
GET_NON_NULL_NATIVE_ARGUMENT(String, debug_name, arguments->NativeArgAt(1));
|
|
Dart_Port port_id = PortMap::CreatePort(isolate->message_handler());
|
|
return ReceivePort::New(port_id, debug_name, false /* not control port */);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(RawReceivePortImpl_get_id, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
|
|
return Integer::New(port.Id());
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(RawReceivePortImpl_get_sendport, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
|
|
return port.send_port();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(RawReceivePortImpl_closeInternal, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
|
|
Dart_Port id = port.Id();
|
|
PortMap::ClosePort(id);
|
|
return Integer::New(id);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(RawReceivePortImpl_setActive, 0, 2) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Bool, active, arguments->NativeArgAt(1));
|
|
Dart_Port id = port.Id();
|
|
PortMap::SetPortState(
|
|
id, active.value() ? PortMap::kLivePort : PortMap::kInactivePort);
|
|
return Object::null();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(SendPortImpl_get_id, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
return Integer::New(port.Id());
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(SendPortImpl_get_hashcode, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
int64_t id = port.Id();
|
|
int32_t hi = static_cast<int32_t>(id >> 32);
|
|
int32_t lo = static_cast<int32_t>(id);
|
|
int32_t hash = (hi ^ lo) & kSmiMax;
|
|
return Smi::New(hash);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(SendPortImpl_sendInternal_, 0, 2) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
// TODO(iposva): Allow for arbitrary messages to be sent.
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1));
|
|
|
|
const Dart_Port destination_port_id = port.Id();
|
|
const bool can_send_any_object = isolate->origin_id() == port.origin_id();
|
|
// We have to check whether the receiver has the same isolate group (e.g.
|
|
// native message handlers such as an IOService handler does not but does
|
|
// share the same origin port).
|
|
const bool same_group = PortMap::IsReceiverInThisIsolateGroup(
|
|
destination_port_id, isolate->group());
|
|
// TODO(turnidge): Throw an exception when the return value is false?
|
|
PortMap::PostMessage(WriteMessage(can_send_any_object, same_group, obj,
|
|
destination_port_id,
|
|
Message::kNormalPriority));
|
|
return Object::null();
|
|
}
|
|
|
|
class UntaggedObjectPtrSetTraits {
|
|
public:
|
|
static bool ReportStats() { return false; }
|
|
static const char* Name() { return "RawObjectPtrSetTraits"; }
|
|
|
|
static bool IsMatch(const ObjectPtr a, const ObjectPtr b) { return a == b; }
|
|
|
|
static uword Hash(const ObjectPtr obj) { return static_cast<uword>(obj); }
|
|
};
|
|
|
|
static ObjectPtr ValidateMessageObject(Zone* zone,
|
|
Isolate* isolate,
|
|
const Object& obj) {
|
|
TIMELINE_DURATION(Thread::Current(), Isolate, "ValidateMessageObject");
|
|
|
|
class SendMessageValidator : public ObjectPointerVisitor {
|
|
public:
|
|
SendMessageValidator(IsolateGroup* isolate_group,
|
|
WeakTable* visited,
|
|
MallocGrowableArray<ObjectPtr>* const working_set)
|
|
: ObjectPointerVisitor(isolate_group),
|
|
visited_(visited),
|
|
working_set_(working_set) {}
|
|
|
|
private:
|
|
void VisitPointers(ObjectPtr* from, ObjectPtr* to) {
|
|
for (ObjectPtr* ptr = from; ptr <= to; ptr++) {
|
|
VisitObject(*ptr);
|
|
}
|
|
}
|
|
|
|
void VisitCompressedPointers(uword heap_base,
|
|
CompressedObjectPtr* from,
|
|
CompressedObjectPtr* to) {
|
|
for (CompressedObjectPtr* ptr = from; ptr <= to; ptr++) {
|
|
VisitObject(ptr->Decompress(heap_base));
|
|
}
|
|
}
|
|
|
|
void VisitObject(ObjectPtr obj) {
|
|
if (!obj->IsHeapObject() || obj->untag()->IsCanonical()) {
|
|
return;
|
|
}
|
|
if (visited_->GetValueExclusive(obj) == 1) {
|
|
return;
|
|
}
|
|
visited_->SetValueExclusive(obj, 1);
|
|
working_set_->Add(obj);
|
|
}
|
|
|
|
WeakTable* visited_;
|
|
MallocGrowableArray<ObjectPtr>* const working_set_;
|
|
};
|
|
if (!obj.ptr()->IsHeapObject() || obj.ptr()->untag()->IsCanonical()) {
|
|
return obj.ptr();
|
|
}
|
|
ClassTable* class_table = isolate->group()->class_table();
|
|
|
|
Class& klass = Class::Handle(zone);
|
|
Closure& closure = Closure::Handle(zone);
|
|
|
|
bool error_found = false;
|
|
Function& erroneous_closure_function = Function::Handle(zone);
|
|
Class& erroneous_nativewrapper_class = Class::Handle(zone);
|
|
const char* error_message = nullptr;
|
|
|
|
{
|
|
NoSafepointScope no_safepoint;
|
|
// working_set contains only elements that have not been visited yet that
|
|
// need to be processed.
|
|
// So before adding elements to working_set ensure to check visited flag,
|
|
// set visited flag at the same time as the element is added.
|
|
MallocGrowableArray<ObjectPtr> working_set;
|
|
std::unique_ptr<WeakTable> visited(new WeakTable());
|
|
|
|
SendMessageValidator visitor(isolate->group(), visited.get(), &working_set);
|
|
|
|
visited->SetValueExclusive(obj.ptr(), 1);
|
|
working_set.Add(obj.ptr());
|
|
|
|
while (!working_set.is_empty() && !error_found) {
|
|
ObjectPtr raw = working_set.RemoveLast();
|
|
|
|
const intptr_t cid = raw->GetClassId();
|
|
// Keep the list in sync with the one in runtime/vm/object_graph_copy.cc
|
|
switch (cid) {
|
|
// Can be shared.
|
|
case kOneByteStringCid:
|
|
case kTwoByteStringCid:
|
|
case kExternalOneByteStringCid:
|
|
case kExternalTwoByteStringCid:
|
|
case kMintCid:
|
|
case kImmutableArrayCid:
|
|
case kNeverCid:
|
|
case kSentinelCid:
|
|
case kInt32x4Cid:
|
|
case kSendPortCid:
|
|
case kCapabilityCid:
|
|
case kRegExpCid:
|
|
case kStackTraceCid:
|
|
continue;
|
|
// Cannot be shared due to possibly being mutable boxes for unboxed
|
|
// fields in JIT, but can be transferred via Isolate.exit()
|
|
case kDoubleCid:
|
|
case kFloat32x4Cid:
|
|
case kFloat64x2Cid:
|
|
continue;
|
|
|
|
case kClosureCid:
|
|
closure ^= raw;
|
|
// Only context has to be checked.
|
|
working_set.Add(closure.context());
|
|
continue;
|
|
|
|
#define MESSAGE_SNAPSHOT_ILLEGAL(type) \
|
|
case k##type##Cid: \
|
|
error_message = \
|
|
"Illegal argument in isolate message : (object is a " #type ")"; \
|
|
error_found = true; \
|
|
break;
|
|
|
|
MESSAGE_SNAPSHOT_ILLEGAL(DynamicLibrary);
|
|
// TODO(http://dartbug.com/47777): Send and exit support: remove this.
|
|
MESSAGE_SNAPSHOT_ILLEGAL(Finalizer);
|
|
MESSAGE_SNAPSHOT_ILLEGAL(MirrorReference);
|
|
MESSAGE_SNAPSHOT_ILLEGAL(Pointer);
|
|
MESSAGE_SNAPSHOT_ILLEGAL(ReceivePort);
|
|
MESSAGE_SNAPSHOT_ILLEGAL(UserTag);
|
|
|
|
default:
|
|
if (cid >= kNumPredefinedCids) {
|
|
klass = class_table->At(cid);
|
|
if (klass.num_native_fields() != 0) {
|
|
erroneous_nativewrapper_class = klass.ptr();
|
|
error_found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
raw->untag()->VisitPointers(&visitor);
|
|
}
|
|
}
|
|
if (error_found) {
|
|
const char* exception_message;
|
|
if (error_message != nullptr) {
|
|
exception_message = error_message;
|
|
} else if (!erroneous_closure_function.IsNull()) {
|
|
exception_message = OS::SCreate(zone,
|
|
"Illegal argument in isolate message"
|
|
" : (object is a closure - %s)",
|
|
erroneous_closure_function.ToCString());
|
|
} else {
|
|
ASSERT(!erroneous_nativewrapper_class.IsNull());
|
|
exception_message =
|
|
OS::SCreate(zone,
|
|
"Illegal argument in isolate message"
|
|
" : (object extends NativeWrapper - %s)",
|
|
erroneous_nativewrapper_class.ToCString());
|
|
}
|
|
return Exceptions::CreateUnhandledException(
|
|
zone, Exceptions::kArgumentValue, exception_message);
|
|
}
|
|
isolate->set_forward_table_new(nullptr);
|
|
return obj.ptr();
|
|
}
|
|
|
|
// TODO(http://dartbug.com/47777): Add support for Finalizers.
|
|
DEFINE_NATIVE_ENTRY(Isolate_exit_, 0, 2) {
|
|
GET_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
if (!port.IsNull()) {
|
|
GET_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1));
|
|
if (!PortMap::IsReceiverInThisIsolateGroup(port.Id(), isolate->group())) {
|
|
const auto& error =
|
|
String::Handle(String::New("exit with final message is only allowed "
|
|
"for isolates in one isolate group."));
|
|
Exceptions::ThrowArgumentError(error);
|
|
UNREACHABLE();
|
|
}
|
|
|
|
Object& validated_result = Object::Handle(zone);
|
|
const Object& msg_obj = Object::Handle(zone, obj.ptr());
|
|
validated_result = ValidateMessageObject(zone, isolate, msg_obj);
|
|
// msg_array = [
|
|
// <message>,
|
|
// <collection-lib-objects-to-rehash>,
|
|
// <core-lib-objects-to-rehash>,
|
|
// ]
|
|
const Array& msg_array = Array::Handle(zone, Array::New(3));
|
|
msg_array.SetAt(0, msg_obj);
|
|
if (validated_result.IsUnhandledException()) {
|
|
Exceptions::PropagateError(Error::Cast(validated_result));
|
|
UNREACHABLE();
|
|
}
|
|
PersistentHandle* handle =
|
|
isolate->group()->api_state()->AllocatePersistentHandle();
|
|
handle->set_ptr(msg_array);
|
|
isolate->bequeath(std::unique_ptr<Bequest>(new Bequest(handle, port.Id())));
|
|
}
|
|
Isolate::KillIfExists(isolate, Isolate::LibMsgId::kKillMsg);
|
|
// Drain interrupts before running so any IMMEDIATE operations on the current
|
|
// isolate happen synchronously.
|
|
const Error& error = Error::Handle(thread->HandleInterrupts());
|
|
RELEASE_ASSERT(error.IsUnwindError());
|
|
Exceptions::PropagateError(error);
|
|
// We will never execute dart code again in this isolate.
|
|
return Object::null();
|
|
}
|
|
|
|
class IsolateSpawnState {
|
|
public:
|
|
IsolateSpawnState(Dart_Port parent_port,
|
|
Dart_Port origin_id,
|
|
const char* script_url,
|
|
PersistentHandle* closure_tuple_handle,
|
|
SerializedObjectBuffer* message_buffer,
|
|
const char* package_config,
|
|
bool paused,
|
|
bool errorsAreFatal,
|
|
Dart_Port onExit,
|
|
Dart_Port onError,
|
|
const char* debug_name,
|
|
IsolateGroup* group);
|
|
IsolateSpawnState(Dart_Port parent_port,
|
|
const char* script_url,
|
|
const char* package_config,
|
|
SerializedObjectBuffer* args_buffer,
|
|
SerializedObjectBuffer* message_buffer,
|
|
bool paused,
|
|
bool errorsAreFatal,
|
|
Dart_Port onExit,
|
|
Dart_Port onError,
|
|
const char* debug_name,
|
|
IsolateGroup* group);
|
|
~IsolateSpawnState();
|
|
|
|
Isolate* isolate() const { return isolate_; }
|
|
void set_isolate(Isolate* value) { isolate_ = value; }
|
|
|
|
Dart_Port parent_port() const { return parent_port_; }
|
|
Dart_Port origin_id() const { return origin_id_; }
|
|
Dart_Port on_exit_port() const { return on_exit_port_; }
|
|
Dart_Port on_error_port() const { return on_error_port_; }
|
|
const char* script_url() const { return script_url_; }
|
|
const char* package_config() const { return package_config_; }
|
|
const char* library_url() const { return library_url_; }
|
|
const char* class_name() const { return class_name_; }
|
|
const char* function_name() const { return function_name_; }
|
|
const char* debug_name() const { return debug_name_; }
|
|
bool is_spawn_uri() const {
|
|
return library_url_ == nullptr && // No top-level entrypoint.
|
|
closure_tuple_handle_ == nullptr; // No closure entrypoint.
|
|
}
|
|
bool paused() const { return paused_; }
|
|
bool errors_are_fatal() const { return errors_are_fatal_; }
|
|
Dart_IsolateFlags* isolate_flags() { return &isolate_flags_; }
|
|
PersistentHandle* closure_tuple_handle() const {
|
|
return closure_tuple_handle_;
|
|
}
|
|
|
|
ObjectPtr ResolveFunction();
|
|
ObjectPtr BuildArgs(Thread* thread);
|
|
ObjectPtr BuildMessage(Thread* thread);
|
|
|
|
IsolateGroup* isolate_group() const { return isolate_group_; }
|
|
|
|
private:
|
|
Isolate* isolate_ = nullptr;
|
|
Dart_Port parent_port_;
|
|
Dart_Port origin_id_ = ILLEGAL_PORT;
|
|
Dart_Port on_exit_port_;
|
|
Dart_Port on_error_port_;
|
|
const char* script_url_;
|
|
const char* package_config_;
|
|
const char* library_url_ = nullptr;
|
|
const char* class_name_ = nullptr;
|
|
const char* function_name_ = nullptr;
|
|
const char* debug_name_;
|
|
PersistentHandle* closure_tuple_handle_ = nullptr;
|
|
IsolateGroup* isolate_group_;
|
|
std::unique_ptr<Message> serialized_args_;
|
|
std::unique_ptr<Message> serialized_message_;
|
|
|
|
Dart_IsolateFlags isolate_flags_;
|
|
bool paused_;
|
|
bool errors_are_fatal_;
|
|
};
|
|
|
|
static const char* NewConstChar(const char* chars) {
|
|
size_t len = strlen(chars);
|
|
char* mem = new char[len + 1];
|
|
memmove(mem, chars, len + 1);
|
|
return mem;
|
|
}
|
|
|
|
IsolateSpawnState::IsolateSpawnState(Dart_Port parent_port,
|
|
Dart_Port origin_id,
|
|
const char* script_url,
|
|
PersistentHandle* closure_tuple_handle,
|
|
SerializedObjectBuffer* message_buffer,
|
|
const char* package_config,
|
|
bool paused,
|
|
bool errors_are_fatal,
|
|
Dart_Port on_exit_port,
|
|
Dart_Port on_error_port,
|
|
const char* debug_name,
|
|
IsolateGroup* isolate_group)
|
|
: parent_port_(parent_port),
|
|
origin_id_(origin_id),
|
|
on_exit_port_(on_exit_port),
|
|
on_error_port_(on_error_port),
|
|
script_url_(script_url),
|
|
package_config_(package_config),
|
|
debug_name_(debug_name),
|
|
closure_tuple_handle_(closure_tuple_handle),
|
|
isolate_group_(isolate_group),
|
|
serialized_args_(nullptr),
|
|
serialized_message_(message_buffer->StealMessage()),
|
|
paused_(paused),
|
|
errors_are_fatal_(errors_are_fatal) {
|
|
ASSERT(closure_tuple_handle_ != nullptr);
|
|
|
|
auto thread = Thread::Current();
|
|
auto isolate = thread->isolate();
|
|
|
|
// Inherit flags from spawning isolate.
|
|
isolate->FlagsCopyTo(isolate_flags());
|
|
}
|
|
|
|
IsolateSpawnState::IsolateSpawnState(Dart_Port parent_port,
|
|
const char* script_url,
|
|
const char* package_config,
|
|
SerializedObjectBuffer* args_buffer,
|
|
SerializedObjectBuffer* message_buffer,
|
|
bool paused,
|
|
bool errors_are_fatal,
|
|
Dart_Port on_exit_port,
|
|
Dart_Port on_error_port,
|
|
const char* debug_name,
|
|
IsolateGroup* isolate_group)
|
|
: parent_port_(parent_port),
|
|
on_exit_port_(on_exit_port),
|
|
on_error_port_(on_error_port),
|
|
script_url_(script_url),
|
|
package_config_(package_config),
|
|
debug_name_(debug_name),
|
|
isolate_group_(isolate_group),
|
|
serialized_args_(args_buffer->StealMessage()),
|
|
serialized_message_(message_buffer->StealMessage()),
|
|
isolate_flags_(),
|
|
paused_(paused),
|
|
errors_are_fatal_(errors_are_fatal) {
|
|
function_name_ = NewConstChar("main");
|
|
|
|
// By default inherit flags from spawning isolate. These can be overridden
|
|
// from the calling code.
|
|
Isolate::Current()->FlagsCopyTo(isolate_flags());
|
|
}
|
|
|
|
IsolateSpawnState::~IsolateSpawnState() {
|
|
delete[] script_url_;
|
|
delete[] package_config_;
|
|
delete[] library_url_;
|
|
delete[] class_name_;
|
|
delete[] function_name_;
|
|
delete[] debug_name_;
|
|
}
|
|
|
|
ObjectPtr IsolateSpawnState::ResolveFunction() {
|
|
Thread* thread = Thread::Current();
|
|
auto IG = thread->isolate_group();
|
|
Zone* zone = thread->zone();
|
|
|
|
const String& func_name = String::Handle(zone, String::New(function_name()));
|
|
|
|
if (library_url() == nullptr) {
|
|
// Handle spawnUri lookup rules.
|
|
// Check whether the root library defines a main function.
|
|
const Library& lib =
|
|
Library::Handle(zone, IG->object_store()->root_library());
|
|
Function& func = Function::Handle(zone, lib.LookupLocalFunction(func_name));
|
|
if (func.IsNull()) {
|
|
// Check whether main is reexported from the root library.
|
|
const Object& obj = Object::Handle(zone, lib.LookupReExport(func_name));
|
|
if (obj.IsFunction()) {
|
|
func ^= obj.ptr();
|
|
}
|
|
}
|
|
if (func.IsNull()) {
|
|
const String& msg = String::Handle(
|
|
zone, String::NewFormatted(
|
|
"Unable to resolve function '%s' in script '%s'.",
|
|
function_name(), script_url()));
|
|
return LanguageError::New(msg);
|
|
}
|
|
return func.ptr();
|
|
}
|
|
|
|
// Lookup the to be spawned function for the Isolate.spawn implementation.
|
|
// Resolve the library.
|
|
const String& lib_url = String::Handle(zone, String::New(library_url()));
|
|
const Library& lib =
|
|
Library::Handle(zone, Library::LookupLibrary(thread, lib_url));
|
|
if (lib.IsNull() || lib.IsError()) {
|
|
const String& msg = String::Handle(
|
|
zone,
|
|
String::NewFormatted("Unable to find library '%s'.", library_url()));
|
|
return LanguageError::New(msg);
|
|
}
|
|
|
|
// Resolve the function.
|
|
if (class_name() == nullptr) {
|
|
const Function& func =
|
|
Function::Handle(zone, lib.LookupLocalFunction(func_name));
|
|
if (func.IsNull()) {
|
|
const String& msg = String::Handle(
|
|
zone, String::NewFormatted(
|
|
"Unable to resolve function '%s' in library '%s'.",
|
|
function_name(), library_url()));
|
|
return LanguageError::New(msg);
|
|
}
|
|
return func.ptr();
|
|
}
|
|
|
|
const String& cls_name = String::Handle(zone, String::New(class_name()));
|
|
const Class& cls = Class::Handle(zone, lib.LookupLocalClass(cls_name));
|
|
if (cls.IsNull()) {
|
|
const String& msg = String::Handle(
|
|
zone, String::NewFormatted(
|
|
"Unable to resolve class '%s' in library '%s'.", class_name(),
|
|
(library_url() != nullptr ? library_url() : script_url())));
|
|
return LanguageError::New(msg);
|
|
}
|
|
Function& func = Function::Handle(zone);
|
|
const auto& error = cls.EnsureIsFinalized(thread);
|
|
if (error == Error::null()) {
|
|
func = cls.LookupStaticFunctionAllowPrivate(func_name);
|
|
}
|
|
if (func.IsNull()) {
|
|
const String& msg = String::Handle(
|
|
zone, String::NewFormatted(
|
|
"Unable to resolve static method '%s.%s' in library '%s'.",
|
|
class_name(), function_name(),
|
|
(library_url() != nullptr ? library_url() : script_url())));
|
|
return LanguageError::New(msg);
|
|
}
|
|
return func.ptr();
|
|
}
|
|
|
|
static ObjectPtr DeserializeMessage(Thread* thread, Message* message) {
|
|
if (message == NULL) {
|
|
return Object::null();
|
|
}
|
|
if (message->IsRaw()) {
|
|
return Object::RawCast(message->raw_obj());
|
|
} else {
|
|
return ReadMessage(thread, message);
|
|
}
|
|
}
|
|
|
|
ObjectPtr IsolateSpawnState::BuildArgs(Thread* thread) {
|
|
const Object& result =
|
|
Object::Handle(DeserializeMessage(thread, serialized_args_.get()));
|
|
serialized_args_.reset();
|
|
return result.ptr();
|
|
}
|
|
|
|
ObjectPtr IsolateSpawnState::BuildMessage(Thread* thread) {
|
|
const Object& result =
|
|
Object::Handle(DeserializeMessage(thread, serialized_message_.get()));
|
|
serialized_message_.reset();
|
|
return result.ptr();
|
|
}
|
|
|
|
static void ThrowIsolateSpawnException(const String& message) {
|
|
const Array& args = Array::Handle(Array::New(1));
|
|
args.SetAt(0, message);
|
|
Exceptions::ThrowByType(Exceptions::kIsolateSpawn, args);
|
|
}
|
|
|
|
class SpawnIsolateTask : public ThreadPool::Task {
|
|
public:
|
|
SpawnIsolateTask(Isolate* parent_isolate,
|
|
std::unique_ptr<IsolateSpawnState> state)
|
|
: parent_isolate_(parent_isolate), state_(std::move(state)) {
|
|
parent_isolate->IncrementSpawnCount();
|
|
}
|
|
|
|
~SpawnIsolateTask() override {
|
|
if (parent_isolate_ != nullptr) {
|
|
parent_isolate_->DecrementSpawnCount();
|
|
}
|
|
}
|
|
|
|
void Run() override {
|
|
const char* name = (state_->debug_name() == nullptr)
|
|
? state_->function_name()
|
|
: state_->debug_name();
|
|
ASSERT(name != nullptr);
|
|
|
|
auto group = state_->isolate_group();
|
|
if (group == nullptr) {
|
|
RunHeavyweight(name);
|
|
} else {
|
|
RunLightweight(name);
|
|
}
|
|
}
|
|
|
|
void RunHeavyweight(const char* name) {
|
|
// The create isolate group callback is mandatory. If not provided we
|
|
// cannot spawn isolates.
|
|
auto create_group_callback = Isolate::CreateGroupCallback();
|
|
if (create_group_callback == nullptr) {
|
|
FailedSpawn("Isolate spawn is not supported by this Dart embedder\n");
|
|
return;
|
|
}
|
|
|
|
char* error = nullptr;
|
|
|
|
// Make a copy of the state's isolate flags and hand it to the callback.
|
|
Dart_IsolateFlags api_flags = *(state_->isolate_flags());
|
|
api_flags.is_system_isolate = false;
|
|
Dart_Isolate isolate =
|
|
(create_group_callback)(state_->script_url(), name, nullptr,
|
|
state_->package_config(), &api_flags,
|
|
parent_isolate_->init_callback_data(), &error);
|
|
parent_isolate_->DecrementSpawnCount();
|
|
parent_isolate_ = nullptr;
|
|
|
|
if (isolate == nullptr) {
|
|
FailedSpawn(error, /*has_current_isolate=*/false);
|
|
free(error);
|
|
return;
|
|
}
|
|
Dart_EnterIsolate(isolate);
|
|
Run(reinterpret_cast<Isolate*>(isolate));
|
|
}
|
|
|
|
void RunLightweight(const char* name) {
|
|
// The create isolate initialize callback is mandatory.
|
|
auto initialize_callback = Isolate::InitializeCallback();
|
|
if (initialize_callback == nullptr) {
|
|
FailedSpawn(
|
|
"Lightweight isolate spawn is not supported by this Dart embedder\n",
|
|
/*has_current_isolate=*/false);
|
|
return;
|
|
}
|
|
|
|
char* error = nullptr;
|
|
|
|
auto group = state_->isolate_group();
|
|
Isolate* isolate = CreateWithinExistingIsolateGroup(group, name, &error);
|
|
parent_isolate_->DecrementSpawnCount();
|
|
parent_isolate_ = nullptr;
|
|
|
|
if (isolate == nullptr) {
|
|
FailedSpawn(error, /*has_current_isolate=*/false);
|
|
free(error);
|
|
return;
|
|
}
|
|
|
|
void* child_isolate_data = nullptr;
|
|
const bool success = initialize_callback(&child_isolate_data, &error);
|
|
if (!success) {
|
|
FailedSpawn(error);
|
|
Dart_ShutdownIsolate();
|
|
free(error);
|
|
return;
|
|
}
|
|
|
|
isolate->set_init_callback_data(child_isolate_data);
|
|
Run(isolate);
|
|
}
|
|
|
|
private:
|
|
void Run(Isolate* child) {
|
|
if (!EnsureIsRunnable(child)) {
|
|
Dart_ShutdownIsolate();
|
|
return;
|
|
}
|
|
|
|
state_->set_isolate(child);
|
|
if (state_->origin_id() != ILLEGAL_PORT) {
|
|
// origin_id is set to parent isolate main port id when spawning via
|
|
// spawnFunction.
|
|
child->set_origin_id(state_->origin_id());
|
|
}
|
|
|
|
bool success = true;
|
|
{
|
|
auto thread = Thread::Current();
|
|
TransitionNativeToVM transition(thread);
|
|
StackZone zone(thread);
|
|
HandleScope hs(thread);
|
|
|
|
success = EnqueueEntrypointInvocationAndNotifySpawner(thread);
|
|
}
|
|
|
|
if (!success) {
|
|
state_ = nullptr;
|
|
Dart_ShutdownIsolate();
|
|
return;
|
|
}
|
|
|
|
// All preconditions are met for this to always succeed.
|
|
char* error = nullptr;
|
|
if (!Dart_RunLoopAsync(state_->errors_are_fatal(), state_->on_error_port(),
|
|
state_->on_exit_port(), &error)) {
|
|
FATAL("Dart_RunLoopAsync() failed: %s. Please file a Dart VM bug report.",
|
|
error);
|
|
}
|
|
}
|
|
|
|
bool EnsureIsRunnable(Isolate* child) {
|
|
// We called out to the embedder to create/initialize a new isolate. The
|
|
// embedder callback sucessfully did so. It is now our responsibility to
|
|
// run the isolate.
|
|
// If the isolate was not marked as runnable, we'll do so here and run it.
|
|
if (!child->is_runnable()) {
|
|
const char* error = child->MakeRunnable();
|
|
if (error != nullptr) {
|
|
FailedSpawn(error);
|
|
return false;
|
|
}
|
|
}
|
|
ASSERT(child->is_runnable());
|
|
return true;
|
|
}
|
|
|
|
bool EnqueueEntrypointInvocationAndNotifySpawner(Thread* thread) {
|
|
auto isolate = thread->isolate();
|
|
auto zone = thread->zone();
|
|
const bool is_spawn_uri = state_->is_spawn_uri();
|
|
|
|
// Step 1) Resolve the entrypoint function.
|
|
auto& entrypoint_closure = Closure::Handle(zone);
|
|
if (state_->closure_tuple_handle() != nullptr) {
|
|
const auto& result = Object::Handle(
|
|
zone,
|
|
ReadObjectGraphCopyMessage(thread, state_->closure_tuple_handle()));
|
|
if (result.IsError()) {
|
|
ReportError(
|
|
"Failed to deserialize the passed entrypoint to the new isolate.");
|
|
return false;
|
|
}
|
|
entrypoint_closure = Closure::RawCast(result.ptr());
|
|
} else {
|
|
const auto& result = Object::Handle(zone, state_->ResolveFunction());
|
|
if (result.IsError()) {
|
|
ASSERT(is_spawn_uri);
|
|
ReportError("Failed to resolve entrypoint function.");
|
|
return false;
|
|
}
|
|
ASSERT(result.IsFunction());
|
|
auto& func = Function::Handle(zone, Function::Cast(result).ptr());
|
|
func = func.ImplicitClosureFunction();
|
|
entrypoint_closure = func.ImplicitStaticClosure();
|
|
}
|
|
|
|
// Step 2) Enqueue delayed invocation of entrypoint callback.
|
|
const auto& args_obj = Object::Handle(zone, state_->BuildArgs(thread));
|
|
if (args_obj.IsError()) {
|
|
ReportError(
|
|
"Failed to deserialize the passed arguments to the new isolate.");
|
|
return false;
|
|
}
|
|
ASSERT(args_obj.IsNull() || args_obj.IsInstance());
|
|
const auto& message_obj =
|
|
Object::Handle(zone, state_->BuildMessage(thread));
|
|
if (message_obj.IsError()) {
|
|
ReportError(
|
|
"Failed to deserialize the passed arguments to the new isolate.");
|
|
return false;
|
|
}
|
|
ASSERT(message_obj.IsNull() || message_obj.IsInstance());
|
|
const Array& args = Array::Handle(zone, Array::New(4));
|
|
args.SetAt(0, entrypoint_closure);
|
|
args.SetAt(1, args_obj);
|
|
args.SetAt(2, message_obj);
|
|
args.SetAt(3, is_spawn_uri ? Bool::True() : Bool::False());
|
|
|
|
const auto& lib = Library::Handle(zone, Library::IsolateLibrary());
|
|
const auto& entry_name = String::Handle(zone, String::New("_startIsolate"));
|
|
const auto& entry_point =
|
|
Function::Handle(zone, lib.LookupLocalFunction(entry_name));
|
|
ASSERT(entry_point.IsFunction() && !entry_point.IsNull());
|
|
const auto& result =
|
|
Object::Handle(zone, DartEntry::InvokeFunction(entry_point, args));
|
|
if (result.IsError()) {
|
|
ReportError("Failed to enqueue delayed entrypoint invocation.");
|
|
return false;
|
|
}
|
|
|
|
// Step 3) Pause the isolate if required & Notify parent isolate about
|
|
// isolate creation.
|
|
const auto& capabilities = Array::Handle(zone, Array::New(2));
|
|
auto& capability = Capability::Handle(zone);
|
|
capability = Capability::New(isolate->pause_capability());
|
|
capabilities.SetAt(0, capability);
|
|
capability = Capability::New(isolate->terminate_capability());
|
|
capabilities.SetAt(1, capability);
|
|
const auto& send_port =
|
|
SendPort::Handle(zone, SendPort::New(isolate->main_port()));
|
|
const auto& message = Array::Handle(zone, Array::New(2));
|
|
message.SetAt(0, send_port);
|
|
message.SetAt(1, capabilities);
|
|
if (state_->paused()) {
|
|
capability ^= capabilities.At(0);
|
|
const bool added = isolate->AddResumeCapability(capability);
|
|
ASSERT(added);
|
|
isolate->message_handler()->increment_paused();
|
|
}
|
|
{
|
|
// If parent isolate died, we ignore the fact that we cannot notify it.
|
|
PortMap::PostMessage(WriteMessage(/* can_send_any_object */ false,
|
|
/* same_group */ false, message,
|
|
state_->parent_port(),
|
|
Message::kNormalPriority));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FailedSpawn(const char* error, bool has_current_isolate = true) {
|
|
ReportError(error != nullptr
|
|
? error
|
|
: "Unknown error occured during Isolate spawning.");
|
|
// Destruction of [IsolateSpawnState] may cause destruction of [Message]
|
|
// which make need to delete persistent handles (which requires a current
|
|
// isolate group).
|
|
if (has_current_isolate) {
|
|
ASSERT(IsolateGroup::Current() == state_->isolate_group());
|
|
state_ = nullptr;
|
|
} else if (state_->isolate_group() != nullptr) {
|
|
ASSERT(IsolateGroup::Current() == nullptr);
|
|
const bool kBypassSafepoint = false;
|
|
const bool result = Thread::EnterIsolateGroupAsHelper(
|
|
state_->isolate_group(), Thread::kUnknownTask, kBypassSafepoint);
|
|
ASSERT(result);
|
|
state_ = nullptr;
|
|
Thread::ExitIsolateGroupAsHelper(kBypassSafepoint);
|
|
} else {
|
|
// The state won't need a current isolate group, because it belongs to a
|
|
// [Isolate.spawnUri] call.
|
|
state_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void ReportError(const char* error) {
|
|
Dart_CObject error_cobj;
|
|
error_cobj.type = Dart_CObject_kString;
|
|
error_cobj.value.as_string = const_cast<char*>(error);
|
|
if (!Dart_PostCObject(state_->parent_port(), &error_cobj)) {
|
|
// Perhaps the parent isolate died or closed the port before we
|
|
// could report the error. Ignore.
|
|
}
|
|
}
|
|
|
|
Isolate* parent_isolate_;
|
|
std::unique_ptr<IsolateSpawnState> state_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(SpawnIsolateTask);
|
|
};
|
|
|
|
static const char* String2UTF8(const String& str) {
|
|
intptr_t len = Utf8::Length(str);
|
|
char* result = new char[len + 1];
|
|
str.ToUTF8(reinterpret_cast<uint8_t*>(result), len);
|
|
result[len] = 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_spawnFunction, 0, 10) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(String, script_uri, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Closure, closure, arguments->NativeArgAt(2));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, message, arguments->NativeArgAt(3));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Bool, paused, arguments->NativeArgAt(4));
|
|
GET_NATIVE_ARGUMENT(Bool, fatalErrors, arguments->NativeArgAt(5));
|
|
GET_NATIVE_ARGUMENT(SendPort, onExit, arguments->NativeArgAt(6));
|
|
GET_NATIVE_ARGUMENT(SendPort, onError, arguments->NativeArgAt(7));
|
|
GET_NATIVE_ARGUMENT(String, packageConfig, arguments->NativeArgAt(8));
|
|
GET_NATIVE_ARGUMENT(String, debugName, arguments->NativeArgAt(9));
|
|
|
|
PersistentHandle* closure_tuple_handle = nullptr;
|
|
// We have a non-toplevel closure that we might need to copy.
|
|
// Result will be [<closure-copy>, <objects-in-msg-to-rehash>]
|
|
const auto& closure_copy_tuple = Object::Handle(
|
|
zone, CopyMutableObjectGraph(closure)); // Throws if it fails.
|
|
ASSERT(closure_copy_tuple.IsArray());
|
|
ASSERT(
|
|
Object::Handle(zone, Array::Cast(closure_copy_tuple).At(0)).IsClosure());
|
|
closure_tuple_handle =
|
|
isolate->group()->api_state()->AllocatePersistentHandle();
|
|
closure_tuple_handle->set_ptr(closure_copy_tuple.ptr());
|
|
|
|
bool fatal_errors = fatalErrors.IsNull() ? true : fatalErrors.value();
|
|
Dart_Port on_exit_port = onExit.IsNull() ? ILLEGAL_PORT : onExit.Id();
|
|
Dart_Port on_error_port = onError.IsNull() ? ILLEGAL_PORT : onError.Id();
|
|
|
|
// We first try to serialize the message. In case the message is not
|
|
// serializable this will throw an exception.
|
|
SerializedObjectBuffer message_buffer;
|
|
message_buffer.set_message(WriteMessage(
|
|
/*can_send_any_object=*/true,
|
|
/*same_group=*/true, message, ILLEGAL_PORT, Message::kNormalPriority));
|
|
|
|
const char* utf8_package_config =
|
|
packageConfig.IsNull() ? NULL : String2UTF8(packageConfig);
|
|
const char* utf8_debug_name =
|
|
debugName.IsNull() ? NULL : String2UTF8(debugName);
|
|
if (closure_tuple_handle != nullptr && utf8_debug_name == nullptr) {
|
|
const auto& closure_function = Function::Handle(zone, closure.function());
|
|
utf8_debug_name =
|
|
NewConstChar(closure_function.QualifiedUserVisibleNameCString());
|
|
}
|
|
|
|
std::unique_ptr<IsolateSpawnState> state(new IsolateSpawnState(
|
|
port.Id(), isolate->origin_id(), String2UTF8(script_uri),
|
|
closure_tuple_handle, &message_buffer, utf8_package_config,
|
|
paused.value(), fatal_errors, on_exit_port, on_error_port,
|
|
utf8_debug_name, isolate->group()));
|
|
|
|
// Since this is a call to Isolate.spawn, copy the parent isolate's code.
|
|
state->isolate_flags()->copy_parent_code = true;
|
|
|
|
isolate->group()->thread_pool()->Run<SpawnIsolateTask>(isolate,
|
|
std::move(state));
|
|
return Object::null();
|
|
}
|
|
|
|
static const char* CanonicalizeUri(Thread* thread,
|
|
const Library& library,
|
|
const String& uri,
|
|
char** error) {
|
|
const char* result = NULL;
|
|
Zone* zone = thread->zone();
|
|
auto isolate_group = thread->isolate_group();
|
|
if (isolate_group->HasTagHandler()) {
|
|
const Object& obj = Object::Handle(
|
|
isolate_group->CallTagHandler(Dart_kCanonicalizeUrl, library, uri));
|
|
if (obj.IsString()) {
|
|
result = String2UTF8(String::Cast(obj));
|
|
} else if (obj.IsError()) {
|
|
Error& error_obj = Error::Handle();
|
|
error_obj ^= obj.ptr();
|
|
*error = zone->PrintToString("Unable to canonicalize uri '%s': %s",
|
|
uri.ToCString(), error_obj.ToErrorCString());
|
|
} else {
|
|
*error = zone->PrintToString(
|
|
"Unable to canonicalize uri '%s': "
|
|
"library tag handler returned wrong type",
|
|
uri.ToCString());
|
|
}
|
|
} else {
|
|
*error = zone->PrintToString(
|
|
"Unable to canonicalize uri '%s': no library tag handler found.",
|
|
uri.ToCString());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_spawnUri, 0, 12) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(String, uri, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, args, arguments->NativeArgAt(2));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, message, arguments->NativeArgAt(3));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Bool, paused, arguments->NativeArgAt(4));
|
|
GET_NATIVE_ARGUMENT(SendPort, onExit, arguments->NativeArgAt(5));
|
|
GET_NATIVE_ARGUMENT(SendPort, onError, arguments->NativeArgAt(6));
|
|
GET_NATIVE_ARGUMENT(Bool, fatalErrors, arguments->NativeArgAt(7));
|
|
GET_NATIVE_ARGUMENT(Bool, checked, arguments->NativeArgAt(8));
|
|
GET_NATIVE_ARGUMENT(Array, environment, arguments->NativeArgAt(9));
|
|
GET_NATIVE_ARGUMENT(String, packageConfig, arguments->NativeArgAt(10));
|
|
GET_NATIVE_ARGUMENT(String, debugName, arguments->NativeArgAt(11));
|
|
|
|
bool fatal_errors = fatalErrors.IsNull() ? true : fatalErrors.value();
|
|
Dart_Port on_exit_port = onExit.IsNull() ? ILLEGAL_PORT : onExit.Id();
|
|
Dart_Port on_error_port = onError.IsNull() ? ILLEGAL_PORT : onError.Id();
|
|
|
|
// We first try to serialize the arguments and the message. In case the
|
|
// arguments or the message are not serializable this will throw an exception.
|
|
SerializedObjectBuffer arguments_buffer;
|
|
SerializedObjectBuffer message_buffer;
|
|
{
|
|
arguments_buffer.set_message(WriteMessage(
|
|
/* can_send_any_object */ false,
|
|
/* same_group */ false, args, ILLEGAL_PORT, Message::kNormalPriority));
|
|
}
|
|
{
|
|
message_buffer.set_message(WriteMessage(/* can_send_any_object */ false,
|
|
/* same_group */ false, message,
|
|
ILLEGAL_PORT,
|
|
Message::kNormalPriority));
|
|
}
|
|
|
|
// Canonicalize the uri with respect to the current isolate.
|
|
const Library& root_lib =
|
|
Library::Handle(isolate->group()->object_store()->root_library());
|
|
char* error = NULL;
|
|
const char* canonical_uri = CanonicalizeUri(thread, root_lib, uri, &error);
|
|
if (canonical_uri == NULL) {
|
|
const String& msg = String::Handle(String::New(error));
|
|
ThrowIsolateSpawnException(msg);
|
|
}
|
|
|
|
const char* utf8_package_config =
|
|
packageConfig.IsNull() ? NULL : String2UTF8(packageConfig);
|
|
const char* utf8_debug_name =
|
|
debugName.IsNull() ? NULL : String2UTF8(debugName);
|
|
|
|
std::unique_ptr<IsolateSpawnState> state(new IsolateSpawnState(
|
|
port.Id(), canonical_uri, utf8_package_config, &arguments_buffer,
|
|
&message_buffer, paused.value(), fatal_errors, on_exit_port,
|
|
on_error_port, utf8_debug_name, /*group=*/nullptr));
|
|
|
|
// If we were passed a value then override the default flags state for
|
|
// checked mode.
|
|
if (!checked.IsNull()) {
|
|
Dart_IsolateFlags* flags = state->isolate_flags();
|
|
flags->enable_asserts = checked.value();
|
|
}
|
|
|
|
// Since this is a call to Isolate.spawnUri, don't copy the parent's code.
|
|
state->isolate_flags()->copy_parent_code = false;
|
|
|
|
isolate->group()->thread_pool()->Run<SpawnIsolateTask>(isolate,
|
|
std::move(state));
|
|
return Object::null();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_getDebugName, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
auto name = Isolate::LookupIsolateNameByPort(port.Id());
|
|
if (name == nullptr) {
|
|
return String::null();
|
|
}
|
|
return String::New(name.get());
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_getPortAndCapabilitiesOfCurrentIsolate, 0, 0) {
|
|
const Array& result = Array::Handle(Array::New(3));
|
|
result.SetAt(0, SendPort::Handle(SendPort::New(isolate->main_port())));
|
|
result.SetAt(
|
|
1, Capability::Handle(Capability::New(isolate->pause_capability())));
|
|
result.SetAt(
|
|
2, Capability::Handle(Capability::New(isolate->terminate_capability())));
|
|
return result.ptr();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_getCurrentRootUriStr, 0, 0) {
|
|
const Library& root_lib =
|
|
Library::Handle(zone, isolate->group()->object_store()->root_library());
|
|
return root_lib.url();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_registerKernelBlob, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(TypedData, kernel_blob,
|
|
arguments->NativeArgAt(0));
|
|
auto register_kernel_blob_callback = Isolate::RegisterKernelBlobCallback();
|
|
if (register_kernel_blob_callback == nullptr) {
|
|
const auto& error =
|
|
String::Handle(zone, String::New("Registration of kernel blobs is not "
|
|
"supported by this Dart embedder.\n"));
|
|
Exceptions::ThrowArgumentError(error);
|
|
UNREACHABLE();
|
|
}
|
|
bool is_kernel = false;
|
|
{
|
|
NoSafepointScope no_safepoint;
|
|
is_kernel =
|
|
Dart_IsKernel(reinterpret_cast<uint8_t*>(kernel_blob.DataAddr(0)),
|
|
kernel_blob.LengthInBytes());
|
|
}
|
|
if (!is_kernel) {
|
|
const auto& error = String::Handle(
|
|
zone, String::New("kernelBlob doesn\'t contain a valid kernel.\n"));
|
|
Exceptions::ThrowArgumentError(error);
|
|
UNREACHABLE();
|
|
}
|
|
const char* uri = nullptr;
|
|
{
|
|
NoSafepointScope no_safepoint;
|
|
uri = register_kernel_blob_callback(
|
|
reinterpret_cast<uint8_t*>(kernel_blob.DataAddr(0)),
|
|
kernel_blob.LengthInBytes());
|
|
}
|
|
if (uri == nullptr) {
|
|
const Instance& exception = Instance::Handle(
|
|
thread->isolate_group()->object_store()->out_of_memory());
|
|
Exceptions::Throw(thread, exception);
|
|
UNREACHABLE();
|
|
}
|
|
return String::New(uri);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_unregisterKernelBlob, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(String, kernel_blob_uri,
|
|
arguments->NativeArgAt(0));
|
|
auto unregister_kernel_blob_callback =
|
|
Isolate::UnregisterKernelBlobCallback();
|
|
if (unregister_kernel_blob_callback == nullptr) {
|
|
const auto& error =
|
|
String::Handle(zone, String::New("Registration of kernel blobs is not "
|
|
"supported by this Dart embedder.\n"));
|
|
Exceptions::ThrowArgumentError(error);
|
|
UNREACHABLE();
|
|
}
|
|
unregister_kernel_blob_callback(kernel_blob_uri.ToCString());
|
|
return Object::null();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(Isolate_sendOOB, 0, 2) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Array, msg, arguments->NativeArgAt(1));
|
|
|
|
// Make sure to route this request to the isolate library OOB mesage handler.
|
|
msg.SetAt(0, Smi::Handle(Smi::New(Message::kIsolateLibOOBMsg)));
|
|
|
|
// Ensure message writer (and it's resources, e.g. forwarding tables) are
|
|
// cleaned up before handling interrupts.
|
|
{
|
|
PortMap::PostMessage(WriteMessage(/* can_send_any_object */ false,
|
|
/* same_group */ false, msg, port.Id(),
|
|
Message::kOOBPriority));
|
|
}
|
|
|
|
// Drain interrupts before running so any IMMEDIATE operations on the current
|
|
// isolate happen synchronously.
|
|
const Error& error = Error::Handle(thread->HandleInterrupts());
|
|
if (!error.IsNull()) {
|
|
Exceptions::PropagateError(error);
|
|
UNREACHABLE();
|
|
}
|
|
|
|
return Object::null();
|
|
}
|
|
|
|
static void ExternalTypedDataFinalizer(void* isolate_callback_data,
|
|
void* peer) {
|
|
free(peer);
|
|
}
|
|
|
|
static intptr_t GetTypedDataSizeOrThrow(const Instance& instance) {
|
|
// From the Dart side we are guaranteed that the type of [instance] is a
|
|
// subtype of TypedData.
|
|
if (instance.IsTypedDataBase()) {
|
|
return TypedDataBase::Cast(instance).LengthInBytes();
|
|
}
|
|
|
|
// This can happen if [instance] is `null` or an instance of a 3rd party class
|
|
// which implements [TypedData].
|
|
Exceptions::ThrowArgumentError(instance);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(TransferableTypedData_factory, 0, 2) {
|
|
ASSERT(
|
|
TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull());
|
|
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, array_instance,
|
|
arguments->NativeArgAt(1));
|
|
|
|
Array& array = Array::Handle();
|
|
intptr_t array_length;
|
|
if (array_instance.IsGrowableObjectArray()) {
|
|
const auto& growable_array = GrowableObjectArray::Cast(array_instance);
|
|
array ^= growable_array.data();
|
|
array_length = growable_array.Length();
|
|
} else if (array_instance.IsArray()) {
|
|
array ^= Array::Cast(array_instance).ptr();
|
|
array_length = array.Length();
|
|
} else {
|
|
Exceptions::ThrowArgumentError(array_instance);
|
|
UNREACHABLE();
|
|
}
|
|
Instance& instance = Instance::Handle();
|
|
uint64_t total_bytes = 0;
|
|
const uint64_t kMaxBytes = TypedData::MaxElements(kTypedDataUint8ArrayCid);
|
|
for (intptr_t i = 0; i < array_length; i++) {
|
|
instance ^= array.At(i);
|
|
total_bytes += static_cast<uintptr_t>(GetTypedDataSizeOrThrow(instance));
|
|
if (total_bytes > kMaxBytes) {
|
|
const Array& error_args = Array::Handle(Array::New(3));
|
|
error_args.SetAt(0, array);
|
|
error_args.SetAt(1, String::Handle(String::New("data")));
|
|
error_args.SetAt(
|
|
2, String::Handle(String::NewFormatted(
|
|
"Aggregated list exceeds max size %" Pu64 "", kMaxBytes)));
|
|
Exceptions::ThrowByType(Exceptions::kArgumentValue, error_args);
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
uint8_t* data = reinterpret_cast<uint8_t*>(::malloc(total_bytes));
|
|
if (data == nullptr) {
|
|
const Instance& exception = Instance::Handle(
|
|
thread->isolate_group()->object_store()->out_of_memory());
|
|
Exceptions::Throw(thread, exception);
|
|
UNREACHABLE();
|
|
}
|
|
intptr_t offset = 0;
|
|
for (intptr_t i = 0; i < array_length; i++) {
|
|
instance ^= array.At(i);
|
|
|
|
{
|
|
NoSafepointScope no_safepoint;
|
|
const auto& typed_data = TypedDataBase::Cast(instance);
|
|
const intptr_t length_in_bytes = typed_data.LengthInBytes();
|
|
|
|
void* source = typed_data.DataAddr(0);
|
|
// The memory does not overlap.
|
|
memcpy(data + offset, source, length_in_bytes); // NOLINT
|
|
offset += length_in_bytes;
|
|
}
|
|
}
|
|
ASSERT(static_cast<uintptr_t>(offset) == total_bytes);
|
|
return TransferableTypedData::New(data, total_bytes);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(TransferableTypedData_materialize, 0, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(TransferableTypedData, t,
|
|
arguments->NativeArgAt(0));
|
|
|
|
void* peer;
|
|
{
|
|
NoSafepointScope no_safepoint;
|
|
peer = thread->heap()->GetPeer(t.ptr());
|
|
// Assume that object's Peer is only used to track transferrability state.
|
|
ASSERT(peer != nullptr);
|
|
}
|
|
|
|
TransferableTypedDataPeer* tpeer =
|
|
reinterpret_cast<TransferableTypedDataPeer*>(peer);
|
|
const intptr_t length = tpeer->length();
|
|
uint8_t* data = tpeer->data();
|
|
if (data == nullptr) {
|
|
const auto& error = String::Handle(String::New(
|
|
"Attempt to materialize object that was transferred already."));
|
|
Exceptions::ThrowArgumentError(error);
|
|
UNREACHABLE();
|
|
}
|
|
tpeer->handle()->EnsureFreedExternal(IsolateGroup::Current());
|
|
tpeer->ClearData();
|
|
|
|
const ExternalTypedData& typed_data = ExternalTypedData::Handle(
|
|
ExternalTypedData::New(kExternalTypedDataUint8ArrayCid, data, length,
|
|
thread->heap()->SpaceForExternal(length)));
|
|
FinalizablePersistentHandle::New(thread->isolate_group(), typed_data,
|
|
/* peer= */ data,
|
|
&ExternalTypedDataFinalizer, length,
|
|
/*auto_delete=*/true);
|
|
return typed_data.ptr();
|
|
}
|
|
|
|
} // namespace dart
|