Standalone Dart runtime library for embedders

For now we use a static library, but I am planning to add support for a shared library in a follow-up CL.

Tested: locally on macOS and Linux, also ci should at least build samples.
Change-Id: I277239359de226c56633884f350b22a030759ab6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392640
Commit-Queue: Ivan Inozemtsev <iinozemtsev@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index d649296..72a56bd 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -29,9 +29,14 @@
     ":dartanalyzer",
     ":ddc",
     ":runtime",
+    ":samples",
   ]
 }
 
+group("samples") {
+  deps = [ "samples/embedder:all" ]
+}
+
 group("runtime") {
   import("runtime/runtime_args.gni")
 
diff --git a/runtime/bin/BUILD.gn b/runtime/bin/BUILD.gn
index c06e619..c4a2388 100644
--- a/runtime/bin/BUILD.gn
+++ b/runtime/bin/BUILD.gn
@@ -1193,3 +1193,32 @@
     }
   }
 }
+
+source_set("dart_embedder_runtime_jit_set") {
+  include_dirs = [
+    "..",
+    "//third_party/boringssl/src/include",
+    "//third_party",
+  ]
+
+  sources = [
+    "dart_embedder_api_impl.cc",
+    "dfe.cc",
+    "dfe.h",
+    "vmservice_impl.cc",
+    "vmservice_impl.h",
+  ]
+
+  deps = [
+    ":dart_io_api",
+    ":dart_kernel_platform_cc",
+    ":libdart_builtin",
+    "..:libdart_jit",
+  ]
+}
+
+static_library("dart_embedder_runtime_jit") {
+  complete_static_lib = true
+  output_name = "dart_embedder_runtime_jit"
+  deps = [ ":dart_embedder_runtime_jit_set" ]
+}
diff --git a/samples/embedder/BUILD.gn b/samples/embedder/BUILD.gn
new file mode 100644
index 0000000..b3a87b3
--- /dev/null
+++ b/samples/embedder/BUILD.gn
@@ -0,0 +1,25 @@
+# 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.
+
+import("../../utils/application_snapshot.gni")
+
+# All samples.
+group("all") {
+  deps = [ ":run_kernel" ]
+}
+
+# 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" ]
+}
+
+# Kernel snapshot of ./hello.dart.
+application_snapshot("hello_kernel") {
+  main_dart = "hello.dart"
+  dart_snapshot_kind = "kernel"
+  training_args = []  # Not used
+}
diff --git a/samples/embedder/README.md b/samples/embedder/README.md
new file mode 100644
index 0000000..c605eea
--- /dev/null
+++ b/samples/embedder/README.md
@@ -0,0 +1,27 @@
+# Dart VM Embedding examples
+
+Examples of using Dart VM and executing Dart code from C++ binaries.
+
+## run_kernel.cc
+
+To run the example:
+
+```sh
+./tools/build.py --no-rbe --mode=release samples/embedder:run_kernel && out/ReleaseX64/run_kernel
+```
+
+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:
+
+```sh
+dart compile kernel --no-link-platform my.dart
+out/ReleaseX64/run_kernel my.dill
+```
+
+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).
diff --git a/samples/embedder/hello.dart b/samples/embedder/hello.dart
new file mode 100644
index 0000000..8101c81
--- /dev/null
+++ b/samples/embedder/hello.dart
@@ -0,0 +1,10 @@
+// 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.
+
+import 'package:collection/collection.dart';
+
+void main(List<String>? args) {
+  final greetee = args?.singleOrNull ?? 'world';
+  print('Hello, $greetee!');
+}
diff --git a/samples/embedder/run_kernel.cc b/samples/embedder/run_kernel.cc
new file mode 100644
index 0000000..3686ffc
--- /dev/null
+++ b/samples/embedder/run_kernel.cc
@@ -0,0 +1,154 @@
+// 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);
+  return std::string(bytes, length);
+}
+
+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.
+  CheckError(dart::embedder::InitOnce(&error), 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_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();
+}