The Engine startup process of Flutter describes how the Engine creates and calls the entry function main. For reasons of length, the creation of the Dart VM is not covered in this article. So this article takes a look at how the Dart VM is created.

1. Dart VM object creation

To look directly at the Shell Create function (read the Engine startup process of Flutter for details on how to call this function), the code reads as follows.

std::unique_ptr<Shell> Shell::Create(
    TaskRunners task_runners,
    const WindowData window_data,
    Settings settings,
    Shell::CreateCallback<PlatformView> on_create_platform_view,
    Shell::CreateCallback<Rasterizer> on_create_rasterizer) {
  PerformInitializationTasks(settings);
  PersistentCache::SetCacheSkSL(settings.cache_sksl);
  
  //Dart VM creation
  auto vm = DartVMRef::Create(settings);

  auto vm_data = vm->GetVMData(a);// Continue with the Shell object creation
  return Shell::Create(std::move(task_runners),        //
                       std::move(window_data),         //
                       std::move(settings),            //
                       vm_data->GetIsolateSnapshot(),  // isolate snapshot
                       on_create_platform_view,        //
                       on_create_rasterizer,           //
                       std::move(vm)                   //
  );
}
Copy the code

The DartVM is primarily created in Create through the DartVMRef Create function. Here’s the code.

DartVMRef DartVMRef::Create(Settings settings, fml::RefPtr
       
         vm_snapshot, fml::RefPtr
        
          isolate_snapshot)
        
        {
  std::scoped_lock lifecycle_lock(gVMMutex);

  If there is a running Dart VM in the process, use the Dart VM directly. That is, only one Dart VM exists in the same process
  if (auto vm = gVM.lock()) {
    // Directly return to the existing Dart VM in the process
    return DartVMRef{std::move(vm)};
  }

  std::scoped_lock dependents_lock(gVMDependentsMutex);

  gVMData.reset(a); gVMServiceProtocol.reset(a); gVMIsolateNameServer.reset(a); gVM.reset(a);// If there is no Dart VM in the process, create and initialize one
  auto isolate_name_server = std::make_shared<IsolateNameServer>();
  auto vm = DartVM::Create(std::move(settings),          //
                           std::move(vm_snapshot),       //
                           std::move(isolate_snapshot),  //
                           isolate_name_server           //
  );

  if(! vm) {// Failed to create DartVM instance
    return {nullptr};
  }

  // Set the Dart VM reference to a global reference to ensure intra-process uniqueness.
  gVMData = vm->GetVMData(a); gVMServiceProtocol = vm->GetServiceProtocol(a); gVMIsolateNameServer = isolate_name_server; gVM = vm;if (settings.leak_vm) {
    gVMLeak = new std::shared_ptr<DartVM>(vm);
  }

  return DartVMRef{std::move(vm)};
}
Copy the code

In DartVMRef, the Create function returns a reference to the DartVM, which is unique in the process. If the DartVM does not exist in the process, then a DartVM is created and initialized. Otherwise, the existing DartVM is used. Let’s take a look at the Dart VM creation process.

std::shared_ptr<DartVM> DartVM::Create( Settings settings, fml::RefPtr
       
         vm_snapshot, fml::RefPtr
        
          isolate_snapshot, std::shared_ptr
         
           isolate_name_server)
         
        
        {
  auto vm_data = DartVMData::Create(settings,                    //
                                    std::move(vm_snapshot),      //
                                    std::move(isolate_snapshot)  //
  );

  if(! vm_data) {return {};
  }

  // Create the Dart VM
  return std::shared_ptr<DartVM>(
      new DartVM(std::move(vm_data), std::move(isolate_name_server)));
}
Copy the code

The above code simply creates a DartVM object and looks at its constructor.

DartVM::DartVM(std::shared_ptr<const DartVMData> vm_data,
               std::shared_ptr<IsolateNameServer> isolate_name_server)
    : settings_(vm_data->GetSettings()),
      concurrent_message_loop_(fml::ConcurrentMessageLoop::Create()),
      skia_concurrent_executor_(
          [runner = concurrent_message_loop_->GetTaskRunner()](
              fml::closure work) { runner->PostTask(work); }),
      vm_data_(vm_data),
      isolate_name_server_(std::move(isolate_name_server)),
      service_protocol_(std::make_shared<ServiceProtocol>()) {
  gVMLaunchCount++;

  Since Dart VM initialization is thread-safe, the call here is thread-safe as well
  SkExecutor::SetDefault(&skia_concurrent_executor_);

  {
    / / create EventHandler
    dart::bin::BootstrapDartIo(a);if(! settings_.temp_directory_path.empty()) {
      dart::bin::SetSystemTempDirectory(settings_.temp_directory_path.c_str()); }}...// Register uI-related classes, such as Canvas, Picture, Window, etc.
  DartUI::InitForGlobal(a); {Dart VM initialization parameters
    Dart_InitializeParams params = {};
    params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION;
    params.vm_snapshot_data = vm_data_->GetVMSnapshot().GetDataMapping(a); params.vm_snapshot_instructions = vm_data_->GetVMSnapshot().GetInstructionsMapping(a); params.create_group =reinterpret_cast<decltype(params.create_group)>(
        DartIsolate::DartIsolateGroupCreateCallback);
    params.initialize_isolate =
        reinterpret_cast<decltype(params.initialize_isolate)>(
            DartIsolate::DartIsolateInitializeCallback);
    params.shutdown_isolate =
        reinterpret_cast<decltype(params.shutdown_isolate)>(
            DartIsolate::DartIsolateShutdownCallback);
    params.cleanup_isolate = reinterpret_cast<decltype(params.cleanup_isolate)>(
        DartIsolate::DartIsolateCleanupCallback);
    params.cleanup_group = reinterpret_cast<decltype(params.cleanup_group)>(
        DartIsolate::DartIsolateGroupCleanupCallback);
    params.thread_exit = ThreadExitCallback;
    params.get_service_assets = GetVMServiceAssetsArchiveCallback;
    params.entropy_source = dart::bin::GetEntropy;
    //Dart VM initialization
    char* init_error = Dart_Initialize(&params); . }... }Copy the code

The following are the main things that are done in the constructor of the DartVM object.

  1. EventHandlerCreation.
  2. Ui-related classes inEngineLayer registration.
  3. Initialize the Dart VM.

Let’s start with the creation of EventHandler.

2. Creation of EventHandler

The EventHandler is created using the BootstrapDartIo function, with the following implementation code.

void BootstrapDartIo(a) {
  // Bootstrap 'dart:io' event handler.
  TimerUtils::InitOnce(a); EventHandler::Start(a); }Copy the code

In the EventHandler Start function, will create a global socket (globalTcpListeningSocketRegistry), a Monitor object and EventHandler object.

The implementation of EventHandler varies from system to system. This section uses Android as an example. EventHandler delegate_ is a EventHandlerImplementation object, his specific implementation in eventhandler_android. Cc.

void EventHandler::Start(a) {
  / / initialize a global socket (globalTcpListeningSocketRegistry)
  ListeningSocketRegistry::Initialize(a); shutdown_monitor =new Monitor(a);Create an EventHandler object
  event_handler = new EventHandler(a); event_handler->delegate_.Start(event_handler); . }Copy the code

In the Delegate_ constructor, an epoll handle is created and an Interrupt_fd is registered into the epoll.

Also look at Delegate_’s start function, where a new thread called DART: IO EventHandler is created. In Java, whenever you create a new thread, you pass a callback method. This is no exception. The corresponding callback is the Poll function, in which epoll is used to wait for events to execute.

After epoll was used to retrieve the completed task, HandleEvents was directly called for further processing, and corresponding callbacks were performed through the Isolate’s MessageHandler.

// Create an epoll handle
EventHandlerImplementation::EventHandlerImplementation()
    : socket_map_(&SimpleHashMap::SamePointerValue, 16) {
  intptr_t result;
  result = NO_RETRY_EXPECTED(pipe(interrupt_fds_));
  shutdown_ = false;
  // The initial size passed to epoll_create. This value is ignored when Linux version >=2.6.8
  static const int kEpollInitialSize = 64;
  // Create an epoll handle
  epoll_fd_ = NO_RETRY_EXPECTED(epoll_create(kEpollInitialSize));
  // Register interrupt_fd into epoll.
  struct epoll_event event;
  event.events = EPOLLIN;
  event.data.ptr = NULL;
  int status = NO_RETRY_EXPECTED(
      epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, interrupt_fds_[0], &event));
}

void EventHandlerImplementation::Start(EventHandler* handler) {
  // Create a new thread named DART: IO EventHandler
  int result =
      Thread::Start("dart:io EventHandler", &EventHandlerImplementation::Poll,
                    reinterpret_cast<uword>(handler));
}

// The specific execution function of the new thread
void EventHandlerImplementation::Poll(uword args) {
  ThreadSignalBlocker signal_blocker(SIGPROF);
  static const intptr_t kMaxEvents = 16;
  struct epoll_event events[kMaxEvents];
  EventHandler* handler = reinterpret_cast<EventHandler*>(args);
  EventHandlerImplementation* handler_impl = &handler->delegate_;

  while(! handler_impl->shutdown_) {int64_t millis = handler_impl->GetTimeout(a);if (millis > kMaxInt32) {
      millis = kMaxInt32;
    }
    // block wait
    intptr_t result = TEMP_FAILURE_RETRY_NO_SIGNAL_BLOCKER(
        epoll_wait(handler_impl->epoll_fd_, events, kMaxEvents, millis));
    if (result == - 1) {... }else {
      handler_impl->HandleTimeout(a);// Call the HandleEvents function in the child thread, which will process all the tasks it gets
      handler_impl->HandleEvents(events, result);
    }
  }
  handler->NotifyShutdownDone(a); }// Handle all the events you get
void EventHandlerImplementation::HandleEvents(struct epoll_event* events,
                                              int size) {
  bool interrupt_seen = false;
  for (int i = 0; i < size; i++) {
    if (events[i].data.ptr == NULL) {
      interrupt_seen = true;
    } else {
      DescriptorInfo* di =
          reinterpret_cast<DescriptorInfo*>(events[i].data.ptr);
      const intptr_t old_mask = di->Mask(a);const intptr_t event_mask = GetPollEvents(events[i].events, di);
      if ((event_mask & (1<< kErrorEvent)) ! =0) {
        di->NotifyAllDartPorts(event_mask);
        UpdateEpollInstance(old_mask, di);
      } else if(event_mask ! =0) {
        Dart_Port port = di->NextNotifyDartPort(event_mask);
        UpdateEpollInstance(old_mask, di);
        // Send messages to MessageHandler on the ISOLATE
        DartUtils::PostInt32(port, event_mask); }}}if (interrupt_seen) {
    // Handle after socket events, so we avoid closing a socket before we handle
    // the current events.
    HandleInterruptFd();
  }
}
Copy the code

In the creation of EventHandler, it is the creation of new threads and the use of epoll. In the upper layer, EventHandler is used to execute scheduled socket and Timer tasks.

In IOS, kQueue is used to achieve asynchronous IO.

3. Dartui-related initialization

After EventHandler is successfully created, uI-related classes will be registered in C/C++ layer, so that the framework layer can call native methods smoothly. In Java, a similar registration is done if you want to call native methods.

void DartUI::InitForGlobal(a) {
  if(! g_natives) { g_natives =new tonic::DartLibraryNatives(a); Canvas::RegisterNatives(g_natives);
    CanvasGradient::RegisterNatives(g_natives);
    CanvasImage::RegisterNatives(g_natives);
    CanvasPath::RegisterNatives(g_natives);
    CanvasPathMeasure::RegisterNatives(g_natives);
    Codec::RegisterNatives(g_natives);
    ColorFilter::RegisterNatives(g_natives);
    DartRuntimeHooks::RegisterNatives(g_natives);
    EngineLayer::RegisterNatives(g_natives);
    FontCollection::RegisterNatives(g_natives);
    FrameInfo::RegisterNatives(g_natives);
    ImageFilter::RegisterNatives(g_natives);
    ImageShader::RegisterNatives(g_natives);
    IsolateNameServerNatives::RegisterNatives(g_natives);
    Paragraph::RegisterNatives(g_natives);
    ParagraphBuilder::RegisterNatives(g_natives);
    Picture::RegisterNatives(g_natives);
    PictureRecorder::RegisterNatives(g_natives);
    Scene::RegisterNatives(g_natives);
    SceneBuilder::RegisterNatives(g_natives);
    SemanticsUpdate::RegisterNatives(g_natives);
    SemanticsUpdateBuilder::RegisterNatives(g_natives);
    Vertices::RegisterNatives(g_natives);
    Window::RegisterNatives(g_natives);
#if defined(OS_FUCHSIA)
    SceneHost::RegisterNatives(g_natives);
#endif

    // Secondary isolates do not provide UI-related APIs.
    g_natives_secondary = new tonic::DartLibraryNatives(a); DartRuntimeHooks::RegisterNatives(g_natives_secondary);
    IsolateNameServerNatives::RegisterNatives(g_natives_secondary); }}Copy the code

You can see that many uI-related native methods are registered in the InitForGlobal function.

4. Initialize the Dart VM

Finally, look at the initialization of the Dart VM.

char* Dart::Init(const uint8_t* vm_isolate_snapshot,
                 const uint8_t* instructions_snapshot,
                 Dart_IsolateGroupCreateCallback create_group,
                 Dart_InitializeIsolateCallback initialize_isolate,
                 Dart_IsolateShutdownCallback shutdown,
                 Dart_IsolateCleanupCallback cleanup,
                 Dart_IsolateGroupCleanupCallback cleanup_group,
                 Dart_ThreadExitCallback thread_exit,
                 Dart_FileOpenCallback file_open,
                 Dart_FileReadCallback file_read,
                 Dart_FileWriteCallback file_write,
                 Dart_FileCloseCallback file_close,
                 Dart_EntropySource entropy_source,
                 Dart_GetVMServiceAssetsArchive get_service_assets,
                 bool start_kernel_isolate,
                 Dart_CodeObserver* observer) {...// Initialize the stack frame layout
  FrameLayout::Init(a);set_thread_exit_callback(thread_exit);
  SetFileCallbacks(file_open, file_read, file_write, file_close);
  set_entropy_source_callback(entropy_source);
  // System specific initialization, for different systems to do adaptation
  OS::Init(a); . start_time_micros_ = OS::GetCurrentMonotonicMicros(a);// Virtual memory initialization, the default is to get the size of a page
  VirtualMemory::Init(a);//OSThread thread initialization, create a Dart_Initialize OSThread and set it to TLS
  OSThread::Init(a);/ / zone initialization
  Zone::Init(a); .// Reset some callbacks of the ISOLATE to NULL, such as create_group_callback_ and initialize_callback_
  Isolate::InitVM(a);/ / IsolateGroup initialization
  IsolateGroup::Init(a);//PortMap initializes to create a map with initial capacity of 8
  PortMap::Init(a);// Initialize a FreeListElement, mainly to make some assert judgments
  FreeListElement::Init(a);//ForwardingCorpse is initialized to make some assert judgments
  ForwardingCorpse::Init(a);/ / Api initialization
  Api::Init(a);// Depending on the operating system, there are currently no implementations
  NativeSymbolResolver::Init(a);//SemiSpace initialization, SemiSpace is mainly used for memory management, the new generation consists of two SemiSpace
  SemiSpace::Init(a);NOT_IN_PRODUCT(Metric::Init());
  StoreBuffer::Init(a); MarkingStack::Init(a); .// Create the read-only handles area.
  predefined_handles_ = new ReadOnlyHandles(a);// Create the VM isolate and finish the VM initialization.
  Create a thread pool
  thread_pool_ = new ThreadPool(a); {// The ISOLATE to be created is vm ISOLATE
    const bool is_vm_isolate = true;

    // Cache value of "non-nullable" experimental flag.
    set_non_nullable_flag(KernelIsolate::GetExperimentalFlag("non-nullable"));

    // Setup default flags for the VM isolate.
    Dart_IsolateFlags api_flags;
    Isolate::FlagsInitialize(&api_flags);

    // Create a pseudo-isolateGroupSource object here, because the VM-ISOLATE to be created is not a real ISOLATE object. It acts more as a container for VM global objects.
    std::unique_ptr<IsolateGroupSource> source(
        new IsolateGroupSource(nullptr, kVmIsolateName, vm_isolate_snapshot,
                               instructions_snapshot, nullptr.- 1, api_flags));
    auto group = new IsolateGroup(std::move(source), /*embedder_data=*/nullptr);
    IsolateGroup::RegisterIsolateGroup(group);
    // Create an ISOLATE object named VM-ISOLATE
    vm_isolate_ =
        Isolate::InitIsolate(kVmIsolateName, group, api_flags, is_vm_isolate);
    group->set_initial_spawn_successful(a); Thread* T = Thread::Current(a);StackZone zone(T);
    HandleScope handle_scope(T);
    // Create a NULL object, which is the first object created by the Dart VM
    Object::InitNull(vm_isolate_);
    // Create an ObjectStore object that stores each ISOLATE's references to objects in the Dart VM
    ObjectStore::Init(vm_isolate_);
    // Initializes CPU information
    TargetCPUFeatures::Init(a);// Initialize the object.
    Object::Init(vm_isolate_);
    ArgumentsDescriptor::Init(a); ICData::Init(a); SubtypeTestCache::Init(a);if(vm_isolate_snapshot ! =NULL) {... }else{... }// Due to bootstrapping, constants need to be initialized in the thread where the VM ISOLATE is located
    T->InitVMConstants(a); { Object::FinalizeVMIsolate(vm_isolate_);
    }
  }
  Api::InitHandles(a);// Unregister the VM ISOLATE in this thread
  Thread::ExitIsolate(a); Isolate::SetCreateGroupCallback(create_group);
  Isolate::SetInitializeCallback_(initialize_isolate);
  Isolate::SetShutdownCallback(shutdown);
  Isolate::SetCleanupCallback(cleanup);
  Isolate::SetGroupCleanupCallback(cleanup_group); .return NULL;
}
Copy the code

Detailed comments are made in the above code. As you can see, there are quite a few operations that need to be done during Dart VM initialization, For example, initialization of stack frame layout, virtual memory, OSThread, NULL objects and other shared objects, PortMap, thread pool, vM-ISOLATE, etc.

This article focuses on VM-ISOLATE. It is an ISOLATE, but it is very different from normal isolate, so it is not a real isolate, but a pseudo isolate. The main differences are as follows:

  1. generalisolateThe heap is divided into Cenozoic and old, and CenozoicfromwithtoThe ratio is 1:1, and newly created objects are almost always allocated intoIn the. whilevm-isolateIn the heap of, the space size of Cenozoic is 0, that is, there is no Cenozoic. Therefore, all objects are assigned directly to the old age.
  2. isolateObjects in similar processes cannot refer to each other. But for thevm-isolateThe exception is that the heap contains immutable objects such as NULL, true, false, and othersisolateYou can referencevm-isolateObjects in the heap.

For details about how to create VM-ISOLATE, see the article Understanding the MECHANISM of ISOLATE in Depth.

The Dart VM startup flowchart is shown below, which is based on the startup process of the Flutter engine

After the Dart VM is successfully initialized, it means that the Dart VM has been successfully started and used in the same process. No matter how many times the Engine is created, the Dart VM is created only once in the same process.

【 References 】

Introduction to Dart VM

In-depth understanding of Dart VIRTUAL machine startup

Flutter: Don’t Fear the Garbage Collector

ARM function call process analysis