// 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(Capability_factory, 0, 0) {
  // Keep capability IDs less than 2^53 so web clients of the service
  // protocol can process it properly.
  //
  // See https://github.com/dart-lang/sdk/issues/53081.
  uint64_t id = thread->random()->NextJSInt();
  return Capability::New(id);
}

DEFINE_NATIVE_ENTRY(Capability_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(Capability_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(RawReceivePort_factory, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(String, debug_name, arguments->NativeArgAt(0));
  if (isolate == nullptr) {
    ThrowCantRunWithoutIsolateError();
    UNREACHABLE();
  }
  return isolate->CreateReceivePort(debug_name);
}

DEFINE_NATIVE_ENTRY(RawReceivePort_get_id, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
  return Integer::New(port.Id());
}

DEFINE_NATIVE_ENTRY(RawReceivePort_closeInternal, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
  Dart_Port id = port.Id();
  isolate->CloseReceivePort(port);
  return Integer::New(id);
}

DEFINE_NATIVE_ENTRY(RawReceivePort_setActive, 0, 2) {
  GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
  GET_NON_NULL_NATIVE_ARGUMENT(Bool, active, arguments->NativeArgAt(1));
  isolate->SetReceivePortKeepAliveState(port, active.value());
  return Object::null();
}

DEFINE_NATIVE_ENTRY(RawReceivePort_getActive, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
  return Bool::Get(port.keep_isolate_alive()).ptr();
}

DEFINE_NATIVE_ENTRY(SendPort_get_id, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
  return Integer::New(port.Id());
}

DEFINE_NATIVE_ENTRY(SendPort_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);
}

static bool InSameGroup(IsolateGroup* sender, const SendPort& receiver) {
  // Only allow arbitrary messages between isolates of the same IG.
  return sender->id() == receiver.origin_id();
}

DEFINE_NATIVE_ENTRY(SendPort_sendInternal_, 0, 2) {
  GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
  GET_NON_NULL_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1));

  const Dart_Port destination_port_id = port.Id();
  IsolateGroup* group = thread->isolate_group();
  const bool same_group = InSameGroup(group, port);
#if defined(DEBUG)
  if (same_group) {
    ASSERT(PortMap::IsReceiverInThisIsolateGroupOrClosed(destination_port_id,
                                                         group));
  }
#endif

  // TODO(turnidge): Throw an exception when the return value is false?
  PortMap::PostMessage(WriteMessage(same_group, obj, destination_port_id,
                                    Message::kNormalPriority));
  return Object::null();
}

class UntaggedObjectPtrSetTraits {
 public:
  static bool ReportStats() { return false; }
  static const char* Name() { return "UntaggedObjectPtrSetTraits"; }

  static bool IsMatch(const ObjectPtr a, const ObjectPtr b) { return a == b; }

  static uword Hash(const ObjectPtr obj) { return static_cast<uword>(obj); }
};

#if defined(HASH_IN_OBJECT_HEADER)
// Written to avoid O(elements) pauses that block safepoints. Compare
// object_graph_copy.cc's IdentityMap, which implements something similar
// but with enough differents to make shared code less readable.
class WorkSet {
 public:
  explicit WorkSet(Thread* thread)
      : thread_(thread), list_(GrowableObjectArray::Handle()) {
    hash_table_used_ = 0;
    hash_table_capacity_ = 32;
    hash_table_ = reinterpret_cast<uint32_t*>(
        malloc(hash_table_capacity_ * sizeof(uint32_t)));
    memset(hash_table_, 0, hash_table_capacity_ * sizeof(uint32_t));

    list_ = GrowableObjectArray::New(256);
    list_.Add(Object::null_object());  // Id 0 is sentinel.
    cursor_ = 1;
  }
  ~WorkSet() { free(hash_table_); }

  void Push(const Object& obj) {
    intptr_t mask = hash_table_capacity_ - 1;
    intptr_t probe = GetHeaderHash(obj.ptr()) & mask;
    for (;;) {
      intptr_t index = hash_table_[probe];
      if (index == 0) {
        intptr_t id = list_.Length();
        list_.Add(obj);
        hash_table_[probe] = id;
        break;
      }
      if (list_.At(index) == obj.ptr()) {
        break;  // Already present.
      }
      probe = (probe + 1) & mask;
    }
    hash_table_used_++;
    if (hash_table_used_ * 2 > hash_table_capacity_) {
      Rehash(hash_table_capacity_ * 2);
    }
  }

  bool Pop(Object* obj) {
    if (cursor_ < list_.Length()) {
      *obj = list_.At(cursor_);
      cursor_++;
      return true;
    }
    return false;
  }

 private:
  DART_FORCE_INLINE
  uint32_t GetHeaderHash(ObjectPtr object) {
    uint32_t hash = Object::GetCachedHash(object);
    if (hash == 0) {
      switch (object->GetClassIdOfHeapObject()) {
        case kMintCid:
          hash = Mint::Value(static_cast<MintPtr>(object));
          // Don't write back: doesn't agree with dart:core's identityHash.
          break;
        case kDoubleCid:
          hash =
              bit_cast<uint64_t>(Double::Value(static_cast<DoublePtr>(object)));
          // Don't write back: doesn't agree with dart:core's identityHash.
          break;
        case kOneByteStringCid:
        case kTwoByteStringCid:
          hash = String::Hash(static_cast<StringPtr>(object));
          hash = Object::SetCachedHashIfNotSet(object, hash);
          break;
        default:
          do {
            hash = thread_->random()->NextUInt32();
          } while (hash == 0 || !Smi::IsValid(hash));
          hash = Object::SetCachedHashIfNotSet(object, hash);
          break;
      }
    }
    return hash;
  }

  void Rehash(intptr_t new_capacity) {
    hash_table_capacity_ = new_capacity;
    hash_table_used_ = 0;
    free(hash_table_);
    hash_table_ = reinterpret_cast<uint32_t*>(
        malloc(hash_table_capacity_ * sizeof(uint32_t)));
    for (intptr_t i = 0; i < hash_table_capacity_; i++) {
      hash_table_[i] = 0;
      if (((i + 1) % kSlotsPerInterruptCheck) == 0) {
        thread_->CheckForSafepoint();
      }
    }
    for (intptr_t id = 1; id < list_.Length(); id++) {
      ObjectPtr obj = list_.At(id);
      intptr_t mask = hash_table_capacity_ - 1;
      intptr_t probe = GetHeaderHash(obj) & mask;
      for (;;) {
        if (hash_table_[probe] == 0) {
          hash_table_[probe] = id;
          hash_table_used_++;
          break;
        }
        probe = (probe + 1) & mask;
      }
      if (((id + 1) % kSlotsPerInterruptCheck) == 0) {
        thread_->CheckForSafepoint();
      }
    }
  }

  Thread* thread_;
  uint32_t* hash_table_;
  uint32_t hash_table_capacity_;
  uint32_t hash_table_used_;
  GrowableObjectArray& list_;
  intptr_t cursor_;
};
#else   // defined(HASH_IN_OBJECT_HEADER)
class WorkSet {
 public:
  explicit WorkSet(Thread* thread)
      : thread_(thread), list_(GrowableObjectArray::Handle()) {
    thread_->set_forward_table_new(new WeakTable());
    thread_->set_forward_table_old(new WeakTable());
    list_ = GrowableObjectArray::New(256);
    cursor_ = 0;
  }
  ~WorkSet() {
    thread_->set_forward_table_new(nullptr);
    thread_->set_forward_table_old(nullptr);
  }

  void Push(const Object& obj) {
    ASSERT(WeakTable::kNoValue == 0);
    if (GetObjectId(obj.ptr()) == 0) {
      SetObjectId(obj.ptr(), 1);
      list_.Add(obj);
    }
  }

  bool Pop(Object* obj) {
    if (cursor_ < list_.Length()) {
      *obj = list_.At(cursor_);
      cursor_++;
      return true;
    }
    return false;
  }

 private:
  DART_FORCE_INLINE
  intptr_t GetObjectId(ObjectPtr object) {
    if (object->IsNewObject()) {
      return thread_->forward_table_new()->GetValueExclusive(object);
    } else {
      return thread_->forward_table_old()->GetValueExclusive(object);
    }
  }

  DART_FORCE_INLINE
  void SetObjectId(ObjectPtr object, intptr_t id) {
    if (object->IsNewObject()) {
      thread_->forward_table_new()->SetValueExclusive(object, id);
    } else {
      thread_->forward_table_old()->SetValueExclusive(object, id);
    }
  }

  Thread* thread_;
  GrowableObjectArray& list_;
  intptr_t cursor_;
};
#endif  // defined(HASH_IN_OBJECT_HEADER)

class MessageValidator : private WorkSet {
 public:
  explicit MessageValidator(Thread* thread)
      : WorkSet(thread),
        value_(PassiveObject::Handle()),
        class_table_(thread->isolate()->group()->class_table()) {}

  ObjectPtr Validate(Thread* thread, const Object& root) {
    TIMELINE_DURATION(thread, Isolate, "ValidateMessageObject");
    Visit(root.ptr());
    Object& current = Object::Handle();
    Class& klass = Class::Handle();
    while (Pop(&current)) {
      const intptr_t cid = current.GetClassId();
      switch (cid) {
        case kArrayCid:
        case kImmutableArrayCid:
          VisitArrayPointers(thread, Array::Cast(current));
          break;
        case kClosureCid:
          VisitClosurePointers(Closure::Cast(current));
          break;
        case kContextCid:
          VisitContextPointers(Context::Cast(current));
          break;
        case kGrowableObjectArrayCid:
          VisitGrowableObjectArrayPointers(GrowableObjectArray::Cast(current));
          break;
        case kSetCid:
        case kConstSetCid:
        case kMapCid:
        case kConstMapCid:
          VisitLinkedHashBasePointers(LinkedHashBase::Cast(current));
          break;
        case kRecordCid:
          VisitRecordPointers(Record::Cast(current));
          break;
        case kWeakPropertyCid:
          VisitWeakPropertyPointers(WeakProperty::Cast(current));
          break;
        case kWeakReferenceCid:
          VisitWeakReferencePointers(WeakReference::Cast(current));
          break;
#define MESSAGE_SNAPSHOT_ILLEGAL(type)                                         \
  case k##type##Cid:                                                           \
    return Error(current, "is a " #type, root);
          MESSAGE_SNAPSHOT_ILLEGAL(DynamicLibrary)
          // TODO(http://dartbug.com/47777): Send and exit support: remove this.
          MESSAGE_SNAPSHOT_ILLEGAL(Finalizer)
          MESSAGE_SNAPSHOT_ILLEGAL(NativeFinalizer)
          MESSAGE_SNAPSHOT_ILLEGAL(MirrorReference)
          MESSAGE_SNAPSHOT_ILLEGAL(Pointer)
          MESSAGE_SNAPSHOT_ILLEGAL(ReceivePort)
          MESSAGE_SNAPSHOT_ILLEGAL(UserTag)
          MESSAGE_SNAPSHOT_ILLEGAL(SuspendState)
        default:
          klass = class_table_->At(cid);
          if (klass.is_isolate_unsendable()) {
            return Error(
                current,
                "is unsendable object (see restrictions listed at"
                "`SendPort.send()` documentation for more information)",
                root);
          }
          VisitInstancePointers(Instance::Cast(current), cid);
      }

      thread->CheckForSafepoint();
    }

    return root.ptr();
  }

 private:
  void VisitArrayPointers(Thread* thread, const Array& array) {
    for (intptr_t i = 0, n = array.Length(); i < n; i++) {
      Visit(array.At(i));
      if (((i + 1) % kSlotsPerInterruptCheck) == 0) {
        thread->CheckForSafepoint();
      }
    }
  }

  void VisitGrowableObjectArrayPointers(const GrowableObjectArray& list) {
    Visit(list.data());
  }

  void VisitRecordPointers(const Record& record) {
    for (intptr_t i = 0, n = record.num_fields(); i < n; i++) {
      Visit(record.FieldAt(i));
    }
  }

  void VisitContextPointers(const Context& context) {
    Visit(context.parent());
    for (intptr_t i = 0, n = context.num_variables(); i < n; i++) {
      Visit(context.At(i));
    }
  }

  void VisitLinkedHashBasePointers(const LinkedHashBase& hash) {
    Visit(hash.data());
  }

  void VisitClosurePointers(const Closure& closure) {
    Visit(closure.RawContext());
  }

  void VisitWeakPropertyPointers(const WeakProperty& weak) {
    Visit(weak.key());
    Visit(weak.value());
  }

  void VisitWeakReferencePointers(const WeakReference& weak) {
    Visit(weak.target());
  }

  void VisitInstancePointers(const Instance& instance, intptr_t cid) {
    ASSERT(cid == kInstanceCid || cid >= kNumPredefinedCids);

    const auto bitmap = class_table_->GetUnboxedFieldsMapAt(cid);

    intptr_t size = instance.ptr()->untag()->HeapSize();
    uword heap_base = instance.ptr()->heap_base();
    intptr_t offset = kWordSize;
    intptr_t bit = offset >> kCompressedWordSizeLog2;
    for (; offset < size; offset += kCompressedWordSize) {
      if (!bitmap.Get(bit++)) {
        Visit(reinterpret_cast<CompressedObjectPtr*>(
                  reinterpret_cast<uword>(instance.ptr()->untag()) + offset)
                  ->Decompress(heap_base));
      }
    }
  }

  void Visit(ObjectPtr obj) {
    if (!obj->IsHeapObject() || obj->untag()->IsCanonical() ||
        obj->untag()->IsImmutable()) {
      return;
    }
    switch (obj->untag()->GetClassId()) {
      case kTransferableTypedDataCid:
#define CASE(clazz)                                                            \
  case kTypedData##clazz##Cid:                                                 \
  case kTypedData##clazz##ViewCid:                                             \
  case kExternalTypedData##clazz##Cid:                                         \
  case kUnmodifiableTypedData##clazz##ViewCid:
        CLASS_LIST_TYPED_DATA(CASE)
#undef CASE
      case kByteDataViewCid:
      case kUnmodifiableByteDataViewCid:
      case kByteBufferCid:
        return;
    }
    value_ = obj;
    Push(value_);
  }

  ObjectPtr Error(const Object& illegal_object,
                  const char* exception_message,
                  const Object& root) {
    Thread* thread = Thread::Current();
    Zone* zone = thread->zone();
    const Array& args = Array::Handle(zone, Array::New(3));
    args.SetAt(0, illegal_object);
    args.SetAt(2, String::Handle(
                      zone, String::NewFormatted(
                                "%s%s",
                                FindRetainingPath(
                                    zone, thread, root, illegal_object,
                                    TraversalRules::kInternalToIsolateGroup),
                                exception_message)));
    const Object& exception = Object::Handle(
        zone, Exceptions::Create(Exceptions::kArgumentValue, args));
    return UnhandledException::New(Instance::Cast(exception),
                                   StackTrace::Handle(zone));
  }

 private:
  PassiveObject& value_;
  ClassTable* class_table_;
};

DEFINE_NATIVE_ENTRY(Isolate_create_, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(String, debug_name, arguments->NativeArgAt(0));
  const char* debug_name_cstr =
      !debug_name.IsNull() ? debug_name.ToCString() : nullptr;

  if (thread->isolate() != nullptr) {
    const auto& error =
        String::Handle(String::New("Should be invoked outside of an isolate"));
    Exceptions::ThrowStateError(error);
    UNREACHABLE();
  }

  char* error = nullptr;
  auto group = IsolateGroup::Current();

  auto& created = Array::Handle(zone, Array::New(2));
  const auto& capabilities = Array::Handle(zone, Array::New(2));
  auto& capability = Capability::Handle(zone);
  auto& send_port = SendPort::Handle(zone);

  Thread::ExitIsolateGroupAsMutator(/*bypass_safepoint=*/false);
  Isolate* created_isolate =
      CreateWithinExistingIsolateGroup(group, debug_name_cstr, &error);
  RELEASE_ASSERT(created_isolate != nullptr);

  Dart_ExitIsolate();
  Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false, thread);

  auto current_thread = Thread::Current();
  StackZone stack_zone(current_thread);

  capability = Capability::New(created_isolate->pause_capability());
  capabilities.SetAt(0, capability);
  capability = Capability::New(created_isolate->terminate_capability());
  capabilities.SetAt(1, capability);
  send_port = SendPort::New(created_isolate->main_port(), group->id());
  created.SetAt(0, send_port);
  created.SetAt(1, capabilities);
  return created.ptr();
}

namespace {

// Scope that gives user an ability to acquire an isolate if it is available,
// gain temporary ownership, which is released at the end of the scope.
class IsolateAcquireScope : public ValueObject {
 public:
  IsolateAcquireScope(Thread* thread, Dart_Port receiver) : isolate_(nullptr) {
    Isolate* target_isolate = nullptr;
    acquire_result_ =
        PortMap::AcquireIsolateByControlPort(receiver, &target_isolate);
    if (target_isolate == nullptr) {
      // Isolate might have exited already.
      return;
    }
    Thread::EnterIsolate(target_isolate);
    isolate_ = target_isolate;
  }

  ~IsolateAcquireScope() {
    if (isolate_ == nullptr) {
      return;
    }
    ASSERT(Thread::Current()->isolate() == isolate_);
    Thread::ExitIsolate();
    if (!isolate_->is_permanently_pinned()) {
      isolate_->ReleaseOwnership();
    }
  }

  void Reset() { isolate_ = nullptr; }
  Isolate* isolate() { return isolate_; }
  IsolateAcquireResult acquire_result() { return acquire_result_; }
  const char* error_message() {
    switch (acquire_result_) {
      case IsolateAcquireResult::ISOLATE_NOT_AVAILABLE:
        return "Unable to enter the isolate as it's unavailable";
      case IsolateAcquireResult::PINNED_TO_ANOTHER_THREAD:
        return "Isolate is pinned to a different thread already";
      case IsolateAcquireResult::BUSY:
        return "Isolate is busy, running on a different thread";
      case IsolateAcquireResult::HAS_MESSAGE_LOOP_OR_UNAVAILABLE:
        return "Isolate has a message loop running or otherwise unavailable.";
      default:
        UNREACHABLE();
    }
  }

 private:
  Isolate* isolate_;
  IsolateAcquireResult acquire_result_;

  DISALLOW_COPY_AND_ASSIGN(IsolateAcquireScope);
};

}  // namespace

DEFINE_NATIVE_ENTRY(Isolate_shutdownSync_, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(SendPort, isolate_control_port,
                               arguments->NativeArgAt(0));
  if (thread->isolate() != nullptr) {
    const auto& error =
        String::Handle(String::New("Should be invoked outside of an isolate"));
    Exceptions::ThrowStateError(error);
    UNREACHABLE();
  }
  auto group = thread->isolate_group();

  auto control_port_id = isolate_control_port.Id();

  Thread::ExitIsolateGroupAsMutator(/*bypass_safepoint=*/false);
  {
    IsolateAcquireScope acquire_scope(thread, control_port_id);
    Isolate* target_isolate = acquire_scope.isolate();
    if (target_isolate != nullptr) {
      Dart::ShutdownIsolate(Thread::Current());
      acquire_scope.Reset();
    }
  }
  Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false, thread);
  return Object::null();
}

DEFINE_NATIVE_ENTRY(Isolate_runEventLoopSync_, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(SendPort, isolate_control_port,
                               arguments->NativeArgAt(0));
  if (thread->isolate() != nullptr) {
    const auto& error =
        String::Handle(String::New("Should be invoked outside of an isolate"));
    Exceptions::ThrowStateError(error);
    UNREACHABLE();
  }

  auto group = thread->isolate_group();

  if (isolate_control_port.origin_id() != group->id()) {
    const auto& error = String::Handle(String::New(
        "Target isolate should be part of the same isolate group."));
    Exceptions::ThrowStateError(error);
    UNREACHABLE();
  }

  Dart_Port control_port_id = isolate_control_port.Id();

  Error& result_error = Error::Handle();
  Thread::ExitIsolateGroupAsMutator(/*bypass_safepoint=*/false);
  {
    // Take over isolate's event loop - block it's original message_handler.
    IsolateAcquireScope acquire_scope(thread, control_port_id);
    Isolate* target_isolate = acquire_scope.isolate();
    if (target_isolate == nullptr) {
      // Reenter the group so we can report an error.
      Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false,
                                         thread);
      const auto& message =
          String::Handle(String::New(acquire_scope.error_message()));
      Exceptions::ThrowStateError(message);
      UNREACHABLE();
    }
    auto current_thread = Thread::Current();

    {
      TransitionVMToNative transition(current_thread);

      Dart_EnterScope();
      Dart_ExitIsolate();
      target_isolate->message_handler()->RunSync();
      Dart_EnterIsolate(reinterpret_cast<Dart_Isolate>(target_isolate));
      if (target_isolate->sticky_error() != Object::null()) {
        result_error = target_isolate->StealStickyError();
      }
      Dart_ExitScope();
    }
  }
  Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false, thread);
  if (!result_error.IsNull()) {
    Exceptions::PropagateError(result_error);
    UNREACHABLE();
  }
  return Object::null();
}

DEFINE_NATIVE_ENTRY(Isolate_runSync_, 1, 2) {
  GET_NON_NULL_NATIVE_ARGUMENT(SendPort, isolate_control_port,
                               arguments->NativeArgAt(0));
  GET_NON_NULL_NATIVE_ARGUMENT(Closure, closure, arguments->NativeArgAt(1));
  auto group = thread->isolate_group();

  if (isolate_control_port.origin_id() != group->id()) {
    const auto& error = String::Handle(String::New(
        "Target isolate should be part of the same isolate group."));
    Exceptions::ThrowStateError(error);
    UNREACHABLE();
  }

  Dart_Port control_port_id = isolate_control_port.Id();

  closure.EnsureDeeplyImmutable(zone);

  Array& args_desc = Array::Handle(zone, ArgumentsDescriptor::NewBoxed(0, 1));
  Array& args = Array::Handle(zone, Array::New(1));
  args.SetAt(0, closure);
  Object& result = Object::Handle(zone);

  if (isolate != nullptr && (isolate->main_port() == control_port_id)) {
    // Fast-path for when we are already running on the desired isolate.
    result = DartEntry::InvokeClosure(thread, args, args_desc);

    if (result.IsUnwindError()) {
      Exceptions::PropagateError(Error::Cast(result));
      UNREACHABLE();
    }
  } else {
    if (isolate != nullptr) {
      ASSERT(Thread::Current()->isolate() == isolate);
      Thread::ExitIsolate(/*isolate_shutdown=*/false);
    } else {
      Thread::ExitIsolateGroupAsMutator(/*bypass_safepoint=*/false);
    }

    {
      IsolateAcquireScope acquire_scope(thread, control_port_id);
      Isolate* target_isolate = acquire_scope.isolate();
      if (target_isolate == nullptr) {
        // Re-enter the group or isolate so we can report an error.
        if (isolate != nullptr) {
          Thread::EnterIsolate(isolate);
        } else {
          Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false,
                                             thread);
        }
        const char* message = acquire_scope.error_message();
        Exceptions::ThrowStateError(String::Handle(String::New(message)));
        UNREACHABLE();
      }

      auto current_thread = Thread::Current();
      {
        StackZone stack_zone(current_thread);
        result = DartEntry::InvokeClosure(current_thread, args, args_desc);
      }
    }

    if (isolate != nullptr) {
      Thread::EnterIsolate(isolate);
    } else {
      Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false,
                                         thread);
    }

    if (result.IsUnwindError()) {
      const auto& error =
          String::Handle(String::New("Isolate was forced to exit."));
      Exceptions::ThrowStateError(error);
      UNREACHABLE();
    }
  }
  if (result.IsUnhandledException()) {
    Exceptions::PropagateError(Error::Cast(result));
    UNREACHABLE();
  }

  result.EnsureDeeplyImmutable(zone);

  return result.ptr();
}

DEFINE_NATIVE_ENTRY(Isolate_isPinnedToCurrentThread, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(SendPort, isolate_control_port,
                               arguments->NativeArgAt(0));
  auto control_port_id = isolate_control_port.Id();
  return Bool::Get(Dart_GetCurrentThreadOwnsIsolate(control_port_id)).ptr();
}

DEFINE_NATIVE_ENTRY(Isolate_pinToCurrentThread, 0, 0) {
  if (isolate == nullptr) {
    ThrowCantRunWithoutIsolateError();
    UNREACHABLE();
  }
  CHECK_ISOLATE(isolate);
  if (!isolate->SetOwnerThread(OSThread::kInvalidThreadId,
                               OSThread::GetCurrentThreadId())) {
    // We might be running this method while running dart code
    // on this target isolate.
    // So first confirm that the isolate is not yet pinned yet.
    if (isolate->is_permanently_pinned()) {
      return Bool::False().ptr();
    }
    // Allow pinning only if current owner is the current thread.
    if (isolate->GetOwnerThread(/*locker=*/nullptr) !=
        OSThread::GetCurrentThreadId()) {
      return Bool::False().ptr();
    }
  }
  isolate->set_is_permanently_pinned();
  return Bool::True().ptr();
}

// TODO(http://dartbug.com/47777): Add support for Finalizers.
DEFINE_NATIVE_ENTRY(Isolate_exit_, 0, 2) {
  if (isolate == nullptr) {
    ThrowCantRunWithoutIsolateError();
    UNREACHABLE();
  }

  GET_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
  if (!port.IsNull()) {
    GET_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1));

    IsolateGroup* group = thread->isolate_group();
    const bool same_group = InSameGroup(group, port);
#if defined(DEBUG)
    if (same_group) {
      ASSERT(PortMap::IsReceiverInThisIsolateGroupOrClosed(port.Id(), group));
    }
#endif
    if (!same_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());
    {
      MessageValidator validator(thread);
      validated_result = validator.Validate(thread, 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 = group->api_state()->AllocatePersistentHandle();
    handle->set_ptr(msg_array);
    isolate->bequeath(std::unique_ptr<Bequest>(new Bequest(handle, port.Id())));
  }

  Thread::Current()->StartUnwindError();
  const String& msg =
      String::Handle(String::New("isolate terminated by Isolate.exit"));
  const UnwindError& error = UnwindError::Handle(UnwindError::New(msg));
  error.set_is_user_initiated(true);
  Exceptions::PropagateError(error);
  UNREACHABLE();
  // We will never execute dart code again in this isolate.
  return Object::null();
}

class IsolateSpawnState {
 public:
  IsolateSpawnState(Dart_Port parent_port,
                    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();

  Dart_Port parent_port() const { return parent_port_; }
  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* debug_name() const { return debug_name_; }
  bool is_spawn_uri() const {
    return 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:
  Dart_Port parent_port_;
  Dart_Port on_exit_port_;
  Dart_Port on_error_port_;
  const char* script_url_;
  const char* package_config_;
  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,
                                     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),
      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) {
  if (debug_name_ == nullptr) {
    debug_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[] debug_name_;
}

ObjectPtr IsolateSpawnState::ResolveFunction() {
  Thread* thread = Thread::Current();
  auto IG = thread->isolate_group();
  Zone* zone = thread->zone();

  // Handle spawnUri lookup rules.
  // Check whether the root library defines a main function.
  const Library& lib =
      Library::Handle(zone, IG->object_store()->root_library());
  const String& main = Symbols::main();
  Function& func = Function::Handle(zone, lib.LookupFunctionAllowPrivate(main));
  if (func.IsNull()) {
    // Check whether main is reexported from the root library.
    const Object& obj = Object::Handle(zone, lib.LookupReExport(main));
    if (obj.IsFunction()) {
      func ^= obj.ptr();
    }
  }
  if (func.IsNull()) {
    const String& msg = String::Handle(
        zone,
        String::NewFormatted(
            "Unable to resolve function 'main' in script '%s'.", script_url()));
    return LanguageError::New(msg);
  }
  return func.ptr();
}

static ObjectPtr DeserializeMessage(Thread* thread, Message* message) {
  if (message == nullptr) {
    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();
    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());

    // Inherit the system isolate property to work around issues with
    // --pause-isolates-on-start and --pause-isolates-on-exit impacting macro
    // generation isolates which are spawned by the kernel-service
    // (see https://github.com/dart-lang/sdk/issues/54729 for details).
    //
    // This flag isn't inherited in the case that the main isolate is marked as
    // a system isolate in the standalone VM using the
    // --mark-main-isolate-as-system-isolate flag as it's currently used to
    // hide test runner implementation details and spawns isolates that should
    // be debuggable as non-system isolates.
    //
    // TODO(bkonyi): revisit this decision, see
    // https://github.com/dart-lang/sdk/issues/54736 for the tracking issue.
    const bool is_parent_main_isolate =
        strcmp(parent_isolate_->name(), "main") == 0;
    api_flags.is_system_isolate =
        api_flags.is_system_isolate && !is_parent_main_isolate;
    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;
    }

    bool errors_are_fatal = state_->errors_are_fatal();
    Dart_Port on_error_port = state_->on_error_port();
    Dart_Port on_exit_port = state_->on_exit_port();

    bool success = true;
    {
      auto thread = Thread::Current();
      TransitionNativeToVM transition(thread);
      StackZone zone(thread);

      success = EnqueueEntrypointInvocationAndNotifySpawner(thread);

      // Destruction of [IsolateSpawnState] may cause destruction of [Message]
      // which make need to delete persistent handles, so explicitly delete it
      // now while we are in the right safepoint state.
      state_ = nullptr;
    }

    if (!success) {
      Dart_ShutdownIsolate();
      return;
    }

    // All preconditions are met for this to always succeed.
    char* error = nullptr;
    if (!Dart_RunLoopAsync(errors_are_fatal, on_error_port, 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 successfully 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.LookupFunctionAllowPrivate(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(), isolate->group()->id()));
    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(/*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 occurred 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());
      TransitionNativeToVM transition(Thread::Current());
      state_ = nullptr;
    } else if (state_->isolate_group() != nullptr) {
      ASSERT(IsolateGroup::Current() == nullptr);
      const bool kBypassSafepoint = false;
      Thread::EnterIsolateGroupAsHelper(state_->isolate_group(),
                                        Thread::kSpawnTask, kBypassSafepoint);
      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) {
  if (isolate == nullptr) {
    ThrowCantRunWithoutIsolateError();
    UNREACHABLE();
  }

  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());
  auto isolate_group = thread->isolate_group();
  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(
      /*same_group=*/true, message, ILLEGAL_PORT, Message::kNormalPriority));

  const char* utf8_package_config =
      packageConfig.IsNull() ? nullptr : String2UTF8(packageConfig);
  const char* utf8_debug_name =
      debugName.IsNull() ? nullptr : 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(), 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));

  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 = nullptr;
  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) {
  if (isolate == nullptr) {
    ThrowCantRunWithoutIsolateError();
    UNREACHABLE();
  }

  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, enableAsserts, 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(
        /*same_group=*/false, args, ILLEGAL_PORT, Message::kNormalPriority));
  }
  {
    message_buffer.set_message(WriteMessage(
        /*same_group=*/false, message, ILLEGAL_PORT, Message::kNormalPriority));
  }

  // Canonicalize the uri with respect to the current isolate.
  auto isolate_group = thread->isolate_group();
  const Library& root_lib =
      Library::Handle(isolate_group->object_store()->root_library());
  char* error = nullptr;
  const char* canonical_uri = CanonicalizeUri(thread, root_lib, uri, &error);
  if (canonical_uri == nullptr) {
    const String& msg = String::Handle(String::New(error));
    ThrowIsolateSpawnException(msg);
  }

  const char* utf8_package_config =
      packageConfig.IsNull() ? nullptr : String2UTF8(packageConfig);
  const char* utf8_debug_name =
      debugName.IsNull() ? nullptr : 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
  // enableAsserts mode. The default state is the state of the current
  // isolate.
  if (!enableAsserts.IsNull()) {
    Dart_IsolateFlags* flags = state->isolate_flags();
    flags->enable_asserts = enableAsserts.value();
  }

  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(),
                                                 isolate->group()->id())));
  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, thread->isolate_group()->object_store()->root_library());
  return root_lib.url();
}

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 message 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(/*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 EnsureThreadLocalsTableExistsAndBigEnough(Thread* thread,
                                                      intptr_t id) {
  Array& locals = Array::Handle(thread->thread_locals());
  if (id >= locals.Length()) {
    intptr_t new_length = id + 1;
    const Array& new_array =
        Array::Handle(Array::Grow(locals, new_length, Heap::kOld));
    for (intptr_t i = locals.Length(); i < new_length; i++) {
      new_array.SetAt(i, Object::sentinel());
    }
    thread->set_thread_locals(new_array);
  }
}

DEFINE_NATIVE_ENTRY(ThreadLocal_allocateId, 0, 0) {
  auto isolate_group = thread->isolate_group();
  isolate_group->increment_thread_locals_count();
  intptr_t new_index = isolate_group->thread_locals_count() - 1;
  EnsureThreadLocalsTableExistsAndBigEnough(thread, new_index);
  Array& locals = Array::Handle(thread->thread_locals());
  locals.SetAt(new_index, Object::sentinel());
  return Integer::New(new_index);
}

static void ValidateScopedThreadLocalId(Thread* thread, intptr_t id) {
  if (id < 0 || id >= thread->isolate_group()->thread_locals_count()) {
    const String& msg = String::Handle(String::New("Invalid local id."));
    Exceptions::ThrowStateError(msg);
    UNREACHABLE();
  }
  EnsureThreadLocalsTableExistsAndBigEnough(thread, id);
}

DEFINE_NATIVE_ENTRY(ThreadLocal_hasValue, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0));
  intptr_t id = id_obj.Value();
  ValidateScopedThreadLocalId(thread, id);
  Array& locals = Array::Handle(thread->thread_locals());
  return locals.At(id) == Object::sentinel().ptr() ? Bool::False().ptr()
                                                   : Bool::True().ptr();
}

DEFINE_NATIVE_ENTRY(ThreadLocal_getValue, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0));
  intptr_t id = id_obj.Value();
  ValidateScopedThreadLocalId(thread, id);
  Array& locals = Array::Handle(thread->thread_locals());
  return locals.At(id);
}

DEFINE_NATIVE_ENTRY(ThreadLocal_setValue, 0, 2) {
  GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0));
  intptr_t id = id_obj.Value();
  ValidateScopedThreadLocalId(thread, id);
  GET_NON_NULL_NATIVE_ARGUMENT(Instance, value, arguments->NativeArgAt(1));
  Array& locals = Array::Handle(thread->thread_locals());
  locals.SetAt(id, value);
  return Object::null();
}

DEFINE_NATIVE_ENTRY(ThreadLocal_clearValue, 0, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0));
  intptr_t id = id_obj.Value();
  ValidateScopedThreadLocalId(thread, id);
  Array& locals = Array::Handle(thread->thread_locals());
  locals.SetAt(id, Object::sentinel());
  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, 1) {
  GET_NON_NULL_NATIVE_ARGUMENT(Instance, array_instance,
                               arguments->NativeArgAt(0));

  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 transferability 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* finalizable_ref =
      FinalizablePersistentHandle::New(thread->isolate_group(), typed_data,
                                       /* peer= */ data,
                                       &ExternalTypedDataFinalizer, length,
                                       /*auto_delete=*/true);
  ASSERT(finalizable_ref != nullptr);
  return typed_data.ptr();
}

DEFINE_NATIVE_ENTRY(Timer_postTimerEvent, 0, 1) {
#if !defined(PRODUCT)
  GET_NON_NULL_NATIVE_ARGUMENT(Integer, milliseconds_overdue,
                               arguments->NativeArgAt(0));
  // |milliseconds_overdue| can get truncated on 32-bit platforms, but in
  // practice, it should never get close to |INT_MAX|.
  Service::SendTimerEvent(isolate,
                          static_cast<intptr_t>(milliseconds_overdue.Value()));
#endif  // !defined(PRODUCT)
  return Object::null();
}

}  // namespace dart
