Skip to content

Instantly share code, notes, and snippets.

@chancila
Created May 27, 2019 08:19
Show Gist options
  • Save chancila/f0b4e1b37f44fa780427711e286abdad to your computer and use it in GitHub Desktop.
Save chancila/f0b4e1b37f44fa780427711e286abdad to your computer and use it in GitHub Desktop.
#include <boost/filesystem.hpp>
#include <bin/dart_io_api.h>
#include <cstdio>
#include <dart_api.h>
#include <string_view>
#include <type_traits>
using namespace std::literals;
// These symbols are provided by our libdart_snapshot archive. We could
// have loaded them using file I/O.
extern "C" {
// this is the core vm data
extern uint8_t kDartVmSnapshotData[];
extern uint8_t kDartVmSnapshotInstructions[];
// this is a snapshot of the platform, we actually don't use this in this
// example since we'll be loading everything from dart dill images. Snapshots
// are images of a running isolate and should typically load faster but for
// the sake of the example we'll use the dills below.
extern uint8_t kDartCoreIsolateSnapshotData[];
extern uint8_t kDartCoreIsolateSnapshotInstructions[];
// The dart sdk compiles the entire base library set as the platform dill.
// This will contain all the dart:* libraries you can find on the dart
// documentation page. This will serve as the base image for our main
// isolate.
extern const uint8_t kPlatformStrongDill[];
extern intptr_t kPlatformStrongDillSize;
// The kernel compilation service dill.
extern const uint8_t kKernelServiceDill[];
extern intptr_t kKernelServiceDillSize;
}
#define ASSERT_NOT_ERROR(e) assert(!Dart_IsError(e))
void embedder_print(Dart_NativeArguments args) {
const char* chars;
Dart_Handle str = Dart_GetNativeArgument(args, 0);
Dart_Handle result = Dart_StringToCString(str, &chars);
if (Dart_IsError(result)) {
Dart_PropagateError(result);
}
printf("%s\n", chars);
}
void embedder_notify_server_state(Dart_NativeArguments args) {
// NO-OP.
}
void embedder_shutdown(Dart_NativeArguments args) {
// NO-OP.
}
Dart_NativeFunction embedder_entry_resolver(Dart_Handle name,
int num_of_arguments,
bool* auto_setup_scope) {
// This function resolves a few dart native functions needed by
// the core libraries in the platform. The dart api exposes
// native resolves for the dart IO library.
//
// As an embedder we seem to only need to expose the VMServiceIO
// functions. The Builtin_PrintString is only here because I'm
// using the builtin utility library that comes with the official dart
// executable, I will probably get rid of it in the future once I have my
// own runtime libraries designed for my needs. The ideal solution here is
// to have a platform dill without some of the extras that the dart sdk
// generates for us.
const char* function_name = NULL;
Dart_Handle err = Dart_StringToCString(name, &function_name);
assert(!Dart_IsError(err));
if (function_name == "Builtin_PrintString"sv) {
return embedder_print;
} else if (function_name == "VMServiceIO_NotifyServerState"sv) {
return embedder_notify_server_state;
} else if (function_name == "VMServiceIO_Shutdown"sv) {
return embedder_shutdown;
}
return dart::bin::LookupIONative(name, num_of_arguments, auto_setup_scope);
}
const uint8_t* embedder_entry_symbol(Dart_NativeFunction nf) {
// this isn't super critical, possibly useful for debugging. Our 3 functions
// we provide would not have native symbol names
return dart::bin::LookupIONativeSymbol(nf);
}
Dart_Handle embedder_library_tag_handler(Dart_LibraryTag tag,
Dart_Handle library,
Dart_Handle url) {
// Since we don't use any custom packages or non-platform
// libraries we don't need to worry about this.
printf("library tag request\n");
return nullptr;
}
Dart_Handle to_dart(const char* s) {
auto result = Dart_NewStringFromCString(s);
ASSERT_NOT_ERROR(result);
return result;
}
template <typename T, std::enable_if_t<std::is_integral_v<T>, bool> = false>
Dart_Handle to_dart(T value) {
if constexpr (std::is_same_v<std::remove_cv_t<T>, bool>) {
if (value) {
return Dart_True();
} else {
return Dart_False();
}
} else if (std::is_signed_v<T>) {
return Dart_NewInteger(value);
} else {
return Dart_NewIntegerFromUint64(value);
}
}
void initialize_internal(Dart_Handle internal_lib, Dart_Handle builtin_lib) {
// Initialize the internal library print function, as a standalone embedded
// we probably don't want to use the builtin library, it seems to be
// specific to the standalone dart vm that comes with the sdk, but we can be
// lazy here and use it, the Builtin_PrintString we resolve above is called
// by the builtin lib print closure.
Dart_Handle print =
Dart_Invoke(builtin_lib, to_dart("_getPrintClosure"), 0, NULL);
ASSERT_NOT_ERROR(print);
Dart_Handle result =
Dart_SetField(internal_lib, to_dart("_printClosure"), print);
ASSERT_NOT_ERROR(result);
}
void initialize_core(Dart_Handle core_lib,
Dart_Handle io_lib,
bool is_service_isolate) {
// I'm not really sure why dart doesn't set this up for the service
// isolate.
if (!is_service_isolate) {
Dart_Handle uri_base =
Dart_Invoke(io_lib, to_dart("_getUriBaseClosure"), 0, NULL);
ASSERT_NOT_ERROR(uri_base);
Dart_Handle result =
Dart_SetField(core_lib, to_dart("_uriBaseClosure"), uri_base);
ASSERT_NOT_ERROR(result);
}
}
void initialize_async(Dart_Handle async_lib, Dart_Handle isolate_lib) {
// this sets up the async library event loop, it uses the default isolate
// runloop. As en embedder if you had your own runloop you would instead
// hook up your own event loop library here.
Dart_Handle schedule_immediate_closure = Dart_Invoke(
isolate_lib, to_dart("_getIsolateScheduleImmediateClosure"), 0, NULL);
ASSERT_NOT_ERROR(schedule_immediate_closure);
ASSERT_NOT_ERROR(Dart_Invoke(async_lib,
to_dart("_setScheduleImmediateClosure"),
1,
&schedule_immediate_closure));
}
void initialize_dart_libs(bool is_service_isolate = false) {
constexpr auto get_lib = [](const char* n) {
auto result = Dart_LookupLibrary(Dart_NewStringFromCString(n));
ASSERT_NOT_ERROR(result);
return result;
};
// this function loads and configures the dart platform libraries.
// this is a minimal set of stuff I came up with after looking in the
// dart sdk source and flutter runtime source. It would be really
// nice if this was somehow exposed by the dart api so that embedders
// don't have to glue up this code manually.
auto core_lib = get_lib("dart:core");
auto async_lib = get_lib("dart:async");
auto isolate_lib = get_lib("dart:isolate");
auto internal_lib = get_lib("dart:_internal");
auto builtin_lib = get_lib("dart:_builtin");
auto io_lib = get_lib("dart:io");
// TODO we don't bootstrap dart:cli, doesn't seem to be needed yet
// the builtin and io library have some native functions, we hook up
// our resolver to them.
ASSERT_NOT_ERROR(Dart_SetNativeResolver(
builtin_lib, &embedder_entry_resolver, &embedder_entry_symbol));
ASSERT_NOT_ERROR(Dart_SetNativeResolver(
io_lib, &embedder_entry_resolver, &embedder_entry_symbol));
ASSERT_NOT_ERROR(Dart_FinalizeLoading(false));
initialize_internal(internal_lib, builtin_lib);
initialize_core(core_lib, io_lib, is_service_isolate);
initialize_async(async_lib, isolate_lib);
// These 2 calls hook up the isolate library and io library to the core vm
// To fully hook up the core vm library you can look at all the available
// hooks in DART_SDK/runtime/lib/internal_patch.dart
ASSERT_NOT_ERROR(Dart_Invoke(isolate_lib, to_dart("_setupHooks"), 0, NULL));
ASSERT_NOT_ERROR(Dart_Invoke(io_lib, to_dart("_setupHooks"), 0, NULL));
}
// This function services isolate creation requests from the vm.
// Here we can configure the kernel and service isolates. If we
// were to use the dart:isolate package we'd also get creation
// requests if we spawn our own isolates here.
Dart_Isolate isolate_create_callback(const char* script_uri,
const char* main,
const char* package_root,
const char* package_config,
Dart_IsolateFlags* flags,
void* callback_data,
char** error) {
if (std::string_view(script_uri) == DART_KERNEL_ISOLATE_NAME) {
// The kernel service dill is in our snapshot archive,
auto result = Dart_CreateIsolateFromKernel(script_uri,
main,
kKernelServiceDill,
kKernelServiceDillSize,
flags,
callback_data,
error);
assert(result);
Dart_EnterScope();
auto script = Dart_LoadScriptFromKernel(kKernelServiceDill,
kKernelServiceDillSize);
ASSERT_NOT_ERROR(script);
// all isolates need the platform setup
initialize_dart_libs();
// I don't think this is needed but I added it to listen if the
// vm does any requests
ASSERT_NOT_ERROR(
Dart_SetLibraryTagHandler(embedder_library_tag_handler));
Dart_ExitScope();
Dart_ExitIsolate();
return result;
}
if (std::string_view(script_uri) == DART_VM_SERVICE_ISOLATE_NAME) {
// make sure to set this to true
flags->load_vmservice_library = true;
// the vm service code is actually in the base platform kernel
auto result = Dart_CreateIsolateFromKernel(script_uri,
main,
kPlatformStrongDill,
kPlatformStrongDillSize,
flags,
callback_data,
error);
Dart_EnterScope();
// all isolates need the platform setup
initialize_dart_libs(true);
// on top of the platform code we need to configure the service library
// most of this code is from VmService::Setup in
// DART_SDK/runtime/bin/vmservice_impl.cc
auto lib = Dart_LookupLibrary(to_dart("dart:vmservice_io"));
ASSERT_NOT_ERROR(lib);
ASSERT_NOT_ERROR(Dart_SetRootLibrary(lib));
// The service library has 2 native symbols it required for startup
// We hook them up in our resolver even though they do nothing.
ASSERT_NOT_ERROR(Dart_SetNativeResolver(
lib, &embedder_entry_resolver, &embedder_entry_symbol));
// I don't think this is needed but I added it to listen if the
// vm does any requests
ASSERT_NOT_ERROR(
Dart_SetLibraryTagHandler(embedder_library_tag_handler));
Dart_EnterScope();
Dart_ExitIsolate();
assert(!Dart_IsolateMakeRunnable(result));
Dart_EnterIsolate(result);
Dart_EnterScope();
lib = Dart_RootLibrary();
ASSERT_NOT_ERROR(lib);
ASSERT_NOT_ERROR(
Dart_SetField(lib, to_dart("_ip"), to_dart("127.0.0.1")));
ASSERT_NOT_ERROR(Dart_SetField(lib, to_dart("_port"), to_dart(12345)));
ASSERT_NOT_ERROR(
Dart_SetField(lib, to_dart("_autoStart"), to_dart(true)));
ASSERT_NOT_ERROR(
Dart_SetField(lib, to_dart("_originCheckDisabled"), to_dart(true)));
ASSERT_NOT_ERROR(
Dart_SetField(lib, to_dart("_authCodesDisabled"), to_dart(true)));
ASSERT_NOT_ERROR(
Dart_SetField(lib, to_dart("_isWindows"), to_dart(false)));
// we don't really need this, I belive it's used when creating
// deterministic isolate snapshots. if (deterministic) {
// result = Dart_SetField(library,
// DartUtils::NewString("_deterministic"),
// Dart_True());
// SHUTDOWN_ON_ERROR(result);
// }
auto io_lib = Dart_LookupLibrary(to_dart("dart:io"));
ASSERT_NOT_ERROR(io_lib);
auto signal_watch =
Dart_Invoke(io_lib, to_dart("_getWatchSignalInternal"), 0, nullptr);
ASSERT_NOT_ERROR(signal_watch);
ASSERT_NOT_ERROR(
Dart_SetField(lib, to_dart("_signalWatch"), signal_watch));
Dart_ExitScope();
Dart_ExitIsolate();
return result;
}
return nullptr;
}
// we ignore all the shutdown stuff
void isolate_shutdown_callback(void* callback_data) {}
void isolate_cleanup_callback(void* callback_data) {}
void thread_exit_callback() {}
// this callback returns a tar of the observatory resources. I hardcoded
// it to the one in the build output. Note that there is one in the root
// of the build tree however it's compressed. The dart callback expects
// a tar blob to be returned.
Dart_Handle get_vm_service_assets_callback() {
std::string m;
boost::filesystem::load_string_file(
"/Users/chancila/src/dart-sdk/sdk/xcodebuild/DebugX64/"
"gen/runtime/observatory/observatory_archive.tar",
m);
Dart_Handle array = Dart_NewTypedData(Dart_TypedData_kUint8, m.size());
Dart_TypedData_Type td_type;
void* td_data;
intptr_t td_len;
Dart_Handle result =
Dart_TypedDataAcquireData(array, &td_type, &td_data, &td_len);
ASSERT_NOT_ERROR(result);
assert(td_type == Dart_TypedData_kUint8);
assert(td_len == m.size());
assert(td_data != NULL);
memmove(td_data, m.data(), td_len);
result = Dart_TypedDataReleaseData(array);
ASSERT_NOT_ERROR(result);
return array;
}
int main(int argc, char* args[]) {
dart::bin::BootstrapDartIo();
Dart_InitializeParams params = {};
params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION;
params.vm_snapshot_data = kDartVmSnapshotData;
params.vm_snapshot_instructions = kDartVmSnapshotInstructions;
params.create = isolate_create_callback;
params.shutdown = isolate_shutdown_callback;
params.cleanup = isolate_cleanup_callback;
params.thread_exit = thread_exit_callback;
// don't need these for hello world
// I imagine they are called once we hit any kind of
// file IO. They should be straightforward to hook up.
// Dart_FileOpenCallback file_open;
// Dart_FileReadCallback file_read;
// Dart_FileWriteCallback file_write;
// Dart_FileCloseCallback file_close;
params.entropy_source = dart::bin::GetEntropy;
params.get_service_assets = get_vm_service_assets_callback;
params.start_kernel_isolate = true;
const char* dart_args[] = {
"--new_gen_semi_max_size=32",
"--new_gen_growth_factor=4",
"--pause_isolates_on_start",
};
// set to 3 if you to pause the isolate to debug it
auto flag_result = Dart_SetVMFlags(3, dart_args);
assert(!flag_result);
// Horray, dart is initalized
auto init_result = Dart_Initialize(&params);
assert(!init_result);
// This gives the kernel compilation service the core libraries to compile
// our dart code with.
Dart_SetDartLibrarySourcesKernel(kPlatformStrongDill,
kPlatformStrongDillSize);
Dart_IsolateFlags flags;
Dart_IsolateFlagsInitialize(&flags);
// we create our main isolate, this will load and run our script. The script
// is located in the same directory as our executable. Mine for reference:
// import 'dart:io';
//
// void main() {
// stdout.writeln("Hello World");
// }
//
// We bootstrap the isolate with the core platform.
char* error;
auto platform_isolate =
Dart_CreateIsolateFromKernel("main.dart",
"main",
kPlatformStrongDill,
kPlatformStrongDillSize,
&flags,
nullptr,
&error);
assert(platform_isolate);
// After a CreateIsolate function succeedes it makes the result the current
// isolate. We enter a scope since we need to do a bunch of tweaking.
Dart_EnterScope();
// I don't think this is needed but I added it to listen if the
// vm does any requests, we don't import any non platform code.
ASSERT_NOT_ERROR(Dart_SetLibraryTagHandler(embedder_library_tag_handler));
// We initialize the core platform libraries
initialize_dart_libs();
// We compile our main script, this returns a dill file. This call makes
// a request to the kernel service isolate to do the compilation.
auto compile_result = Dart_CompileToKernel("main.dart",
kPlatformStrongDill,
kPlatformStrongDillSize,
true,
nullptr);
assert(!compile_result.status);
// We call Dart_LoadScriptFromKernel which loads and sets the root library
// from the dill file. The root library is the special library that has main
// defined. Since we just compiled a single dart file there are no other
// libraries in the dill.
ASSERT_NOT_ERROR(Dart_LoadScriptFromKernel(compile_result.kernel,
compile_result.kernel_size));
Dart_ExitScope();
Dart_ExitIsolate();
// We exit the isolate in order to make it runnable. This marks the isolate
// as being fully loaded.
assert(!Dart_IsolateMakeRunnable(platform_isolate));
// We re-enter and run the isolate.
Dart_EnterIsolate(platform_isolate);
Dart_EnterScope();
Dart_Handle root_lib = Dart_RootLibrary();
ASSERT_NOT_ERROR(root_lib);
// This grabs the main function defined in our main.dart
Dart_Handle main_closure =
Dart_GetField(root_lib, Dart_NewStringFromCString("main"));
ASSERT_NOT_ERROR(main_closure);
assert(Dart_IsClosure(main_closure));
// To start up the isolate we need to call _startMainIsolate from the
// isolate library and pass it our main function and any arguments we
// want to forward to it. I just passed no extra arguments since I'm using the
// void main() variation of the signature.
const intptr_t kNumIsolateArgs = 2;
Dart_Handle isolate_args[kNumIsolateArgs];
isolate_args[0] = main_closure; // entryPoint
isolate_args[1] = Dart_Null();
auto isolate_lib =
Dart_LookupLibrary(Dart_NewStringFromCString("dart:isolate"));
auto invoke_result =
Dart_Invoke(isolate_lib,
Dart_NewStringFromCString("_startMainIsolate"),
2,
isolate_args);
ASSERT_NOT_ERROR(invoke_result);
// Our isolate is ready to run
ASSERT_NOT_ERROR(Dart_RunLoop());
// TODO all sorts of cleanup
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment