From 6e33c95463bd3bc71da5a35e571d6e8c596c4edd Mon Sep 17 00:00:00 2001 From: Ivan Inozemtsev Date: Tue, 28 Jan 2025 03:53:40 -0800 Subject: [PATCH] Dart Engine Adds shared libraries for embedding Dart VM and new API (runtime/engine/include/dart_engine.h). TEST=tests/standalone/embedder_samples_test.dart Cq-Include-Trybots: luci.dart.try:vm-aot-android-release-arm64c-try,vm-aot-android-release-arm_x64-try,vm-aot-asan-linux-release-x64-try,vm-aot-dwarf-linux-product-x64-try,vm-aot-dyn-linux-debug-x64-try,vm-aot-linux-debug-simarm_x64-try,vm-aot-linux-debug-simriscv32-try,vm-aot-linux-debug-simriscv64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-linux-product-x64-try,vm-aot-linux-release-arm64-try,vm-aot-linux-release-simarm_x64-try,vm-aot-linux-release-x64-try,vm-aot-mac-product-arm64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-msan-linux-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-aot-tsan-linux-release-x64-try,vm-aot-ubsan-linux-release-x64-try,vm-aot-win-debug-x64-try,vm-aot-win-debug-x64c-try,vm-aot-win-product-x64-try,vm-aot-win-release-x64-try,vm-appjit-linux-debug-x64-try,vm-appjit-linux-product-x64-try,vm-appjit-linux-release-x64-try,vm-asan-linux-release-arm64-try,vm-asan-linux-release-x64-try,vm-checked-mac-release-arm64-try,vm-eager-optimization-linux-release-ia32-try,vm-eager-optimization-linux-release-x64-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-ffi-android-product-arm-try,vm-ffi-android-product-arm64c-try,vm-ffi-android-release-arm-try,vm-ffi-android-release-arm64c-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-fuchsia-release-arm64-try,vm-fuchsia-release-x64-try,vm-gcc-linux-try,vm-linux-debug-ia32-try,vm-linux-debug-simriscv32-try,vm-linux-debug-simriscv64-try,vm-linux-debug-x64-try,vm-linux-debug-x64c-try,vm-linux-release-arm64-try,vm-linux-release-ia32-try,vm-linux-release-simarm-try,vm-linux-release-x64-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-mac-release-arm64-try,vm-mac-release-x64-try,vm-msan-linux-release-arm64-try,vm-msan-linux-release-x64-try,vm-msvc-windows-try,vm-reload-linux-debug-x64-try,vm-reload-linux-release-x64-try,vm-reload-rollback-linux-debug-x64-try,vm-reload-rollback-linux-release-x64-try,vm-tsan-linux-release-arm64-try,vm-tsan-linux-release-x64-try,vm-ubsan-linux-release-arm64-try,vm-ubsan-linux-release-x64-try,vm-win-debug-x64-try,vm-win-debug-x64c-try,vm-win-release-ia32-try,vm-win-release-x64-try Change-Id: Ia4e4d1b871ddef515cfb2f4639bdaa9fe3676936 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/402860 Reviewed-by: Slava Egorov Reviewed-by: Martin Kustermann Commit-Queue: Martin Kustermann --- BUILD.gn | 8 +- build/rbe/rewrapper_dart.py | 1 + runtime/bin/BUILD.gn | 5 +- runtime/engine/BUILD.gn | 109 ++++++ runtime/engine/README.md | 48 +++ runtime/engine/dart_engine_impl.cc | 76 ++++ runtime/engine/engine.cc | 349 ++++++++++++++++++ runtime/engine/engine.h | 134 +++++++ runtime/engine/include/dart_engine.h | 181 +++++++++ .../{run_kernel.plist => run_main_aot.plist} | 0 .../tools/entitlements/run_main_kernel.plist | 10 + .../tools/entitlements/run_timer_aot.plist | 10 + .../entitlements/run_timer_async_aot.plist | 10 + .../entitlements/run_timer_async_kernel.plist | 10 + .../tools/entitlements/run_timer_kernel.plist | 10 + .../entitlements/run_two_programs_aot.plist | 10 + .../run_two_programs_kernel.plist | 10 + runtime/tools/run_clang_tidy.dart | 2 + samples/embedder/BUILD.gn | 139 ++++++- samples/embedder/README.md | 45 ++- samples/embedder/hello.dart | 13 +- samples/embedder/helpers.h | 94 +++++ samples/embedder/program1.dart | 8 + samples/embedder/program2.dart | 8 + samples/embedder/run_kernel.cc | 157 -------- samples/embedder/run_main.cc | 53 +++ samples/embedder/run_timer.cc | 118 ++++++ samples/embedder/run_timer_async.cc | 75 ++++ samples/embedder/run_two_programs.cc | 50 +++ samples/embedder/timer.dart | 33 ++ tests/standalone/embedder_samples_test.dart | 55 ++- 31 files changed, 1634 insertions(+), 197 deletions(-) create mode 100644 runtime/engine/BUILD.gn create mode 100644 runtime/engine/README.md create mode 100644 runtime/engine/dart_engine_impl.cc create mode 100644 runtime/engine/engine.cc create mode 100644 runtime/engine/engine.h create mode 100644 runtime/engine/include/dart_engine.h rename runtime/tools/entitlements/{run_kernel.plist => run_main_aot.plist} (100%) create mode 100644 runtime/tools/entitlements/run_main_kernel.plist create mode 100644 runtime/tools/entitlements/run_timer_aot.plist create mode 100644 runtime/tools/entitlements/run_timer_async_aot.plist create mode 100644 runtime/tools/entitlements/run_timer_async_kernel.plist create mode 100644 runtime/tools/entitlements/run_timer_kernel.plist create mode 100644 runtime/tools/entitlements/run_two_programs_aot.plist create mode 100644 runtime/tools/entitlements/run_two_programs_kernel.plist create mode 100644 samples/embedder/helpers.h create mode 100644 samples/embedder/program1.dart create mode 100644 samples/embedder/program2.dart delete mode 100644 samples/embedder/run_kernel.cc create mode 100644 samples/embedder/run_main.cc create mode 100644 samples/embedder/run_timer.cc create mode 100644 samples/embedder/run_timer_async.cc create mode 100644 samples/embedder/run_two_programs.cc create mode 100644 samples/embedder/timer.dart diff --git a/BUILD.gn b/BUILD.gn index 133f8e43f98..7af5c7945d1 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -52,7 +52,7 @@ group("runtime") { "runtime/bin:process_test", "runtime/bin:run_vm_tests", "runtime/vm:kernel_platform_files($host_toolchain)", - "samples/embedder:all", + "samples/embedder:kernel", "samples/ffi/http:fake_http", "utils/dartdev:dartdev", "utils/kernel-service:kernel-service", @@ -67,6 +67,12 @@ group("runtime") { deps += [ "utils/kernel-service:frontend_server" ] } + # AOT samples use dlopen to load AOT snapshots and it works only on + # 64-bit Linux right now. + if (is_linux && (dart_target_arch == "x64" || dart_target_arch == "arm64")) { + deps += [ "samples/embedder:aot" ] + } + if (!is_win) { # The test isn't run on windows deps += [ "runtime/bin:entrypoints_verification_test" ] diff --git a/build/rbe/rewrapper_dart.py b/build/rbe/rewrapper_dart.py index add039f3f9e..76b753dd7cb 100644 --- a/build/rbe/rewrapper_dart.py +++ b/build/rbe/rewrapper_dart.py @@ -577,6 +577,7 @@ trace to find the place to insert the appropriate support. '--no-embed-sources', '--no-link-platform', '--enable-asserts', + '--link-platform', ]: pass elif self.get_option(['--depfile']): diff --git a/runtime/bin/BUILD.gn b/runtime/bin/BUILD.gn index bce9504a99c..b3a1350fd57 100644 --- a/runtime/bin/BUILD.gn +++ b/runtime/bin/BUILD.gn @@ -736,7 +736,10 @@ source_set("dart_snapshot_cc") { } source_set("dart_kernel_platform_cc") { - visibility = [ ":*" ] + visibility = [ + ":*", + "../engine:*", + ] deps = [ ":kernel_service_dill_linkable", ":platform_strong_dill_linkable", diff --git a/runtime/engine/BUILD.gn b/runtime/engine/BUILD.gn new file mode 100644 index 00000000000..ed11c8184b8 --- /dev/null +++ b/runtime/engine/BUILD.gn @@ -0,0 +1,109 @@ +# 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. + +source_set("engine_jit_set") { + include_dirs = [ + ".", + "..", + "//third_party/boringssl/src/include", + "//third_party", + ] + + sources = [ + "../bin/dart_embedder_api_impl.cc", + "../bin/vmservice_impl.cc", + "../bin/vmservice_impl.h", + "dart_engine_impl.cc", + "engine.cc", + "engine.h", + ] + + configs += [ + "..:dart_arch_config", + "..:dart_config", + "..:dart_os_config", + ] + + deps = [ + "..:libdart_jit", + "../bin:dart_io_api", + "../bin:dart_kernel_platform_cc", + "../bin:libdart_builtin", + ] +} + +source_set("engine_aot_set") { + include_dirs = [ + ".", + "..", + "//third_party/boringssl/src/include", + "//third_party", + ] + + sources = [ + "../bin/dart_embedder_api_impl.cc", + "../bin/vmservice_impl.cc", + "../bin/vmservice_impl.h", + "dart_engine_impl.cc", + "engine.cc", + "engine.h", + ] + + configs += [ + "..:dart_aotruntime_config", + "..:dart_arch_config", + "..:dart_config", + "..:dart_os_config", + ] + + deps = [ + "..:libdart_aotruntime", + "../bin:dart_io_api", + "../bin:libdart_builtin", + ] +} + +shared_library("dart_engine_jit_shared") { + deps = [ ":engine_jit_set" ] + + if (is_mac) { + ldflags = [ "-Wl,-install_name,@rpath/libdart_engine_jit_shared.dylib" ] + } + + if (is_win) { + ldflags = [ "/EXPORT:Dart_True" ] + + libs = [ + "ole32.lib", + "iphlpapi.lib", + "psapi.lib", + "ws2_32.lib", + "Rpcrt4.lib", + "shlwapi.lib", + "winmm.lib", + ] + } +} + +shared_library("dart_engine_aot_shared") { + deps = [ ":engine_aot_set" ] + + if (is_mac) { + ldflags = [ "-Wl,-install_name,@rpath/libdart_engine_aot_shared.dylib" ] + } + + if (is_win) { + ldflags = [ "/EXPORT:Dart_True" ] + + libs = [ + "ole32.lib", + "iphlpapi.lib", + "psapi.lib", + "ws2_32.lib", + "Rpcrt4.lib", + "shlwapi.lib", + "winmm.lib", + ] + } +} diff --git a/runtime/engine/README.md b/runtime/engine/README.md new file mode 100644 index 00000000000..5ea46ba25e7 --- /dev/null +++ b/runtime/engine/README.md @@ -0,0 +1,48 @@ +# Dart Engine + +The `include/dart_engine.h` provides additional functions to embed the Dart VM +and to call Dart functions from kernel and AOT snapshots. It is not intended to +be a full-featured API, and should be used together with `dart_api.h`. + +It is intended for reusing of existing Dart code in non-Dart programs, and +allows to use Dart snapshots as shared libraries: the caller can start one or +several isolates from Dart snapshots and call Dart functions from it. + +Comparing to `dart_api.h` it brings the following: + +- Full initialization of Dart VM, including initializing core libraries. +- Easier handling of isolate messages +- Lock-guarded functions to enter/exit isolates. + +See examples in `samples/embedder` for usages of the API. + +## Handling isolate messages + +In `dart_api.h` there are two possible ways to handle asynchronous +isolate messages: + +- `Dart_MessageNotifyCallback`: Users can pass a callback to get + notifications about messages to handle, but handling messages still + requires entering an isolate, entering a scope, calling + `Dart_HandleMessage`, and then leaving a scope and an isolate. +- `Dart_RunLoop`: Users can invoke it on a separate thread, but then they won't + be able to enter isolates from other threads. + +As an alternative, `dart_engine.h` provides a simpler function +`DartEngine_HandleMessage`, which encapsulates isolate and scope management, and +it allows to specify per isolate / default message schedulers, which only need +to schedule an execution `DartEngine_HandleMessage` with the right isolate. + +Users can create their own `DartEngine_MessageScheduler` struct and pass it to +`DartEngine_SetMessageScheduler` / `DartEngine_SetDefaultMessageScheduler`. See +`samples/embedder/run_timer_async.cc` and `samples/embedder/run_timer.cc` +examples. + +## Entering / leaving isolates + +Because engine API uses its own message handling, it is important to use +`DartEngine_AcquireIsolate` / `DartEngine_ReleaseIsolate` instead of +`Dart_EnterIsolate` / `Dart_ExitIsolate`. `DartEngine_AcquireIsolate` blocks +until an isolate can be entered by trying to obtain an internal lock (which is +released by `DartEngine_ReleaseIsolate`), while `Dart_EnterIsolate` crashes if +some other thread has entered the same isolate. diff --git a/runtime/engine/dart_engine_impl.cc b/runtime/engine/dart_engine_impl.cc new file mode 100644 index 00000000000..aa2e0e2ffc5 --- /dev/null +++ b/runtime/engine/dart_engine_impl.cc @@ -0,0 +1,76 @@ +// 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 +#include +#include +#include "engine/engine.h" +#include "include/dart_api.h" +#include "include/dart_engine.h" +#include "platform/utils.h" + +namespace dart { +namespace engine { + +DART_EXPORT bool DartEngine_Init(char** error) { + return Engine::instance()->Initialize(error); +} + +DART_EXPORT void DartEngine_Shutdown() { + Engine::instance()->Shutdown(); +} + +DART_EXPORT DartEngine_SnapshotData DartEngine_KernelFromFile(const char* path, + char** error) { + return Engine::instance()->KernelFromFile(path, error); +} + +DART_EXPORT DartEngine_SnapshotData +DartEngine_AotSnapshotFromFile(const char* path, char** error) { + return Engine::instance()->AotFromFile(path, error); +} + +DART_EXPORT Dart_Isolate +DartEngine_CreateIsolate(DartEngine_SnapshotData snapshot_data, char** error) { + if (!Engine::instance()->Initialize(error)) { + return nullptr; + } + return Engine::instance()->StartIsolate(snapshot_data, error); +} + +DART_EXPORT void DartEngine_AcquireIsolate(Dart_Isolate isolate) { + Engine::instance()->LockIsolate(isolate); + Dart_EnterIsolate(isolate); +} + +DART_EXPORT void DartEngine_ReleaseIsolate() { + Dart_Isolate current = Dart_CurrentIsolate(); + ASSERT(current != nullptr); + Dart_ExitIsolate(); + Engine::instance()->UnlockIsolate(current); +} + +DART_EXPORT void DartEngine_SetHandleMessageErrorCallback( + DartEngine_HandleMessageErrorCallback handle_message_error_callback) { + Engine::instance()->SetHandleMessageErrorCallback( + handle_message_error_callback); +} + +DART_EXPORT void DartEngine_SetDefaultMessageScheduler( + DartEngine_MessageScheduler scheduler) { + Engine::instance()->SetDefaultMessageScheduler(scheduler); +} + +DART_EXPORT void DartEngine_SetMessageScheduler( + DartEngine_MessageScheduler scheduler, + Dart_Isolate isolate) { + Engine::instance()->SetMessageScheduler(scheduler, isolate); +} + +DART_EXPORT void DartEngine_HandleMessage(Dart_Isolate isolate) { + Engine::instance()->HandleMessage(isolate); +} + +} // namespace engine +} // namespace dart diff --git a/runtime/engine/engine.cc b/runtime/engine/engine.cc new file mode 100644 index 00000000000..c2a082e46a0 --- /dev/null +++ b/runtime/engine/engine.cc @@ -0,0 +1,349 @@ +// 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 +#include +#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 { + +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(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(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( + Utils::ResolveSymbolInDynamicLibrary(library, kVmSnapshotDataCSymbol)); + result.vm_snapshot_instructions = + reinterpret_cast(Utils::ResolveSymbolInDynamicLibrary( + library, kVmSnapshotInstructionsCSymbol)); + result.vm_isolate_data = + reinterpret_cast(Utils::ResolveSymbolInDynamicLibrary( + library, kIsolateSnapshotDataCSymbol)); + result.vm_isolate_instructions = + reinterpret_cast(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 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); + if (Dart_IsError(core_libs_result)) { + Dart_ShutdownIsolate(); + *error = Utils::StrDup(Dart_GetError(core_libs_result)); + 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)) { + Dart_ShutdownIsolate(); + *error = Utils::StrDup(Dart_GetError(library)); + return 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_) { + LockIsolate(isolate); + Dart_EnterIsolate(isolate); + Dart_SetMessageNotifyCallback(nullptr); + Dart_ShutdownIsolate(); + UnlockIsolate(isolate); + } + + MutexLocker state_locker(&engine_state_); + for (auto& snapshot : owned_snapshots_) { + switch (snapshot.kind) { + case DartEngine_SnapshotKind_Kernel: + free(const_cast(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(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); +} + +Mutex& Engine::MutexForIsolate(Dart_Isolate isolate) { + MutexLocker ml(&engine_state_); + return mutexes_[isolate]; +} + +void Engine::LockIsolate(Dart_Isolate isolate) { + MutexForIsolate(isolate).Lock(); +} + +void Engine::UnlockIsolate(Dart_Isolate isolate) { + MutexForIsolate(isolate).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; + { + MutexLocker ml(&engine_state_); + scheduler = schedulers_[isolate]; + } + + 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) { + MutexLocker ml(&engine_state_); + schedulers_[isolate] = scheduler; +} + +} // namespace engine +} // namespace dart diff --git a/runtime/engine/engine.h b/runtime/engine/engine.h new file mode 100644 index 00000000000..61d57ca7b6a --- /dev/null +++ b/runtime/engine/engine.h @@ -0,0 +1,134 @@ +// 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. + +#ifndef RUNTIME_ENGINE_ENGINE_H_ +#define RUNTIME_ENGINE_ENGINE_H_ + +#include +#include +#include +#include +#include "include/dart_engine.h" +#include "platform/synchronization.h" + +namespace dart { +namespace engine { + +// Manages Dart VM initialization/shutdown, isolates, and all state that needs +// to be disposed (e.g. buffers of loaded snapshots). +// +// Since Dart VM has a global state, there should be only one instance of this +// class (accessible through Engine::instance()). +class Engine { + public: + // Loads Kernel snapshot from given file path. + // + // Just reads all bytes into a single buffer, owned by this class. + DartEngine_SnapshotData KernelFromFile(const char* path, char** error); + + // Loads AOT snapshot from given file path. + // + // Opens given AOT snapshot as a shared library and loads buffer symbols from + // it. + DartEngine_SnapshotData AotFromFile(const char* path, char** error); + + // Starts an isolate from a snapshot. + // + // Starting an isolate also routes message notify callback to NotifyMessage. + Dart_Isolate StartIsolate(const DartEngine_SnapshotData snapshot, + char** error); + + // Acquires a lock for an isolate, so that the thread can enter it. + void LockIsolate(Dart_Isolate isolate); + + // Releases the lock obtained by LockIsolate. + void UnlockIsolate(Dart_Isolate isolate); + + // Called by Dart VM via Engine::MessageNotifyCallback. + // + // Calls isolate-specific message scheduler (if any), default message + // scheduler, or ignores the message. + void NotifyMessage(Dart_Isolate isolate); + + // Calls Dart_HandleMessage, managing an isolate lock and Dart scope. + void HandleMessage(Dart_Isolate isolate); + + // Sets a callback to be called when Dart_HandleMessage returns an error. + void SetHandleMessageErrorCallback( + DartEngine_HandleMessageErrorCallback callback); + + // Sets a message scheduler for a given isolate. + void SetDefaultMessageScheduler(DartEngine_MessageScheduler scheduler); + + // Sets default message scheduler for isolates without dedicated + // message scheduler. + void SetMessageScheduler(DartEngine_MessageScheduler scheduler, + Dart_Isolate isolate); + + // Initializes embedder and partially initializes Dart VM. + // + // Full initialization happens only when the user starts the first isolate, as + // in case of AOT snapshots we don't have vm snapshot data and vm snapshot + // instuructions before the user makes the first call to StartIsolate. + bool Initialize(char** error); + + // Shuts down the engine. + // + // Once shut down, it is not supposed to be re-initialized. + void Shutdown(); + + static Engine* instance(); + // Passed to Dart_SetMessageNotifyCallback. + static void MessageNotifyCallback(Dart_Isolate isolate); + // Passed to user-previded message scheduler callback. + static void HandleMessageCallback(Dart_Isolate isolate); + + private: + // Set to false once shutdown starts. + bool is_running_ = false; + + // Controls initalization and shutdown, and prevents deadlocks when delivering + // messages during shutdown. + Mutex engine_lifecycle_; + + // Guards access to engine state. + Mutex engine_state_; + + // Whether Engine::Initialize is called. + bool initialized_ = false; + + // Added because of AOT mode, because in AOT mode Dart VM cannot be fully + // initialized until we get vm snapshot data, which we typically get when the + // user starts the first isolate. + bool first_isolate_started_ = false; + + // Snapshots, whose buffers are owned by the engine. + // and are freed on Shutdown. + std::vector owned_snapshots_; + + // Loaded dynamic libraries. Dynamic libraries are loaded + // when reading AOT snapshots. + std::vector loaded_libraries_; + + // All isolates, started via Engine::StartIsolate. + std::vector isolates_; + + // Stores per-isolate mutexes, used by Engine::LockIsolate/UnlockIsolate. + std::unordered_map mutexes_; + + // Default scheduler. + DartEngine_MessageScheduler default_scheduler_; + // Per-isolate message schedulers. + std::unordered_map schedulers_; + + // Callback to notify about Dart_HandleMessage errors. + DartEngine_HandleMessageErrorCallback handle_message_error_callback_; + + // Helper function to get an element from mutexes_. + Mutex& MutexForIsolate(Dart_Isolate isolate); +}; + +} // namespace engine +} // namespace dart +#endif // RUNTIME_ENGINE_ENGINE_H_ diff --git a/runtime/engine/include/dart_engine.h b/runtime/engine/include/dart_engine.h new file mode 100644 index 00000000000..adf669f48d7 --- /dev/null +++ b/runtime/engine/include/dart_engine.h @@ -0,0 +1,181 @@ +// 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. + +#ifndef RUNTIME_ENGINE_INCLUDE_DART_ENGINE_H_ +#define RUNTIME_ENGINE_INCLUDE_DART_ENGINE_H_ +#include "include/dart_api.h" + +/** + * Initializes the Dart engine. + * + * \param[out] error Set to NULL if initialization was successful. + * Otherwise is set to a description of error which occurred during + * initialization. The caller is responsible for freeing the error string. + * + * \return true if initialization is successful and false otherwise. + */ +DART_EXPORT bool DartEngine_Init(char** error); + +/** + * Shuts down the Dart engine. + * + * Stops all isolates and frees all resources. + */ +DART_EXPORT void DartEngine_Shutdown(); + +/** + * Kind of a snapshot represented by DartEngine_SnapshotData. + */ +typedef enum { + DartEngine_SnapshotKind_Kernel, + DartEngine_SnapshotKind_AOT, +} DartEngine_SnapshotKind; + +/** + * Dart snapshot data, sufficient to create an isolate. + */ +typedef struct DartEngine_SnapshotData { + /** + * Uniquely identifies a snapshot. + * + * It is passed to \ref Dart_CreateIsolateGroup or + * \ref Dart_CreateIsolateGroupFromKernel. + */ + const char* script_uri; + /** + * Whether this snapshot data reprents Kernel or AOT snapshot. + */ + DartEngine_SnapshotKind kind; + union { + struct { + const uint8_t* kernel_buffer; + intptr_t kernel_buffer_size; + }; + struct { + const uint8_t* vm_snapshot_data; + const uint8_t* vm_snapshot_instructions; + const uint8_t* vm_isolate_data; + const uint8_t* vm_isolate_instructions; + }; + }; +} DartEngine_SnapshotData; + +/** + * Creates a new isolate for a given snapshot. + * + * \param snapshot_data Snapshot to start an isolate from. + * \param[out] error Set to NULL if an isolate is created successfully. + * Otherwise is set to a description of error which occurred during + * isolate creation. The caller is responsible for freeing the error string. + * + * \return The new isolate on success, or NULL if isolate creation failed. + */ +DART_EXPORT Dart_Isolate +DartEngine_CreateIsolate(DartEngine_SnapshotData snapshot_data, char** error); + +/** + * Blocks until the isolate is available for entering and enters an isolate. + * + * This is an alternative to \ref Dart_EnterIsolate, which can be used when + * multiple threads may try to enter the same isolate at the same time. + * + * Use \ref DartEngine_ReleaseIsolate instead of `Dart_ExitIsolate` if you + * use this function. + */ +DART_EXPORT void DartEngine_AcquireIsolate(Dart_Isolate isolate); + +/** + * Exits an isolate and makes it available for entering by other threads. + * + * This is an alternative to \ref Dart_ExitIsolate, which should be used + * if you use \ref DartEngine_AcquireIsolate. + */ +DART_EXPORT void DartEngine_ReleaseIsolate(); + +/** + * A callback invoked when there's an error handling isolate message. + * + * \param error Dart error happened during handling a message. + * \param destination_isolate The isolate where the error occurred. + */ +typedef void (*DartEngine_HandleMessageErrorCallback)( + Dart_Handle error, + Dart_Isolate destination_isolate); + +/** + * Sets the callback for errors during handling isolate messages. + */ +DART_EXPORT void DartEngine_SetHandleMessageErrorCallback( + DartEngine_HandleMessageErrorCallback handle_message_error_callback); + +/** + * Handles a single message for an isolate. + */ +DART_EXPORT void DartEngine_HandleMessage(Dart_Isolate isolate); + +/** + * Message scheduling callback. + * + * Dart Engine calls this callback whenever there's a new message for an + * isolate, and the callback should schedule an execution of + * DartEngine_HandleMessage with given isolate. + * + * \param isolate Destination isolate for a mesage. + * \param context Context from \ref DartEngine_MessageScheduler. + */ +typedef void (*DartEngine_ScheduleMessageCallback)(Dart_Isolate isolate, + void* context); + +/** + * Scheduler for isolate messages. + * + * Whenever Dart isolate has a new message, the Dart engine will + * call schedule_callback and pass context into it. + */ +typedef struct DartEngine_MessageScheduler { + DartEngine_ScheduleMessageCallback schedule_callback; + void* context; +} DartEngine_MessageScheduler; + +/** + * Sets default message scheduler for all isolates. + */ +DART_EXPORT void DartEngine_SetDefaultMessageScheduler( + DartEngine_MessageScheduler scheduler); + +/** + * Sets message scheduler for isolate. + */ +DART_EXPORT void DartEngine_SetMessageScheduler( + DartEngine_MessageScheduler scheduler, + Dart_Isolate isolate); + +/** + * Loads \ref DartEngine_SnapshotData from file + * + * User should not free uri and buffer. + * + * \param[out] error Set to NULL if snapshot could be loaded successfully. + * Otherwise is set to a description of error which occurred during + * snapshot loading. The caller is responsible for freeing the error string. + * + * \return Snapshot data with kind set to \ref DartEngine_SnapshotKind_Kernel. + */ +DART_EXPORT DartEngine_SnapshotData DartEngine_KernelFromFile(const char* path, + char** error); +/** + * Loads \ref DartEngine_SnapshotData from file + * + * User should not free uri and buffers. + * + * \param[out] error Set to NULL if snapshot could be loaded successfully. + * Otherwise is set to a description of error which occurred during + * snapshot loading. The caller is responsible for freeing the error string. + * + * \return Snapshot data with kind set to \ref DartEngine_SnapshotKind_AOT. + */ +DART_EXPORT DartEngine_SnapshotData +DartEngine_AotSnapshotFromFile(const char* path, char** error); + +#endif // RUNTIME_ENGINE_INCLUDE_DART_ENGINE_H_ diff --git a/runtime/tools/entitlements/run_kernel.plist b/runtime/tools/entitlements/run_main_aot.plist similarity index 100% rename from runtime/tools/entitlements/run_kernel.plist rename to runtime/tools/entitlements/run_main_aot.plist diff --git a/runtime/tools/entitlements/run_main_kernel.plist b/runtime/tools/entitlements/run_main_kernel.plist new file mode 100644 index 00000000000..eeda4be95dc --- /dev/null +++ b/runtime/tools/entitlements/run_main_kernel.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.disable-library-validation + + + \ No newline at end of file diff --git a/runtime/tools/entitlements/run_timer_aot.plist b/runtime/tools/entitlements/run_timer_aot.plist new file mode 100644 index 00000000000..eeda4be95dc --- /dev/null +++ b/runtime/tools/entitlements/run_timer_aot.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.disable-library-validation + + + \ No newline at end of file diff --git a/runtime/tools/entitlements/run_timer_async_aot.plist b/runtime/tools/entitlements/run_timer_async_aot.plist new file mode 100644 index 00000000000..eeda4be95dc --- /dev/null +++ b/runtime/tools/entitlements/run_timer_async_aot.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.disable-library-validation + + + \ No newline at end of file diff --git a/runtime/tools/entitlements/run_timer_async_kernel.plist b/runtime/tools/entitlements/run_timer_async_kernel.plist new file mode 100644 index 00000000000..eeda4be95dc --- /dev/null +++ b/runtime/tools/entitlements/run_timer_async_kernel.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.disable-library-validation + + + \ No newline at end of file diff --git a/runtime/tools/entitlements/run_timer_kernel.plist b/runtime/tools/entitlements/run_timer_kernel.plist new file mode 100644 index 00000000000..eeda4be95dc --- /dev/null +++ b/runtime/tools/entitlements/run_timer_kernel.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.disable-library-validation + + + \ No newline at end of file diff --git a/runtime/tools/entitlements/run_two_programs_aot.plist b/runtime/tools/entitlements/run_two_programs_aot.plist new file mode 100644 index 00000000000..eeda4be95dc --- /dev/null +++ b/runtime/tools/entitlements/run_two_programs_aot.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.disable-library-validation + + + \ No newline at end of file diff --git a/runtime/tools/entitlements/run_two_programs_kernel.plist b/runtime/tools/entitlements/run_two_programs_kernel.plist new file mode 100644 index 00000000000..eeda4be95dc --- /dev/null +++ b/runtime/tools/entitlements/run_two_programs_kernel.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.disable-library-validation + + + \ No newline at end of file diff --git a/runtime/tools/run_clang_tidy.dart b/runtime/tools/run_clang_tidy.dart index 7ea0348e969..f3437d207bb 100644 --- a/runtime/tools/run_clang_tidy.dart +++ b/runtime/tools/run_clang_tidy.dart @@ -46,6 +46,8 @@ List compilerFlagsForFile(String filepath) { '-Iruntime', '-Ithird_party', '-Iruntime/include', + if (filepath.contains('samples/embedder')) + '-Iruntime/engine', '-Ithird_party/boringssl/src/include', '-Ithird_party/perfetto/include', '-Ithird_party/zlib', diff --git a/samples/embedder/BUILD.gn b/samples/embedder/BUILD.gn index b3a87b3ea39..f6a51c4a381 100644 --- a/samples/embedder/BUILD.gn +++ b/samples/embedder/BUILD.gn @@ -2,24 +2,143 @@ # 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. +import("../../utils/aot_snapshot.gni") import("../../utils/application_snapshot.gni") # All samples. group("all") { - deps = [ ":run_kernel" ] + deps = [ + ":aot", + ":kernel", + ] +} + +group("aot") { + deps = [ + ":run_main_aot", + ":run_timer_aot", + ":run_timer_async_aot", + ":run_two_programs_aot", + ] +} + +group("kernel") { + deps = [ + ":run_main_kernel", + ":run_timer_async_kernel", + ":run_timer_kernel", + ":run_two_programs_kernel", + ] +} + +# Generates a pair of executables for kernel and AOT snapshots. +template("sample") { + executable("${target_name}_kernel") { + # Otherwise build with --no-clang fails. + if (is_linux) { + ldflags = [ "-Wl,--allow-shlib-undefined" ] + } + include_dirs = [ + "../../runtime", + "../../runtime/engine", + ] + deps = [ "../../runtime/engine:dart_engine_jit_shared" ] + if (defined(invoker.deps)) { + deps += invoker.deps + } + data_deps = [] + foreach(snapshot, invoker.snapshots) { + data_deps += [ "${snapshot}_kernel" ] + } + forward_variables_from(invoker, + "*", + [ + "snapshots", + "deps", + "data_deps", + ]) + } + + executable("${target_name}_aot") { + # Otherwise build with MSAN fails. + if (is_linux) { + ldflags = [ "-Wl,--allow-shlib-undefined" ] + } + include_dirs = [ + "../../runtime", + "../../runtime/engine", + ] + deps = [ "../../runtime/engine:dart_engine_aot_shared" ] + if (defined(invoker.deps)) { + deps += invoker.deps + } + data_deps = [] + foreach(snapshot, invoker.snapshots) { + data_deps += [ "${snapshot}_aot" ] + } + forward_variables_from(invoker, + "*", + [ + "snapshots", + "deps", + "data_deps", + ]) + } +} + +# For a given main_dart generates Kernel and AOT snapshots +template("snapshots") { + # Kernel snapshot + application_snapshot("${target_name}_kernel") { + main_dart = invoker.main_dart + dart_snapshot_kind = "kernel" + training_args = [] # Not used + gen_kernel_args = [ "--link-platform" ] + } + + # AOT snapshot + aot_snapshot("${target_name}_aot") { + main_dart = invoker.main_dart + } } # Sample binary to run given kernel snapshot. -executable("run_kernel") { - sources = [ "run_kernel.cc" ] - deps = [ "../../runtime/bin:dart_embedder_runtime_jit" ] - include_dirs = [ "../../runtime" ] - data_deps = [ ":hello_kernel" ] +sample("run_main") { + sources = [ "run_main.cc" ] + snapshots = [ ":hello" ] } -# Kernel snapshot of ./hello.dart. -application_snapshot("hello_kernel") { +snapshots("hello") { main_dart = "hello.dart" - dart_snapshot_kind = "kernel" - training_args = [] # Not used +} + +# Sample binary to run two snapshots simultaneously. +sample("run_two_programs") { + sources = [ "run_two_programs.cc" ] + snapshots = [ + ":program1", + ":program2", + ] +} + +snapshots("program1") { + main_dart = "program1.dart" +} + +snapshots("program2") { + main_dart = "program2.dart" +} + +sample("run_timer") { + sources = [ "run_timer.cc" ] + snapshots = [ ":timer" ] +} + +sample("run_timer_async") { + sources = [ "run_timer_async.cc" ] + snapshots = [ ":timer" ] +} + +snapshots("timer") { + main_dart = "timer.dart" } diff --git a/samples/embedder/README.md b/samples/embedder/README.md index c605eea2afa..00f85428daa 100644 --- a/samples/embedder/README.md +++ b/samples/embedder/README.md @@ -2,26 +2,43 @@ Examples of using Dart VM and executing Dart code from C++ binaries. -## run_kernel.cc +All examples can run either AOT or Kernel snapshots, depending on +which shared library variant they depend on. -To run the example: +Since snapshot file formats are unstable, the `dart` binary needs to +be of a matching version. The simplest way to ensure this is to build +Dart SDK from the same checkout, see [Building Dart +SDK](https://github.com/dart-lang/sdk/blob/main/docs/Building.md#building). + +## `run_main.cc` + +This is the simplest example, which just calls a `main` function from a given AOT/Kernel +snapshot. It does not handle isolate messages, so it cannot run Dart +programs with async functions. + +To run the example with a Kernel snapshot: ```sh -./tools/build.py --no-rbe --mode=release samples/embedder:run_kernel && out/ReleaseX64/run_kernel +./tools/build.py --mode=release samples/embedder:run_main_kernel && \ + out/ReleaseX64/run_main_kernel out/ReleaseX64/gen/hello_kernel.dart.snapshot. ``` -The example initializes Dart VM, creates an isolate from a kernel file (by -default it uses kernel-compiled `hello.dart`), launches its `main` function with -args and exits. - -You can also compile your own Dart kernel like this: +To run the example with an AOT snapshot: ```sh -dart compile kernel --no-link-platform my.dart -out/ReleaseX64/run_kernel my.dill +./tools/build.py --mode=release samples/embedder:run_main_aot && \ + out/ReleaseX64/run_main_aot out/ReleaseX64/hello_aot.snapshot. ``` -Since the kernel file format is unstable, the `dart` binary needs to be of a -matching version. The simplest way to ensure this is to build Dart SDK from the -same checkout, see -[Building Dart SDK](https://github.com/dart-lang/sdk/blob/main/docs/Building.md#building). +## `run_two_programs.cc` + +This example calls a function from one Dart snapshot and then passes +the returned string to another Dart snapshot. + +## `run_timer.cc` + +Demonstrates running an isolate event loop in a separate thread. + +## `run_timer_async.cc` + +Demonstrates a custom message scheduler using `std::async`. diff --git a/samples/embedder/hello.dart b/samples/embedder/hello.dart index cbd3e888b52..cd6145c1087 100644 --- a/samples/embedder/hello.dart +++ b/samples/embedder/hello.dart @@ -2,10 +2,11 @@ // 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. -import 'package:collection/collection.dart'; - -@pragma('vm:entry-point') -void main(List? args) { - final greetee = args?.singleOrNull ?? 'world'; - print('Hello, $greetee!'); +@pragma('vm:entry-point', 'call') +void main(List args) { + greet(args[0]); +} + +void greet(String person) { + print("hi, $person!"); } diff --git a/samples/embedder/helpers.h b/samples/embedder/helpers.h new file mode 100644 index 00000000000..1784aa342f7 --- /dev/null +++ b/samples/embedder/helpers.h @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#ifndef SAMPLES_EMBEDDER_HELPERS_H_ +#define SAMPLES_EMBEDDER_HELPERS_H_ + +#include +#include +#include +#include +#include "include/dart_api.h" +#include "include/dart_engine.h" + +// Loads kernel/AOT snapshot from path depending on +// whether we use precompiled runtime. +inline DartEngine_SnapshotData AutoSnapshotFromFile(std::string_view path, + char** error) { + std::string path_string(path); + if (Dart_IsPrecompiledRuntime()) { + return DartEngine_AotSnapshotFromFile(path_string.c_str(), error); + } else { + return DartEngine_KernelFromFile(path_string.c_str(), error); + } +} + +inline void CheckError(char* error, std::string_view context = "") { + if (error != nullptr) { + std::cerr << "Error " << context << ": " << error << std::endl; + std::exit(1); + } +} + +inline Dart_Handle CheckError(Dart_Handle handle, + std::string_view context = "") { + if (Dart_IsError(handle)) { + std::cerr << "Error " << context << ": " << Dart_GetError(handle) + << std::endl; + std::exit(1); + } + return handle; +} + +inline std::string StringFromHandle(Dart_Handle handle) { + CheckError(handle, "StringFromHandle received an error"); + + if (!Dart_IsString(handle)) { + std::cerr << "StringFromHandle handle is not a string" << std::endl; + std::exit(1); + } + + const char* return_value_tmp; + Dart_Handle to_string_result = + Dart_StringToCString(handle, &return_value_tmp); + CheckError(to_string_result, "Dart_StringToCString"); + + return std::string(return_value_tmp); +} + +inline int64_t IntFromHandle(Dart_Handle handle) { + CheckError(handle, "IntFromHandle received an error"); + + if (!Dart_IsInteger(handle)) { + std::cerr << "IntFromHandle handle is not an int" << std::endl; + std::exit(1); + } + + int64_t result; + Dart_Handle to_int64_result = Dart_IntegerToInt64(handle, &result); + CheckError(to_int64_result, "Dart_IntegerToInt64"); + return result; +} + +template +inline T WithIsolate(Dart_Isolate isolate, std::function body) { + DartEngine_AcquireIsolate(isolate); + Dart_EnterScope(); + T result = body(); + Dart_ExitScope(); + DartEngine_ReleaseIsolate(); + return result; +} + +inline void WithIsolate(Dart_Isolate isolate, std::function body) { + DartEngine_AcquireIsolate(isolate); + Dart_EnterScope(); + body(); + Dart_ExitScope(); + DartEngine_ReleaseIsolate(); +} + +#endif /* SAMPLES_EMBEDDER_HELPERS_H_ */ diff --git a/samples/embedder/program1.dart b/samples/embedder/program1.dart new file mode 100644 index 00000000000..25b7d96125f --- /dev/null +++ b/samples/embedder/program1.dart @@ -0,0 +1,8 @@ +void main(List args) { + throw 'Unimplemented'; +} + +@pragma('vm:entry-point', 'call') +String getValue() { + return 'program1'; +} diff --git a/samples/embedder/program2.dart b/samples/embedder/program2.dart new file mode 100644 index 00000000000..051ae6e13d8 --- /dev/null +++ b/samples/embedder/program2.dart @@ -0,0 +1,8 @@ +void main(List args) { + throw 'Unimplemented'; +} + +@pragma('vm:entry-point', 'call') +void printValue(String value) { + print('program2 received: $value'); +} diff --git a/samples/embedder/run_kernel.cc b/samples/embedder/run_kernel.cc deleted file mode 100644 index a98d38e0630..00000000000 --- a/samples/embedder/run_kernel.cc +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) 2024, 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. - -// Executes `main` function from given Dart kernel binary (by default uses -// compiled ./hello.dart). -#include -#include -#include -#include -#include -#include -#include "bin/dartutils.h" -#include "bin/dfe.h" -#include "bin/platform.h" -#include "include/dart_api.h" -#include "include/dart_embedder_api.h" -#include "platform/assert.h" - -Dart_Handle CheckHandle(Dart_Handle handle, - const char* context = "unknown context") { - if (Dart_IsError(handle)) { - FATAL("Dart error (%s): %s", context, Dart_GetError(handle)); - } - return handle; -} - -void CheckError(bool condition, const char* error, const char* context) { - if (!condition) { - FATAL("Dart error (%s): %s", context, error); - } -} - -void CheckError(const char* error, const char* context) { - if (error != nullptr) { - FATAL("Dart error (%s): %s", context, error); - } -} - -Dart_InitializeParams CreateInitializeParams() { - Dart_InitializeParams params; - memset(¶ms, 0, sizeof(params)); - params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION; - return params; -} - -std::string GetExecutablePath() { - const size_t kPathBufSize = PATH_MAX + 1; - char executable_path[kPathBufSize] = {}; - - intptr_t path_length = dart::bin::Platform::ResolveExecutablePathInto( - executable_path, kPathBufSize); - CheckError(path_length > 0, "empty executable path", - "ResolveExecutablePathInfo"); - return std::string(executable_path, path_length); -} - -std::string GetDefaultSnapshotPath() { - std::string executable_path = GetExecutablePath(); - std::string directory = - executable_path.substr(0, executable_path.find_last_of("/\\")); - return directory + "/gen/hello_kernel.dart.snapshot"; -} - -std::string ReadSnapshot(std::string_view path) { - std::string path_string{path}; - std::ifstream source_file{path_string, std::ios::binary}; - - ASSERT(source_file.good()); - source_file.seekg(0, source_file.end); - uint64_t length = source_file.tellg(); - source_file.seekg(0, source_file.beg); - - char* bytes = static_cast(std::malloc(length)); - source_file.read(bytes, length); - auto result = std::string(bytes, length); - std::free(bytes); - return result; -} - -Dart_Handle ToDartStringList(const std::vector& values) { - Dart_Handle string_type = - CheckHandle(dart::bin::DartUtils::GetDartType("dart:core", "String")); - Dart_Handle filler = CheckHandle(Dart_NewStringFromCString("")); - - Dart_Handle result = - CheckHandle(Dart_NewListOfTypeFilled(string_type, filler, values.size())); - for (size_t i = 0; i < values.size(); i++) { - Dart_Handle element = - CheckHandle(Dart_NewStringFromCString(values[i].c_str())); - CheckHandle(Dart_ListSetAt(result, i, element)); - } - - return result; -} - -int main(int argc, char** argv) { - std::string snapshot_path = - argc == 1 ? GetDefaultSnapshotPath() : std::string(argv[1]); - - std::string snapshot_name = - snapshot_path.substr(snapshot_path.find_last_of("/\\") + 1); - std::string snapshot_data = ReadSnapshot(snapshot_path); - std::string snapshot_uri = "file://" + snapshot_path; - std::cout << "Snapshot path: " << snapshot_path << std::endl; - char* error; - - // Start Dart VM. - bool result = dart::embedder::InitOnce(&error); - CheckError(result, error, "dart::embedder::InitOnce"); - - std::vector flags{}; - CheckError(Dart_SetVMFlags(flags.size(), flags.data()), "Dart_SetVMFlags"); - - Dart_InitializeParams initialize_params = CreateInitializeParams(); - CheckError(Dart_Initialize(&initialize_params), "Dart_Initialize"); - - dart::bin::DFE dfe; - dfe.Init(); - const uint8_t* platform_buffer = nullptr; - intptr_t platform_buffer_size = 0; - - dfe.LoadPlatform(&platform_buffer, &platform_buffer_size); - - // Start an isolate from a platform kernel. - Dart_IsolateFlags isolate_flags; - Dart_IsolateFlagsInitialize(&isolate_flags); - - Dart_CreateIsolateGroupFromKernel( - /*script_uri=*/snapshot_uri.c_str(), - /*name=*/snapshot_name.c_str(), - /*kernel_buffer=*/platform_buffer, - /*kernel_buffer_size=*/platform_buffer_size, - /*flags=*/&isolate_flags, - /*isolate_group_data=*/nullptr, - /*isolate_data=*/nullptr, &error); - CheckError(error, "Dart_CreateIsolateGroupFromKernel"); - Dart_EnterScope(); - CheckHandle(dart::bin::DartUtils::PrepareForScriptLoading( - /*is_service_isolate=*/false, /*trace_loading=*/false), - "PrepareForScriptLoading"); - - // Load kernel snapshot to run `main` from. - Dart_Handle library = - CheckHandle(Dart_LoadLibraryFromKernel( - reinterpret_cast(snapshot_data.c_str()), - snapshot_data.size()), - "Dart_LoadLibraryFromKernel"); - - // Call main function with args. - std::initializer_list main_args{ToDartStringList({"universe"})}; - CheckHandle(Dart_Invoke(library, Dart_NewStringFromCString("main"), 1, - const_cast(main_args.begin())), - "Dart_Invoke('main')"); - Dart_ExitScope(); - Dart_ShutdownIsolate(); -} diff --git a/samples/embedder/run_main.cc b/samples/embedder/run_main.cc new file mode 100644 index 00000000000..b7bc6537137 --- /dev/null +++ b/samples/embedder/run_main.cc @@ -0,0 +1,53 @@ +// 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 +#include "helpers.h" +#include "include/dart_api.h" +#include "include/dart_engine.h" + +Dart_Handle ToDartStringList(const std::vector& values) { + Dart_Handle core_library = + CheckError(Dart_LookupLibrary(Dart_NewStringFromCString("dart:core"))); + Dart_Handle string_type = CheckError(Dart_GetNonNullableType( + core_library, Dart_NewStringFromCString("String"), 0, nullptr)); + Dart_Handle filler = Dart_NewStringFromCString(""); + + Dart_Handle result = + CheckError(Dart_NewListOfTypeFilled(string_type, filler, values.size())); + for (size_t i = 0; i < values.size(); i++) { + Dart_Handle element = Dart_NewStringFromCString(values[i].c_str()); + CheckError(Dart_ListSetAt(result, i, element)); + } + + return result; +} + +int main(int argc, char** argv) { + if (argc == 1) { + std::cerr << "Must specify snapshot path" << std::endl; + std::exit(1); + } + char* error = nullptr; + + DartEngine_SnapshotData snapshot_data = AutoSnapshotFromFile(argv[1], &error); + CheckError(error, "reading snapshot"); + + Dart_Isolate isolate = DartEngine_CreateIsolate(snapshot_data, &error); + CheckError(error, "starting isolate"); + + DartEngine_AcquireIsolate(isolate); + Dart_EnterScope(); + + std::initializer_list main_args{ToDartStringList({"world"})}; + + CheckError(Dart_Invoke(Dart_RootLibrary(), Dart_NewStringFromCString("main"), + 1, const_cast(main_args.begin())), + "calling main"); + + Dart_ExitScope(); + DartEngine_ReleaseIsolate(); + + DartEngine_Shutdown(); +} diff --git a/samples/embedder/run_timer.cc b/samples/embedder/run_timer.cc new file mode 100644 index 00000000000..5ddd1308f5c --- /dev/null +++ b/samples/embedder/run_timer.cc @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include "helpers.h" +#include "include/dart_api.h" +#include "include/dart_engine.h" + +// Calls `startTimer` from timer.dart +void StartTimer(Dart_Isolate isolate, uint32_t millis) { + WithIsolate(isolate, [&]() { + std::initializer_list args{Dart_NewInteger(millis)}; + CheckError( + Dart_Invoke(Dart_RootLibrary(), Dart_NewStringFromCString("startTimer"), + 1, const_cast(args.begin())), + "calling startTimer"); + }); +} + +// Calls `stopTimer` from timer.dart +void StopTimer(Dart_Isolate isolate) { + WithIsolate(isolate, [&]() { + CheckError(Dart_Invoke(Dart_RootLibrary(), + Dart_NewStringFromCString("stopTimer"), 0, nullptr), + "calling stopTimer"); + }); +} + +// Gets `ticks` from timer.dart +int64_t GetTicks(Dart_Isolate isolate) { + return WithIsolate(isolate, []() { + return IntFromHandle( + Dart_GetField(Dart_RootLibrary(), Dart_NewStringFromCString("ticks"))); + }); +} + +// Queue-based message handler, running on a separate thread. +class ThreadedMessageHandler { + public: + void Run() { + is_running = true; + while (is_running) { + Dart_Isolate isolate; + { + std::unique_lock notifications_lock(notifications_mutex_); + can_pop_.wait(notifications_lock); + if (notifications_.empty()) { + continue; + } + isolate = notifications_.front(); + notifications_.pop(); + } + DartEngine_HandleMessage(isolate); + } + } + + void Notify(Dart_Isolate isolate) { + std::unique_lock notifications_lock(notifications_mutex_); + notifications_.push(isolate); + can_pop_.notify_one(); + } + + void Stop() { + is_running = false; + can_pop_.notify_one(); + } + + static void ScheduleDartMessage(Dart_Isolate isolate, void* context) { + reinterpret_cast(context)->Notify(isolate); + } + + private: + std::queue notifications_; + std::condition_variable can_pop_; + std::atomic is_running; + std::mutex notifications_mutex_; +}; + +int main(int argc, char** argv) { + if (argc == 1) { + std::cerr << "Must specify snapshot path" << std::endl; + std::exit(1); + } + char* error = nullptr; + + // Start an event loop on a separate thread and use it as a default + // scheduler. + ThreadedMessageHandler message_handler; + std::thread message_handler_thread(&ThreadedMessageHandler::Run, + &message_handler); + DartEngine_SetDefaultMessageScheduler( + {ThreadedMessageHandler::ScheduleDartMessage, &message_handler}); + + // Load snapshot and create an isolate + DartEngine_SnapshotData snapshot_data = AutoSnapshotFromFile(argv[1], &error); + CheckError(error, "reading snapshot"); + Dart_Isolate isolate = DartEngine_CreateIsolate(snapshot_data, &error); + CheckError(error, "creating isolate"); + + // Call Dart function to start a timer. + StartTimer(isolate, 1); + + // Wait a bit. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Stop the timer. + StopTimer(isolate); + + // Get timer value. + std::cout << "Ticks: " << GetTicks(isolate) << std::endl; + + // Stop event loop. + message_handler.Stop(); + message_handler_thread.join(); + + DartEngine_Shutdown(); +} diff --git a/samples/embedder/run_timer_async.cc b/samples/embedder/run_timer_async.cc new file mode 100644 index 00000000000..13e610c02e6 --- /dev/null +++ b/samples/embedder/run_timer_async.cc @@ -0,0 +1,75 @@ +// Same as run_timer.cc, but uses std::async instead of a dedicated event loop +// thread. Used as a demonstration of a custom message scheduler. +#include +#include +#include +#include "helpers.h" +#include "include/dart_api.h" +#include "include/dart_engine.h" + +// Calls `startTimer` from timer.dart +void StartTimer(Dart_Isolate isolate, uint32_t millis) { + WithIsolate(isolate, [&]() { + std::initializer_list args{Dart_NewInteger(millis)}; + CheckError( + Dart_Invoke(Dart_RootLibrary(), Dart_NewStringFromCString("startTimer"), + 1, const_cast(args.begin())), + "calling startTimer"); + }); +} + +// Calls `stopTimer` from timer.dart +void StopTimer(Dart_Isolate isolate) { + WithIsolate(isolate, [&]() { + CheckError(Dart_Invoke(Dart_RootLibrary(), + Dart_NewStringFromCString("stopTimer"), 0, nullptr), + "calling stopTimer"); + }); +} + +// Gets `ticks` from timer.dart +int64_t GetTicks(Dart_Isolate isolate) { + return WithIsolate(isolate, []() { + return IntFromHandle( + Dart_GetField(Dart_RootLibrary(), Dart_NewStringFromCString("ticks"))); + }); +} + +std::mutex shutdown_mutex; + +void ScheduleDartMessage(Dart_Isolate isolate, void* context) { + std::ignore = std::async(DartEngine_HandleMessage, isolate); +} + +int main(int argc, char** argv) { + if (argc == 1) { + std::cerr << "Must specify snapshot path" << std::endl; + std::exit(1); + } + char* error = nullptr; + + // Start an event loop on a separate thread and use it as a default + // scheduler. + DartEngine_MessageScheduler scheduler{ScheduleDartMessage, nullptr}; + DartEngine_SetDefaultMessageScheduler(scheduler); + + // Load snapshot and create an isolate + DartEngine_SnapshotData snapshot_data = AutoSnapshotFromFile(argv[1], &error); + CheckError(error, "reading snapshot"); + Dart_Isolate isolate = DartEngine_CreateIsolate(snapshot_data, &error); + CheckError(error, "creating isolate"); + + // Call Dart function to start a timer. + StartTimer(isolate, 1); + + // Wait a bit. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Stop the timer. + StopTimer(isolate); + + // Get timer value. + std::cout << "Ticks: " << GetTicks(isolate) << std::endl; + + DartEngine_Shutdown(); +} diff --git a/samples/embedder/run_two_programs.cc b/samples/embedder/run_two_programs.cc new file mode 100644 index 00000000000..a6707b86fa7 --- /dev/null +++ b/samples/embedder/run_two_programs.cc @@ -0,0 +1,50 @@ +#include +#include "helpers.h" +#include "include/dart_api.h" +#include "include/dart_engine.h" + +int main(int argc, char** argv) { + if (argc < 3) { + std::cerr << "Must specify two snapshot paths" << std::endl; + std::exit(1); + } + char* error = nullptr; + + DartEngine_SnapshotData snapshot1 = AutoSnapshotFromFile(argv[1], &error); + CheckError(error, "reading snapshot"); + + DartEngine_SnapshotData snapshot2 = AutoSnapshotFromFile(argv[2], &error); + CheckError(error, "reading snapshot"); + + Dart_Isolate isolate1 = DartEngine_CreateIsolate(snapshot1, &error); + CheckError(error, "starting 1st isolate"); + + Dart_Isolate isolate2 = DartEngine_CreateIsolate(snapshot2, &error); + CheckError(error, "starting 2nd isolate"); + + DartEngine_AcquireIsolate(isolate1); + Dart_EnterScope(); + + Dart_Handle invoke_result = Dart_Invoke( + Dart_RootLibrary(), Dart_NewStringFromCString("getValue"), 0, nullptr); + std::string return_value = StringFromHandle(invoke_result); + + std::cout << "program1 returned: " << return_value << std::endl; + Dart_ExitScope(); + DartEngine_ReleaseIsolate(); + + DartEngine_AcquireIsolate(isolate2); + Dart_EnterScope(); + + std::initializer_list args{ + Dart_NewStringFromCString(return_value.c_str())}; + Dart_Handle invoke_result2 = + Dart_Invoke(Dart_RootLibrary(), Dart_NewStringFromCString("printValue"), + 1, const_cast(args.begin())); + CheckError(invoke_result2); + + Dart_ExitScope(); + DartEngine_ReleaseIsolate(); + + DartEngine_Shutdown(); +} diff --git a/samples/embedder/timer.dart b/samples/embedder/timer.dart new file mode 100644 index 00000000000..635668127d0 --- /dev/null +++ b/samples/embedder/timer.dart @@ -0,0 +1,33 @@ +import 'dart:async'; + +void main() { + throw 'Unimplemented'; +} + +var _tickCount = 0; +Timer? _timer; + +@pragma('vm:entry-point', 'call') +void startTimer(int millis) { + if (_timer == null) { + final period = Duration(milliseconds: millis); + _timer = Timer.periodic(period, (_) { + _tickCount++; + }); + print('Started timer with period $period'); + } +} + +@pragma('vm:entry-point', 'call') +void stopTimer() { + _timer?.cancel(); + _timer = null; +} + +@pragma('vm:entry-point', 'call') +void resetTimer() { + _tickCount = 0; +} + +@pragma('vm:entry-point', 'get') +int get ticks => _tickCount; diff --git a/tests/standalone/embedder_samples_test.dart b/tests/standalone/embedder_samples_test.dart index f7a396d4525..71e56a5a0c8 100644 --- a/tests/standalone/embedder_samples_test.dart +++ b/tests/standalone/embedder_samples_test.dart @@ -2,17 +2,26 @@ import 'dart:io'; import 'package:expect/expect.dart'; -void main() { - final executable = Platform.executable; - final outDir = executable.substring(0, executable.lastIndexOf('dart')); - - final runKernelExecutable = outDir + 'run_kernel'; - - final result = Process.runSync(runKernelExecutable, []); +void checkSample( + String binary, + List args, { + bool skipIfNotBuilt = false, +}) { + if (Platform.isWindows) { + binary = '$binary.exe'; + } + if (!File(binary).existsSync()) { + if (skipIfNotBuilt) { + return; + } else { + Expect.fail('Binary $binary does not exist!'); + } + } + final result = Process.runSync(binary, args); Expect.equals( 0, result.exitCode, - 'process failed:\n' + 'process $binary failed:\n' ' exit code: ${result.exitCode}\n' ' -- stdout --\n' '${result.stdout}' @@ -21,3 +30,33 @@ void main() { ' ------------', ); } + +void main() { + final executable = Platform.executable; + final out = executable.substring(0, executable.lastIndexOf('dart') - 1); + + checkSample('$out/run_main_kernel', ['$out/gen/hello_kernel.dart.snapshot']); + checkSample('$out/run_two_programs_kernel', [ + '$out/gen/program1_kernel.dart.snapshot', + '$out/gen/program2_kernel.dart.snapshot', + ]); + checkSample('$out/run_timer_kernel', ['$out/gen/timer_kernel.dart.snapshot']); + checkSample('$out/run_timer_async_kernel', [ + '$out/gen/timer_kernel.dart.snapshot', + ]); + + // AOT Samples aren't built on some platforms. + checkSample('$out/run_main_aot', [ + '$out/hello_aot.snapshot', + ], skipIfNotBuilt: true); + checkSample('$out/run_two_programs_aot', [ + '$out/program1_aot.snapshot', + '$out/program2_aot.snapshot', + ], skipIfNotBuilt: true); + checkSample('$out/run_timer_aot', [ + '$out/timer_aot.snapshot', + ], skipIfNotBuilt: true); + checkSample('$out/run_timer_async_aot', [ + '$out/timer_aot.snapshot', + ], skipIfNotBuilt: true); +}