Created
May 27, 2019 08:19
-
-
Save chancila/f0b4e1b37f44fa780427711e286abdad to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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(¶ms); | |
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