[vm/ffi] Support missing info in marshaller

When ABI-specific integers are introduced, their mappings can be
partial. We need to account for this in the marshaller.

This CL refactors the marshaller API to take an char** error return
parameter and changes the return type to a pointer (nullable) rather
than a reference.

Note that with only this CL we can not generate errors yet, because all
native types are still complete.

TEST=runtime/vm/compiler/ffi/native_type_vm_test.cc
TEST=tests/ffi*

Bug: https://github.com/dart-lang/sdk/issues/42816

Change-Id: Id97e73795c357e129e6280e8c5b528d18072c14d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/221632
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
diff --git a/runtime/vm/compiler/ffi/marshaller.cc b/runtime/vm/compiler/ffi/marshaller.cc
index dbaff91..6cf5ece 100644
--- a/runtime/vm/compiler/ffi/marshaller.cc
+++ b/runtime/vm/compiler/ffi/marshaller.cc
@@ -25,9 +25,10 @@
 const intptr_t kNativeParamsStartAt = 1;
 
 // Representations of the arguments and return value of a C signature function.
-static const NativeFunctionType& NativeFunctionSignature(
+const NativeFunctionType* NativeFunctionTypeFromFunctionType(
     Zone* zone,
-    const FunctionType& c_signature) {
+    const FunctionType& c_signature,
+    const char** error) {
   ASSERT(c_signature.NumOptionalParameters() == 0);
   ASSERT(c_signature.NumOptionalPositionalParameters() == 0);
 
@@ -38,29 +39,41 @@
   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 rep = NativeType::FromAbstractType(zone, arg_type, error);
+    if (*error != nullptr) {
+      return nullptr;
+    }
+    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_representation =
+      NativeType::FromAbstractType(zone, result_type, error);
+  if (*error != nullptr) {
+    return nullptr;
+  }
 
-  const auto& result = *new (zone) NativeFunctionType(argument_representations,
-                                                      result_representation);
+  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_(
-          FunctionType::ZoneHandle(zone, dart_signature.FfiCSignature())),
-      native_calling_convention_(NativeCallingConvention::FromSignature(
-          zone,
-          NativeFunctionSignature(zone_, c_signature_))) {
-  ASSERT(dart_signature_.IsZoneHandle());
+CallMarshaller* CallMarshaller::FromFunction(Zone* zone,
+                                             const Function& function,
+                                             const char** error) {
+  ASSERT(function.IsZoneHandle());
+  const auto& c_signature =
+      FunctionType::ZoneHandle(zone, function.FfiCSignature());
+  const auto native_function_signature =
+      NativeFunctionTypeFromFunctionType(zone, c_signature, error);
+  if (*error != nullptr) {
+    return nullptr;
+  }
+  const auto& native_calling_convention =
+      NativeCallingConvention::FromSignature(zone, *native_function_signature);
+  return new (zone)
+      CallMarshaller(zone, function, c_signature, native_calling_convention);
 }
 
 AbstractTypePtr BaseMarshaller::CType(intptr_t arg_index) const {
@@ -72,6 +85,27 @@
   return c_signature_.ParameterTypeAt(arg_index + kNativeParamsStartAt);
 }
 
+// Keep consistent with Function::FfiCSignatureReturnsStruct.
+bool BaseMarshaller::IsCompound(intptr_t arg_index) const {
+  const auto& type = AbstractType::Handle(zone_, CType(arg_index));
+  if (IsFfiTypeClassId(type.type_class_id())) {
+    return false;
+  }
+#ifdef DEBUG
+  const auto& cls = Class::Handle(this->zone_, type.type_class());
+  const auto& superClass = Class::Handle(this->zone_, cls.SuperClass());
+  // TODO(http://dartbug.com/42563): Implement AbiSpecificInt.
+  const bool is_struct =
+      String::Handle(this->zone_, superClass.UserVisibleName())
+          .Equals(Symbols::Struct());
+  const bool is_union =
+      String::Handle(this->zone_, superClass.UserVisibleName())
+          .Equals(Symbols::Union());
+  RELEASE_ASSERT(is_struct || is_union);
+#endif
+  return true;
+}
+
 bool BaseMarshaller::ContainsHandles() const {
   return dart_signature_.FfiCSignatureContainsHandles();
 }
@@ -574,13 +608,26 @@
   intptr_t argument_slots_required_ = 0;
 };
 
-CallbackMarshaller::CallbackMarshaller(Zone* zone,
-                                       const Function& dart_signature)
-    : BaseMarshaller(zone, dart_signature),
-      callback_locs_(CallbackArgumentTranslator::TranslateArgumentLocations(
-          zone_,
-          native_calling_convention_.argument_locations(),
-          native_calling_convention_.return_location())) {}
+CallbackMarshaller* CallbackMarshaller::FromFunction(Zone* zone,
+                                                     const Function& function,
+                                                     const char** error) {
+  ASSERT(function.IsZoneHandle());
+  const auto& c_signature =
+      FunctionType::ZoneHandle(zone, function.FfiCSignature());
+  const auto native_function_signature =
+      NativeFunctionTypeFromFunctionType(zone, c_signature, error);
+  if (*error != nullptr) {
+    return nullptr;
+  }
+  const auto& native_calling_convention =
+      NativeCallingConvention::FromSignature(zone, *native_function_signature);
+  const auto& callback_locs =
+      CallbackArgumentTranslator::TranslateArgumentLocations(
+          zone, native_calling_convention.argument_locations(),
+          native_calling_convention.return_location());
+  return new (zone) CallbackMarshaller(
+      zone, function, c_signature, native_calling_convention, callback_locs);
+}
 
 const NativeLocation& CallbackMarshaller::NativeLocationOfNativeParameter(
     intptr_t def_index) const {
diff --git a/runtime/vm/compiler/ffi/marshaller.h b/runtime/vm/compiler/ffi/marshaller.h
index fc29f67..b7607e9 100644
--- a/runtime/vm/compiler/ffi/marshaller.h
+++ b/runtime/vm/compiler/ffi/marshaller.h
@@ -28,6 +28,13 @@
 // Values below 0 index result (result might be multiple if composite).
 const intptr_t kResultIndex = -1;
 
+// Inspects the function signature and transitively any class and field
+// definitions and annotations.
+const NativeFunctionType* NativeFunctionTypeFromFunctionType(
+    Zone* zone,
+    const FunctionType& c_signature,
+    const char** error);
+
 // Provides the mapping from the native calling convention to the Dart calling
 // convention.
 //
@@ -114,11 +121,7 @@
            kFfiBoolCid;
   }
 
-  bool IsCompound(intptr_t arg_index) const {
-    const auto& type = AbstractType::Handle(zone_, CType(arg_index));
-    const bool predefined = IsFfiTypeClassId(type.type_class_id());
-    return !predefined;
-  }
+  bool IsCompound(intptr_t arg_index) const;
 
   // Treated as a null constant in Dart.
   bool IsVoid(intptr_t arg_index) const {
@@ -131,7 +134,14 @@
   StringPtr function_name() const { return dart_signature_.name(); }
 
  protected:
-  BaseMarshaller(Zone* zone, const Function& dart_signature);
+  BaseMarshaller(Zone* zone,
+                 const Function& dart_signature,
+                 const FunctionType& c_signature,
+                 const NativeCallingConvention& native_calling_convention)
+      : zone_(zone),
+        dart_signature_(dart_signature),
+        c_signature_(c_signature),
+        native_calling_convention_(native_calling_convention) {}
 
   ~BaseMarshaller() {}
 
@@ -145,8 +155,18 @@
 
 class CallMarshaller : public BaseMarshaller {
  public:
-  CallMarshaller(Zone* zone, const Function& dart_signature)
-      : BaseMarshaller(zone, dart_signature) {}
+  static CallMarshaller* FromFunction(Zone* zone,
+                                      const Function& function,
+                                      const char** error);
+
+  CallMarshaller(Zone* zone,
+                 const Function& dart_signature,
+                 const FunctionType& c_signature,
+                 const NativeCallingConvention& native_calling_convention)
+      : BaseMarshaller(zone,
+                       dart_signature,
+                       c_signature,
+                       native_calling_convention) {}
 
   virtual Representation RepInFfiCall(intptr_t def_index_global) const;
 
@@ -173,7 +193,20 @@
 
 class CallbackMarshaller : public BaseMarshaller {
  public:
-  CallbackMarshaller(Zone* zone, const Function& dart_signature);
+  static CallbackMarshaller* FromFunction(Zone* zone,
+                                          const Function& function,
+                                          const char** error);
+
+  CallbackMarshaller(Zone* zone,
+                     const Function& dart_signature,
+                     const FunctionType& c_signature,
+                     const NativeCallingConvention& native_calling_convention,
+                     const NativeLocations& callback_locs)
+      : BaseMarshaller(zone,
+                       dart_signature,
+                       c_signature,
+                       native_calling_convention),
+        callback_locs_(callback_locs) {}
 
   virtual Representation RepInFfiCall(intptr_t def_index_global) const;
 
diff --git a/runtime/vm/compiler/ffi/native_type.cc b/runtime/vm/compiler/ffi/native_type.cc
index 6dfaeb7..bf4d452a 100644
--- a/runtime/vm/compiler/ffi/native_type.cc
+++ b/runtime/vm/compiler/ffi/native_type.cc
@@ -7,6 +7,7 @@
 #include "platform/assert.h"
 #include "platform/globals.h"
 #include "vm/class_id.h"
+#include "vm/compiler/ffi/abi.h"
 #include "vm/constants.h"
 #include "vm/zone_text_buffer.h"
 
@@ -386,53 +387,22 @@
   }
 }
 
-NativeType& NativeType::FromTypedDataClassId(Zone* zone, classid_t class_id) {
+const NativeType& NativeType::FromTypedDataClassId(Zone* zone,
+                                                   classid_t class_id) {
   ASSERT(IsFfiPredefinedClassId(class_id));
   const auto fundamental_rep = TypeRepresentation(class_id);
   return *new (zone) NativePrimitiveType(fundamental_rep);
 }
 
 #if !defined(FFI_UNIT_TESTS)
-NativeType& NativeType::FromAbstractType(Zone* zone, const AbstractType& type) {
-  const classid_t class_id = type.type_class_id();
-  if (IsFfiPredefinedClassId(class_id)) {
-    return NativeType::FromTypedDataClassId(zone, class_id);
-  }
-
-  // User-defined structs.
-  const auto& cls = Class::Handle(zone, type.type_class());
-  const auto& superClass = Class::Handle(zone, cls.SuperClass());
-  const bool is_struct = String::Handle(zone, superClass.UserVisibleName())
-                             .Equals(Symbols::Struct());
-  ASSERT(is_struct || String::Handle(zone, superClass.UserVisibleName())
-                          .Equals(Symbols::Union()));
-
-  auto& pragmas = Object::Handle(zone);
-  Library::FindPragma(dart::Thread::Current(), /*only_core=*/false, cls,
-                      Symbols::vm_ffi_struct_fields(), /*multiple=*/true,
-                      &pragmas);
-  ASSERT(!pragmas.IsNull());
-  ASSERT(pragmas.IsGrowableObjectArray());
-  const auto& pragmas_array = GrowableObjectArray::Cast(pragmas);
-  auto& pragma = Instance::Handle(zone);
-  auto& clazz = Class::Handle(zone);
-  auto& library = Library::Handle(zone);
-  for (intptr_t i = 0; i < pragmas_array.Length(); i++) {
-    pragma ^= pragmas_array.At(i);
-    clazz ^= pragma.clazz();
-    library ^= clazz.library();
-    if (String::Handle(zone, clazz.UserVisibleName())
-            .Equals(Symbols::FfiStructLayout()) &&
-        String::Handle(zone, library.url()).Equals(Symbols::DartFfi())) {
-      break;
-    }
-  }
-
+static const NativeType* CompoundFromPragma(Zone* zone,
+                                            const Instance& pragma,
+                                            bool is_struct,
+                                            const char** error) {
   const auto& struct_layout = pragma;
-  const auto& struct_layout_class = clazz;
-  ASSERT(String::Handle(zone, struct_layout_class.UserVisibleName())
+  const auto& clazz = Class::Handle(zone, struct_layout.clazz());
+  ASSERT(String::Handle(zone, clazz.UserVisibleName())
              .Equals(Symbols::FfiStructLayout()));
-  ASSERT(String::Handle(zone, library.url()).Equals(Symbols::DartFfi()));
   const auto& struct_layout_fields = Array::Handle(zone, clazz.fields());
   ASSERT(struct_layout_fields.Length() == 2);
   const auto& types_field =
@@ -460,8 +430,11 @@
       // Subtype of NativeType: Struct, native integer or native float.
       field_type ^= field_types.At(i);
       const auto& field_native_type =
-          NativeType::FromAbstractType(zone, field_type);
-      field_native_types.Add(&field_native_type);
+          NativeType::FromAbstractType(zone, field_type, error);
+      if (*error != nullptr) {
+        return nullptr;
+      }
+      field_native_types.Add(field_native_type);
     } else {
       // Inline array.
       const auto& struct_layout_array_class =
@@ -482,20 +455,67 @@
                  .Equals(Symbols::Length()));
       const auto& length = Smi::Handle(
           zone, Smi::RawCast(field_instance.GetField(length_field)));
-      const auto& element_type = NativeType::FromAbstractType(zone, field_type);
-      const auto& field_native_type =
-          *new (zone) NativeArrayType(element_type, length.AsInt64Value());
-      field_native_types.Add(&field_native_type);
+      const auto element_type =
+          NativeType::FromAbstractType(zone, field_type, error);
+      if (*error != nullptr) {
+        return nullptr;
+      }
+      const auto field_native_type =
+          new (zone) NativeArrayType(*element_type, length.AsInt64Value());
+      field_native_types.Add(field_native_type);
     }
   }
 
   if (is_struct) {
-    return NativeStructType::FromNativeTypes(zone, field_native_types,
-                                             member_packing);
+    return &NativeStructType::FromNativeTypes(zone, field_native_types,
+                                              member_packing);
   } else {
-    return NativeUnionType::FromNativeTypes(zone, field_native_types);
+    return &NativeUnionType::FromNativeTypes(zone, field_native_types);
   }
 }
+
+// TODO(http://dartbug.com/42563): Implement AbiSpecificInt.
+const NativeType* NativeType::FromAbstractType(Zone* zone,
+                                               const AbstractType& type,
+                                               const char** error) {
+  const classid_t class_id = type.type_class_id();
+  if (IsFfiPredefinedClassId(class_id)) {
+    return &NativeType::FromTypedDataClassId(zone, class_id);
+  }
+
+  // User-defined structs or unions.
+  const auto& cls = Class::Handle(zone, type.type_class());
+  const auto& superClass = Class::Handle(zone, cls.SuperClass());
+  const bool is_struct = String::Handle(zone, superClass.UserVisibleName())
+                             .Equals(Symbols::Struct());
+  const bool is_union = String::Handle(zone, superClass.UserVisibleName())
+                            .Equals(Symbols::Union());
+  RELEASE_ASSERT(is_struct || is_union);
+
+  auto& pragmas = Object::Handle(zone);
+  String& pragma_name = String::Handle(zone);
+  pragma_name = Symbols::vm_ffi_struct_fields().ptr();
+  Library::FindPragma(dart::Thread::Current(), /*only_core=*/false, cls,
+                      pragma_name, /*multiple=*/true, &pragmas);
+  ASSERT(!pragmas.IsNull());
+  ASSERT(pragmas.IsGrowableObjectArray());
+  const auto& pragmas_array = GrowableObjectArray::Cast(pragmas);
+  auto& pragma = Instance::Handle(zone);
+  auto& clazz = Class::Handle(zone);
+  auto& library = Library::Handle(zone);
+  const String& class_symbol = Symbols::FfiStructLayout();
+  for (intptr_t i = 0; i < pragmas_array.Length(); i++) {
+    pragma ^= pragmas_array.At(i);
+    clazz ^= pragma.clazz();
+    library ^= clazz.library();
+    if (String::Handle(zone, clazz.UserVisibleName()).Equals(class_symbol) &&
+        String::Handle(zone, library.url()).Equals(Symbols::DartFfi())) {
+      break;
+    }
+  }
+
+  return CompoundFromPragma(zone, pragma, is_struct, error);
+}
 #endif
 
 #if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
diff --git a/runtime/vm/compiler/ffi/native_type.h b/runtime/vm/compiler/ffi/native_type.h
index 2ee284a..f60fea6 100644
--- a/runtime/vm/compiler/ffi/native_type.h
+++ b/runtime/vm/compiler/ffi/native_type.h
@@ -57,9 +57,11 @@
 class NativeType : public ZoneAllocated {
  public:
 #if !defined(FFI_UNIT_TESTS)
-  static NativeType& FromAbstractType(Zone* zone, const AbstractType& type);
+  static const NativeType* FromAbstractType(Zone* zone,
+                                            const AbstractType& type,
+                                            const char** error);
 #endif
-  static NativeType& FromTypedDataClassId(Zone* zone, classid_t class_id);
+  static const NativeType& FromTypedDataClassId(Zone* zone, classid_t class_id);
 
 #if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
   static NativePrimitiveType& FromUnboxedRepresentation(Zone* zone,
diff --git a/runtime/vm/compiler/ffi/native_type_vm_test.cc b/runtime/vm/compiler/ffi/native_type_vm_test.cc
index 196ae4c..50a1e07 100644
--- a/runtime/vm/compiler/ffi/native_type_vm_test.cc
+++ b/runtime/vm/compiler/ffi/native_type_vm_test.cc
@@ -17,7 +17,9 @@
   const auto& ffi_library = Library::Handle(Library::FfiLibrary());
   const auto& int8_class = Class::Handle(GetClass(ffi_library, "Int8"));
   const auto& int8_type = Type::Handle(int8_class.DeclarationType());
-  const auto& native_type = NativeType::FromAbstractType(Z, int8_type);
+  const char* error = nullptr;
+  const auto& native_type = *NativeType::FromAbstractType(Z, int8_type, &error);
+  EXPECT_NULLPTR(error);
 
   EXPECT_EQ(1, native_type.SizeInBytes());
   EXPECT_STREQ("int8", native_type.ToCString());
@@ -36,7 +38,10 @@
   const auto& ffi_library = Library::Handle(Library::FfiLibrary());
   const auto& bool_class = Class::Handle(GetClass(ffi_library, "Bool"));
   const auto& bool_type = Type::Handle(bool_class.DeclarationType());
-  const auto& bool_native_type = NativeType::FromAbstractType(Z, bool_type);
+  const char* error = nullptr;
+  const auto& bool_native_type =
+      *NativeType::FromAbstractType(Z, bool_type, &error);
+  EXPECT_NULLPTR(error);
 
   const auto& uint8_native_type = *new (Z) NativePrimitiveType(kUint8);
 
@@ -67,8 +72,10 @@
   const auto& struct_class = Class::Handle(GetClass(root_library, "MyStruct"));
   const auto& struct_type = Type::Handle(struct_class.DeclarationType());
 
+  const char* error = nullptr;
   const auto& native_type =
-      NativeType::FromAbstractType(Z, struct_type).AsCompound();
+      NativeType::FromAbstractType(Z, struct_type, &error)->AsCompound();
+  EXPECT_NULLPTR(error);
 
   EXPECT_EQ(2, native_type.members().length());
 
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 981ca96..2b7346a 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -4488,7 +4488,12 @@
   Fragment function_body(instruction_cursor);
   function_body += CheckStackOverflowInPrologue(function.token_pos());
 
-  const auto& marshaller = *new (Z) compiler::ffi::CallMarshaller(Z, function);
+  const char* error = nullptr;
+  const auto marshaller_ptr =
+      compiler::ffi::CallMarshaller::FromFunction(Z, function, &error);
+  RELEASE_ASSERT(error == nullptr);
+  RELEASE_ASSERT(marshaller_ptr != nullptr);
+  const auto& marshaller = *marshaller_ptr;
 
   const bool signature_contains_handles = marshaller.ContainsHandles();
 
@@ -4634,8 +4639,12 @@
 }
 
 FlowGraph* FlowGraphBuilder::BuildGraphOfFfiCallback(const Function& function) {
-  const auto& marshaller =
-      *new (Z) compiler::ffi::CallbackMarshaller(Z, function);
+  const char* error = nullptr;
+  const auto marshaller_ptr =
+      compiler::ffi::CallbackMarshaller::FromFunction(Z, function, &error);
+  RELEASE_ASSERT(error == nullptr);
+  RELEASE_ASSERT(marshaller_ptr != nullptr);
+  const auto& marshaller = *marshaller_ptr;
 
   graph_entry_ =
       new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 2513eb4..df10c58 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -7553,12 +7553,26 @@
          kFfiHandleCid;
 }
 
+// Keep consistent with BaseMarshaller::IsCompound.
 bool Function::FfiCSignatureReturnsStruct() const {
   ASSERT(IsFfiTrampoline());
-  const FunctionType& c_signature = FunctionType::Handle(FfiCSignature());
-  const auto& return_type = AbstractType::Handle(c_signature.result_type());
-  const bool predefined = IsFfiTypeClassId(return_type.type_class_id());
-  return !predefined;
+  Zone* zone = Thread::Current()->zone();
+  const auto& c_signature = FunctionType::Handle(zone, FfiCSignature());
+  const auto& type = AbstractType::Handle(zone, c_signature.result_type());
+  if (IsFfiTypeClassId(type.type_class_id())) {
+    return false;
+  }
+  // TODO(http://dartbug.com/42563): Implement AbiSpecificInt.
+#ifdef DEBUG
+  const auto& cls = Class::Handle(zone, type.type_class());
+  const auto& superClass = Class::Handle(zone, cls.SuperClass());
+  const bool is_struct = String::Handle(zone, superClass.UserVisibleName())
+                             .Equals(Symbols::Struct());
+  const bool is_union = String::Handle(zone, superClass.UserVisibleName())
+                            .Equals(Symbols::Union());
+  RELEASE_ASSERT(is_struct || is_union);
+#endif
+  return true;
 }
 
 int32_t Function::FfiCallbackId() const {