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 <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Ivan Inozemtsev
2025-01-28 03:53:40 -08:00
committed by Commit Queue
parent 79df09999e
commit 6e33c95463
31 changed files with 1634 additions and 197 deletions
+7 -1
View File
@@ -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" ]
+1
View File
@@ -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']):
+4 -1
View File
@@ -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",
+109
View File
@@ -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",
]
}
}
+48
View File
@@ -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.
+76
View File
@@ -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 <cstdint>
#include <memory>
#include <vector>
#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
+349
View File
@@ -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 <memory>
#include <utility>
#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<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(&params, 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);
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<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);
}
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
+134
View File
@@ -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 <memory>
#include <queue>
#include <unordered_map>
#include <vector>
#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<DartEngine_SnapshotData> owned_snapshots_;
// Loaded dynamic libraries. Dynamic libraries are loaded
// when reading AOT snapshots.
std::vector<void*> loaded_libraries_;
// All isolates, started via Engine::StartIsolate.
std::vector<Dart_Isolate> isolates_;
// Stores per-isolate mutexes, used by Engine::LockIsolate/UnlockIsolate.
std::unordered_map<Dart_Isolate, Mutex> mutexes_;
// Default scheduler.
DartEngine_MessageScheduler default_scheduler_;
// Per-isolate message schedulers.
std::unordered_map<Dart_Isolate, DartEngine_MessageScheduler> 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_
+181
View File
@@ -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_
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
+2
View File
@@ -46,6 +46,8 @@ List<String> 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',
+129 -10
View File
@@ -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"
}
+31 -14
View File
@@ -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`.
+7 -6
View File
@@ -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<String>? args) {
final greetee = args?.singleOrNull ?? 'world';
print('Hello, $greetee!');
@pragma('vm:entry-point', 'call')
void main(List<String> args) {
greet(args[0]);
}
void greet(String person) {
print("hi, $person!");
}
+94
View File
@@ -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 <functional>
#include <iostream>
#include <string>
#include <string_view>
#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 <typename T>
inline T WithIsolate(Dart_Isolate isolate, std::function<T()> body) {
DartEngine_AcquireIsolate(isolate);
Dart_EnterScope();
T result = body();
Dart_ExitScope();
DartEngine_ReleaseIsolate();
return result;
}
inline void WithIsolate(Dart_Isolate isolate, std::function<void()> body) {
DartEngine_AcquireIsolate(isolate);
Dart_EnterScope();
body();
Dart_ExitScope();
DartEngine_ReleaseIsolate();
}
#endif /* SAMPLES_EMBEDDER_HELPERS_H_ */
+8
View File
@@ -0,0 +1,8 @@
void main(List<String> args) {
throw 'Unimplemented';
}
@pragma('vm:entry-point', 'call')
String getValue() {
return 'program1';
}
+8
View File
@@ -0,0 +1,8 @@
void main(List<String> args) {
throw 'Unimplemented';
}
@pragma('vm:entry-point', 'call')
void printValue(String value) {
print('program2 received: $value');
}
-157
View File
@@ -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 <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
#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(&params, 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<char*>(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<std::string>& 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<const char*> 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<const uint8_t*>(snapshot_data.c_str()),
snapshot_data.size()),
"Dart_LoadLibraryFromKernel");
// Call main function with args.
std::initializer_list<Dart_Handle> main_args{ToDartStringList({"universe"})};
CheckHandle(Dart_Invoke(library, Dart_NewStringFromCString("main"), 1,
const_cast<Dart_Handle*>(main_args.begin())),
"Dart_Invoke('main')");
Dart_ExitScope();
Dart_ShutdownIsolate();
}
+53
View File
@@ -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 <iostream>
#include "helpers.h"
#include "include/dart_api.h"
#include "include/dart_engine.h"
Dart_Handle ToDartStringList(const std::vector<std::string>& 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<Dart_Handle> main_args{ToDartStringList({"world"})};
CheckError(Dart_Invoke(Dart_RootLibrary(), Dart_NewStringFromCString("main"),
1, const_cast<Dart_Handle*>(main_args.begin())),
"calling main");
Dart_ExitScope();
DartEngine_ReleaseIsolate();
DartEngine_Shutdown();
}
+118
View File
@@ -0,0 +1,118 @@
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
#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<Dart_Handle> args{Dart_NewInteger(millis)};
CheckError(
Dart_Invoke(Dart_RootLibrary(), Dart_NewStringFromCString("startTimer"),
1, const_cast<Dart_Handle*>(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<int64_t>(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<ThreadedMessageHandler*>(context)->Notify(isolate);
}
private:
std::queue<Dart_Isolate> notifications_;
std::condition_variable can_pop_;
std::atomic<bool> 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();
}
+75
View File
@@ -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 <future>
#include <iostream>
#include <thread>
#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<Dart_Handle> args{Dart_NewInteger(millis)};
CheckError(
Dart_Invoke(Dart_RootLibrary(), Dart_NewStringFromCString("startTimer"),
1, const_cast<Dart_Handle*>(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<int64_t>(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();
}
+50
View File
@@ -0,0 +1,50 @@
#include <iostream>
#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<Dart_Handle> args{
Dart_NewStringFromCString(return_value.c_str())};
Dart_Handle invoke_result2 =
Dart_Invoke(Dart_RootLibrary(), Dart_NewStringFromCString("printValue"),
1, const_cast<Dart_Handle*>(args.begin()));
CheckError(invoke_result2);
Dart_ExitScope();
DartEngine_ReleaseIsolate();
DartEngine_Shutdown();
}
+33
View File
@@ -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;
+47 -8
View File
@@ -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<String> 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);
}