[ VM / CLI ] Run DartDev isolate from snapshot when possible

This change tries to run from dartdev.dart.snapshot and falls back to
running from dartdev.dill if incompatible VM flags are provided.

Fixes https://github.com/dart-lang/sdk/issues/43969

Performance results:

dart test.dart (no CLI isolate): 0.167s
dart run test (from snapshot):   0.208s
dart run test (from kernel):     0.326s

TEST=pkg/dartdev/test/load_from_dill_test.dart

Change-Id: I3195886b86676580ef2a0221f0284328964ef061
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/178300
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
diff --git a/pkg/dartdev/test/load_from_dill_test.dart b/pkg/dartdev/test/load_from_dill_test.dart
new file mode 100644
index 0000000..6de6c6c
--- /dev/null
+++ b/pkg/dartdev/test/load_from_dill_test.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, 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 'dart:io';
+
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+  TestProject p;
+
+  tearDown(() => p?.dispose());
+
+  test("Fallback to dartdev.dill from dartdev.dart.snapshot for 'Hello World'",
+      () {
+    p = project(mainSrc: "void main() { print('Hello World'); }");
+    // The DartDev snapshot includes the --use-bare-instructions flag. If
+    // --no-use-bare-instructions is passed, the VM will fail to load the
+    // snapshot and should fall back to using the DartDev dill file.
+    ProcessResult result =
+        p.runSync(['--no-use-bare-instructions', 'run', p.relativeFilePath]);
+
+    expect(result.stdout, contains('Hello World'));
+    expect(result.stderr, isEmpty);
+    expect(result.exitCode, 0);
+  });
+}
diff --git a/runtime/bin/dartdev_isolate.cc b/runtime/bin/dartdev_isolate.cc
index df01614..468ef64 100644
--- a/runtime/bin/dartdev_isolate.cc
+++ b/runtime/bin/dartdev_isolate.cc
@@ -57,13 +57,14 @@
           (strncmp(script_uri, "google3://", 10) != 0));
 }
 
-Utils::CStringUniquePtr DartDevIsolate::TryResolveDartDevSnapshotPath() {
+Utils::CStringUniquePtr DartDevIsolate::TryResolveArtifactPath(
+    const char* filename) {
   // |dir_prefix| includes the last path seperator.
   auto dir_prefix = EXEUtils::GetDirectoryPrefixFromExeName();
 
   // First assume we're in dart-sdk/bin.
   char* snapshot_path =
-      Utils::SCreate("%ssnapshots/dartdev.dill", dir_prefix.get());
+      Utils::SCreate("%ssnapshots/%s", dir_prefix.get(), filename);
   if (File::Exists(nullptr, snapshot_path)) {
     return Utils::CreateCStringUniquePtr(snapshot_path);
   }
@@ -71,7 +72,7 @@
 
   // If we're not in dart-sdk/bin, we might be in one of the $SDK/out/*
   // directories. Try to use a snapshot from a previously built SDK.
-  snapshot_path = Utils::SCreate("%sdartdev.dill", dir_prefix.get());
+  snapshot_path = Utils::SCreate("%s%s", dir_prefix.get(), filename);
   if (File::Exists(nullptr, snapshot_path)) {
     return Utils::CreateCStringUniquePtr(snapshot_path);
   }
@@ -79,6 +80,14 @@
   return Utils::CreateCStringUniquePtr(nullptr);
 }
 
+Utils::CStringUniquePtr DartDevIsolate::TryResolveDartDevSnapshotPath() {
+  return TryResolveArtifactPath("dartdev.dart.snapshot");
+}
+
+Utils::CStringUniquePtr DartDevIsolate::TryResolveDartDevKernelPath() {
+  return TryResolveArtifactPath("dartdev.dill");
+}
+
 void DartDevIsolate::DartDevRunner::Run(
     Dart_IsolateGroupCreateCallback create_isolate,
     char** packages_file,
diff --git a/runtime/bin/dartdev_isolate.h b/runtime/bin/dartdev_isolate.h
index 8fd0bd9..a25664c 100644
--- a/runtime/bin/dartdev_isolate.h
+++ b/runtime/bin/dartdev_isolate.h
@@ -41,8 +41,10 @@
 
   static bool should_run_dart_dev() { return should_run_dart_dev_; }
 
-  // Attempts to find the DartDev snapshot. If the snapshot cannot be found,
-  // the VM will shutdown.
+  // Attempts to find the path of the DartDev kernel file.
+  static Utils::CStringUniquePtr TryResolveDartDevKernelPath();
+
+  // Attempts to find the path of the DartDev snapshot.
   static Utils::CStringUniquePtr TryResolveDartDevSnapshotPath();
 
   // Starts a DartDev instance in a new isolate and runs it to completion.
@@ -89,6 +91,8 @@
   };
 
  private:
+  static Utils::CStringUniquePtr TryResolveArtifactPath(const char* filename);
+
   static DartDevRunner runner_;
   static bool should_run_dart_dev_;
 
diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc
index 7299ade..cdffae9 100644
--- a/runtime/bin/main.cc
+++ b/runtime/bin/main.cc
@@ -584,40 +584,76 @@
   int64_t start = Dart_TimelineGetMicros();
 
   auto dartdev_path = DartDevIsolate::TryResolveDartDevSnapshotPath();
-  if (dartdev_path == nullptr) {
-    *error = Utils::StrDup("Unable to find DartDev snapshot");
-    return nullptr;
-  }
 
+  Dart_Isolate isolate = nullptr;
   const uint8_t* isolate_snapshot_data = core_isolate_snapshot_data;
   const uint8_t* isolate_snapshot_instructions =
       core_isolate_snapshot_instructions;
+  IsolateGroupData* isolate_group_data = nullptr;
+  IsolateData* isolate_data = nullptr;
 
-  auto isolate_group_data =
-      new IsolateGroupData(DART_DEV_ISOLATE_NAME, packages_config, nullptr,
-                           /*isolate_run_app_snapshot*/ false);
-  uint8_t* application_kernel_buffer = NULL;
-  intptr_t application_kernel_buffer_size = 0;
-  dfe.ReadScript(dartdev_path.get(), &application_kernel_buffer,
-                 &application_kernel_buffer_size);
-  isolate_group_data->SetKernelBufferNewlyOwned(application_kernel_buffer,
-                                                application_kernel_buffer_size);
+  AppSnapshot* app_snapshot;
+  bool isolate_run_app_snapshot = true;
+  if (dartdev_path.get() != nullptr &&
+      (app_snapshot = Snapshot::TryReadAppSnapshot(dartdev_path.get())) !=
+          nullptr) {
+    const uint8_t* isolate_snapshot_data = NULL;
+    const uint8_t* isolate_snapshot_instructions = NULL;
+    const uint8_t* ignore_vm_snapshot_data;
+    const uint8_t* ignore_vm_snapshot_instructions;
+    app_snapshot->SetBuffers(
+        &ignore_vm_snapshot_data, &ignore_vm_snapshot_instructions,
+        &isolate_snapshot_data, &isolate_snapshot_instructions);
+    isolate_group_data =
+        new IsolateGroupData(dartdev_path.get(), packages_config, app_snapshot,
+                             isolate_run_app_snapshot);
+    isolate_data = new IsolateData(isolate_group_data);
+    isolate = Dart_CreateIsolateGroup(
+        DART_DEV_ISOLATE_NAME, DART_DEV_ISOLATE_NAME, isolate_snapshot_data,
+        isolate_snapshot_instructions, flags, isolate_group_data, isolate_data,
+        error);
+  }
 
-  auto isolate_data = new IsolateData(isolate_group_data);
-  Dart_Isolate isolate = nullptr;
-  isolate = Dart_CreateIsolateGroup(
-      DART_DEV_ISOLATE_NAME, DART_DEV_ISOLATE_NAME, isolate_snapshot_data,
-      isolate_snapshot_instructions, flags, isolate_group_data, isolate_data,
-      error);
+  if (isolate == nullptr) {
+    isolate_run_app_snapshot = false;
+    dartdev_path = DartDevIsolate::TryResolveDartDevKernelPath();
+    // Clear error from app snapshot and retry from kernel.
+    free(*error);
+    *error = nullptr;
+
+    if (app_snapshot != nullptr) {
+      delete app_snapshot;
+    }
+
+    if (dartdev_path.get() != nullptr) {
+      isolate_group_data =
+          new IsolateGroupData(DART_DEV_ISOLATE_NAME, packages_config, nullptr,
+                               isolate_run_app_snapshot);
+      uint8_t* application_kernel_buffer = NULL;
+      intptr_t application_kernel_buffer_size = 0;
+      dfe.ReadScript(dartdev_path.get(), &application_kernel_buffer,
+                     &application_kernel_buffer_size);
+      isolate_group_data->SetKernelBufferNewlyOwned(
+          application_kernel_buffer, application_kernel_buffer_size);
+
+      isolate_data = new IsolateData(isolate_group_data);
+      isolate = Dart_CreateIsolateGroup(
+          DART_DEV_ISOLATE_NAME, DART_DEV_ISOLATE_NAME, isolate_snapshot_data,
+          isolate_snapshot_instructions, flags, isolate_group_data,
+          isolate_data, error);
+    }
+  }
 
   Dart_Isolate created_isolate = nullptr;
   if (isolate == nullptr) {
+    Syslog::PrintErr("Failed to start the Dart CLI isolate\n");
     delete isolate_data;
     delete isolate_group_data;
+    return nullptr;
   } else {
     created_isolate = IsolateSetupHelper(
         isolate, false, DART_DEV_ISOLATE_NAME, packages_config,
-        /*isolate_run_app_snapshot*/ false, flags, error, exit_code);
+        isolate_run_app_snapshot, flags, error, exit_code);
   }
   int64_t end = Dart_TimelineGetMicros();
   Dart_TimelineEvent("CreateAndSetupDartDevIsolate", start, end,
diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn
index efa745a..4214311 100644
--- a/sdk/BUILD.gn
+++ b/sdk/BUILD.gn
@@ -48,6 +48,7 @@
 # ........dart2js.dart.snapshot
 # ........dart2native.dart.snapshot (if not on ia32)
 # ........dartanalyzer.dart.snapshot
+# ........dartdev.dart.snapshot
 # ........dartdev.dill
 # ........dartdevc.dart.snapshot
 # ........dartdoc.dart.snapshot
@@ -118,6 +119,10 @@
     "../utils/dartanalyzer:generate_dartanalyzer_snapshot",
   ],
   [
+    "dartdev",
+    "../utils/dartdev:dartdev",
+  ],
+  [
     "dartdoc",
     "../utils/dartdoc",
   ],
@@ -163,6 +168,10 @@
     "../utils/dartanalyzer:generate_dartanalyzer_snapshot",
   ],
   [
+    "dartdev",
+    "../utils/dartdev:generate_dartdev_snapshot",
+  ],
+  [
     "dartdevc",
     "../utils/dartdevc",
   ],
diff --git a/utils/dartdev/BUILD.gn b/utils/dartdev/BUILD.gn
index 02823ae..c7d8c12 100644
--- a/utils/dartdev/BUILD.gn
+++ b/utils/dartdev/BUILD.gn
@@ -12,7 +12,10 @@
                             "list lines")
 
 group("dartdev") {
-  public_deps = [ ":copy_dartdev_kernel" ]
+  public_deps = [
+    ":copy_dartdev_kernel",
+    ":copy_dartdev_snapshot",
+  ]
 }
 
 copy("copy_dartdev_kernel") {
@@ -30,3 +33,18 @@
   inputs = dartdev_files
   output = "$root_gen_dir/dartdev.dill"
 }
+
+copy("copy_dartdev_snapshot") {
+  visibility = [ ":dartdev" ]
+  public_deps = [ ":generate_dartdev_snapshot" ]
+  sources = [ "$root_gen_dir/dartdev.dart.snapshot" ]
+  outputs = [ "$root_out_dir/dartdev.dart.snapshot" ]
+}
+
+application_snapshot("generate_dartdev_snapshot") {
+  main_dart = "../../pkg/dartdev/bin/dartdev.dart"
+  training_args = [ "--help" ]
+  deps = [ "../dds:dds" ]
+  inputs = dartdev_files
+  output = "$root_gen_dir/dartdev.dart.snapshot"
+}