Version 2.16.0-72.0.dev

Merge commit 'aa27868c84977eea064b02723978ac92c9df9e52' into 'dev'
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index cc621a7..b4924ce 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -477,6 +477,9 @@
   CompileTimeErrorCode.YIELD_IN_NON_GENERATOR,
   CompileTimeErrorCode.YIELD_EACH_OF_INVALID_TYPE,
   CompileTimeErrorCode.YIELD_OF_INVALID_TYPE,
+  FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_EXTRA,
+  FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_MISSING,
+  FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED,
   FfiCode.ANNOTATION_ON_POINTER_FIELD,
   FfiCode.ARGUMENT_MUST_BE_A_CONSTANT,
   FfiCode.CREATION_OF_STRUCT_OR_UNION,
diff --git a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
index 4264f45..e794a9b 100644
--- a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
+++ b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
@@ -18,6 +18,34 @@
   /**
    * No parameters.
    */
+  static const FfiCode ABI_SPECIFIC_INTEGER_MAPPING_EXTRA = FfiCode(
+    'ABI_SPECIFIC_INTEGER_MAPPING_EXTRA',
+    "Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a 'NativeType' integer with a fixed size.",
+    correctionMessage: "Try removing the extra annotation.",
+  );
+
+  /**
+   * No parameters.
+   */
+  static const FfiCode ABI_SPECIFIC_INTEGER_MAPPING_MISSING = FfiCode(
+    'ABI_SPECIFIC_INTEGER_MAPPING_MISSING',
+    "Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a 'NativeType' integer with a fixed size.",
+    correctionMessage: "Try adding an annotation.",
+  );
+
+  /**
+   * No parameters.
+   */
+  static const FfiCode ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED = FfiCode(
+    'ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED',
+    "Only mappings to 'Int8', 'Int16', 'Int32', 'Int64', 'Uint8', 'Uint16', 'UInt32', and 'Uint64' are supported.",
+    correctionMessage:
+        "Try changing the value to 'Int8', 'Int16', 'Int32', 'Int64', 'Uint8', 'Uint16', 'UInt32', or 'Uint64'.",
+  );
+
+  /**
+   * No parameters.
+   */
   static const FfiCode ANNOTATION_ON_POINTER_FIELD = FfiCode(
     'ANNOTATION_ON_POINTER_FIELD',
     "Fields in a struct class whose type is 'Pointer' should not have any annotations.",
@@ -302,9 +330,9 @@
    */
   static const FfiCode NON_SIZED_TYPE_ARGUMENT = FfiCode(
     'NON_SIZED_TYPE_ARGUMENT',
-    "Type arguments to '{0}' can't have the type '{1}'. They can only be declared as native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct' or 'Union'.",
+    "Type arguments to '{0}' can't have the type '{1}'. They can only be declared as native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct', 'Union', or 'AbiSpecificInteger'.",
     correctionMessage:
-        "Try using a native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct' or 'Union'.",
+        "Try using a native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct', 'Union', or 'AbiSpecificInteger'.",
   );
 
   /**
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index 767d937..69b37a5 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -16,6 +16,9 @@
 /// used. See 'pkg/vm/lib/transformations/ffi_checks.md' for the specification
 /// of the desired hints.
 class FfiVerifier extends RecursiveAstVisitor<void> {
+  static const _abiSpecificIntegerClassName = 'AbiSpecificInteger';
+  static const _abiSpecificIntegerMappingClassName =
+      'AbiSpecificIntegerMapping';
   static const _allocatorClassName = 'Allocator';
   static const _allocateExtensionMethodName = 'call';
   static const _allocatorExtensionName = 'AllocatorAlloc';
@@ -25,7 +28,7 @@
   static const _opaqueClassName = 'Opaque';
   static const _ffiNativeName = 'FfiNative';
 
-  static const Set<String> _primitiveIntegerNativeTypes = {
+  static const Set<String> _primitiveIntegerNativeTypesFixedSize = {
     'Int8',
     'Int16',
     'Int32',
@@ -34,6 +37,9 @@
     'Uint16',
     'Uint32',
     'Uint64',
+  };
+  static const Set<String> _primitiveIntegerNativeTypes = {
+    ..._primitiveIntegerNativeTypesFixedSize,
     'IntPtr'
   };
 
@@ -85,8 +91,12 @@
           if (className == _structClassName) {
             _validatePackedAnnotation(node.metadata);
           }
+        } else if (className == _abiSpecificIntegerClassName) {
+          _validateAbiSpecificIntegerMappingAnnotation(
+              node.name, node.metadata);
         } else if (className != _allocatorClassName &&
-            className != _opaqueClassName) {
+            className != _opaqueClassName &&
+            className != _abiSpecificIntegerClassName) {
           _errorReporter.reportErrorForNode(
               FfiCode.SUBTYPE_OF_FFI_CLASS_IN_EXTENDS,
               superclass.name,
@@ -452,6 +462,9 @@
     if (nativeType.isArray) {
       return true;
     }
+    if (nativeType.isAbiSpecificIntegerSubtype) {
+      return true;
+    }
     return false;
   }
 
@@ -523,6 +536,9 @@
       if (nativeType.isOpaqueSubtype) {
         return true;
       }
+      if (nativeType.isAbiSpecificIntegerSubtype) {
+        return true;
+      }
       if (allowArray && nativeType.isArray) {
         return _isValidFfiNativeType(nativeType.typeArguments.single,
             allowVoid: false, allowEmptyStruct: false);
@@ -591,10 +607,51 @@
       } else if (_primitiveBoolNativeType == name) {
         return _PrimitiveDartType.bool;
       }
+      if (element.type.returnType.isAbiSpecificIntegerSubtype) {
+        return _PrimitiveDartType.int;
+      }
     }
     return _PrimitiveDartType.none;
   }
 
+  /// Validate that the [annotations] include at most one mapping annotation.
+  void _validateAbiSpecificIntegerMappingAnnotation(
+      AstNode errorNode, NodeList<Annotation> annotations) {
+    final ffiPackedAnnotations = annotations
+        .where((annotation) => annotation.isAbiSpecificIntegerMapping)
+        .toList();
+
+    if (ffiPackedAnnotations.isEmpty) {
+      _errorReporter.reportErrorForNode(
+          FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_MISSING, errorNode);
+      return;
+    }
+
+    if (ffiPackedAnnotations.length > 1) {
+      final extraAnnotations = ffiPackedAnnotations.skip(1);
+      for (final annotation in extraAnnotations) {
+        _errorReporter.reportErrorForNode(
+            FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_EXTRA, annotation.name);
+      }
+    }
+
+    final annotationConstant =
+        ffiPackedAnnotations.first.elementAnnotation?.computeConstantValue();
+    final mappingValues = annotationConstant?.getField('mapping')?.toMapValue();
+    if (mappingValues == null) {
+      return;
+    }
+    for (final nativeType in mappingValues.values) {
+      final nativeTypeName = nativeType?.type?.element?.name;
+      if (nativeTypeName != null &&
+          !_primitiveIntegerNativeTypesFixedSize.contains(nativeTypeName)) {
+        _errorReporter.reportErrorForNode(
+            FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED,
+            ffiPackedAnnotations.first.name);
+      }
+    }
+  }
+
   void _validateAllocate(FunctionExpressionInvocation node) {
     final typeArgumentTypes = node.typeArgumentTypes;
     if (typeArgumentTypes == null || typeArgumentTypes.length != 1) {
@@ -619,7 +676,9 @@
     bool requiredFound = false;
     List<Annotation> extraAnnotations = [];
     for (Annotation annotation in annotations) {
-      if (annotation.element.ffiClass != null) {
+      if (annotation.element.ffiClass != null ||
+          annotation.element?.enclosingElement.isAbiSpecificIntegerSubclass ==
+              true) {
         if (requiredFound) {
           extraAnnotations.add(annotation);
         } else {
@@ -744,7 +803,10 @@
   bool _validateCompatibleNativeType(
       DartType dartType, DartType nativeType, bool checkCovariance) {
     final nativeReturnType = _primitiveNativeType(nativeType);
-    if (nativeReturnType == _PrimitiveDartType.int) {
+    if (nativeReturnType == _PrimitiveDartType.int ||
+        (nativeType is InterfaceType &&
+            nativeType.superclass?.element.name ==
+                _abiSpecificIntegerClassName)) {
       return dartType.isDartCoreInt;
     } else if (nativeReturnType == _PrimitiveDartType.double) {
       return dartType.isDartCoreDouble;
@@ -1197,6 +1259,14 @@
         element.ffiClass != null &&
         element.enclosingElement.name == 'Packed';
   }
+
+  bool get isAbiSpecificIntegerMapping {
+    final element = this.element;
+    return element is ConstructorElement &&
+        element.ffiClass != null &&
+        element.enclosingElement.name ==
+            FfiVerifier._abiSpecificIntegerMappingClassName;
+  }
 }
 
 extension on ElementAnnotation {
@@ -1332,6 +1402,21 @@
     return element is ClassElement && element.supertype.isUnion;
   }
 
+  /// Return `true` if this represents the class `AbiSpecificInteger`.
+  bool get isAbiSpecificInteger {
+    final element = this;
+    return element is ClassElement &&
+        element.name == FfiVerifier._abiSpecificIntegerClassName &&
+        element.isFfiClass;
+  }
+
+  /// Return `true` if this represents a subclass of the class
+  /// `AbiSpecificInteger`.
+  bool get isAbiSpecificIntegerSubclass {
+    final element = this;
+    return element is ClassElement && element.supertype.isAbiSpecificInteger;
+  }
+
   /// If this is a class element from `dart:ffi`, return it.
   ClassElement? get ffiClass {
     var element = this;
@@ -1398,6 +1483,11 @@
     final self = this;
     return self is InterfaceType && self.element.isUnion;
   }
+
+  bool get isAbiSpecificInteger {
+    final self = this;
+    return self is InterfaceType && self.element.isAbiSpecificInteger;
+  }
 }
 
 extension on DartType {
@@ -1467,6 +1557,22 @@
     return false;
   }
 
+  /// Returns `true` iff this is an Abi-specific integer type,
+  /// i.e. a subtype of `AbiSpecificInteger`.
+  bool get isAbiSpecificIntegerSubtype {
+    final self = this;
+    if (self is InterfaceType) {
+      final superType = self.element.supertype;
+      if (superType != null) {
+        final superClassElement = superType.element;
+        return superClassElement.name ==
+                FfiVerifier._abiSpecificIntegerClassName &&
+            superClassElement.isFfiClass;
+      }
+    }
+    return false;
+  }
+
   /// Returns `true` iff this is a opaque type, i.e. a subtype of `Opaque`.
   bool get isOpaqueSubtype {
     final self = this;
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
index a7ecd5a..7693ddd 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -47,7 +47,7 @@
 
   Future<T> whenComplete(action());
 
-  static Future<List<T>> wait<T>(Iterable<Future<T>> futures, 
+  static Future<List<T>> wait<T>(Iterable<Future<T>> futures,
     {void cleanUp(T successValue)?}) => throw 0;
 }
 
@@ -705,6 +705,10 @@
   const Double();
 }
 
+class IntPtr extends NativeType {
+  const IntPtr();
+}
+
 class Pointer<T extends NativeType> extends NativeType {
   external factory Pointer.fromAddress(int ptr);
 
@@ -789,6 +793,50 @@
   final bool isLeaf;
   const FfiNative(this.nativeName, {this.isLeaf: false});
 }
+
+class Abi {
+  static const androidArm = _androidArm;
+  static const androidArm64 = _androidArm64;
+  static const androidIA32 = _androidIA32;
+
+  static const _androidArm = Abi._(_Architecture.arm, _OS.android);
+  static const _androidArm64 = Abi._(_Architecture.arm64, _OS.android);
+  static const _androidIA32 = Abi._(_Architecture.ia32, _OS.android);
+
+  final _OS _os;
+
+  final _Architecture _architecture;
+
+  const Abi._(this._architecture, this._os);
+}
+
+enum _Architecture {
+  arm,
+  arm64,
+  ia32,
+  x64,
+}
+
+enum _OS {
+  android,
+  fuchsia,
+  ios,
+  linux,
+  macos,
+  windows,
+}
+
+
+class AbiSpecificInteger extends NativeType {
+  const AbiSpecificInteger();
+}
+
+class AbiSpecificIntegerMapping {
+  final Map<Abi, NativeType> mapping;
+
+  const AbiSpecificIntegerMapping(this.mapping);
+}
+
 ''',
   )
 ]);
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 8c2b4be..7ad997e 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -13775,6 +13775,18 @@
       }
       ```
 FfiCode:
+  ABI_SPECIFIC_INTEGER_MAPPING_EXTRA:
+    problemMessage: "Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a 'NativeType' integer with a fixed size."
+    correctionMessage: Try removing the extra annotation.
+    comment: No parameters.
+  ABI_SPECIFIC_INTEGER_MAPPING_MISSING:
+    problemMessage: "Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a 'NativeType' integer with a fixed size."
+    correctionMessage: Try adding an annotation.
+    comment: No parameters.
+  ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED:
+    problemMessage: "Only mappings to 'Int8', 'Int16', 'Int32', 'Int64', 'Uint8', 'Uint16', 'UInt32', and 'Uint64' are supported."
+    correctionMessage: Try changing the value to 'Int8', 'Int16', 'Int32', 'Int64', 'Uint8', 'Uint16', 'UInt32', or 'Uint64'.
+    comment: No parameters.
   ANNOTATION_ON_POINTER_FIELD:
     problemMessage: "Fields in a struct class whose type is 'Pointer' should not have any annotations."
     correctionMessage: Try removing the annotation.
@@ -13916,8 +13928,8 @@
     correctionMessage: Try changing the input to a positive number.
     comment: No parameters.
   NON_SIZED_TYPE_ARGUMENT:
-    problemMessage: "Type arguments to '{0}' can't have the type '{1}'. They can only be declared as native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct' or 'Union'."
-    correctionMessage: "Try using a native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct' or 'Union'."
+    problemMessage: "Type arguments to '{0}' can't have the type '{1}'. They can only be declared as native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct', 'Union', or 'AbiSpecificInteger'."
+    correctionMessage: "Try using a native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct', 'Union', or 'AbiSpecificInteger'."
     comment: |-
       Parameters:
       0: the type of the field
diff --git a/pkg/analyzer/test/src/diagnostics/abi_specific_integer_mapping_test.dart b/pkg/analyzer/test/src/diagnostics/abi_specific_integer_mapping_test.dart
new file mode 100644
index 0000000..746ce71
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/abi_specific_integer_mapping_test.dart
@@ -0,0 +1,81 @@
+// 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 'package:analyzer/src/dart/error/ffi_code.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AbiSpecificIntegerMappingTest);
+  });
+}
+
+@reflectiveTest
+class AbiSpecificIntegerMappingTest extends PubPackageResolutionTest {
+  test_doubleMapping() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+@AbiSpecificIntegerMapping({})
+@AbiSpecificIntegerMapping({})
+class UintPtr extends AbiSpecificInteger {
+  const UintPtr();
+}
+''', [
+      error(FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_EXTRA, 51, 25),
+    ]);
+  }
+
+  test_invalidMapping() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+@AbiSpecificIntegerMapping({
+  Abi.androidArm: Uint32(),
+  Abi.androidArm64: IntPtr(),
+  Abi.androidIA32: UintPtr(),
+})
+class UintPtr extends AbiSpecificInteger {
+  const UintPtr();
+}
+''', [
+      error(FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED, 20, 25),
+    ]);
+  }
+
+  test_noMapping() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+class UintPtr extends AbiSpecificInteger {
+  const UintPtr();
+}
+''', [
+      error(FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_MISSING, 25, 7),
+    ]);
+  }
+
+  test_singleMapping() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+@AbiSpecificIntegerMapping({})
+class UintPtr extends AbiSpecificInteger {
+  const UintPtr();
+}
+''');
+  }
+
+  test_validMapping() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+@AbiSpecificIntegerMapping({
+  Abi.androidArm: Uint32(),
+  Abi.androidArm64: Uint64(),
+  Abi.androidIA32: Uint32(),
+})
+class UintPtr extends AbiSpecificInteger {
+  const UintPtr();
+}
+''');
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index 779326c..4456cef 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -4,6 +4,7 @@
 
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
+import 'abi_specific_integer_mapping_test.dart' as abi_specific_integer_mapping;
 import 'abstract_class_member_test.dart' as abstract_class_member;
 import 'abstract_field_constructor_initializer_test.dart'
     as abstract_field_constructor_initializer;
@@ -721,6 +722,7 @@
 
 main() {
   defineReflectiveSuite(() {
+    abi_specific_integer_mapping.main();
     abstract_class_member.main();
     abstract_field_constructor_initializer.main();
     abstract_field_initializer.main();
diff --git a/pkg/meta/lib/meta.dart b/pkg/meta/lib/meta.dart
index 584fe42..ba14be9 100644
--- a/pkg/meta/lib/meta.dart
+++ b/pkg/meta/lib/meta.dart
@@ -274,7 +274,7 @@
 
 /// Used to annotate a method, field, or getter within a class, mixin, or
 /// extension, or a or top-level getter, variable or function to indicate that
-/// the value obtained by invoking it should be use. A value is considered used
+/// the value obtained by invoking it should be used. A value is considered used
 /// if it is assigned to a variable, passed to a function, or used as the target
 /// of an invocation, or invoked (if the result is itself a function).
 ///
diff --git a/pkg/vm/lib/transformations/ffi/common.dart b/pkg/vm/lib/transformations/ffi/common.dart
index 54ffd5c..42235ff 100644
--- a/pkg/vm/lib/transformations/ffi/common.dart
+++ b/pkg/vm/lib/transformations/ffi/common.dart
@@ -234,6 +234,7 @@
   final Procedure lookupFunctionTearoff;
   final Procedure getNativeFieldFunction;
   final Procedure reachabilityFenceFunction;
+  final Procedure checkAbiSpecificIntegerMappingFunction;
 
   late final InterfaceType nativeFieldWrapperClass1Type;
   late final InterfaceType voidType;
@@ -417,7 +418,9 @@
         getNativeFieldFunction = index.getTopLevelProcedure(
             'dart:nativewrappers', '_getNativeField'),
         reachabilityFenceFunction =
-            index.getTopLevelProcedure('dart:_internal', 'reachabilityFence') {
+            index.getTopLevelProcedure('dart:_internal', 'reachabilityFence'),
+        checkAbiSpecificIntegerMappingFunction = index.getTopLevelProcedure(
+            'dart:ffi', "_checkAbiSpecificIntegerMapping") {
     nativeFieldWrapperClass1Type = nativeFieldWrapperClass1Class.getThisType(
         coreTypes, Nullability.nonNullable);
     voidType = nativeTypesClasses[NativeType.kVoid]!
@@ -453,7 +456,7 @@
   /// [Bool]                               -> [bool]
   /// [Void]                               -> [void]
   /// [Pointer]<T>                         -> [Pointer]<T>
-  /// T extends [Pointer]                  -> T
+  /// T extends [Compound]                 -> T
   /// [Handle]                             -> [Object]
   /// [NativeFunction]<T1 Function(T2, T3) -> S1 Function(S2, S3)
   ///    where DartRepresentationOf(Tn) -> Sn
@@ -535,27 +538,42 @@
   InterfaceType _listOfIntType() => InterfaceType(
       listClass, Nullability.legacy, [coreTypes.intLegacyRawType]);
 
-  ConstantExpression intListConstantExpression(List<int> values) =>
+  ConstantExpression intListConstantExpression(List<int?> values) =>
       ConstantExpression(
-          ListConstant(coreTypes.intLegacyRawType,
-              [for (var v in values) IntConstant(v)]),
+          ListConstant(coreTypes.intLegacyRawType, [
+            for (var v in values)
+              if (v != null) IntConstant(v) else NullConstant()
+          ]),
           _listOfIntType());
 
   /// Expression that queries VM internals at runtime to figure out on which ABI
   /// we are.
-  Expression runtimeBranchOnLayout(Map<Abi, int> values) {
-    return InstanceInvocation(
+  Expression runtimeBranchOnLayout(Map<Abi, int?> values) {
+    final result = InstanceInvocation(
         InstanceAccessKind.Instance,
         intListConstantExpression([
-          for (final abi in Abi.values) values[abi]!,
+          for (final abi in Abi.values) values[abi],
         ]),
         listElementAt.name,
         Arguments([StaticInvocation(abiMethod, Arguments([]))]),
         interfaceTarget: listElementAt,
         functionType: Substitution.fromInterfaceType(_listOfIntType())
             .substituteType(listElementAt.getterType) as FunctionType);
+    if (values.isPartial) {
+      return checkAbiSpecificIntegerMapping(result);
+    }
+    return result;
   }
 
+  Expression checkAbiSpecificIntegerMapping(Expression nullableExpression) =>
+      StaticInvocation(
+        checkAbiSpecificIntegerMappingFunction,
+        Arguments(
+          [nullableExpression],
+          types: [InterfaceType(intClass, Nullability.nonNullable)],
+        ),
+      );
+
   /// Generates an expression that returns a new `Pointer<dartType>` offset
   /// by [offset] from [pointer].
   ///
@@ -819,3 +837,8 @@
   }
   return false;
 }
+
+extension on Map<Abi, Object?> {
+  bool get isPartial =>
+      [for (final abi in Abi.values) this[abi]].contains(null);
+}
diff --git a/pkg/vm/lib/transformations/ffi/definitions.dart b/pkg/vm/lib/transformations/ffi/definitions.dart
index 29ae1d9..fbf2052 100644
--- a/pkg/vm/lib/transformations/ffi/definitions.dart
+++ b/pkg/vm/lib/transformations/ffi/definitions.dart
@@ -2,8 +2,6 @@
 // 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:math' as math;
-
 import 'package:front_end/src/api_unstable/vm.dart'
     show
         messageFfiPackedAnnotationAlignment,
@@ -33,6 +31,7 @@
 
 import 'abi.dart';
 import 'common.dart';
+import 'native_type_cfe.dart';
 
 /// Checks and elaborates the dart:ffi compounds and their fields.
 ///
@@ -449,9 +448,8 @@
         // This class is invalid, but continue reporting other errors on it.
         success = false;
       } else {
-        final DartType nativeType = InterfaceType(
-            nativeTypesClasses[_getFieldType(nativeTypeAnnos.first)!]!,
-            Nullability.legacy);
+        final DartType nativeType =
+            InterfaceType(nativeTypeAnnos.first, Nullability.legacy);
         final DartType? shouldBeDartType = convertNativeTypeToDartType(
             nativeType,
             allowCompounds: true,
@@ -705,7 +703,6 @@
 
   static const vmFfiStructFields = "vm:ffi:struct-fields";
 
-  // return value is nullable.
   InstanceConstant? _compoundAnnotatedFields(Class node) {
     for (final annotation in node.annotations) {
       if (annotation is ConstantExpression) {
@@ -775,7 +772,6 @@
     return UnionNativeTypeCfe(compoundClass, members);
   }
 
-  // packing is `int?`.
   void _annoteCompoundWithFields(
       Class node, List<NativeTypeCfe> types, int? packing) {
     List<Constant> constants =
@@ -795,8 +791,13 @@
         InterfaceType(pragmaClass, Nullability.nonNullable, [])));
   }
 
-  void _generateMethodsForField(Class node, Field field, NativeTypeCfe type,
-      Map<Abi, int> offsets, bool unalignedAccess, IndexedClass? indexedClass) {
+  void _generateMethodsForField(
+      Class node,
+      Field field,
+      NativeTypeCfe type,
+      Map<Abi, int?> offsets,
+      bool unalignedAccess,
+      IndexedClass? indexedClass) {
     // TODO(johnniwinther): Avoid passing [indexedClass]. When compiling
     // incrementally, [field] should already carry the references from
     // [indexedClass].
@@ -847,7 +848,7 @@
   /// If sizes are not supplied still emits a field so that the use site
   /// transformer can still rewrite to it.
   void _addSizeOfField(Class compound, IndexedClass? indexedClass,
-      [Map<Abi, int>? sizes = null]) {
+      [Map<Abi, int?>? sizes = null]) {
     if (sizes == null) {
       sizes = {for (var abi in Abi.values) abi: 0};
     }
@@ -956,546 +957,3 @@
 
   CompoundField(this.type, this.field, this.getter, this.setter);
 }
-
-/// The layout of a `Struct` or `Union` in one [Abi].
-class CompoundLayout {
-  /// Size of the entire struct or union.
-  final int size;
-
-  /// Alignment of struct or union when nested in a struct.
-  final int alignment;
-
-  /// Offset in bytes for each field, indexed by field number.
-  ///
-  /// Always 0 for unions.
-  final List<int> offsets;
-
-  CompoundLayout(this.size, this.alignment, this.offsets);
-}
-
-/// AST node wrapper for native types.
-///
-/// This algebraic data structure does not stand on its own but refers
-/// intimately to AST nodes such as [Class].
-abstract class NativeTypeCfe {
-  factory NativeTypeCfe(FfiTransformer transformer, DartType dartType,
-      {List<int>? arrayDimensions,
-      Map<Class, NativeTypeCfe> compoundCache = const {}}) {
-    if (transformer.isPrimitiveType(dartType)) {
-      final clazz = (dartType as InterfaceType).classNode;
-      final nativeType = transformer.getType(clazz)!;
-      return PrimitiveNativeTypeCfe(nativeType, clazz);
-    }
-    if (transformer.isPointerType(dartType)) {
-      return PointerNativeTypeCfe();
-    }
-    if (transformer.isCompoundSubtype(dartType)) {
-      final clazz = (dartType as InterfaceType).classNode;
-      if (compoundCache.containsKey(clazz)) {
-        return compoundCache[clazz]!;
-      } else {
-        throw "Class '$clazz' not found in compoundCache.";
-      }
-    }
-    if (transformer.isArrayType(dartType)) {
-      if (arrayDimensions == null) {
-        throw "Must have array dimensions for ArrayType.";
-      }
-      if (arrayDimensions.length == 0) {
-        throw "Must have a size for this array dimension.";
-      }
-      final elementType = transformer.arraySingleElementType(dartType);
-      final elementCfeType =
-          NativeTypeCfe(transformer, elementType, compoundCache: compoundCache);
-      if (elementCfeType is InvalidNativeTypeCfe) {
-        return elementCfeType;
-      }
-      return ArrayNativeTypeCfe.multi(elementCfeType, arrayDimensions);
-    }
-    throw "Invalid type $dartType";
-  }
-
-  /// The size in bytes per [Abi].
-  Map<Abi, int> get size;
-
-  /// The alignment inside structs in bytes per [Abi].
-  ///
-  /// This is not the alignment on stack, this is only calculated in the VM.
-  Map<Abi, int> get alignment;
-
-  /// Generates a Constant representing the type which is consumed by the VM.
-  ///
-  /// Takes [transformer] to be able to lookup classes and methods.
-  ///
-  /// See runtime/vm/compiler/ffi/native_type.cc:NativeType::FromAbstractType.
-  Constant generateConstant(FfiTransformer transformer);
-
-  /// Generates the return statement for a compound field getter with this type.
-  ///
-  /// Takes [transformer] to be able to lookup classes and methods.
-  ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
-      Map<Abi, int> offsets, bool unalignedAccess, FfiTransformer transformer);
-
-  /// Generates the return statement for a compound field setter with this type.
-  ///
-  /// Takes [transformer] to be able to lookup classes and methods.
-  ReturnStatement generateSetterStatement(
-      DartType dartType,
-      int fileOffset,
-      Map<Abi, int> offsets,
-      bool unalignedAccess,
-      VariableDeclaration argument,
-      FfiTransformer transformer);
-}
-
-class InvalidNativeTypeCfe implements NativeTypeCfe {
-  final String reason;
-
-  InvalidNativeTypeCfe(this.reason);
-
-  @override
-  Map<Abi, int> get alignment => throw reason;
-
-  @override
-  Constant generateConstant(FfiTransformer transformer) => throw reason;
-
-  @override
-  ReturnStatement generateGetterStatement(
-          DartType dartType,
-          int fileOffset,
-          Map<Abi, int> offsets,
-          bool unalignedAccess,
-          FfiTransformer transformer) =>
-      throw reason;
-
-  @override
-  ReturnStatement generateSetterStatement(
-          DartType dartType,
-          int fileOffset,
-          Map<Abi, int> offsets,
-          bool unalignedAccess,
-          VariableDeclaration argument,
-          FfiTransformer transformer) =>
-      throw reason;
-
-  @override
-  Map<Abi, int> get size => throw reason;
-}
-
-class PrimitiveNativeTypeCfe implements NativeTypeCfe {
-  final NativeType nativeType;
-
-  final Class clazz;
-
-  PrimitiveNativeTypeCfe(this.nativeType, this.clazz);
-
-  @override
-  Map<Abi, int> get size {
-    final int size = nativeTypeSizes[nativeType]!;
-    if (size == WORD_SIZE) {
-      return wordSize;
-    }
-    return {for (var abi in Abi.values) abi: size};
-  }
-
-  @override
-  Map<Abi, int> get alignment => {
-        for (var abi in Abi.values)
-          abi: nonSizeAlignment[abi]![nativeType] ?? size[abi]!
-      };
-
-  @override
-  Constant generateConstant(FfiTransformer transformer) =>
-      TypeLiteralConstant(InterfaceType(clazz, Nullability.nonNullable));
-
-  bool get isFloat =>
-      nativeType == NativeType.kFloat || nativeType == NativeType.kDouble;
-
-  bool isUnaligned(Map<Abi, int> offsets) {
-    final alignments = alignment;
-    for (final abi in offsets.keys) {
-      final offset = offsets[abi]!;
-      final alignment = alignments[abi]!;
-      if (offset % alignment != 0) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /// Sample output for `int get x =>`:
-  ///
-  /// ```
-  /// _loadInt8(_typedDataBase, offset);
-  /// ```
-  @override
-  ReturnStatement generateGetterStatement(
-          DartType dartType,
-          int fileOffset,
-          Map<Abi, int> offsets,
-          bool unalignedAccess,
-          FfiTransformer transformer) =>
-      ReturnStatement(StaticInvocation(
-          (unalignedAccess && isFloat
-              ? transformer.loadUnalignedMethods
-              : transformer.loadMethods)[nativeType]!,
-          Arguments([
-            transformer.getCompoundTypedDataBaseField(
-                ThisExpression(), fileOffset),
-            transformer.runtimeBranchOnLayout(offsets)
-          ]))
-        ..fileOffset = fileOffset);
-
-  /// Sample output for `set x(int #v) =>`:
-  ///
-  /// ```
-  /// _storeInt8(_typedDataBase, offset, #v);
-  /// ```
-  @override
-  ReturnStatement generateSetterStatement(
-          DartType dartType,
-          int fileOffset,
-          Map<Abi, int> offsets,
-          bool unalignedAccess,
-          VariableDeclaration argument,
-          FfiTransformer transformer) =>
-      ReturnStatement(StaticInvocation(
-          (unalignedAccess && isFloat
-              ? transformer.storeUnalignedMethods
-              : transformer.storeMethods)[nativeType]!,
-          Arguments([
-            transformer.getCompoundTypedDataBaseField(
-                ThisExpression(), fileOffset),
-            transformer.runtimeBranchOnLayout(offsets),
-            VariableGet(argument)
-          ]))
-        ..fileOffset = fileOffset);
-}
-
-class PointerNativeTypeCfe implements NativeTypeCfe {
-  @override
-  Map<Abi, int> get size => wordSize;
-
-  @override
-  Map<Abi, int> get alignment => wordSize;
-
-  @override
-  Constant generateConstant(FfiTransformer transformer) => TypeLiteralConstant(
-          InterfaceType(transformer.pointerClass, Nullability.nonNullable, [
-        InterfaceType(
-            transformer.pointerClass.superclass!, Nullability.nonNullable)
-      ]));
-
-  /// Sample output for `Pointer<Int8> get x =>`:
-  ///
-  /// ```
-  /// _fromAddress<Int8>(_loadIntPtr(_typedDataBase, offset));
-  /// ```
-  @override
-  ReturnStatement generateGetterStatement(
-          DartType dartType,
-          int fileOffset,
-          Map<Abi, int> offsets,
-          bool unalignedAccess,
-          FfiTransformer transformer) =>
-      ReturnStatement(StaticInvocation(
-          transformer.fromAddressInternal,
-          Arguments([
-            StaticInvocation(
-                transformer.loadMethods[NativeType.kIntptr]!,
-                Arguments([
-                  transformer.getCompoundTypedDataBaseField(
-                      ThisExpression(), fileOffset),
-                  transformer.runtimeBranchOnLayout(offsets)
-                ]))
-              ..fileOffset = fileOffset
-          ], types: [
-            (dartType as InterfaceType).typeArguments.single
-          ]))
-        ..fileOffset = fileOffset);
-
-  /// Sample output for `set x(Pointer<Int8> #v) =>`:
-  ///
-  /// ```
-  /// _storeIntPtr(_typedDataBase, offset, (#v as Pointer<Int8>).address);
-  /// ```
-  @override
-  ReturnStatement generateSetterStatement(
-          DartType dartType,
-          int fileOffset,
-          Map<Abi, int> offsets,
-          bool unalignedAccess,
-          VariableDeclaration argument,
-          FfiTransformer transformer) =>
-      ReturnStatement(StaticInvocation(
-          transformer.storeMethods[NativeType.kIntptr]!,
-          Arguments([
-            transformer.getCompoundTypedDataBaseField(
-                ThisExpression(), fileOffset),
-            transformer.runtimeBranchOnLayout(offsets),
-            InstanceGet(InstanceAccessKind.Instance, VariableGet(argument),
-                transformer.addressGetter.name,
-                interfaceTarget: transformer.addressGetter,
-                resultType: transformer.addressGetter.getterType)
-              ..fileOffset = fileOffset
-          ]))
-        ..fileOffset = fileOffset);
-}
-
-abstract class CompoundNativeTypeCfe implements NativeTypeCfe {
-  final Class clazz;
-
-  final List<NativeTypeCfe> members;
-
-  final Map<Abi, CompoundLayout> layout;
-
-  CompoundNativeTypeCfe._(this.clazz, this.members, this.layout);
-
-  @override
-  Map<Abi, int> get size =>
-      layout.map((abi, layout) => MapEntry(abi, layout.size));
-
-  @override
-  Map<Abi, int> get alignment =>
-      layout.map((abi, layout) => MapEntry(abi, layout.alignment));
-
-  @override
-  Constant generateConstant(FfiTransformer transformer) =>
-      TypeLiteralConstant(InterfaceType(clazz, Nullability.nonNullable));
-
-  /// Sample output for `MyStruct get x =>`:
-  ///
-  /// ```
-  /// MyStruct.#fromTypedDataBase(
-  ///   typedDataBaseOffset(_typedDataBase, offset, size, dartType)
-  /// );
-  /// ```
-  @override
-  ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
-      Map<Abi, int> offsets, bool unalignedAccess, FfiTransformer transformer) {
-    final constructor = clazz.constructors
-        .firstWhere((c) => c.name == Name("#fromTypedDataBase"));
-
-    return ReturnStatement(ConstructorInvocation(
-        constructor,
-        Arguments([
-          transformer.typedDataBaseOffset(
-              transformer.getCompoundTypedDataBaseField(
-                  ThisExpression(), fileOffset),
-              transformer.runtimeBranchOnLayout(offsets),
-              transformer.runtimeBranchOnLayout(size),
-              dartType,
-              fileOffset)
-        ]))
-      ..fileOffset = fileOffset);
-  }
-
-  /// Sample output for `set x(MyStruct #v) =>`:
-  ///
-  /// ```
-  /// _memCopy(_typedDataBase, offset, #v._typedDataBase, 0, size);
-  /// ```
-  @override
-  ReturnStatement generateSetterStatement(
-          DartType dartType,
-          int fileOffset,
-          Map<Abi, int> offsets,
-          bool unalignedAccess,
-          VariableDeclaration argument,
-          FfiTransformer transformer) =>
-      ReturnStatement(StaticInvocation(
-          transformer.memCopy,
-          Arguments([
-            transformer.getCompoundTypedDataBaseField(
-                ThisExpression(), fileOffset),
-            transformer.runtimeBranchOnLayout(offsets),
-            transformer.getCompoundTypedDataBaseField(
-                VariableGet(argument), fileOffset),
-            ConstantExpression(IntConstant(0)),
-            transformer.runtimeBranchOnLayout(size),
-          ]))
-        ..fileOffset = fileOffset);
-}
-
-class StructNativeTypeCfe extends CompoundNativeTypeCfe {
-  // Nullable int.
-  final int? packing;
-
-  factory StructNativeTypeCfe(Class clazz, List<NativeTypeCfe> members,
-      {int? packing}) {
-    final layout = {
-      for (var abi in Abi.values) abi: _calculateLayout(members, packing, abi)
-    };
-    return StructNativeTypeCfe._(clazz, members, packing, layout);
-  }
-
-  StructNativeTypeCfe._(Class clazz, List<NativeTypeCfe> members, this.packing,
-      Map<Abi, CompoundLayout> layout)
-      : super._(clazz, members, layout);
-
-  // Keep consistent with runtime/vm/compiler/ffi/native_type.cc
-  // NativeStructType::FromNativeTypes.
-  static CompoundLayout _calculateLayout(
-      List<NativeTypeCfe> types, int? packing, Abi abi) {
-    int offset = 0;
-    final offsets = <int>[];
-    int structAlignment = 1;
-    for (int i = 0; i < types.length; i++) {
-      final int size = types[i].size[abi]!;
-      int alignment = types[i].alignment[abi]!;
-      if (packing != null && packing < alignment) {
-        alignment = packing;
-      }
-      if (alignment > 0) {
-        offset = _alignOffset(offset, alignment);
-      }
-      offsets.add(offset);
-      offset += size;
-      structAlignment = math.max(structAlignment, alignment);
-    }
-    final int size = _alignOffset(offset, structAlignment);
-    return CompoundLayout(size, structAlignment, offsets);
-  }
-}
-
-class UnionNativeTypeCfe extends CompoundNativeTypeCfe {
-  factory UnionNativeTypeCfe(Class clazz, List<NativeTypeCfe> members) {
-    final layout = {
-      for (var abi in Abi.values) abi: _calculateLayout(members, abi)
-    };
-    return UnionNativeTypeCfe._(clazz, members, layout);
-  }
-
-  UnionNativeTypeCfe._(
-      Class clazz, List<NativeTypeCfe> members, Map<Abi, CompoundLayout> layout)
-      : super._(clazz, members, layout);
-
-  // Keep consistent with runtime/vm/compiler/ffi/native_type.cc
-  // NativeUnionType::FromNativeTypes.
-  static CompoundLayout _calculateLayout(List<NativeTypeCfe> types, Abi abi) {
-    int unionSize = 1;
-    int unionAlignment = 1;
-    for (int i = 0; i < types.length; i++) {
-      final int size = types[i].size[abi]!;
-      int alignment = types[i].alignment[abi]!;
-      unionSize = math.max(unionSize, size);
-      unionAlignment = math.max(unionAlignment, alignment);
-    }
-    final int size = _alignOffset(unionSize, unionAlignment);
-    return CompoundLayout(size, unionAlignment, List.filled(types.length, 0));
-  }
-}
-
-class ArrayNativeTypeCfe implements NativeTypeCfe {
-  final NativeTypeCfe elementType;
-  final int length;
-
-  ArrayNativeTypeCfe(this.elementType, this.length);
-
-  factory ArrayNativeTypeCfe.multi(
-      NativeTypeCfe elementType, List<int> dimensions) {
-    if (dimensions.length == 1) {
-      return ArrayNativeTypeCfe(elementType, dimensions.single);
-    }
-    return ArrayNativeTypeCfe(
-        ArrayNativeTypeCfe.multi(elementType, dimensions.sublist(1)),
-        dimensions.first);
-  }
-
-  List<int> get dimensions {
-    final elementType = this.elementType;
-    if (elementType is ArrayNativeTypeCfe) {
-      return [length, ...elementType.dimensions];
-    }
-    return [length];
-  }
-
-  List<int> get nestedDimensions => dimensions.sublist(1);
-
-  int get dimensionsFlattened =>
-      dimensions.fold(1, (accumulator, element) => accumulator * element);
-
-  NativeTypeCfe get singleElementType {
-    final elementType = this.elementType;
-    if (elementType is ArrayNativeTypeCfe) {
-      return elementType.singleElementType;
-    }
-    return elementType;
-  }
-
-  @override
-  Map<Abi, int> get size =>
-      elementType.size.map((abi, size) => MapEntry(abi, size * length));
-
-  @override
-  Map<Abi, int> get alignment => elementType.alignment;
-
-  // Note that we flatten multi dimensional arrays.
-  @override
-  Constant generateConstant(FfiTransformer transformer) =>
-      InstanceConstant(transformer.ffiInlineArrayClass.reference, [], {
-        transformer.ffiInlineArrayElementTypeField.fieldReference:
-            singleElementType.generateConstant(transformer),
-        transformer.ffiInlineArrayLengthField.fieldReference:
-            IntConstant(dimensionsFlattened)
-      });
-
-  /// Sample output for `Array<Int8> get x =>`:
-  ///
-  /// ```
-  /// Array<Int8>._(
-  ///   typedDataBaseOffset(_typedDataBase, offset, size, typeArgument)
-  /// );
-  /// ```
-  @override
-  ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
-      Map<Abi, int> offsets, bool unalignedAccess, FfiTransformer transformer) {
-    InterfaceType typeArgument =
-        (dartType as InterfaceType).typeArguments.single as InterfaceType;
-    return ReturnStatement(ConstructorInvocation(
-        transformer.arrayConstructor,
-        Arguments([
-          transformer.typedDataBaseOffset(
-              transformer.getCompoundTypedDataBaseField(
-                  ThisExpression(), fileOffset),
-              transformer.runtimeBranchOnLayout(offsets),
-              transformer.runtimeBranchOnLayout(size),
-              typeArgument,
-              fileOffset),
-          ConstantExpression(IntConstant(length)),
-          transformer.intListConstantExpression(nestedDimensions)
-        ], types: [
-          typeArgument
-        ]))
-      ..fileOffset = fileOffset);
-  }
-
-  /// Sample output for `set x(Array #v) =>`:
-  ///
-  /// ```
-  /// _memCopy(_typedDataBase, offset, #v._typedDataBase, 0, size);
-  /// ```
-  @override
-  ReturnStatement generateSetterStatement(
-          DartType dartType,
-          int fileOffset,
-          Map<Abi, int> offsets,
-          bool unalignedAccess,
-          VariableDeclaration argument,
-          FfiTransformer transformer) =>
-      ReturnStatement(StaticInvocation(
-          transformer.memCopy,
-          Arguments([
-            transformer.getCompoundTypedDataBaseField(
-                ThisExpression(), fileOffset),
-            transformer.runtimeBranchOnLayout(offsets),
-            transformer.getArrayTypedDataBaseField(
-                VariableGet(argument), fileOffset),
-            ConstantExpression(IntConstant(0)),
-            transformer.runtimeBranchOnLayout(size),
-          ]))
-        ..fileOffset = fileOffset);
-}
-
-int _alignOffset(int offset, int alignment) =>
-    ((offset + alignment - 1) ~/ alignment) * alignment;
diff --git a/pkg/vm/lib/transformations/ffi/native_type_cfe.dart b/pkg/vm/lib/transformations/ffi/native_type_cfe.dart
new file mode 100644
index 0000000..862d1bd
--- /dev/null
+++ b/pkg/vm/lib/transformations/ffi/native_type_cfe.dart
@@ -0,0 +1,627 @@
+// 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:math' as math;
+
+import 'package:kernel/ast.dart';
+
+import 'abi.dart';
+import 'common.dart';
+
+/// AST node wrapper for native types.
+///
+/// This algebraic data structure does not stand on its own but refers
+/// intimately to AST nodes such as [Class].
+abstract class NativeTypeCfe {
+  factory NativeTypeCfe(FfiTransformer transformer, DartType dartType,
+      {List<int>? arrayDimensions,
+      Map<Class, NativeTypeCfe> compoundCache = const {}}) {
+    if (transformer.isPrimitiveType(dartType)) {
+      final clazz = (dartType as InterfaceType).classNode;
+      final nativeType = transformer.getType(clazz)!;
+      return PrimitiveNativeTypeCfe(nativeType, clazz);
+    }
+    if (transformer.isPointerType(dartType)) {
+      return PointerNativeTypeCfe();
+    }
+    if (transformer.isCompoundSubtype(dartType)) {
+      final clazz = (dartType as InterfaceType).classNode;
+      if (compoundCache.containsKey(clazz)) {
+        return compoundCache[clazz]!;
+      } else {
+        throw "Class '$clazz' not found in compoundCache.";
+      }
+    }
+    if (transformer.isArrayType(dartType)) {
+      if (arrayDimensions == null) {
+        throw "Must have array dimensions for ArrayType.";
+      }
+      if (arrayDimensions.length == 0) {
+        throw "Must have a size for this array dimension.";
+      }
+      final elementType = transformer.arraySingleElementType(dartType);
+      final elementCfeType =
+          NativeTypeCfe(transformer, elementType, compoundCache: compoundCache);
+      if (elementCfeType is InvalidNativeTypeCfe) {
+        return elementCfeType;
+      }
+      return ArrayNativeTypeCfe.multi(elementCfeType, arrayDimensions);
+    }
+    throw "Invalid type $dartType";
+  }
+
+  /// The size in bytes per [Abi].
+  Map<Abi, int?> get size;
+
+  /// The alignment inside structs in bytes per [Abi].
+  ///
+  /// This is not the alignment on stack, this is only calculated in the VM.
+  Map<Abi, int?> get alignment;
+
+  /// Generates a Constant representing the type which is consumed by the VM.
+  ///
+  /// Takes [transformer] to be able to lookup classes and methods.
+  ///
+  /// See runtime/vm/compiler/ffi/native_type.cc:NativeType::FromAbstractType.
+  Constant generateConstant(FfiTransformer transformer);
+
+  /// Generates the return statement for a compound field getter with this type.
+  ///
+  /// Takes [transformer] to be able to lookup classes and methods.
+  ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
+      Map<Abi, int?> offsets, bool unalignedAccess, FfiTransformer transformer);
+
+  /// Generates the return statement for a compound field setter with this type.
+  ///
+  /// Takes [transformer] to be able to lookup classes and methods.
+  ReturnStatement generateSetterStatement(
+      DartType dartType,
+      int fileOffset,
+      Map<Abi, int?> offsets,
+      bool unalignedAccess,
+      VariableDeclaration argument,
+      FfiTransformer transformer);
+}
+
+class InvalidNativeTypeCfe implements NativeTypeCfe {
+  final String reason;
+
+  InvalidNativeTypeCfe(this.reason);
+
+  @override
+  Map<Abi, int?> get alignment => throw reason;
+
+  @override
+  Constant generateConstant(FfiTransformer transformer) => throw reason;
+
+  @override
+  ReturnStatement generateGetterStatement(
+          DartType dartType,
+          int fileOffset,
+          Map<Abi, int?> offsets,
+          bool unalignedAccess,
+          FfiTransformer transformer) =>
+      throw reason;
+
+  @override
+  ReturnStatement generateSetterStatement(
+          DartType dartType,
+          int fileOffset,
+          Map<Abi, int?> offsets,
+          bool unalignedAccess,
+          VariableDeclaration argument,
+          FfiTransformer transformer) =>
+      throw reason;
+
+  @override
+  Map<Abi, int?> get size => throw reason;
+}
+
+class PrimitiveNativeTypeCfe implements NativeTypeCfe {
+  final NativeType nativeType;
+
+  final Class clazz;
+
+  PrimitiveNativeTypeCfe(this.nativeType, this.clazz);
+
+  @override
+  Map<Abi, int?> get size {
+    final int size = nativeTypeSizes[nativeType]!;
+    if (size == WORD_SIZE) {
+      return wordSize;
+    }
+    return {for (var abi in Abi.values) abi: size};
+  }
+
+  @override
+  Map<Abi, int> get alignment => {
+        for (var abi in Abi.values)
+          abi: nonSizeAlignment[abi]![nativeType] ?? size[abi]!
+      };
+
+  @override
+  Constant generateConstant(FfiTransformer transformer) =>
+      TypeLiteralConstant(InterfaceType(clazz, Nullability.nonNullable));
+
+  bool get isFloat =>
+      nativeType == NativeType.kFloat || nativeType == NativeType.kDouble;
+
+  bool isUnaligned(Map<Abi, int?> offsets) {
+    final alignments = alignment;
+    for (final abi in offsets.keys) {
+      final offset = offsets[abi]!;
+      final alignment = alignments[abi]!;
+      if (offset % alignment != 0) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /// Sample output for `int get x =>`:
+  ///
+  /// ```
+  /// _loadInt8(_typedDataBase, offset);
+  /// ```
+  @override
+  ReturnStatement generateGetterStatement(
+          DartType dartType,
+          int fileOffset,
+          Map<Abi, int?> offsets,
+          bool unalignedAccess,
+          FfiTransformer transformer) =>
+      ReturnStatement(StaticInvocation(
+          (unalignedAccess && isFloat
+              ? transformer.loadUnalignedMethods
+              : transformer.loadMethods)[nativeType]!,
+          Arguments([
+            transformer.getCompoundTypedDataBaseField(
+                ThisExpression(), fileOffset),
+            transformer.runtimeBranchOnLayout(offsets)
+          ]))
+        ..fileOffset = fileOffset);
+
+  /// Sample output for `set x(int #v) =>`:
+  ///
+  /// ```
+  /// _storeInt8(_typedDataBase, offset, #v);
+  /// ```
+  @override
+  ReturnStatement generateSetterStatement(
+          DartType dartType,
+          int fileOffset,
+          Map<Abi, int?> offsets,
+          bool unalignedAccess,
+          VariableDeclaration argument,
+          FfiTransformer transformer) =>
+      ReturnStatement(StaticInvocation(
+          (unalignedAccess && isFloat
+              ? transformer.storeUnalignedMethods
+              : transformer.storeMethods)[nativeType]!,
+          Arguments([
+            transformer.getCompoundTypedDataBaseField(
+                ThisExpression(), fileOffset),
+            transformer.runtimeBranchOnLayout(offsets),
+            VariableGet(argument)
+          ]))
+        ..fileOffset = fileOffset);
+}
+
+class PointerNativeTypeCfe implements NativeTypeCfe {
+  @override
+  Map<Abi, int?> get size => wordSize;
+
+  @override
+  Map<Abi, int?> get alignment => wordSize;
+
+  @override
+  Constant generateConstant(FfiTransformer transformer) => TypeLiteralConstant(
+          InterfaceType(transformer.pointerClass, Nullability.nonNullable, [
+        InterfaceType(
+            transformer.pointerClass.superclass!, Nullability.nonNullable)
+      ]));
+
+  /// Sample output for `Pointer<Int8> get x =>`:
+  ///
+  /// ```
+  /// _fromAddress<Int8>(_loadIntPtr(_typedDataBase, offset));
+  /// ```
+  @override
+  ReturnStatement generateGetterStatement(
+          DartType dartType,
+          int fileOffset,
+          Map<Abi, int?> offsets,
+          bool unalignedAccess,
+          FfiTransformer transformer) =>
+      ReturnStatement(StaticInvocation(
+          transformer.fromAddressInternal,
+          Arguments([
+            StaticInvocation(
+                transformer.loadMethods[NativeType.kIntptr]!,
+                Arguments([
+                  transformer.getCompoundTypedDataBaseField(
+                      ThisExpression(), fileOffset),
+                  transformer.runtimeBranchOnLayout(offsets)
+                ]))
+              ..fileOffset = fileOffset
+          ], types: [
+            (dartType as InterfaceType).typeArguments.single
+          ]))
+        ..fileOffset = fileOffset);
+
+  /// Sample output for `set x(Pointer<Int8> #v) =>`:
+  ///
+  /// ```
+  /// _storeIntPtr(_typedDataBase, offset, (#v as Pointer<Int8>).address);
+  /// ```
+  @override
+  ReturnStatement generateSetterStatement(
+          DartType dartType,
+          int fileOffset,
+          Map<Abi, int?> offsets,
+          bool unalignedAccess,
+          VariableDeclaration argument,
+          FfiTransformer transformer) =>
+      ReturnStatement(StaticInvocation(
+          transformer.storeMethods[NativeType.kIntptr]!,
+          Arguments([
+            transformer.getCompoundTypedDataBaseField(
+                ThisExpression(), fileOffset),
+            transformer.runtimeBranchOnLayout(offsets),
+            InstanceGet(InstanceAccessKind.Instance, VariableGet(argument),
+                transformer.addressGetter.name,
+                interfaceTarget: transformer.addressGetter,
+                resultType: transformer.addressGetter.getterType)
+              ..fileOffset = fileOffset
+          ]))
+        ..fileOffset = fileOffset);
+}
+
+/// The layout of a `Struct` or `Union` in one [Abi].
+class CompoundLayout {
+  /// Size of the entire struct or union.
+  final int? size;
+
+  /// Alignment of struct or union when nested in a struct.
+  final int? alignment;
+
+  /// Offset in bytes for each field, indexed by field number.
+  ///
+  /// Always 0 for unions.
+  final List<int?> offsets;
+
+  CompoundLayout(this.size, this.alignment, this.offsets);
+}
+
+abstract class CompoundNativeTypeCfe implements NativeTypeCfe {
+  final Class clazz;
+
+  final List<NativeTypeCfe> members;
+
+  final Map<Abi, CompoundLayout> layout;
+
+  CompoundNativeTypeCfe._(this.clazz, this.members, this.layout);
+
+  @override
+  Map<Abi, int?> get size =>
+      layout.map((abi, layout) => MapEntry(abi, layout.size));
+
+  @override
+  Map<Abi, int?> get alignment =>
+      layout.map((abi, layout) => MapEntry(abi, layout.alignment));
+
+  @override
+  Constant generateConstant(FfiTransformer transformer) =>
+      TypeLiteralConstant(InterfaceType(clazz, Nullability.nonNullable));
+
+  /// Sample output for `MyStruct get x =>`:
+  ///
+  /// ```
+  /// MyStruct.#fromTypedDataBase(
+  ///   typedDataBaseOffset(_typedDataBase, offset, size, dartType)
+  /// );
+  /// ```
+  @override
+  ReturnStatement generateGetterStatement(
+      DartType dartType,
+      int fileOffset,
+      Map<Abi, int?> offsets,
+      bool unalignedAccess,
+      FfiTransformer transformer) {
+    final constructor = clazz.constructors
+        .firstWhere((c) => c.name == Name("#fromTypedDataBase"));
+
+    return ReturnStatement(ConstructorInvocation(
+        constructor,
+        Arguments([
+          transformer.typedDataBaseOffset(
+              transformer.getCompoundTypedDataBaseField(
+                  ThisExpression(), fileOffset),
+              transformer.runtimeBranchOnLayout(offsets),
+              transformer.runtimeBranchOnLayout(size),
+              dartType,
+              fileOffset)
+        ]))
+      ..fileOffset = fileOffset);
+  }
+
+  /// Sample output for `set x(MyStruct #v) =>`:
+  ///
+  /// ```
+  /// _memCopy(_typedDataBase, offset, #v._typedDataBase, 0, size);
+  /// ```
+  @override
+  ReturnStatement generateSetterStatement(
+          DartType dartType,
+          int fileOffset,
+          Map<Abi, int?> offsets,
+          bool unalignedAccess,
+          VariableDeclaration argument,
+          FfiTransformer transformer) =>
+      ReturnStatement(StaticInvocation(
+          transformer.memCopy,
+          Arguments([
+            transformer.getCompoundTypedDataBaseField(
+                ThisExpression(), fileOffset),
+            transformer.runtimeBranchOnLayout(offsets),
+            transformer.getCompoundTypedDataBaseField(
+                VariableGet(argument), fileOffset),
+            ConstantExpression(IntConstant(0)),
+            transformer.runtimeBranchOnLayout(size),
+          ]))
+        ..fileOffset = fileOffset);
+}
+
+class StructNativeTypeCfe extends CompoundNativeTypeCfe {
+  // Nullable int.
+  final int? packing;
+
+  factory StructNativeTypeCfe(Class clazz, List<NativeTypeCfe> members,
+      {int? packing}) {
+    final layout = {
+      for (var abi in Abi.values) abi: _calculateLayout(members, packing, abi)
+    };
+    return StructNativeTypeCfe._(clazz, members, packing, layout);
+  }
+
+  StructNativeTypeCfe._(Class clazz, List<NativeTypeCfe> members, this.packing,
+      Map<Abi, CompoundLayout> layout)
+      : super._(clazz, members, layout);
+
+  // Keep consistent with runtime/vm/compiler/ffi/native_type.cc
+  // NativeStructType::FromNativeTypes.
+  static CompoundLayout _calculateLayout(
+      List<NativeTypeCfe> types, int? packing, Abi abi) {
+    int? offset = 0;
+    final offsets = <int?>[];
+    int? structAlignment = 1;
+    for (int i = 0; i < types.length; i++) {
+      final int? size = types[i].size[abi];
+      int? alignment = types[i].alignment[abi];
+      if (packing != null) {
+        alignment = min(packing, alignment);
+      }
+      if (alignment != null && alignment > 0) {
+        offset = offset.align(alignment);
+      }
+      offsets.add(offset);
+      offset += size;
+      structAlignment = max(structAlignment, alignment);
+    }
+    final int? size = offset.align(structAlignment);
+    return CompoundLayout(size, structAlignment, offsets);
+  }
+}
+
+class UnionNativeTypeCfe extends CompoundNativeTypeCfe {
+  factory UnionNativeTypeCfe(Class clazz, List<NativeTypeCfe> members) {
+    final layout = {
+      for (var abi in Abi.values) abi: _calculateLayout(members, abi)
+    };
+    return UnionNativeTypeCfe._(clazz, members, layout);
+  }
+
+  UnionNativeTypeCfe._(
+      Class clazz, List<NativeTypeCfe> members, Map<Abi, CompoundLayout> layout)
+      : super._(clazz, members, layout);
+
+  // Keep consistent with runtime/vm/compiler/ffi/native_type.cc
+  // NativeUnionType::FromNativeTypes.
+  static CompoundLayout _calculateLayout(List<NativeTypeCfe> types, Abi abi) {
+    int? unionSize = 1;
+    int? unionAlignment = 1;
+    for (int i = 0; i < types.length; i++) {
+      final int? size = types[i].size[abi];
+      int? alignment = types[i].alignment[abi];
+      unionSize = max(unionSize, size);
+      unionAlignment = max(unionAlignment, alignment);
+    }
+    final int? size = unionSize.align(unionAlignment);
+    return CompoundLayout(size, unionAlignment, List.filled(types.length, 0));
+  }
+}
+
+class ArrayNativeTypeCfe implements NativeTypeCfe {
+  final NativeTypeCfe elementType;
+  final int length;
+
+  ArrayNativeTypeCfe(this.elementType, this.length);
+
+  factory ArrayNativeTypeCfe.multi(
+      NativeTypeCfe elementType, List<int> dimensions) {
+    if (dimensions.length == 1) {
+      return ArrayNativeTypeCfe(elementType, dimensions.single);
+    }
+    return ArrayNativeTypeCfe(
+        ArrayNativeTypeCfe.multi(elementType, dimensions.sublist(1)),
+        dimensions.first);
+  }
+
+  List<int> get dimensions {
+    final elementType = this.elementType;
+    if (elementType is ArrayNativeTypeCfe) {
+      return [length, ...elementType.dimensions];
+    }
+    return [length];
+  }
+
+  List<int> get nestedDimensions => dimensions.sublist(1);
+
+  int get dimensionsFlattened =>
+      dimensions.fold(1, (accumulator, element) => accumulator * element);
+
+  NativeTypeCfe get singleElementType {
+    final elementType = this.elementType;
+    if (elementType is ArrayNativeTypeCfe) {
+      return elementType.singleElementType;
+    }
+    return elementType;
+  }
+
+  @override
+  Map<Abi, int?> get size =>
+      elementType.size.map((abi, size) => MapEntry(abi, size * length));
+
+  @override
+  Map<Abi, int?> get alignment => elementType.alignment;
+
+  // Note that we flatten multi dimensional arrays.
+  @override
+  Constant generateConstant(FfiTransformer transformer) =>
+      InstanceConstant(transformer.ffiInlineArrayClass.reference, [], {
+        transformer.ffiInlineArrayElementTypeField.fieldReference:
+            singleElementType.generateConstant(transformer),
+        transformer.ffiInlineArrayLengthField.fieldReference:
+            IntConstant(dimensionsFlattened)
+      });
+
+  /// Sample output for `Array<Int8> get x =>`:
+  ///
+  /// ```
+  /// Array<Int8>._(
+  ///   typedDataBaseOffset(_typedDataBase, offset, size, typeArgument)
+  /// );
+  /// ```
+  @override
+  ReturnStatement generateGetterStatement(
+      DartType dartType,
+      int fileOffset,
+      Map<Abi, int?> offsets,
+      bool unalignedAccess,
+      FfiTransformer transformer) {
+    InterfaceType typeArgument =
+        (dartType as InterfaceType).typeArguments.single as InterfaceType;
+    return ReturnStatement(ConstructorInvocation(
+        transformer.arrayConstructor,
+        Arguments([
+          transformer.typedDataBaseOffset(
+              transformer.getCompoundTypedDataBaseField(
+                  ThisExpression(), fileOffset),
+              transformer.runtimeBranchOnLayout(offsets),
+              transformer.runtimeBranchOnLayout(size),
+              typeArgument,
+              fileOffset),
+          ConstantExpression(IntConstant(length)),
+          transformer.intListConstantExpression(nestedDimensions)
+        ], types: [
+          typeArgument
+        ]))
+      ..fileOffset = fileOffset);
+  }
+
+  /// Sample output for `set x(Array #v) =>`:
+  ///
+  /// ```
+  /// _memCopy(_typedDataBase, offset, #v._typedDataBase, 0, size);
+  /// ```
+  @override
+  ReturnStatement generateSetterStatement(
+          DartType dartType,
+          int fileOffset,
+          Map<Abi, int?> offsets,
+          bool unalignedAccess,
+          VariableDeclaration argument,
+          FfiTransformer transformer) =>
+      ReturnStatement(StaticInvocation(
+          transformer.memCopy,
+          Arguments([
+            transformer.getCompoundTypedDataBaseField(
+                ThisExpression(), fileOffset),
+            transformer.runtimeBranchOnLayout(offsets),
+            transformer.getArrayTypedDataBaseField(
+                VariableGet(argument), fileOffset),
+            ConstantExpression(IntConstant(0)),
+            transformer.runtimeBranchOnLayout(size),
+          ]))
+        ..fileOffset = fileOffset);
+}
+
+extension on int? {
+  int? align(int? alignment) =>
+      ((this + alignment - 1) ~/ alignment) * alignment;
+
+  int? operator *(int? other) {
+    final this_ = this;
+    if (this_ == null) {
+      return null;
+    }
+    if (other == null) {
+      return null;
+    }
+    return this_ * other;
+  }
+
+  int? operator +(int? other) {
+    final this_ = this;
+    if (this_ == null) {
+      return null;
+    }
+    if (other == null) {
+      return null;
+    }
+    return this_ + other;
+  }
+
+  int? operator -(int? other) {
+    final this_ = this;
+    if (this_ == null) {
+      return null;
+    }
+    if (other == null) {
+      return null;
+    }
+    return this_ - other;
+  }
+
+  int? operator ~/(int? other) {
+    final this_ = this;
+    if (this_ == null) {
+      return null;
+    }
+    if (other == null) {
+      return null;
+    }
+    return this_ ~/ other;
+  }
+}
+
+int? max(int? a, int? b) {
+  if (a == null) {
+    return null;
+  }
+  if (b == null) {
+    return null;
+  }
+  return math.max(a, b);
+}
+
+int? min(int? a, int? b) {
+  if (a == null) {
+    return null;
+  }
+  if (b == null) {
+    return null;
+  }
+  return math.min(a, b);
+}
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 9b0e7cf..2b7346a 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -838,20 +838,46 @@
     case MethodRecognizer::kTypedData_Float32x4Array_factory:
     case MethodRecognizer::kTypedData_Int32x4Array_factory:
     case MethodRecognizer::kTypedData_Float64x2Array_factory:
-#define FFI_LOAD_STORE(type)                                                   \
-  case MethodRecognizer::kFfiLoad##type:                                       \
-  case MethodRecognizer::kFfiStore##type:
-    CLASS_LIST_FFI_NUMERIC(FFI_LOAD_STORE)
-    FFI_LOAD_STORE(FloatUnaligned)
-    FFI_LOAD_STORE(DoubleUnaligned)
-    FFI_LOAD_STORE(Pointer)
-#undef FFI_LOAD_STORE
+    case MethodRecognizer::kFfiLoadInt8:
+    case MethodRecognizer::kFfiLoadInt16:
+    case MethodRecognizer::kFfiLoadInt32:
+    case MethodRecognizer::kFfiLoadInt64:
+    case MethodRecognizer::kFfiLoadUint8:
+    case MethodRecognizer::kFfiLoadUint16:
+    case MethodRecognizer::kFfiLoadUint32:
+    case MethodRecognizer::kFfiLoadUint64:
+    case MethodRecognizer::kFfiLoadIntPtr:
+    case MethodRecognizer::kFfiLoadFloat:
+    case MethodRecognizer::kFfiLoadFloatUnaligned:
+    case MethodRecognizer::kFfiLoadDouble:
+    case MethodRecognizer::kFfiLoadDoubleUnaligned:
+    case MethodRecognizer::kFfiLoadPointer:
+    case MethodRecognizer::kFfiStoreInt8:
+    case MethodRecognizer::kFfiStoreInt16:
+    case MethodRecognizer::kFfiStoreInt32:
+    case MethodRecognizer::kFfiStoreInt64:
+    case MethodRecognizer::kFfiStoreUint8:
+    case MethodRecognizer::kFfiStoreUint16:
+    case MethodRecognizer::kFfiStoreUint32:
+    case MethodRecognizer::kFfiStoreUint64:
+    case MethodRecognizer::kFfiStoreIntPtr:
+    case MethodRecognizer::kFfiStoreFloat:
+    case MethodRecognizer::kFfiStoreFloatUnaligned:
+    case MethodRecognizer::kFfiStoreDouble:
+    case MethodRecognizer::kFfiStoreDoubleUnaligned:
+    case MethodRecognizer::kFfiStorePointer:
     case MethodRecognizer::kFfiFromAddress:
     case MethodRecognizer::kFfiGetAddress:
-#define FFI_AS_EXTERNAL_TYPED_DATA(type)                                       \
-  case MethodRecognizer::kFfiAsExternalTypedData##type:
-    CLASS_LIST_FFI_NUMERIC_FIXED_SIZE(FFI_AS_EXTERNAL_TYPED_DATA)
-#undef FFI_AS_EXTERNAL_TYPED_DATA
+    case MethodRecognizer::kFfiAsExternalTypedDataInt8:
+    case MethodRecognizer::kFfiAsExternalTypedDataInt16:
+    case MethodRecognizer::kFfiAsExternalTypedDataInt32:
+    case MethodRecognizer::kFfiAsExternalTypedDataInt64:
+    case MethodRecognizer::kFfiAsExternalTypedDataUint8:
+    case MethodRecognizer::kFfiAsExternalTypedDataUint16:
+    case MethodRecognizer::kFfiAsExternalTypedDataUint32:
+    case MethodRecognizer::kFfiAsExternalTypedDataUint64:
+    case MethodRecognizer::kFfiAsExternalTypedDataFloat:
+    case MethodRecognizer::kFfiAsExternalTypedDataDouble:
     case MethodRecognizer::kGetNativeField:
     case MethodRecognizer::kObjectEquals:
     case MethodRecognizer::kStringBaseLength:
@@ -1331,13 +1357,20 @@
       ASSERT_EQUAL(function.NumParameters(), 0);
       body += IntConstant(static_cast<int64_t>(compiler::ffi::TargetAbi()));
       break;
-#define FFI_LOAD(type) case MethodRecognizer::kFfiLoad##type:
-    CLASS_LIST_FFI_NUMERIC(FFI_LOAD)
-    FFI_LOAD(FloatUnaligned)
-    FFI_LOAD(DoubleUnaligned)
-    FFI_LOAD(Pointer)
-#undef FFI_LOAD
-    {
+    case MethodRecognizer::kFfiLoadInt8:
+    case MethodRecognizer::kFfiLoadInt16:
+    case MethodRecognizer::kFfiLoadInt32:
+    case MethodRecognizer::kFfiLoadInt64:
+    case MethodRecognizer::kFfiLoadUint8:
+    case MethodRecognizer::kFfiLoadUint16:
+    case MethodRecognizer::kFfiLoadUint32:
+    case MethodRecognizer::kFfiLoadUint64:
+    case MethodRecognizer::kFfiLoadIntPtr:
+    case MethodRecognizer::kFfiLoadFloat:
+    case MethodRecognizer::kFfiLoadFloatUnaligned:
+    case MethodRecognizer::kFfiLoadDouble:
+    case MethodRecognizer::kFfiLoadDoubleUnaligned:
+    case MethodRecognizer::kFfiLoadPointer: {
       const classid_t ffi_type_arg_cid =
           compiler::ffi::RecognizedMethodTypeArgCid(kind);
       const AlignmentType alignment =
@@ -1403,13 +1436,20 @@
       }
       body += DropTempsPreserveTop(1);  // Drop [arg_offset].
     } break;
-#define FFI_STORE(type) case MethodRecognizer::kFfiStore##type:
-    CLASS_LIST_FFI_NUMERIC(FFI_STORE)
-    FFI_STORE(FloatUnaligned)
-    FFI_STORE(DoubleUnaligned)
-    FFI_STORE(Pointer)
-#undef FFI_STORE
-    {
+    case MethodRecognizer::kFfiStoreInt8:
+    case MethodRecognizer::kFfiStoreInt16:
+    case MethodRecognizer::kFfiStoreInt32:
+    case MethodRecognizer::kFfiStoreInt64:
+    case MethodRecognizer::kFfiStoreUint8:
+    case MethodRecognizer::kFfiStoreUint16:
+    case MethodRecognizer::kFfiStoreUint32:
+    case MethodRecognizer::kFfiStoreUint64:
+    case MethodRecognizer::kFfiStoreIntPtr:
+    case MethodRecognizer::kFfiStoreFloat:
+    case MethodRecognizer::kFfiStoreFloatUnaligned:
+    case MethodRecognizer::kFfiStoreDouble:
+    case MethodRecognizer::kFfiStoreDoubleUnaligned:
+    case MethodRecognizer::kFfiStorePointer: {
       const classid_t ffi_type_arg_cid =
           compiler::ffi::RecognizedMethodTypeArgCid(kind);
       const AlignmentType alignment =
@@ -1519,11 +1559,16 @@
       body += Constant(Bool::False());
 #endif  // defined(ARCH_IS_64_BIT)
     } break;
-#define FFI_AS_EXTERNAL_TYPED_DATA(type)                                       \
-  case MethodRecognizer::kFfiAsExternalTypedData##type:
-    CLASS_LIST_FFI_NUMERIC_FIXED_SIZE(FFI_AS_EXTERNAL_TYPED_DATA)
-#undef FFI_AS_EXTERNAL_TYPED_DATA
-    {
+    case MethodRecognizer::kFfiAsExternalTypedDataInt8:
+    case MethodRecognizer::kFfiAsExternalTypedDataInt16:
+    case MethodRecognizer::kFfiAsExternalTypedDataInt32:
+    case MethodRecognizer::kFfiAsExternalTypedDataInt64:
+    case MethodRecognizer::kFfiAsExternalTypedDataUint8:
+    case MethodRecognizer::kFfiAsExternalTypedDataUint16:
+    case MethodRecognizer::kFfiAsExternalTypedDataUint32:
+    case MethodRecognizer::kFfiAsExternalTypedDataUint64:
+    case MethodRecognizer::kFfiAsExternalTypedDataFloat:
+    case MethodRecognizer::kFfiAsExternalTypedDataDouble: {
       const classid_t ffi_type_arg_cid =
           compiler::ffi::RecognizedMethodTypeArgCid(kind);
       const classid_t external_typed_data_cid =
@@ -1531,8 +1576,8 @@
 
       auto class_table = thread_->isolate_group()->class_table();
       ASSERT(class_table->HasValidClassAt(external_typed_data_cid));
-      const auto& typed_data_class = Class::ZoneHandle(
-          H.zone(), class_table->At(external_typed_data_cid));
+      const auto& typed_data_class =
+          Class::ZoneHandle(H.zone(), class_table->At(external_typed_data_cid));
 
       // We assume that the caller has checked that the arguments are non-null
       // and length is in the range [0, kSmiMax/elementSize].
@@ -4443,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();
 
@@ -4589,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 {
diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart
index 14180b6..3af29c3 100644
--- a/sdk/lib/_internal/vm/lib/ffi_patch.dart
+++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart
@@ -448,6 +448,16 @@
         Pointer<Pointer<S>> pointer, int index) =>
     Pointer.fromAddress(pointer.address + _intPtrSize * index);
 
+@pragma("vm:prefer-inline")
+@pragma("vm:entry-point")
+T _checkAbiSpecificIntegerMapping<T>(T? object) {
+  if (object == null) {
+    throw ArgumentError(
+        'AbiSpecificInteger is missing mapping for "${Abi.current()}".');
+  }
+  return object;
+}
+
 extension NativeFunctionPointer<NF extends Function>
     on Pointer<NativeFunction<NF>> {
   @patch
diff --git a/tools/VERSION b/tools/VERSION
index 9dfdd80..e7063a5 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 71
+PRERELEASE 72
 PRERELEASE_PATCH 0
\ No newline at end of file