[vm/ffi] Refactor NativeCallingConvention to be standalone

This CL refactors NativeCallingConvention to be a standalone class that
can be unit tested separately. It no longer uses Function to calcalate
the native ABI, but a NativeFunctionType.

The BaseMarshaller no longer extends NativeCallingConvention but nests
it instead.

The actual unit tests will be added in a follow up CL.

Issue: https://github.com/dart-lang/sdk/issues/44117

Change-Id: Ib529cbaa7e52b51ebdec831eeb772faee153335c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/170981
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/runtime/vm/compiler/ffi/marshaller.cc b/runtime/vm/compiler/ffi/marshaller.cc
index c095e9e..6620468 100644
--- a/runtime/vm/compiler/ffi/marshaller.cc
+++ b/runtime/vm/compiler/ffi/marshaller.cc
@@ -4,9 +4,13 @@
 
 #include "vm/compiler/ffi/marshaller.h"
 
+#include "platform/assert.h"
+#include "platform/globals.h"
 #include "vm/compiler/ffi/frame_rebase.h"
+#include "vm/compiler/ffi/native_calling_convention.h"
 #include "vm/compiler/ffi/native_location.h"
 #include "vm/compiler/ffi/native_type.h"
+#include "vm/log.h"
 #include "vm/raw_object.h"
 #include "vm/stack_frame.h"
 #include "vm/symbols.h"
@@ -17,6 +21,56 @@
 
 namespace ffi {
 
+// Argument #0 is the function pointer.
+const intptr_t kNativeParamsStartAt = 1;
+
+// Representations of the arguments and return value of a C signature function.
+static const NativeFunctionType& NativeFunctionSignature(
+    Zone* zone,
+    const Function& c_signature) {
+  ASSERT(c_signature.NumOptionalParameters() == 0);
+  ASSERT(c_signature.NumOptionalPositionalParameters() == 0);
+
+  const intptr_t num_arguments =
+      c_signature.num_fixed_parameters() - kNativeParamsStartAt;
+  auto& argument_representations =
+      *new ZoneGrowableArray<const NativeType*>(zone, num_arguments);
+  for (intptr_t i = 0; i < num_arguments; i++) {
+    AbstractType& arg_type = AbstractType::Handle(
+        zone, c_signature.ParameterTypeAt(i + kNativeParamsStartAt));
+    const auto& rep = NativeType::FromAbstractType(zone, arg_type);
+    argument_representations.Add(&rep);
+  }
+
+  const auto& result_type =
+      AbstractType::Handle(zone, c_signature.result_type());
+  const auto& result_representation =
+      NativeType::FromAbstractType(zone, result_type);
+
+  const auto& result = *new (zone) NativeFunctionType(argument_representations,
+                                                      result_representation);
+  return result;
+}
+
+BaseMarshaller::BaseMarshaller(Zone* zone, const Function& dart_signature)
+    : zone_(zone),
+      dart_signature_(dart_signature),
+      c_signature_(Function::ZoneHandle(zone, dart_signature.FfiCSignature())),
+      native_calling_convention_(NativeCallingConvention::FromSignature(
+          zone,
+          NativeFunctionSignature(zone_, c_signature_))) {
+  ASSERT(dart_signature_.IsZoneHandle());
+}
+
+AbstractTypePtr BaseMarshaller::CType(intptr_t arg_index) const {
+  if (arg_index == kResultIndex) {
+    return c_signature_.result_type();
+  }
+
+  // Skip #0 argument, the function pointer.
+  return c_signature_.ParameterTypeAt(arg_index + kNativeParamsStartAt);
+}
+
 bool BaseMarshaller::ContainsHandles() const {
   return dart_signature_.FfiCSignatureContainsHandles();
 }
@@ -80,14 +134,14 @@
   static NativeLocations& TranslateArgumentLocations(
       Zone* zone,
       const NativeLocations& arg_locs) {
-    auto& pushed_locs = *(new NativeLocations(arg_locs.length()));
+    auto& pushed_locs = *(new (zone) NativeLocations(arg_locs.length()));
 
     CallbackArgumentTranslator translator;
     for (intptr_t i = 0, n = arg_locs.length(); i < n; i++) {
       translator.AllocateArgument(*arg_locs[i]);
     }
     for (intptr_t i = 0, n = arg_locs.length(); i < n; ++i) {
-      pushed_locs.Add(&translator.TranslateArgument(*arg_locs[i], zone));
+      pushed_locs.Add(&translator.TranslateArgument(zone, *arg_locs[i]));
     }
 
     return pushed_locs;
@@ -97,16 +151,17 @@
   void AllocateArgument(const NativeLocation& arg) {
     if (arg.IsStack()) return;
 
-    ASSERT(arg.IsRegisters() || arg.IsFpuRegisters());
     if (arg.IsRegisters()) {
       argument_slots_required_ += arg.AsRegisters().num_regs();
-    } else {
+    } else if (arg.IsFpuRegisters()) {
       argument_slots_required_ += 8 / target::kWordSize;
+    } else {
+      UNREACHABLE();
     }
   }
 
-  const NativeLocation& TranslateArgument(const NativeLocation& arg,
-                                          Zone* zone) {
+  const NativeLocation& TranslateArgument(Zone* zone,
+                                          const NativeLocation& arg) {
     if (arg.IsStack()) {
       // Add extra slots after the saved arguments for the return address and
       // frame pointer of the dummy arguments frame, which will be between the
@@ -152,10 +207,9 @@
 CallbackMarshaller::CallbackMarshaller(Zone* zone,
                                        const Function& dart_signature)
     : BaseMarshaller(zone, dart_signature),
-      callback_locs_(
-          CallbackArgumentTranslator::TranslateArgumentLocations(zone_,
-                                                                 arg_locs_)) {}
-
+      callback_locs_(CallbackArgumentTranslator::TranslateArgumentLocations(
+          zone_,
+          native_calling_convention_.argument_locations())) {}
 }  // namespace ffi
 
 }  // namespace compiler
diff --git a/runtime/vm/compiler/ffi/marshaller.h b/runtime/vm/compiler/ffi/marshaller.h
index d175ddc..be567b6 100644
--- a/runtime/vm/compiler/ffi/marshaller.h
+++ b/runtime/vm/compiler/ffi/marshaller.h
@@ -11,6 +11,7 @@
 
 #include <platform/globals.h>
 
+#include "platform/assert.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/compiler/ffi/callback.h"
 #include "vm/compiler/ffi/native_calling_convention.h"
@@ -24,16 +25,32 @@
 
 namespace ffi {
 
+// Values below 0 index result (result might be multiple if composite).
+const intptr_t kResultIndex = -1;
+
 // Provides the mapping from the native calling convention to the Dart calling
 // convention.
 //
 // This class is set up in a query-able way so that it's underlying logic can
 // be extended to support more native ABI features and calling conventions.
-//
-// TODO(36730): Add a way to query arguments that are broken into multiple
-// parts.
-class BaseMarshaller : public NativeCallingConvention {
+class BaseMarshaller : public ZoneAllocated {
  public:
+  intptr_t num_args() const {
+    return native_calling_convention_.argument_locations().length();
+  }
+
+  intptr_t StackTopInBytes() const {
+    return native_calling_convention_.StackTopInBytes();
+  }
+
+  // The location of the argument at `arg_index`.
+  const NativeLocation& Location(intptr_t arg_index) const {
+    if (arg_index == kResultIndex) {
+      return native_calling_convention_.return_location();
+    }
+    return *native_calling_convention_.argument_locations().At(arg_index);
+  }
+
   // Unboxed representation on how the value is passed or received from regular
   // Dart code.
   Representation RepInDart(intptr_t arg_index) const {
@@ -62,6 +79,11 @@
     return Location(arg_index).payload_type();
   }
 
+  // The C Type (expressed in a Dart Type) of the argument at `arg_index`.
+  //
+  // Excluding the #0 argument which is the function pointer.
+  AbstractTypePtr CType(intptr_t arg_index) const;
+
   // Requires boxing or unboxing.
   bool IsPointer(intptr_t arg_index) const {
     return AbstractType::Handle(zone_, CType(arg_index)).type_class_id() ==
@@ -83,18 +105,16 @@
   StringPtr function_name() const { return dart_signature_.name(); }
 
  protected:
-  BaseMarshaller(Zone* zone, const Function& dart_signature)
-      : NativeCallingConvention(
-            zone,
-            Function::ZoneHandle(zone, dart_signature.FfiCSignature())),
-        dart_signature_(dart_signature) {
-    ASSERT(dart_signature_.IsZoneHandle());
-  }
+  BaseMarshaller(Zone* zone, const Function& dart_signature);
 
- private:
+  ~BaseMarshaller() {}
+
+  Zone* zone_;
   // Contains the function pointer as argument #0.
   // The Dart signature is used for the function and argument names.
   const Function& dart_signature_;
+  const Function& c_signature_;
+  const NativeCallingConvention& native_calling_convention_;
 };
 
 class CallMarshaller : public BaseMarshaller {
@@ -103,6 +123,9 @@
       : BaseMarshaller(zone, dart_signature) {}
 
   dart::Location LocInFfiCall(intptr_t arg_index) const;
+
+ protected:
+  ~CallMarshaller() {}
 };
 
 class CallbackMarshaller : public BaseMarshaller {
@@ -125,6 +148,8 @@
   }
 
  protected:
+  ~CallbackMarshaller() {}
+
   const NativeLocations& callback_locs_;
 };
 
diff --git a/runtime/vm/compiler/ffi/native_calling_convention.cc b/runtime/vm/compiler/ffi/native_calling_convention.cc
index 84081aa..2ac1445 100644
--- a/runtime/vm/compiler/ffi/native_calling_convention.cc
+++ b/runtime/vm/compiler/ffi/native_calling_convention.cc
@@ -16,9 +16,6 @@
 
 namespace ffi {
 
-// Argument #0 is the function pointer.
-const intptr_t kNativeParamsStartAt = 1;
-
 const intptr_t kNoFpuRegister = -1;
 
 // In Soft FP, floats and doubles get passed in integer registers.
@@ -44,29 +41,6 @@
   return rep;
 }
 
-// Representations of the arguments to a C signature function.
-static ZoneGrowableArray<const NativeType*>& ArgumentRepresentations(
-    Zone* zone,
-    const Function& signature) {
-  const intptr_t num_arguments =
-      signature.num_fixed_parameters() - kNativeParamsStartAt;
-  auto& result = *new ZoneGrowableArray<const NativeType*>(zone, num_arguments);
-  for (intptr_t i = 0; i < num_arguments; i++) {
-    AbstractType& arg_type = AbstractType::Handle(
-        zone, signature.ParameterTypeAt(i + kNativeParamsStartAt));
-    const auto& rep = NativeType::FromAbstractType(zone, arg_type);
-    result.Add(&rep);
-  }
-  return result;
-}
-
-// Representation of the result of a C signature function.
-static NativeType& ResultRepresentation(Zone* zone, const Function& signature) {
-  AbstractType& result_type =
-      AbstractType::Handle(zone, signature.result_type());
-  return NativeType::FromAbstractType(zone, result_type);
-}
-
 // Represents the state of a stack frame going into a call, between allocations
 // of argument locations.
 class ArgumentAllocator : public ValueObject {
@@ -90,9 +64,9 @@
         if (CallingConventions::kArgumentIntRegXorFpuReg) {
           ASSERT(cpu_regs_used == CallingConventions::kNumArgRegs);
         }
+        // Transfer on stack.
       }
-    } else {
-      ASSERT(payload_type_converted.IsInt());
+    } else if (payload_type_converted.IsInt()) {
       // Some calling conventions require the callee to make the lowest 32 bits
       // in registers non-garbage.
       const auto& container_type =
@@ -115,8 +89,12 @@
         if (cpu_regs_used + 1 <= CallingConventions::kNumArgRegs) {
           return *new (zone_) NativeRegistersLocation(
               payload_type, container_type, AllocateCpuRegister());
+        } else {
+          // Transfer on stack.
         }
       }
+    } else {
+      UNREACHABLE();
     }
 
     return AllocateStack(payload_type);
@@ -227,7 +205,7 @@
     Zone* zone,
     const ZoneGrowableArray<const NativeType*>& arg_reps) {
   intptr_t num_arguments = arg_reps.length();
-  auto& result = *new NativeLocations(zone, num_arguments);
+  auto& result = *new (zone) NativeLocations(zone, num_arguments);
 
   // Loop through all arguments and assign a register or a stack location.
   ArgumentAllocator frame_state(zone);
@@ -239,8 +217,8 @@
 }
 
 // Location for the result of a C signature function.
-static NativeLocation& ResultLocation(Zone* zone,
-                                      const NativeType& payload_type) {
+static const NativeLocation& ResultLocation(Zone* zone,
+                                            const NativeType& payload_type) {
   const auto& payload_type_converted = ConvertIfSoftFp(zone, payload_type);
   const auto& container_type =
       CallingConventions::kReturnRegisterExtension == kExtendedTo4
@@ -263,46 +241,45 @@
                                              CallingConventions::kReturnReg);
 }
 
-NativeCallingConvention::NativeCallingConvention(Zone* zone,
-                                                 const Function& c_signature)
-    : zone_(ASSERT_NOTNULL(zone)),
-      c_signature_(c_signature),
-      arg_locs_(
-          ArgumentLocations(zone_,
-                            ArgumentRepresentations(zone_, c_signature_))),
-      result_loc_(
-          ResultLocation(zone_, ResultRepresentation(zone_, c_signature_))) {}
-
-intptr_t NativeCallingConvention::num_args() const {
-  ASSERT(c_signature_.NumOptionalParameters() == 0);
-  ASSERT(c_signature_.NumOptionalPositionalParameters() == 0);
-
-  // Subtract the #0 argument, the function pointer.
-  return c_signature_.num_fixed_parameters() - kNativeParamsStartAt;
-}
-
-AbstractTypePtr NativeCallingConvention::CType(intptr_t arg_index) const {
-  if (arg_index == kResultIndex) {
-    return c_signature_.result_type();
-  }
-
-  // Skip #0 argument, the function pointer.
-  return c_signature_.ParameterTypeAt(arg_index + kNativeParamsStartAt);
+const NativeCallingConvention& NativeCallingConvention::FromSignature(
+    Zone* zone,
+    const NativeFunctionType& signature) {
+  const auto& argument_locations =
+      ArgumentLocations(zone, signature.argument_types());
+  const auto& return_location = ResultLocation(zone, signature.return_type());
+  return *new (zone)
+      NativeCallingConvention(argument_locations, return_location);
 }
 
 intptr_t NativeCallingConvention::StackTopInBytes() const {
-  const intptr_t num_arguments = arg_locs_.length();
+  const intptr_t num_arguments = argument_locations_.length();
   intptr_t max_height_in_bytes = 0;
   for (intptr_t i = 0; i < num_arguments; i++) {
-    if (Location(i).IsStack()) {
-      const intptr_t height = Location(i).AsStack().offset_in_bytes() +
-                              Location(i).container_type().SizeInBytes();
-      max_height_in_bytes = Utils::Maximum(height, max_height_in_bytes);
-    }
+    max_height_in_bytes = Utils::Maximum(
+        max_height_in_bytes, argument_locations_[i]->StackTopInBytes());
   }
   return Utils::RoundUp(max_height_in_bytes, compiler::target::kWordSize);
 }
 
+const char* NativeCallingConvention::ToCString() const {
+  char buffer[1024];
+  BufferFormatter bf(buffer, 1024);
+  PrintTo(&bf);
+  return Thread::Current()->zone()->MakeCopyOfString(buffer);
+}
+
+void NativeCallingConvention::PrintTo(BaseTextBuffer* f) const {
+  f->AddString("(");
+  for (intptr_t i = 0; i < argument_locations_.length(); i++) {
+    if (i > 0) {
+      f->AddString(", ");
+    }
+    argument_locations_[i]->PrintTo(f);
+  }
+  f->AddString(") => ");
+  return_location_.PrintTo(f);
+}
+
 }  // namespace ffi
 
 }  // namespace compiler
diff --git a/runtime/vm/compiler/ffi/native_calling_convention.h b/runtime/vm/compiler/ffi/native_calling_convention.h
index 5eae152..4948910 100644
--- a/runtime/vm/compiler/ffi/native_calling_convention.h
+++ b/runtime/vm/compiler/ffi/native_calling_convention.h
@@ -23,43 +23,35 @@
 
 using NativeLocations = ZoneGrowableArray<const NativeLocation*>;
 
-
-// Values below 0 index result (result might be multiple if composite).
-const intptr_t kResultIndex = -1;
-
 // Calculates native calling convention, is not aware of Dart calling
 // convention constraints.
 //
-// This class is meant to be extended or embedded in a class that is aware
-// of Dart calling convention constraints.
+// This class is meant to beembedded in a class that is aware of Dart calling
+// convention constraints.
 class NativeCallingConvention : public ZoneAllocated {
  public:
-  NativeCallingConvention(Zone* zone, const Function& c_signature);
+  static const NativeCallingConvention& FromSignature(
+      Zone* zone,
+      const NativeFunctionType& signature);
 
-  // Excluding the #0 argument which is the function pointer.
-  intptr_t num_args() const;
-
-  // The C Type (expressed in a Dart Type) of the argument at `arg_index`.
-  //
-  // Excluding the #0 argument which is the function pointer.
-  AbstractTypePtr CType(intptr_t arg_index) const;
-
-  // The location of the argument at `arg_index`.
-  const NativeLocation& Location(intptr_t arg_index) const {
-    if (arg_index == kResultIndex) {
-      return result_loc_;
-    }
-    return *arg_locs_.At(arg_index);
+  const NativeLocations& argument_locations() const {
+    return argument_locations_;
   }
+  const NativeLocation& return_location() const { return return_location_; }
 
   intptr_t StackTopInBytes() const;
 
- protected:
-  Zone* const zone_;
-  // Contains the function pointer as argument #0.
-  const Function& c_signature_;
-  const NativeLocations& arg_locs_;
-  const NativeLocation& result_loc_;
+  void PrintTo(BaseTextBuffer* f) const;
+  const char* ToCString() const;
+
+ private:
+  NativeCallingConvention(const NativeLocations& argument_locations,
+                          const NativeLocation& return_location)
+      : argument_locations_(argument_locations),
+        return_location_(return_location) {}
+
+  const NativeLocations& argument_locations_;
+  const NativeLocation& return_location_;
 };
 
 }  // namespace ffi
diff --git a/runtime/vm/compiler/ffi/native_location.h b/runtime/vm/compiler/ffi/native_location.h
index 78caa24..6c7e980 100644
--- a/runtime/vm/compiler/ffi/native_location.h
+++ b/runtime/vm/compiler/ffi/native_location.h
@@ -9,6 +9,7 @@
 #error "AOT runtime should not use compiler sources (including header files)"
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 
+#include "platform/assert.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/compiler/ffi/native_type.h"
 #include "vm/growable_array.h"
@@ -34,14 +35,15 @@
 // * The container type, equal to or larger than the payload. If the
 //   container is larger than the payload, the upper bits are defined by sign
 //   or zero extension.
+//   Container type is also used to denote an integer container when floating
+//   point values are passed in integer registers.
 //
 // NativeLocations can express things that dart::Locations cannot express:
 // * Multiple consecutive registers.
 // * Multiple sizes of FPU registers (e.g. S, D, and Q on Arm32).
 // * Arbitrary byte-size stack locations, at byte-size offsets.
 //   (The Location class uses word-size offsets.)
-// * Pointers including a backing location on the stack.
-// * No location.
+// * Pointers to a memory location.
 // * Split between multiple registers and stack.
 //
 // NativeLocations cannot express the following dart::Locations:
@@ -106,6 +108,9 @@
     UNREACHABLE();
   }
 
+  // Return the top of the stack in bytes.
+  virtual intptr_t StackTopInBytes() const { return 0; }
+
   // Equality of location, ignores the payload and container native types.
   virtual bool Equals(const NativeLocation& other) const { UNREACHABLE(); }
 
@@ -303,6 +308,10 @@
 
   virtual NativeStackLocation& Split(Zone* zone, intptr_t index) const;
 
+  virtual intptr_t StackTopInBytes() const {
+    return offset_in_bytes() + container_type().SizeInBytes();
+  }
+
   virtual void PrintTo(BaseTextBuffer* f) const;
 
   virtual bool Equals(const NativeLocation& other) const;
diff --git a/runtime/vm/compiler/ffi/native_type.cc b/runtime/vm/compiler/ffi/native_type.cc
index 5bb9f4c..c5cd230 100644
--- a/runtime/vm/compiler/ffi/native_type.cc
+++ b/runtime/vm/compiler/ffi/native_type.cc
@@ -6,8 +6,12 @@
 
 #include "platform/assert.h"
 #include "platform/globals.h"
+#include "vm/class_id.h"
 #include "vm/compiler/runtime_api.h"
+#include "vm/growable_array.h"
+#include "vm/log.h"
 #include "vm/object.h"
+#include "vm/symbols.h"
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
 #include "vm/compiler/backend/locations.h"
@@ -324,6 +328,25 @@
   f->Printf("%s", PrimitiveTypeToCString(representation_));
 }
 
+const char* NativeFunctionType::ToCString() const {
+  char buffer[1024];
+  BufferFormatter bf(buffer, 1024);
+  PrintTo(&bf);
+  return Thread::Current()->zone()->MakeCopyOfString(buffer);
+}
+
+void NativeFunctionType::PrintTo(BaseTextBuffer* f) const {
+  f->AddString("(");
+  for (intptr_t i = 0; i < argument_types_.length(); i++) {
+    if (i > 0) {
+      f->AddString(", ");
+    }
+    argument_types_[i]->PrintTo(f);
+  }
+  f->AddString(") => ");
+  return_type_.PrintTo(f);
+}
+
 const NativeType& NativeType::WidenTo4Bytes(Zone* zone) const {
   if (IsInt() && SizeInBytes() <= 2) {
     if (IsSigned()) {
diff --git a/runtime/vm/compiler/ffi/native_type.h b/runtime/vm/compiler/ffi/native_type.h
index 66d5f31..693b7c0 100644
--- a/runtime/vm/compiler/ffi/native_type.h
+++ b/runtime/vm/compiler/ffi/native_type.h
@@ -10,6 +10,7 @@
 #include "platform/assert.h"
 #include "vm/allocation.h"
 #include "vm/compiler/runtime_api.h"
+#include "vm/growable_array.h"
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
 #include "vm/compiler/backend/locations.h"
@@ -66,7 +67,7 @@
   virtual bool IsFloat() const { return false; }
   virtual bool IsVoid() const { return false; }
 
-  virtual bool IsSigned() const = 0;
+  virtual bool IsSigned() const { return false; }
 
   // The size in bytes of this representation.
   //
@@ -84,7 +85,7 @@
   virtual bool IsExpressibleAsRepresentation() const { return false; }
 
   // Unboxed Representation if it exists.
-  virtual Representation AsRepresentation() const = 0;
+  virtual Representation AsRepresentation() const { UNREACHABLE(); }
 
   // Unboxed Representation, over approximates if needed.
   Representation AsRepresentationOverApprox(Zone* zone_) const {
@@ -167,6 +168,25 @@
   const PrimitiveType representation_;
 };
 
+using NativeTypes = ZoneGrowableArray<const NativeType*>;
+
+class NativeFunctionType : public ZoneAllocated {
+ public:
+  NativeFunctionType(const NativeTypes& argument_types,
+                     const NativeType& return_type)
+      : argument_types_(argument_types), return_type_(return_type) {}
+
+  const NativeTypes& argument_types() const { return argument_types_; }
+  const NativeType& return_type() const { return return_type_; }
+
+  void PrintTo(BaseTextBuffer* f) const;
+  const char* ToCString() const;
+
+ private:
+  const NativeTypes& argument_types_;
+  const NativeType& return_type_;
+};
+
 }  // namespace ffi
 
 }  // namespace compiler