[vm] Runtime option to not use the simulator for simarm64_arm64.

TEST=ci
Cq-Include-Trybots: luci.dart.try:vm-ffi-mac-debug-simarm64_arm64-try,vm-ffi-mac-release-simarm64_arm64-try
Change-Id: Id98d337ce16ba4b434b5c69016f01eec9b85f9d2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/434982
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index e90b911..f7dea8a 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -1429,8 +1429,10 @@
     __ mov(CSP, SP);
 
 #if defined(SIMULATOR_FFI)
-    __ Emit(Instr::kSimulatorFfiRedirectInstruction);
-    ASSERT(branch == R9);
+    if (FLAG_use_simulator) {
+      __ Emit(Instr::kSimulatorFfiRedirectInstruction);
+      ASSERT(branch == R9);
+    }
 #endif
     __ blr(branch);
 
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index 1ee528d..bed546c 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -417,7 +417,9 @@
 #endif
 
 #if defined(SIMULATOR_FFI)
-  __ Emit(Instr::kSimulatorFfiRedirectInstruction);
+  if (FLAG_use_simulator) {
+    __ Emit(Instr::kSimulatorFfiRedirectInstruction);
+  }
 #endif
   __ blr(R9);
 
@@ -469,11 +471,6 @@
 }
 
 void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
-#if defined(USING_SIMULATOR) && !defined(DART_PRECOMPILER)
-  // TODO(37299): FFI is not supported in SIMARM64.
-  // See Simulator::DoDirectedFfiCallback.
-  __ Breakpoint();
-#else
   Label body;
 
   // R9 is volatile and not used for passing any arguments.
@@ -705,7 +702,6 @@
     __ Breakpoint();
   }
 #endif
-#endif  // !defined(HOST_ARCH_ARM64)
 }
 
 void StubCodeCompiler::GenerateDispatchTableNullErrorStub() {
diff --git a/runtime/vm/cpu_arm64.cc b/runtime/vm/cpu_arm64.cc
index b64a2ee..d435bf1 100644
--- a/runtime/vm/cpu_arm64.cc
+++ b/runtime/vm/cpu_arm64.cc
@@ -10,7 +10,6 @@
 
 #include "vm/cpuinfo.h"
 
-#if !defined(USING_SIMULATOR)
 #if defined(DART_HOST_OS_FUCHSIA)
 #include <zircon/syscalls.h>
 #elif defined(DART_HOST_OS_MACOS) || defined(DART_HOST_OS_IOS)
@@ -18,14 +17,19 @@
 #elif defined(DART_HOST_OS_WINDOWS)
 #include <processthreadsapi.h>
 #endif
-#endif
 
 namespace dart {
 
 void CPU::FlushICache(uword start, uword size) {
 #if defined(DART_PRECOMPILED_RUNTIME)
   UNREACHABLE();
-#elif !defined(USING_SIMULATOR)
+#else
+#if defined(USING_SIMULATOR)
+  if (FLAG_use_simulator) {
+    return;
+  }
+#endif
+
   // Nothing to do. Flushing no instructions.
   if (size == 0) {
     return;
diff --git a/runtime/vm/dart_entry.cc b/runtime/vm/dart_entry.cc
index 17d236c..9be9ccc 100644
--- a/runtime/vm/dart_entry.cc
+++ b/runtime/vm/dart_entry.cc
@@ -104,15 +104,20 @@
 
   const uword stub = StubCode::InvokeDartCode().EntryPoint();
 #if defined(USING_SIMULATOR)
-  auto invoke = [&](uword entry_point, uword arguments_descriptor,
-                    uword arguments, Thread* thread) -> uword {
-    return Simulator::Current()->Call(stub, entry_point, arguments_descriptor,
-                                      arguments,
-                                      reinterpret_cast<int64_t>(thread));
-  };
-#else
-  auto invoke = reinterpret_cast<invokestub>(stub);
+  if (FLAG_use_simulator) {
+    auto invoke = [&](uword entry_point, uword arguments_descriptor,
+                      uword arguments, Thread* thread) -> uword {
+      return Simulator::Current()->Call(stub, entry_point, arguments_descriptor,
+                                        arguments,
+                                        reinterpret_cast<int64_t>(thread));
+    };
+    uword result =
+        invoke(entry_point, static_cast<uword>(arguments_descriptor.ptr()),
+               static_cast<uword>(arguments.ptr()), thread);
+    return static_cast<ObjectPtr>(result);
+  }
 #endif
+  auto invoke = reinterpret_cast<invokestub>(stub);
   uword result =
       invoke(entry_point, static_cast<uword>(arguments_descriptor.ptr()),
              static_cast<uword>(arguments.ptr()), thread);
diff --git a/runtime/vm/exceptions.cc b/runtime/vm/exceptions.cc
index d4ee65c..667fcdd 100644
--- a/runtime/vm/exceptions.cc
+++ b/runtime/vm/exceptions.cc
@@ -627,9 +627,12 @@
   // exception object in the kExceptionObjectReg register and the stacktrace
   // object (may be raw null) in the kStackTraceObjectReg register.
 
-  Simulator::Current()->JumpToFrame(program_counter, stack_pointer,
-                                    frame_pointer, thread);
-#else
+  if (FLAG_use_simulator) {
+    Simulator::Current()->JumpToFrame(program_counter, stack_pointer,
+                                      frame_pointer, thread);
+    UNREACHABLE();
+  }
+#endif
 
   // Zero out HWASAN tags from the current stack pointer to the destination.
   //
@@ -677,7 +680,6 @@
   }
   func(program_counter, stack_pointer, frame_pointer, thread);
 
-#endif
   UNREACHABLE();
 }
 
diff --git a/runtime/vm/ffi_callback_metadata.cc b/runtime/vm/ffi_callback_metadata.cc
index 879c967..4e78cd7 100644
--- a/runtime/vm/ffi_callback_metadata.cc
+++ b/runtime/vm/ffi_callback_metadata.cc
@@ -29,11 +29,19 @@
   ASSERT_LESS_OR_EQUAL(VirtualMemory::PageSize(), kPageSize);
 
 #if defined(SIMULATOR_FFI) && defined(HOST_ARCH_ARM64)
-  const uword code_start =
-      reinterpret_cast<uword>(SimulatorFfiCallbackTrampoline);
-  const uword code_end =
-      reinterpret_cast<uword>(SimulatorFfiCallbackTrampolineEnd);
-  const uword page_start = code_start & ~(VirtualMemory::PageSize() - 1);
+  uword code_start, code_end, page_start;
+  if (FLAG_use_simulator) {
+    code_start = reinterpret_cast<uword>(SimulatorFfiCallbackTrampoline);
+    code_end = reinterpret_cast<uword>(SimulatorFfiCallbackTrampolineEnd);
+    page_start = code_start & ~(VirtualMemory::PageSize() - 1);
+  } else {
+    const Code& trampoline_code = StubCode::FfiCallbackTrampoline();
+    code_start = trampoline_code.EntryPoint();
+    code_end = code_start + trampoline_code.Size();
+    page_start = code_start & ~(VirtualMemory::PageSize() - 1);
+    ASSERT_LESS_OR_EQUAL((code_start - page_start) + trampoline_code.Size(),
+                         RXMappingSize());
+  }
 #else
   const Code& trampoline_code = StubCode::FfiCallbackTrampoline();
   const uword code_start = trampoline_code.EntryPoint();
@@ -50,8 +58,7 @@
 
   offset_of_first_trampoline_in_page_ = code_start - page_start;
 
-#if defined(DART_TARGET_OS_FUCHSIA) ||                                         \
-    (defined(SIMULATOR_FFI) && defined(HOST_ARCH_ARM64))
+#if defined(DART_TARGET_OS_FUCHSIA)
   // On Fuchsia we can't currently duplicate pages, so use the first page of
   // trampolines. Store the stub page's metadata in a separately allocated RW
   // page.
@@ -64,6 +71,17 @@
   for (intptr_t i = 0; i < NumCallbackTrampolinesPerPage(); ++i) {
     AddToFreeListLocked(&metadata_entry[i]);
   }
+#elif defined(SIMULATOR_FFI) && defined(HOST_ARCH_ARM64)
+  if (FLAG_use_simulator) {
+    original_metadata_page_ = VirtualMemory::AllocateAligned(
+        MappingSize(), MappingAlignment(), /*is_executable=*/false,
+        /*is_compressed=*/false, "FfiCallbackMetadata::TrampolinePage");
+    MetadataEntry* metadata_entry = reinterpret_cast<MetadataEntry*>(
+        original_metadata_page_->start() + MetadataOffset());
+    for (intptr_t i = 0; i < NumCallbackTrampolinesPerPage(); ++i) {
+      AddToFreeListLocked(&metadata_entry[i]);
+    }
+  }
 #endif  // defined(DART_TARGET_OS_FUCHSIA)
 }
 
@@ -112,12 +130,17 @@
 }
 
 VirtualMemory* FfiCallbackMetadata::AllocateTrampolinePage() {
-#if defined(DART_TARGET_OS_FUCHSIA) ||                                         \
-    (defined(SIMULATOR_FFI) && defined(HOST_ARCH_ARM64))
+#if defined(DART_TARGET_OS_FUCHSIA)
   // TODO(https://dartbug.com/52579): Remove.
   UNREACHABLE();
   return nullptr;
 #else
+#if defined(SIMULATOR_FFI) && defined(HOST_ARCH_ARM64)
+  if (FLAG_use_simulator) {
+    UNREACHABLE();
+    return nullptr;
+  }
+#endif
 
 #if defined(DART_HOST_OS_MACOS) && defined(DART_PRECOMPILED_RUNTIME)
   const bool should_remap_stub_page = true;
@@ -413,8 +436,13 @@
       reinterpret_cast<MetadataEntry*>(start + MetadataOffset());
   const uword index = metadata_entry - metadata_entries;
 #if defined(SIMULATOR_FFI) && defined(HOST_ARCH_ARM64)
-  return reinterpret_cast<uword>(SimulatorFfiCallbackTrampoline) +
-         index * kNativeCallbackTrampolineSize;
+  if (FLAG_use_simulator) {
+    return reinterpret_cast<uword>(SimulatorFfiCallbackTrampoline) +
+           index * kNativeCallbackTrampolineSize;
+  } else {
+    return start + offset_of_first_trampoline_in_page_ +
+           index * kNativeCallbackTrampolineSize;
+  }
 #elif defined(DART_TARGET_OS_FUCHSIA)
   return StubCode::FfiCallbackTrampoline().EntryPoint() +
          index * kNativeCallbackTrampolineSize;
@@ -426,8 +454,7 @@
 
 FfiCallbackMetadata::MetadataEntry*
 FfiCallbackMetadata::MetadataEntryOfTrampoline(Trampoline trampoline) const {
-#if defined(DART_TARGET_OS_FUCHSIA) ||                                         \
-    (defined(SIMULATOR_FFI) && defined(HOST_ARCH_ARM64))
+#if defined(DART_TARGET_OS_FUCHSIA)
   // On Fuchsia the metadata page is separate to the trampoline page.
   // TODO(https://dartbug.com/52579): Remove.
   const uword page_start =
@@ -440,6 +467,27 @@
   MetadataEntry* metadata_etnry_table = reinterpret_cast<MetadataEntry*>(
       original_metadata_page_->start() + MetadataOffset());
   return metadata_etnry_table + index;
+#elif defined(SIMULATOR_FFI) && defined(HOST_ARCH_ARM64)
+  if (FLAG_use_simulator) {
+    const uword page_start =
+        Utils::RoundDown(trampoline - offset_of_first_trampoline_in_page_,
+                         VirtualMemory::PageSize());
+    const uword index =
+        (trampoline - offset_of_first_trampoline_in_page_ - page_start) /
+        kNativeCallbackTrampolineSize;
+    ASSERT(index < NumCallbackTrampolinesPerPage());
+    MetadataEntry* metadata_etnry_table = reinterpret_cast<MetadataEntry*>(
+        original_metadata_page_->start() + MetadataOffset());
+    return metadata_etnry_table + index;
+  } else {
+    const uword start = MappingStart(trampoline);
+    MetadataEntry* metadata_entries =
+        reinterpret_cast<MetadataEntry*>(start + MetadataOffset());
+    const uword index =
+        (trampoline - start - offset_of_first_trampoline_in_page_) /
+        kNativeCallbackTrampolineSize;
+    return &metadata_entries[index];
+  }
 #else
   const uword start = MappingStart(trampoline);
   MetadataEntry* metadata_entries =
diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h
index d560266..b90b3e1 100644
--- a/runtime/vm/flag_list.h
+++ b/runtime/vm/flag_list.h
@@ -249,6 +249,7 @@
     "Throw API error on invalid member access through native API. See "        \
     "entry_point_pragma.md")                                                   \
   C(branch_coverage, false, false, bool, false, "Enable branch coverage")      \
-  C(coverage, false, false, bool, true, "Enable coverage")
+  C(coverage, false, false, bool, true, "Enable coverage")                     \
+  P(use_simulator, bool, true, "Use simulator if available")
 
 #endif  // RUNTIME_VM_FLAG_LIST_H_
diff --git a/runtime/vm/native_entry.cc b/runtime/vm/native_entry.cc
index eba30df..ff78062 100644
--- a/runtime/vm/native_entry.cc
+++ b/runtime/vm/native_entry.cc
@@ -120,9 +120,11 @@
 uword NativeEntry::BootstrapNativeCallWrapperEntry() {
   uword entry = reinterpret_cast<uword>(DRT_BootstrapNativeCall);
 #if defined(USING_SIMULATOR)
-  entry = Simulator::RedirectExternalReference(
-      entry, Simulator::kNativeCallWrapper,
-      NativeEntry::kNumCallWrapperArguments);
+  if (FLAG_use_simulator) {
+    entry = Simulator::RedirectExternalReference(
+        entry, Simulator::kNativeCallWrapper,
+        NativeEntry::kNumCallWrapperArguments);
+  }
 #endif
   return entry;
 }
@@ -164,9 +166,11 @@
 uword NativeEntry::NoScopeNativeCallWrapperEntry() {
   uword entry = reinterpret_cast<uword>(DRT_NoScopeNativeCall);
 #if defined(USING_SIMULATOR)
-  entry = Simulator::RedirectExternalReference(
-      entry, Simulator::kNativeCallWrapper,
-      NativeEntry::kNumCallWrapperArguments);
+  if (FLAG_use_simulator) {
+    entry = Simulator::RedirectExternalReference(
+        entry, Simulator::kNativeCallWrapper,
+        NativeEntry::kNumCallWrapperArguments);
+  }
 #endif
   return entry;
 }
@@ -195,9 +199,11 @@
 uword NativeEntry::AutoScopeNativeCallWrapperEntry() {
   uword entry = reinterpret_cast<uword>(DRT_AutoScopeNativeCall);
 #if defined(USING_SIMULATOR)
-  entry = Simulator::RedirectExternalReference(
-      entry, Simulator::kNativeCallWrapper,
-      NativeEntry::kNumCallWrapperArguments);
+  if (FLAG_use_simulator) {
+    entry = Simulator::RedirectExternalReference(
+        entry, Simulator::kNativeCallWrapper,
+        NativeEntry::kNumCallWrapperArguments);
+  }
 #endif
   return entry;
 }
diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc
index 2961c45..cc9c00b 100644
--- a/runtime/vm/profiler.cc
+++ b/runtime/vm/profiler.cc
@@ -365,7 +365,7 @@
 
 #if defined(USING_SIMULATOR)
   const bool use_simulator_stack_bounds =
-      thread != nullptr && thread->IsExecutingDartCode();
+      FLAG_use_simulator && thread != nullptr && thread->IsExecutingDartCode();
   if (use_simulator_stack_bounds) {
     Isolate* isolate = thread->isolate();
     ASSERT(isolate != nullptr);
@@ -1253,9 +1253,11 @@
   // When running in the simulator, the runtime entry function address
   // (stored as the vm tag) is the address of a redirect function.
   // Attempt to find the real runtime entry function address and use that.
-  uword redirect_vm_tag = Simulator::FunctionForRedirect(vm_tag);
-  if (redirect_vm_tag != 0) {
-    vm_tag = redirect_vm_tag;
+  if (FLAG_use_simulator) {
+    uword redirect_vm_tag = Simulator::FunctionForRedirect(vm_tag);
+    if (redirect_vm_tag != 0) {
+      vm_tag = redirect_vm_tag;
+    }
   }
 #endif
   sample->set_vm_tag(vm_tag);
@@ -1388,18 +1390,19 @@
   uintptr_t fp = state.fp;
   uintptr_t pc = state.pc;
   uintptr_t lr = state.lr;
-#if defined(USING_SIMULATOR)
-  Simulator* simulator = nullptr;
-#endif
 
   if (in_dart_code) {
-// If we're in Dart code, use the Dart stack pointer.
+    // If we're in Dart code, use the Dart stack pointer.
 #if defined(USING_SIMULATOR)
-    simulator = isolate->simulator();
-    sp = simulator->get_register(SPREG);
-    fp = simulator->get_register(FPREG);
-    pc = simulator->get_pc();
-    lr = simulator->get_lr();
+    if (FLAG_use_simulator) {
+      Simulator* simulator = isolate->simulator();
+      sp = simulator->get_register(SPREG);
+      fp = simulator->get_register(FPREG);
+      pc = simulator->get_pc();
+      lr = simulator->get_lr();
+    } else {
+      sp = state.dsp;
+    }
 #else
     sp = state.dsp;
 #endif
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 306f0e3..88cf909 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -124,17 +124,20 @@
   // into the runtime system.
   uword entry = reinterpret_cast<uword>(function());
 #if defined(USING_SIMULATOR)
-  // Redirection to leaf runtime calls supports a maximum of 4 arguments passed
-  // in registers (maximum 2 double arguments for leaf float runtime calls).
-  ASSERT(argument_count() >= 0);
-  ASSERT(!is_leaf() || (!is_float() && (argument_count() <= 4)) ||
-         (argument_count() <= 2));
-  Simulator::CallKind call_kind =
-      is_leaf() ? (is_float() ? Simulator::kLeafFloatRuntimeCall
-                              : Simulator::kLeafRuntimeCall)
-                : Simulator::kRuntimeCall;
-  entry =
-      Simulator::RedirectExternalReference(entry, call_kind, argument_count());
+  if (FLAG_use_simulator) {
+    // Redirection to leaf runtime calls supports a maximum of 4 arguments
+    // passed in registers (maximum 2 double arguments for leaf float runtime
+    // calls).
+    ASSERT(argument_count() >= 0);
+    ASSERT(!is_leaf() || (!is_float() && (argument_count() <= 4)) ||
+           (argument_count() <= 2));
+    Simulator::CallKind call_kind =
+        is_leaf() ? (is_float() ? Simulator::kLeafFloatRuntimeCall
+                                : Simulator::kLeafRuntimeCall)
+                  : Simulator::kRuntimeCall;
+    entry = Simulator::RedirectExternalReference(entry, call_kind,
+                                                 argument_count());
+  }
 #endif
   return entry;
 }
@@ -152,7 +155,7 @@
 
 #if defined(USING_SIMULATOR)
 #define CHECK_SIMULATOR_STACK_OVERFLOW()                                       \
-  if (!OSThread::Current()->HasStackHeadroom()) {                              \
+  if (FLAG_use_simulator && !OSThread::Current()->HasStackHeadroom()) {        \
     Exceptions::ThrowStackOverflow();                                          \
   }
 #else
@@ -3486,16 +3489,17 @@
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 DEFINE_RUNTIME_ENTRY(InterruptOrStackOverflow, 0) {
-#if defined(USING_SIMULATOR)
-  uword stack_pos = Simulator::Current()->get_sp();
-  // If simulator was never called it may return 0 as a value of SPREG.
-  if (stack_pos == 0) {
-    // Use any reasonable value which would not be treated
-    // as stack overflow.
-    stack_pos = thread->saved_stack_limit();
-  }
-#else
   uword stack_pos = OSThread::GetCurrentStackPointer();
+#if defined(USING_SIMULATOR)
+  if (FLAG_use_simulator) {
+    stack_pos = Simulator::Current()->get_sp();
+    // If simulator was never called it may return 0 as a value of SPREG.
+    if (stack_pos == 0) {
+      // Use any reasonable value which would not be treated
+      // as stack overflow.
+      stack_pos = thread->saved_stack_limit();
+    }
+  }
 #endif
   // Always clear the stack overflow flags.  They are meant for this
   // particular stack overflow runtime call and are not meant to
@@ -4381,8 +4385,10 @@
 #if defined(DART_DYNAMIC_MODULES)
   uword entry = reinterpret_cast<uword>(InterpretCall);
 #if defined(USING_SIMULATOR)
-  entry = Simulator::RedirectExternalReference(entry,
-                                               Simulator::kLeafRuntimeCall, 5);
+  if (FLAG_use_simulator) {
+    entry = Simulator::RedirectExternalReference(
+        entry, Simulator::kLeafRuntimeCall, 5);
+  }
 #endif
   return entry;
 #else
diff --git a/runtime/vm/thread.cc b/runtime/vm/thread.cc
index 1178afa..07c5646 100644
--- a/runtime/vm/thread.cc
+++ b/runtime/vm/thread.cc
@@ -533,7 +533,11 @@
 
   ResumeThreadInternal(thread);
 #if defined(USING_SIMULATOR)
-  thread->SetStackLimit(Simulator::Current()->overflow_stack_limit());
+  if (FLAG_use_simulator) {
+    thread->SetStackLimit(Simulator::Current()->overflow_stack_limit());
+  } else {
+    thread->SetStackLimit(OSThread::Current()->overflow_stack_limit());
+  }
 #else
   thread->SetStackLimit(OSThread::Current()->overflow_stack_limit());
 #endif
@@ -583,7 +587,11 @@
   if (Dart::vm_isolate() != nullptr &&
       thread->isolate() != Dart::vm_isolate()) {
 #if defined(USING_SIMULATOR)
-    thread->SetStackLimit(Simulator::Current()->overflow_stack_limit());
+    if (FLAG_use_simulator) {
+      thread->SetStackLimit(Simulator::Current()->overflow_stack_limit());
+    } else {
+      thread->SetStackLimit(OSThread::Current()->overflow_stack_limit());
+    }
 #else
     thread->SetStackLimit(OSThread::Current()->overflow_stack_limit());
 #endif
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index b8a6afe..431dbff 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -297,7 +297,7 @@
         "builder-tag": "ffi"
       }
     },
-    "vm-(linux|mac|win|android|fuchsia)-(debug|product|release)-(ia32|x64|x64c|arm|arm64|arm64c|simarm|simarm64|simarm64_arm64|simriscv32|simriscv64)": {
+    "vm-(linux|mac|win|android|fuchsia)-(debug|product|release)-(ia32|x64|x64c|arm|arm64|arm64c|simarm|simarm64|simriscv32|simriscv64)": {
       "options": {}
     },
     "vm-checked-(linux|mac|win|fuchsia)-(debug|product|release)-(ia32|x64|x64c|arm64|arm64c|simarm|simarm64|simriscv32|simriscv64)": {
@@ -348,6 +348,26 @@
         "gen-snapshot-format" : "elf"
       }
     },
+    "vm-linux-(debug|product|release)-simarm64_arm64": {
+      "options": {
+        "vm-options": ["--use_simulator=true"], "use-qemu": true
+      }
+    },
+    "vm-linux-(debug|product|release)-simarm64_arm64-nosim": {
+      "options": {
+        "vm-options": ["--use_simulator=false"], "use-qemu": true
+      }
+    },
+    "vm-mac-(debug|product|release)-simarm64_arm64": {
+      "options": {
+        "vm-options": ["--use_simulator=true"]
+      }
+    },
+    "vm-mac-(debug|product|release)-simarm64_arm64-nosim": {
+      "options": {
+        "vm-options": ["--use_simulator=false"]
+      }
+    },
     "dart2js-(linux|win)-chrome": {
       "options": {
         "use-sdk": true
@@ -814,6 +834,13 @@
             "-nvm-${system}-${mode}-${arch}",
             "ffi"
           ]
+        },
+        {
+          "name": "vm ffi tests",
+          "arguments": [
+            "-nvm-${system}-${mode}-${arch}-nosim",
+            "ffi"
+          ]
         }
       ]
     },