| // 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.snapshot_data = reinterpret_cast<const uint8_t*>( | |
| Utils::ResolveSymbolInDynamicLibrary(library, kSnapshotDataCSymbol)); | |
| result.snapshot_text = reinterpret_cast<const uint8_t*>( | |
| Utils::ResolveSymbolInDynamicLibrary(library, kSnapshotTextCSymbol)); | |
| 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(); | |
| *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.snapshot_data, snapshot.snapshot_text, &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::SetupCoreLibraries( | |
| /*is_service_isolate=*/false, /*trace_loading=*/false, | |
| /*flag_profile_microtasks=*/false, bin::DartIoSettings{}); | |
| 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 |