| // Copyright (c) 2025, 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 "engine/engine.h" |
| #include <errno.h> |
| #include <memory> |
| #include "bin/dartutils.h" |
| #include "include/dart_api.h" |
| #include "include/dart_embedder_api.h" |
| #include "include/dart_engine.h" |
| #include "platform/lockers.h" |
| #include "platform/syslog.h" |
| #include "platform/utils.h" |
| |
| namespace dart { |
| namespace engine { |
| |
| constexpr char kRunPendingImmediateCallback[] = "_runPendingImmediateCallback"; |
| |
| using platform::MutexLocker; |
| |
| Engine* Engine::instance() { |
| static Engine* instance = new Engine(); |
| return instance; |
| } |
| |
| DartEngine_SnapshotData Engine::KernelFromFile(const char* path, char** error) { |
| DartEngine_SnapshotData result; |
| result.kind = DartEngine_SnapshotKind_Kernel; |
| result.script_uri = Utils::SCreate("file://%s", path); |
| FILE* file = fopen(path, "rb"); |
| if (file == nullptr) { |
| *error = Utils::SCreate("Error %d", errno); |
| return result; |
| } |
| |
| fseek(file, 0, SEEK_END); |
| intptr_t size = ftell(file); |
| rewind(file); |
| |
| char* buffer = reinterpret_cast<char*>(malloc(sizeof(char) * size)); |
| |
| intptr_t bytes_read = fread(buffer, 1, size, file); |
| fclose(file); |
| if (bytes_read != size) { |
| free(buffer); |
| *error = Utils::SCreate("Error reading %s: read %zu bytes instead of %zu", |
| path, bytes_read, size); |
| return result; |
| } |
| |
| result.kernel_buffer_size = size; |
| result.kernel_buffer = reinterpret_cast<uint8_t*>(buffer); |
| |
| { |
| MutexLocker ml(&engine_state_); |
| owned_snapshots_.emplace_back(result); |
| } |
| |
| return result; |
| } |
| |
| DartEngine_SnapshotData Engine::AotFromFile(const char* path, char** error) { |
| DartEngine_SnapshotData result; |
| result.kind = DartEngine_SnapshotKind_AOT; |
| result.script_uri = Utils::SCreate("file://%s", path); |
| |
| void* library = |
| Utils::LoadDynamicLibrary(path, /*search_dll_load_dir=*/false, error); |
| |
| result.vm_snapshot_data = reinterpret_cast<const uint8_t*>( |
| Utils::ResolveSymbolInDynamicLibrary(library, kVmSnapshotDataCSymbol)); |
| result.vm_snapshot_instructions = |
| reinterpret_cast<const uint8_t*>(Utils::ResolveSymbolInDynamicLibrary( |
| library, kVmSnapshotInstructionsCSymbol)); |
| result.vm_isolate_data = |
| reinterpret_cast<const uint8_t*>(Utils::ResolveSymbolInDynamicLibrary( |
| library, kIsolateSnapshotDataCSymbol)); |
| result.vm_isolate_instructions = |
| reinterpret_cast<const uint8_t*>(Utils::ResolveSymbolInDynamicLibrary( |
| library, kIsolateSnapshotInstructionsCSymbol)); |
| |
| if (*error != nullptr) { |
| return result; |
| } |
| |
| { |
| MutexLocker ml(&engine_state_); |
| loaded_libraries_.emplace_back(library); |
| owned_snapshots_.emplace_back(result); |
| } |
| |
| return result; |
| } |
| |
| namespace { |
| Dart_InitializeParams CreateInitializeParams() { |
| Dart_InitializeParams params; |
| memset(¶ms, 0, sizeof(params)); |
| params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION; |
| params.shutdown_isolate = nullptr; |
| params.create_group = nullptr; |
| return params; |
| } |
| } // namespace |
| |
| bool Engine::Initialize(char** error) { |
| if (initialized_) { |
| return true; |
| } |
| MutexLocker ml(&engine_lifecycle_); |
| |
| if (initialized_) { |
| return true; |
| } |
| if (!dart::embedder::InitOnce(error)) { |
| return false; |
| } |
| |
| std::vector<const char*> flags{}; |
| if (Dart_IsPrecompiledRuntime()) { |
| flags.push_back("--precompilation"); |
| } |
| *error = Dart_SetVMFlags(flags.size(), flags.data()); |
| |
| if (*error != nullptr) { |
| return false; |
| } |
| |
| initialized_ = true; |
| |
| return true; |
| } |
| |
| Dart_Isolate Engine::StartIsolate(DartEngine_SnapshotData snapshot, |
| char** error) { |
| if (Dart_IsPrecompiledRuntime() && |
| snapshot.kind != DartEngine_SnapshotKind_AOT) { |
| *error = Utils::StrDup("AOT Dart VM supports only AOT snapshots"); |
| return nullptr; |
| } |
| |
| if (!Dart_IsPrecompiledRuntime() && |
| snapshot.kind != DartEngine_SnapshotKind_Kernel) { |
| *error = Utils::StrDup("AOT Dart VM supports only AOT snapshots"); |
| return nullptr; |
| } |
| |
| if (!first_isolate_started_) { |
| // This is a part of an initialization, so using engine_lifecycle_ mutex |
| // here. |
| MutexLocker ml(&engine_lifecycle_); |
| if (!first_isolate_started_) { |
| Dart_InitializeParams initialize_params = CreateInitializeParams(); |
| |
| if (Dart_IsPrecompiledRuntime()) { |
| initialize_params.vm_snapshot_data = snapshot.vm_snapshot_data; |
| initialize_params.vm_snapshot_instructions = |
| snapshot.vm_snapshot_instructions; |
| } |
| *error = Dart_Initialize(&initialize_params); |
| if (*error != nullptr) { |
| return nullptr; |
| } |
| first_isolate_started_ = true; |
| } |
| } |
| |
| // Now we can start an isolate. |
| Dart_IsolateFlags isolate_flags; |
| Dart_IsolateFlagsInitialize(&isolate_flags); |
| |
| Dart_Isolate isolate; |
| if (Dart_IsPrecompiledRuntime()) { |
| // Automatically sets the root library for the isolate. |
| isolate = Dart_CreateIsolateGroup( |
| snapshot.script_uri, strrchr(snapshot.script_uri, '/'), |
| snapshot.vm_isolate_data, snapshot.vm_isolate_instructions, |
| &isolate_flags, nullptr, nullptr, error); |
| } else { |
| isolate = Dart_CreateIsolateGroupFromKernel( |
| snapshot.script_uri, snapshot.script_uri, snapshot.kernel_buffer, |
| snapshot.kernel_buffer_size, &isolate_flags, nullptr, nullptr, error); |
| } |
| |
| if (*error != nullptr) { |
| return nullptr; |
| } |
| |
| Dart_SetMessageNotifyCallback(Engine::MessageNotifyCallback); |
| |
| Dart_EnterScope(); |
| |
| // In fact, this call initializes core libraries, (e.g. `print` doesn't work |
| // without it). |
| Dart_Handle core_libs_result = bin::DartUtils::PrepareForScriptLoading( |
| false, false, /*flag_profile_microtasks=*/false); |
| if (Dart_IsError(core_libs_result)) { |
| *error = Utils::StrDup(Dart_GetError(core_libs_result)); |
| Dart_ShutdownIsolate(); |
| return nullptr; |
| } |
| |
| if (!Dart_IsPrecompiledRuntime()) { |
| // In kernel mode, also call LoadScriptFromKernel to set the root library. |
| // Technically, the library is already loaded after |
| // Dart_CreateIsolateGroupFromKernel, the problem is we don't know its URI |
| // (it is not related to snapshot->uri and depends on where the kernel |
| // snapshot was built, e.g. file:///Users/user/samples/hello.dart) |
| Dart_Handle library = Dart_LoadScriptFromKernel( |
| snapshot.kernel_buffer, snapshot.kernel_buffer_size); |
| |
| if (Dart_IsError(library)) { |
| *error = Utils::StrDup(Dart_GetError(library)); |
| Dart_ShutdownIsolate(); |
| return nullptr; |
| } |
| } |
| |
| Dart_Handle isolate_library = Dart_LookupLibrary( |
| Dart_NewStringFromCString(bin::DartUtils::kIsolateLibURL)); |
| if (Dart_IsError(isolate_library)) { |
| *error = Utils::StrDup(Dart_GetError(isolate_library)); |
| Dart_ShutdownIsolate(); |
| return nullptr; |
| } |
| |
| std::shared_ptr<Engine::IsolateData> isolate_data = DataForIsolate(isolate); |
| isolate_data->isolate_library = Dart_NewPersistentHandle(isolate_library); |
| isolate_data->drain_microtasks_function_name = Dart_NewPersistentHandle( |
| Dart_NewStringFromCString(kRunPendingImmediateCallback)); |
| isolate_data->scheduler.context = nullptr; |
| isolate_data->scheduler.schedule_callback = nullptr; |
| |
| Dart_ExitScope(); |
| Dart_ExitIsolate(); |
| is_running_ = true; |
| isolates_.emplace_back(isolate); |
| return isolate; |
| } |
| |
| void Engine::MessageNotifyCallback(Dart_Isolate isolate) { |
| Engine::instance()->NotifyMessage(isolate); |
| } |
| |
| void Engine::Shutdown() { |
| MutexLocker shutdown_locker(&engine_lifecycle_); |
| |
| is_running_ = false; |
| for (auto isolate : isolates_) { |
| std::shared_ptr<Engine::IsolateData> isolate_data = DataForIsolate(isolate); |
| LockIsolate(isolate); |
| Dart_EnterIsolate(isolate); |
| Dart_SetMessageNotifyCallback(nullptr); |
| Dart_DeletePersistentHandle(isolate_data->isolate_library); |
| Dart_DeletePersistentHandle(isolate_data->drain_microtasks_function_name); |
| Dart_ShutdownIsolate(); |
| UnlockIsolate(isolate); |
| } |
| |
| MutexLocker state_locker(&engine_state_); |
| for (auto& snapshot : owned_snapshots_) { |
| switch (snapshot.kind) { |
| case DartEngine_SnapshotKind_Kernel: |
| free(const_cast<uint8_t*>(snapshot.kernel_buffer)); |
| break; |
| case DartEngine_SnapshotKind_AOT: |
| // No need to free AOT buffers loaded from dynamic library. |
| break; |
| default: |
| Syslog::PrintErr("Unsupported SnapshotKind: %d\n", snapshot.kind); |
| break; |
| } |
| free(const_cast<char*>(snapshot.script_uri)); |
| } |
| } |
| |
| void Engine::HandleMessage(Dart_Isolate isolate) { |
| LockIsolate(isolate); |
| Dart_EnterIsolate(isolate); |
| Dart_EnterScope(); |
| |
| Dart_Handle handle_result = Dart_HandleMessage(); |
| |
| if (Dart_IsError(handle_result)) { |
| if (handle_message_error_callback_ != nullptr) { |
| handle_message_error_callback_(handle_result, isolate); |
| } else { |
| Syslog::PrintErr("Error handling isolate message: %s", |
| Dart_GetError(handle_result)); |
| } |
| } |
| |
| Dart_ExitScope(); |
| Dart_ExitIsolate(); |
| UnlockIsolate(isolate); |
| } |
| |
| Dart_Handle Engine::DrainMicrotasksQueue() { |
| std::shared_ptr<Engine::IsolateData> isolate_data = |
| DataForIsolate(Dart_CurrentIsolate()); |
| return Dart_Invoke(isolate_data->isolate_library, |
| isolate_data->drain_microtasks_function_name, 0, nullptr); |
| } |
| |
| std::shared_ptr<Engine::IsolateData> Engine::DataForIsolate( |
| Dart_Isolate isolate) { |
| MutexLocker ml(&engine_state_); |
| auto it = isolate_data_.find(isolate); |
| if (it == isolate_data_.end()) { |
| it = isolate_data_.emplace(isolate, std::make_shared<Engine::IsolateData>()) |
| .first; |
| } |
| return it->second; |
| } |
| |
| void Engine::LockIsolate(Dart_Isolate isolate) { |
| DataForIsolate(isolate)->mutex.Lock(); |
| } |
| |
| void Engine::UnlockIsolate(Dart_Isolate isolate) { |
| DataForIsolate(isolate)->mutex.Unlock(); |
| } |
| |
| void Engine::NotifyMessage(Dart_Isolate isolate) { |
| if (!engine_lifecycle_.TryLock() || !is_running_) { |
| // Shutdown is in progress or complete. |
| // |
| // Used to prevent a potential deadlock during shutdown. |
| // |
| // If Engine::MessageNotifyCallback is waiting for Engine::HandleMessage |
| // completion (which happens in samples/embedder/run_timer_async), and |
| // another thread calls Engine::Shutdown, the deadlock may occur: |
| // |
| // 1. MessageNotifyCallback thread owns PortMap::mutex_ (through |
| // PortMap::PostMessage) and wants to lock an isolate (via |
| // Engine::LockIsolate). |
| // 2. Shutdown thread owns an isolate lock and wants to lock PortMap::mutex_ |
| // (inside Dart_ShutdownIsolate call). |
| // |
| // This mutex is used to prevent it: |
| // - Engine::Shutdown locks it. |
| // - Engine::NotifyMessage tries to lock it, and treats lock failure as |
| // shutdown in progress. |
| return; |
| } |
| |
| DartEngine_MessageScheduler scheduler = DataForIsolate(isolate)->scheduler; |
| |
| if (scheduler.schedule_callback == nullptr) { |
| scheduler = default_scheduler_; |
| } |
| if (scheduler.schedule_callback == nullptr) { |
| engine_lifecycle_.Unlock(); |
| return; |
| } |
| scheduler.schedule_callback(isolate, scheduler.context); |
| engine_lifecycle_.Unlock(); |
| } |
| |
| void Engine::SetHandleMessageErrorCallback( |
| DartEngine_HandleMessageErrorCallback callback) { |
| handle_message_error_callback_ = callback; |
| } |
| |
| void Engine::SetDefaultMessageScheduler(DartEngine_MessageScheduler scheduler) { |
| default_scheduler_ = scheduler; |
| } |
| |
| void Engine::SetMessageScheduler(DartEngine_MessageScheduler scheduler, |
| Dart_Isolate isolate) { |
| DataForIsolate(isolate)->scheduler = scheduler; |
| } |
| |
| } // namespace engine |
| } // namespace dart |