[vm] Support FFI on ARM64 and Android.

Change-Id: I33f3fb1dbf5a4aee4eaea08d0ca51b60114c8680
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97109
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Aart Bik <ajcbik@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
diff --git a/runtime/lib/ffi_dynamic_library.cc b/runtime/lib/ffi_dynamic_library.cc
index af4a52e..4bd67e2 100644
--- a/runtime/lib/ffi_dynamic_library.cc
+++ b/runtime/lib/ffi_dynamic_library.cc
@@ -2,7 +2,8 @@
 // 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.
 
-#if !defined(TARGET_OS_LINUX) && !defined(TARGET_OS_MACOS)
+#if !defined(TARGET_OS_LINUX) && !defined(TARGET_OS_MACOS) &&                  \
+    !defined(TARGET_OS_ANDROID)
 // TODO(dacoharkes): Implement dynamic libraries for other targets & merge the
 // implementation with:
 // - runtime/bin/extensions.h
@@ -20,7 +21,8 @@
 namespace dart {
 
 static void* LoadExtensionLibrary(const char* library_file) {
-#if defined(TARGET_OS_LINUX) || defined(TARGET_OS_MACOS)
+#if defined(TARGET_OS_LINUX) || defined(TARGET_OS_MACOS) ||                    \
+    defined(TARGET_OS_ANDROID)
   void* handle = dlopen(library_file, RTLD_LAZY);
   if (handle == nullptr) {
     char* error = dlerror();
@@ -68,7 +70,8 @@
 }
 
 static void* ResolveSymbol(void* handle, const char* symbol) {
-#if defined(TARGET_OS_LINUX) || defined(TARGET_OS_MACOS)
+#if defined(TARGET_OS_LINUX) || defined(TARGET_OS_MACOS) ||                    \
+    defined(TARGET_OS_ANDROID)
   dlerror();  // Clear any errors.
   void* pointer = dlsym(handle, symbol);
   if (pointer == nullptr) {
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 5f1a3d6..d89e51c 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -5193,7 +5193,8 @@
   set_native_c_function(native_function);
 }
 
-#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_IA32)
+#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64) ||                  \
+    defined(TARGET_ARCH_IA32)
 
 #define Z zone_
 
@@ -5212,7 +5213,7 @@
   ASSERT(((1 << CallingConventions::kFirstCalleeSavedCpuReg) &
           CallingConventions::kArgumentRegisters) == 0);
 
-#if defined(TARGET_ARCH_IA32)
+#if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_IA32)
   constexpr intptr_t kNumTemps = 2;
 #else
   constexpr intptr_t kNumTemps = 1;
@@ -5227,7 +5228,7 @@
                       CallingConventions::kFirstNonArgumentRegister));
   summary->set_temp(0, Location::RegisterLocation(
                            CallingConventions::kSecondNonArgumentRegister));
-#if defined(TARGET_ARCH_IA32)
+#if defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_ARM64)
   summary->set_temp(1, Location::RegisterLocation(
                            CallingConventions::kFirstCalleeSavedCpuReg));
 #endif
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index 6a4649d..a7807d9 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -873,7 +873,102 @@
 }
 
 void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
-  UNREACHABLE();
+  Register saved_fp = locs()->temp(0).reg();
+  Register temp = locs()->temp(1).reg();
+  Register branch = locs()->in(TargetAddressIndex()).reg();
+
+  // Save frame pointer because we're going to update it when we enter the exit
+  // frame.
+  __ mov(saved_fp, FPREG);
+
+  // We need to create a dummy "exit frame". It will share the same pool pointer
+  // but have a null code object.
+  __ LoadObject(CODE_REG, Object::null_object());
+  __ set_constant_pool_allowed(false);
+  __ EnterDartFrame(0, PP);
+
+  // Save exit frame information to enable stack walking as we are about
+  // to transition to Dart VM C++ code.
+  __ StoreToOffset(FPREG, THR,
+                   compiler::target::Thread::top_exit_frame_info_offset());
+
+  // Make space for arguments and align the frame.
+  __ ReserveAlignedFrameSpace(compiler::ffi::NumStackSlots(arg_locations_) *
+                              kWordSize);
+
+  for (intptr_t i = 0, n = NativeArgCount(); i < n; ++i) {
+    Location origin = locs()->in(i);
+    Location target = arg_locations_[i];
+
+    if (target.IsStackSlot()) {
+      if (origin.IsRegister()) {
+        __ StoreToOffset(origin.reg(), SPREG, target.ToStackSlotOffset());
+      } else if (origin.IsFpuRegister()) {
+        __ StoreDToOffset(origin.fpu_reg(), SPREG, target.ToStackSlotOffset());
+      } else if (origin.IsStackSlot() || origin.IsDoubleStackSlot()) {
+        // The base register cannot be SPREG because we've moved it.
+        ASSERT(origin.base_reg() == FPREG);
+        __ LoadFromOffset(TMP, saved_fp, origin.ToStackSlotOffset());
+        __ StoreToOffset(TMP, SPREG, target.ToStackSlotOffset());
+      }
+    } else {
+      ASSERT(origin.Equals(target));
+    }
+  }
+
+  // Mark that the thread is executing VM code.
+  __ StoreToOffset(branch, THR, compiler::target::Thread::vm_tag_offset());
+
+  // We need to copy the return address up into the dummy stack frame so the
+  // stack walker will know which safepoint to use.
+  const intptr_t call_sequence_start = __ CodeSize();
+
+  // 5 instructions, 4 bytes each.
+  constexpr intptr_t kCallSequenceLength = 5 * 4;
+
+  __ adr(temp, Immediate(kCallSequenceLength));
+  __ StoreToOffset(temp, FPREG, kSavedCallerPcSlotFromFp * kWordSize);
+
+  // We are entering runtime code, so the C stack pointer must be restored from
+  // the stack limit to the top of the stack. We cache the stack limit address
+  // in a callee-saved register.
+  __ mov(temp, CSP);
+  __ mov(CSP, SP);
+
+  __ blr(branch);
+
+  ASSERT(__ CodeSize() - call_sequence_start == kCallSequenceLength);
+
+  // Restore the Dart stack pointer and the saved C stack pointer.
+  __ mov(SP, CSP);
+  __ mov(CSP, temp);
+
+  compiler->EmitCallsiteMetadata(token_pos(), DeoptId::kNone,
+                                 RawPcDescriptors::Kind::kOther, locs());
+
+  // Mark that the thread is executing Dart code.
+  __ LoadImmediate(temp, VMTag::kDartCompiledTagId);
+  __ StoreToOffset(temp, THR, compiler::target::Thread::vm_tag_offset());
+
+  // Reset exit frame information in Isolate structure.
+  __ StoreToOffset(ZR, THR,
+                   compiler::target::Thread::top_exit_frame_info_offset());
+
+  // Refresh write barrier mask.
+  __ ldr(BARRIER_MASK,
+         Address(THR, compiler::target::Thread::write_barrier_mask_offset()));
+
+  // Although PP is a callee-saved register, it may have been moved by the GC.
+  __ LeaveDartFrame(compiler::kRestoreCallerPP);
+
+  // Restore the global object pool after returning from runtime (old space is
+  // moving, so the GOP could have been relocated).
+  if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
+    __ ldr(PP, Address(THR, Thread::global_object_pool_offset()));
+    __ sub(PP, PP, Operand(kHeapObjectTag));  // Pool in PP is untagged!
+  }
+
+  __ set_constant_pool_allowed(true);
 }
 
 LocationSummary* OneByteStringFromCharCodeInstr::MakeLocationSummary(
diff --git a/runtime/vm/compiler/ffi.cc b/runtime/vm/compiler/ffi.cc
index a92d033..0c75e40 100644
--- a/runtime/vm/compiler/ffi.cc
+++ b/runtime/vm/compiler/ffi.cc
@@ -11,7 +11,8 @@
 
 namespace ffi {
 
-#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_IA32)
+#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64) ||                  \
+    defined(TARGET_ARCH_IA32)
 
 static const size_t kSizeUnknown = 0;
 
@@ -127,7 +128,7 @@
           Location result = Location::RegisterLocation(
               CallingConventions::ArgumentRegisters[cpu_regs_used]);
           cpu_regs_used++;
-          if (CallingConventions::kArgumentIntRegXorXmmReg) {
+          if (CallingConventions::kArgumentIntRegXorFpuReg) {
             fpu_regs_used++;
           }
           return result;
@@ -135,11 +136,11 @@
         break;
       case kUnboxedFloat:
       case kUnboxedDouble:
-        if (fpu_regs_used < CallingConventions::kNumXmmArgRegs) {
+        if (fpu_regs_used < CallingConventions::kNumFpuArgRegs) {
           Location result = Location::FpuRegisterLocation(
-              CallingConventions::XmmArgumentRegisters[fpu_regs_used]);
+              CallingConventions::FpuArgumentRegisters[fpu_regs_used]);
           fpu_regs_used++;
-          if (CallingConventions::kArgumentIntRegXorXmmReg) {
+          if (CallingConventions::kArgumentIntRegXorFpuReg) {
             cpu_regs_used++;
           }
           return result;
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 169355a..59e1498 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -2485,7 +2485,8 @@
 
 FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline(
     const Function& function) {
-#if !defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_IA32)
+#if !defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_ARM64) &&                \
+    !defined(TARGET_ARCH_IA32)
   UNREACHABLE();
 #else
   graph_entry_ =
diff --git a/runtime/vm/constants_arm64.cc b/runtime/vm/constants_arm64.cc
new file mode 100644
index 0000000..0714ae3
--- /dev/null
+++ b/runtime/vm/constants_arm64.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2019, 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.
+
+#if defined(TARGET_ARCH_ARM64)
+
+#include "vm/constants_arm64.h"
+
+namespace dart {
+
+const Register CallingConventions::ArgumentRegisters[] = {
+    R0, R1, R2, R3, R4, R5, R6, R7,
+};
+
+const FpuRegister CallingConventions::FpuArgumentRegisters[] = {
+    V0, V1, V2, V3, V4, V5, V6, V7,
+};
+
+}  // namespace dart
+
+#endif
diff --git a/runtime/vm/constants_arm64.h b/runtime/vm/constants_arm64.h
index 0e2d6ea..fa592e1 100644
--- a/runtime/vm/constants_arm64.h
+++ b/runtime/vm/constants_arm64.h
@@ -137,6 +137,9 @@
 typedef uint32_t RegList;
 const RegList kAllCpuRegistersList = 0xFFFFFFFF;
 
+// See "Procedure Call Standard for the ARM 64-bit Architecture", document
+// number "ARM IHI 0055B", May 22 2013.
+
 // C++ ABI call registers.
 const RegList kAbiArgumentCpuRegs = (1 << R0) | (1 << R1) | (1 << R2) |
                                     (1 << R3) | (1 << R4) | (1 << R5) |
@@ -173,6 +176,32 @@
 
 constexpr int kStoreBufferWrapperSize = 32;
 
+#define R(REG) (1 << REG)
+
+class CallingConventions {
+ public:
+  static const intptr_t kArgumentRegisters = kAbiArgumentCpuRegs;
+  static const Register ArgumentRegisters[];
+  static const intptr_t kNumArgRegs = 8;
+
+  static const FpuRegister FpuArgumentRegisters[];
+  static const intptr_t kFpuArgumentRegisters =
+      R(V0) | R(V1) | R(V2) | R(V3) | R(V4) | R(V5) | R(V6) | R(V7);
+  static const intptr_t kNumFpuArgRegs = 8;
+
+  static const bool kArgumentIntRegXorFpuReg = false;
+
+  static constexpr Register kReturnReg = R0;
+  static constexpr Register kSecondReturnReg = kNoRegister;
+  static constexpr FpuRegister kReturnFpuReg = V0;
+
+  static constexpr Register kFirstCalleeSavedCpuReg = kAbiFirstPreservedCpuReg;
+  static constexpr Register kFirstNonArgumentRegister = R8;
+  static constexpr Register kSecondNonArgumentRegister = R9;
+};
+
+#undef R
+
 static inline Register ConcreteRegister(Register r) {
   return ((r == ZR) || (r == CSP)) ? R31 : r;
 }
diff --git a/runtime/vm/constants_ia32.cc b/runtime/vm/constants_ia32.cc
index 16ae341..62c3f6a 100644
--- a/runtime/vm/constants_ia32.cc
+++ b/runtime/vm/constants_ia32.cc
@@ -8,13 +8,13 @@
 
 namespace dart {
 
-// Although 'kArgumentRegisters' and 'kXmmArgumentRegisters' are both 0, we have
+// Although 'kArgumentRegisters' and 'kFpuArgumentRegisters' are both 0, we have
 // to give these arrays at least one element to appease MSVC.
 
 const Register CallingConventions::ArgumentRegisters[] = {
     static_cast<Register>(0)};
-const XmmRegister CallingConventions::XmmArgumentRegisters[] = {
-    static_cast<XmmRegister>(0)};
+const FpuRegister CallingConventions::FpuArgumentRegisters[] = {
+    static_cast<FpuRegister>(0)};
 
 }  // namespace dart
 
diff --git a/runtime/vm/constants_ia32.h b/runtime/vm/constants_ia32.h
index 43d3064..24e3783 100644
--- a/runtime/vm/constants_ia32.h
+++ b/runtime/vm/constants_ia32.h
@@ -125,17 +125,17 @@
   static const intptr_t kArgumentRegisters = 0;
   static const intptr_t kNumArgRegs = 0;
 
-  static const XmmRegister XmmArgumentRegisters[];
+  static const XmmRegister FpuArgumentRegisters[];
   static const intptr_t kXmmArgumentRegisters = 0;
-  static const intptr_t kNumXmmArgRegs = 0;
+  static const intptr_t kNumFpuArgRegs = 0;
 
-  static const bool kArgumentIntRegXorXmmReg = false;
+  static const bool kArgumentIntRegXorFpuReg = false;
 
   static constexpr Register kReturnReg = EAX;
   static constexpr Register kSecondReturnReg = EDX;
 
   // Floating point values are returned on the "FPU stack" (in "ST" registers).
-  static constexpr XmmRegister kReturnXmmReg = kNoXmmRegister;
+  static constexpr XmmRegister kReturnFpuReg = kNoXmmRegister;
 
   static constexpr Register kFirstCalleeSavedCpuReg = EBX;
   static constexpr Register kFirstNonArgumentRegister = EAX;
diff --git a/runtime/vm/constants_x64.cc b/runtime/vm/constants_x64.cc
index 397977e..3f38d9d 100644
--- a/runtime/vm/constants_x64.cc
+++ b/runtime/vm/constants_x64.cc
@@ -13,7 +13,7 @@
     CallingConventions::kArg1Reg, CallingConventions::kArg2Reg,
     CallingConventions::kArg3Reg, CallingConventions::kArg4Reg};
 
-const XmmRegister CallingConventions::XmmArgumentRegisters[] = {
+const XmmRegister CallingConventions::FpuArgumentRegisters[] = {
     XmmRegister::XMM0, XmmRegister::XMM1, XmmRegister::XMM2, XmmRegister::XMM3};
 #else
 const Register CallingConventions::ArgumentRegisters[] = {
@@ -21,7 +21,7 @@
     CallingConventions::kArg3Reg, CallingConventions::kArg4Reg,
     CallingConventions::kArg5Reg, CallingConventions::kArg6Reg};
 
-const XmmRegister CallingConventions::XmmArgumentRegisters[] = {
+const XmmRegister CallingConventions::FpuArgumentRegisters[] = {
     XmmRegister::XMM0, XmmRegister::XMM1, XmmRegister::XMM2, XmmRegister::XMM3,
     XmmRegister::XMM4, XmmRegister::XMM5, XmmRegister::XMM6, XmmRegister::XMM7};
 #endif
diff --git a/runtime/vm/constants_x64.h b/runtime/vm/constants_x64.h
index cbaedaa..3e8d0070 100644
--- a/runtime/vm/constants_x64.h
+++ b/runtime/vm/constants_x64.h
@@ -157,14 +157,14 @@
       R(kArg1Reg) | R(kArg2Reg) | R(kArg3Reg) | R(kArg4Reg);
   static const intptr_t kNumArgRegs = 4;
 
-  static const XmmRegister XmmArgumentRegisters[];
-  static const intptr_t kXmmArgumentRegisters =
+  static const XmmRegister FpuArgumentRegisters[];
+  static const intptr_t kFpuArgumentRegisters =
       R(XMM0) | R(XMM1) | R(XMM2) | R(XMM3);
-  static const intptr_t kNumXmmArgRegs = 4;
+  static const intptr_t kNumFpuArgRegs = 4;
 
   // can ArgumentRegisters[i] and XmmArgumentRegisters[i] both be used at the
   // same time? (Windows no, rest yes)
-  static const bool kArgumentIntRegXorXmmReg = true;
+  static const bool kArgumentIntRegXorFpuReg = true;
 
   static const intptr_t kShadowSpaceBytes = 4 * kWordSize;
 
@@ -203,15 +203,15 @@
                                              R(kArg5Reg) | R(kArg6Reg);
   static const intptr_t kNumArgRegs = 6;
 
-  static const XmmRegister XmmArgumentRegisters[];
+  static const XmmRegister FpuArgumentRegisters[];
   static const intptr_t kXmmArgumentRegisters = R(XMM0) | R(XMM1) | R(XMM2) |
                                                 R(XMM3) | R(XMM4) | R(XMM5) |
                                                 R(XMM6) | R(XMM7);
-  static const intptr_t kNumXmmArgRegs = 8;
+  static const intptr_t kNumFpuArgRegs = 8;
 
   // can ArgumentRegisters[i] and XmmArgumentRegisters[i] both be used at the
   // same time? (Windows no, rest yes)
-  static const bool kArgumentIntRegXorXmmReg = false;
+  static const bool kArgumentIntRegXorFpuReg = false;
 
   static const intptr_t kShadowSpaceBytes = 0;
 
diff --git a/runtime/vm/dart_api_impl.h b/runtime/vm/dart_api_impl.h
index 0fa7622..202143e 100644
--- a/runtime/vm/dart_api_impl.h
+++ b/runtime/vm/dart_api_impl.h
@@ -297,12 +297,13 @@
 
   static bool IsFfiEnabled() {
     // dart:ffi is not implemented for the following configurations
-#if !defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_IA32)
+#if !defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_ARM64) &&                \
+    !defined(TARGET_ARCH_IA32)
     // https://github.com/dart-lang/sdk/issues/35760 Arm32 && Android
     // https://github.com/dart-lang/sdk/issues/35772 Arm64
     return false;
 #elif !defined(TARGET_OS_LINUX) && !defined(TARGET_OS_MACOS) &&                \
-    !defined(TARGET_OS_WINDOWS)
+    !defined(TARGET_OS_ANDROID) && !defined(TARGET_OS_WINDOWS)
     // https://github.com/dart-lang/sdk/issues/35760 Arm32 && Android
     // https://github.com/dart-lang/sdk/issues/35772 Arm64
     // https://github.com/dart-lang/sdk/issues/35773 DBC
diff --git a/runtime/vm/vm_sources.gni b/runtime/vm/vm_sources.gni
index 7fb6f1f..eadae4d 100644
--- a/runtime/vm/vm_sources.gni
+++ b/runtime/vm/vm_sources.gni
@@ -43,6 +43,7 @@
   "compilation_trace.cc",
   "compilation_trace.h",
   "constants_arm.h",
+  "constants_arm64.cc",
   "constants_arm64.h",
   "constants_dbc.h",
   "constants_ia32.h",
diff --git a/tests/standalone_2/ffi/dylib_utils.dart b/tests/standalone_2/ffi/dylib_utils.dart
index 1c924d4..fb8153a 100644
--- a/tests/standalone_2/ffi/dylib_utils.dart
+++ b/tests/standalone_2/ffi/dylib_utils.dart
@@ -7,7 +7,8 @@
 
 String _platformPath(String name, {String path}) {
   if (path == null) path = "";
-  if (Platform.isLinux) return path + "lib" + name + ".so";
+  if (Platform.isLinux || Platform.isAndroid)
+    return path + "lib" + name + ".so";
   if (Platform.isMacOS) return path + "lib" + name + ".dylib";
   if (Platform.isWindows) return path + name + ".dll";
   throw Exception("Platform not implemented");
diff --git a/tests/standalone_2/ffi/gc_helper.dart b/tests/standalone_2/ffi/gc_helper.dart
index 5e9431d..182e1df 100644
--- a/tests/standalone_2/ffi/gc_helper.dart
+++ b/tests/standalone_2/ffi/gc_helper.dart
@@ -12,8 +12,9 @@
 abstract class GCWatcher {
   factory GCWatcher() => _GCWatcherImpl();
   factory GCWatcher.dummy() => _MockGCWatcher();
-  factory GCWatcher.ifAvailable() =>
-      Platform.isWindows ? GCWatcher.dummy() : GCWatcher();
+  factory GCWatcher.ifAvailable() => (Platform.isWindows || Platform.isAndroid)
+      ? GCWatcher.dummy()
+      : GCWatcher();
 
   Future<int> size();
   void dispose();
diff --git a/tests/standalone_2/standalone_2_kernel.status b/tests/standalone_2/standalone_2_kernel.status
index 75ee38a..fdbc2ab 100644
--- a/tests/standalone_2/standalone_2_kernel.status
+++ b/tests/standalone_2/standalone_2_kernel.status
@@ -244,11 +244,11 @@
 io/web_socket_compression_test: Skip # Timeout
 io/web_socket_test: Skip # Timeout
 
-[ $compiler != dartk || $mode == product || $arch != ia32 && $arch != x64 || $system != linux && $system != macos && $system != windows ]
+[ $compiler != dartk || $mode == product || $arch != arm64 && $arch != ia32 && $arch != x64 || $system != android && $system != linux && $system != macos && $system != windows ]
 ffi/function_stress_test: SkipByDesign # FFI must be supported. Also requires --verbose-gc, which isn't included in product.
 ffi/subtype_test: SkipByDesign # FFI must be supported. Also requires --verbose-gc, which isn't included in product.
 
-[ $arch != ia32 && $arch != x64 || $system != linux && $system != macos && $system != windows ]
+[ $arch != arm64 && $arch != ia32 && $arch != x64 || $system != android && $system != linux && $system != macos && $system != windows ]
 ffi: Skip # ffi not yet supported on other systems than linux/macos/windows x64/ia32
 
 [ $compiler != dartk && $compiler != dartkb && $compiler != dartkp || $compiler == dartkp && $system == windows ]