Version 2.15.0-211.0.dev

Merge commit '396e58e0f20c36005240e5ced436b18d0094a5c6' into 'dev'
diff --git a/DEPS b/DEPS
index c6cd91f..d2446c0 100644
--- a/DEPS
+++ b/DEPS
@@ -163,7 +163,7 @@
   "test_reflective_loader_rev": "fcfce37666672edac849d2af6dffc0f8df236a94",
   "test_rev": "099dcc4d052a30c6921489cfbefa1c8531d12975",
   "typed_data_rev": "29ce5a92b03326d0b8035916ac04f528874994bd",
-  "usage_rev": "e0780cd8b2f8af69a28dc52678ffe8492da27d06",
+  "usage_rev": "ac53249e5230a77624f46c07c2ed965efcfc0c59",
   "vector_math_rev": "0c9f5d68c047813a6dcdeb88ba7a42daddf25025",
   "watcher_rev": "3924194385fb215cef483193ed2879a618a3d69c",
   "webdriver_rev": "ff5ccb1522edf4bed578ead4d65e0cbc1f2c4f02",
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index b0ff32b..073c5da 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -4106,14 +4106,79 @@
     problemMessage: r"""FFI leaf call must not have Handle argument types.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const Code<Null> codeFfiNativeAnnotationMustAnnotateStatic =
-    messageFfiNativeAnnotationMustAnnotateStatic;
+const Code<Null> codeFfiNativeMustBeExternal = messageFfiNativeMustBeExternal;
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const MessageCode messageFfiNativeAnnotationMustAnnotateStatic =
-    const MessageCode("FfiNativeAnnotationMustAnnotateStatic",
+const MessageCode messageFfiNativeMustBeExternal = const MessageCode(
+    "FfiNativeMustBeExternal",
+    problemMessage: r"""FfiNative functions must be marked external.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeFfiNativeOnlyNativeFieldWrapperClassCanBePointer =
+    messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer =
+    const MessageCode("FfiNativeOnlyNativeFieldWrapperClassCanBePointer",
         problemMessage:
-            r"""FfiNative annotations can only be used on static functions.""");
+            r"""Only classes extending NativeFieldWrapperClass1 can be passed as Pointer.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<Message Function(int count, int count2)>
+    templateFfiNativeUnexpectedNumberOfParameters =
+    const Template<Message Function(int count, int count2)>(
+        problemMessageTemplate:
+            r"""Unexpected number of FfiNative annotation parameters. Expected #count but has #count2.""",
+        withArguments: _withArgumentsFfiNativeUnexpectedNumberOfParameters);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(int count, int count2)>
+    codeFfiNativeUnexpectedNumberOfParameters =
+    const Code<Message Function(int count, int count2)>(
+  "FfiNativeUnexpectedNumberOfParameters",
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsFfiNativeUnexpectedNumberOfParameters(
+    int count, int count2) {
+  // ignore: unnecessary_null_comparison
+  if (count == null) throw 'No count provided';
+  // ignore: unnecessary_null_comparison
+  if (count2 == null) throw 'No count provided';
+  return new Message(codeFfiNativeUnexpectedNumberOfParameters,
+      problemMessage:
+          """Unexpected number of FfiNative annotation parameters. Expected ${count} but has ${count2}.""",
+      arguments: {'count': count, 'count2': count2});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<Message Function(int count, int count2)>
+    templateFfiNativeUnexpectedNumberOfParametersWithReceiver =
+    const Template<Message Function(int count, int count2)>(
+        problemMessageTemplate:
+            r"""Unexpected number of FfiNative annotation parameters. Expected #count but has #count2. FfiNative instance method annotation must have receiver as first argument.""",
+        withArguments:
+            _withArgumentsFfiNativeUnexpectedNumberOfParametersWithReceiver);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(int count, int count2)>
+    codeFfiNativeUnexpectedNumberOfParametersWithReceiver =
+    const Code<Message Function(int count, int count2)>(
+  "FfiNativeUnexpectedNumberOfParametersWithReceiver",
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsFfiNativeUnexpectedNumberOfParametersWithReceiver(
+    int count, int count2) {
+  // ignore: unnecessary_null_comparison
+  if (count == null) throw 'No count provided';
+  // ignore: unnecessary_null_comparison
+  if (count2 == null) throw 'No count provided';
+  return new Message(codeFfiNativeUnexpectedNumberOfParametersWithReceiver,
+      problemMessage:
+          """Unexpected number of FfiNative annotation parameters. Expected ${count} but has ${count2}. FfiNative instance method annotation must have receiver as first argument.""",
+      arguments: {'count': count, 'count2': count2});
+}
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 26d0396..311591d 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -480,7 +480,11 @@
   FfiCode.EMPTY_STRUCT,
   FfiCode.EXTRA_ANNOTATION_ON_STRUCT_FIELD,
   FfiCode.EXTRA_SIZE_ANNOTATION_CARRAY,
-  FfiCode.FFI_NATIVE_ONLY_STATIC,
+  FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL,
+  FfiCode
+      .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
+  FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS,
+  FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER,
   FfiCode.FIELD_IN_STRUCT_WITH_INITIALIZER,
   FfiCode.FIELD_INITIALIZER_IN_STRUCT,
   FfiCode.FIELD_MUST_BE_EXTERNAL_IN_STRUCT,
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 3af2022..c2b7d56 100644
--- a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
+++ b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
@@ -77,10 +77,45 @@
   /**
    * No parameters.
    */
-  static const FfiCode FFI_NATIVE_ONLY_STATIC = FfiCode(
-    'FFI_NATIVE_ONLY_STATIC',
-    "FfiNative annotations can only be used on static functions.",
-    correctionMessage: "Change the method to static.",
+  static const FfiCode FFI_NATIVE_MUST_BE_EXTERNAL = FfiCode(
+    'FFI_NATIVE_MUST_BE_EXTERNAL',
+    "FfiNative functions must be declared external.",
+    correctionMessage: "Add the `external` keyword to the function.",
+  );
+
+  /**
+   * No parameters.
+   */
+  static const FfiCode
+      FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER =
+      FfiCode(
+    'FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER',
+    "Only classes extending NativeFieldWrapperClass1 can be passed as Pointer.",
+    correctionMessage: "Pass as Handle instead.",
+  );
+
+  /**
+   * Parameters:
+   * 0: the expected number of parameters
+   * 1: the actual number of parameters
+   */
+  static const FfiCode FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS = FfiCode(
+    'FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS',
+    "Unexpected number of FfiNative annotation parameters. Expected {0} but has {1}.",
+    correctionMessage: "Make sure parameters match the function annotated.",
+  );
+
+  /**
+   * Parameters:
+   * 0: the expected number of parameters
+   * 1: the actual number of parameters
+   */
+  static const FfiCode
+      FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER = FfiCode(
+    'FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER',
+    "Unexpected number of FfiNative annotation parameters. Expected {0} but has {1}. FfiNative instance method annotation must have receiver as first argument.",
+    correctionMessage:
+        "Make sure parameters match the function annotated, including an extra first parameter for the receiver.",
   );
 
   /**
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index c8fc471..af80e94 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -276,24 +276,119 @@
     }
 
     for (Annotation annotation in annotations) {
-      if (annotation.name.name == _ffiNativeName) {
-        // All FFI Natives must be static.
-        final isStatic = (node is FunctionDeclaration) ||
-            ((node is MethodDeclaration) && node.isStatic);
-        if (!isStatic) {
+      if (annotation.name.name != _ffiNativeName) {
+        continue;
+      }
+
+      final NodeList<Expression> arguments = annotation.arguments!.arguments;
+      final NodeList<TypeAnnotation> typeArguments =
+          annotation.typeArguments!.arguments;
+
+      final ffiSignature = typeArguments[0].type! as FunctionType;
+
+      // Leaf call FFI Natives can't use Handles.
+      _validateFfiLeafCallUsesNoHandles(arguments, ffiSignature, node);
+
+      if (node is MethodDeclaration) {
+        if (!node.declaredElement!.isExternal) {
           _errorReporter.reportErrorForNode(
-              FfiCode.FFI_NATIVE_ONLY_STATIC, node);
+              FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL, node);
         }
-        // Leaf call FFI Natives can't use Handles.
-        ArgumentList? argumentList = annotation.arguments;
-        if (argumentList != null) {
-          NodeList<Expression> arguments = argumentList.arguments;
-          TypeArgumentList? typeArgumentList = annotation.typeArguments;
-          if (typeArgumentList != null) {
-            NodeList<TypeAnnotation> typeArguments = typeArgumentList.arguments;
-            if (typeArguments.isNotEmpty && typeArguments[0].type != null) {
-              _validateFfiLeafCallUsesNoHandles(
-                  arguments, typeArguments[0].type!, node);
+
+        List<DartType> ffiParameterTypes;
+        if (!node.isStatic) {
+          // Instance methods must have the receiver as an extra parameter in the
+          // FfiNative annotation.
+          if (node.parameters!.parameters.length + 1 !=
+              ffiSignature.parameters.length) {
+            _errorReporter.reportErrorForNode(
+                FfiCode
+                    .FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER,
+                node,
+                [
+                  node.parameters!.parameters.length + 1,
+                  ffiSignature.parameters.length
+                ]);
+            return;
+          }
+
+          // Receiver can only be Pointer if the class extends
+          // NativeFieldWrapperClass1.
+          if (ffiSignature.normalParameterTypes[0].isPointer) {
+            final cls = node.declaredElement!.enclosingElement as ClassElement;
+            if (!_extendsNativeFieldWrapperClass1(cls.thisType)) {
+              _errorReporter.reportErrorForNode(
+                  FfiCode
+                      .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
+                  node);
+            }
+          }
+
+          ffiParameterTypes = ffiSignature.normalParameterTypes.sublist(1);
+        } else {
+          // Number of parameters in the FfiNative annotation must match the
+          // annotated declaration.
+          if (node.parameters!.parameters.length !=
+              ffiSignature.parameters.length) {
+            _errorReporter.reportErrorForNode(
+                FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS, node, [
+              ffiSignature.parameters.length,
+              node.parameters!.parameters.length
+            ]);
+            return;
+          }
+
+          ffiParameterTypes = ffiSignature.normalParameterTypes;
+        }
+
+        // Arguments can only be Pointer if the class extends
+        // NativeFieldWrapperClass1.
+        for (var i = 0; i < node.parameters!.parameters.length; i++) {
+          if (ffiParameterTypes[i].isPointer) {
+            final type = node.parameters!.parameters[i].declaredElement!.type;
+            if (!_extendsNativeFieldWrapperClass1(type as InterfaceType)) {
+              _errorReporter.reportErrorForNode(
+                  FfiCode
+                      .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
+                  node);
+            }
+          }
+        }
+
+        continue;
+      }
+
+      if (node is FunctionDeclaration) {
+        if (!node.declaredElement!.isExternal) {
+          _errorReporter.reportErrorForNode(
+              FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL, node);
+        }
+
+        // Number of parameters in the FfiNative annotation must match the
+        // annotated declaration.
+        if (node.functionExpression.parameters!.parameters.length !=
+            ffiSignature.parameters.length) {
+          _errorReporter.reportErrorForNode(
+              FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS, node, [
+            ffiSignature.parameters.length,
+            node.functionExpression.parameters!.parameters.length
+          ]);
+          return;
+        }
+
+        // Arguments can only be Pointer if the class extends
+        // NativeFieldWrapperClass1.
+        for (var i = 0;
+            i < node.functionExpression.parameters!.parameters.length;
+            i++) {
+          if (ffiSignature.normalParameterTypes[i].isPointer) {
+            final type = node.functionExpression.parameters!.parameters[i]
+                .declaredElement!.type;
+            if (!_extendsNativeFieldWrapperClass1(type as InterfaceType)) {
+              _errorReporter.reportErrorForNode(
+                  FfiCode
+                      .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
+                  node);
             }
           }
         }
@@ -301,6 +396,17 @@
     }
   }
 
+  bool _extendsNativeFieldWrapperClass1(InterfaceType? type) {
+    while (type != null) {
+      if (type.getDisplayString(withNullability: false) ==
+          'NativeFieldWrapperClass1') {
+        return true;
+      }
+      type = type.element.supertype;
+    }
+    return false;
+  }
+
   /// Returns `true` if [nativeType] is a C type that has a size.
   bool _isSized(DartType nativeType) {
     switch (_primitiveNativeType(nativeType)) {
@@ -639,27 +745,26 @@
 
   void _validateFfiLeafCallUsesNoHandles(
       NodeList<Expression> args, DartType nativeType, AstNode errorNode) {
-    if (args.isNotEmpty) {
-      for (final arg in args) {
-        if (arg is NamedExpression) {
-          if (arg.element?.name == _isLeafParamName) {
-            // Handles are ok for regular (non-leaf) calls. Check `isLeaf:true`.
-            final bool? isLeaf = _maybeGetBoolConstValue(arg.expression);
-            if (isLeaf != null && isLeaf) {
-              if (nativeType is FunctionType) {
-                if (_primitiveNativeType(nativeType.returnType) ==
-                    _PrimitiveDartType.handle) {
-                  _errorReporter.reportErrorForNode(
-                      FfiCode.LEAF_CALL_MUST_NOT_RETURN_HANDLE, errorNode);
-                }
-                for (final param in nativeType.normalParameterTypes) {
-                  if (_primitiveNativeType(param) ==
-                      _PrimitiveDartType.handle) {
-                    _errorReporter.reportErrorForNode(
-                        FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE, errorNode);
-                  }
-                }
-              }
+    if (args.isEmpty) {
+      return;
+    }
+    for (final arg in args) {
+      if (arg is! NamedExpression || arg.element?.name != _isLeafParamName) {
+        continue;
+      }
+      // Handles are ok for regular (non-leaf) calls. Check `isLeaf:true`.
+      final bool? isLeaf = _maybeGetBoolConstValue(arg.expression);
+      if (isLeaf != null && isLeaf) {
+        if (nativeType is FunctionType) {
+          if (_primitiveNativeType(nativeType.returnType) ==
+              _PrimitiveDartType.handle) {
+            _errorReporter.reportErrorForNode(
+                FfiCode.LEAF_CALL_MUST_NOT_RETURN_HANDLE, errorNode);
+          }
+          for (final param in nativeType.normalParameterTypes) {
+            if (_primitiveNativeType(param) == _PrimitiveDartType.handle) {
+              _errorReporter.reportErrorForNode(
+                  FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE, errorNode);
             }
           }
         }
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 7f21e4e..ebd0349 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -13760,10 +13760,28 @@
     problemMessage: "'Array's must have exactly one 'Array' annotation."
     correctionMessage: Try removing the extra annotation.
     comment: No parameters.
-  FFI_NATIVE_ONLY_STATIC:
-    problemMessage: FfiNative annotations can only be used on static functions.
-    correctionMessage: Change the method to static.
+  FFI_NATIVE_MUST_BE_EXTERNAL:
+    problemMessage: FfiNative functions must be declared external.
+    correctionMessage: Add the `external` keyword to the function.
     comment: No parameters.
+  FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER:
+    problemMessage: Only classes extending NativeFieldWrapperClass1 can be passed as Pointer.
+    correctionMessage: Pass as Handle instead.
+    comment: No parameters.
+  FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS:
+    problemMessage: Unexpected number of FfiNative annotation parameters. Expected {0} but has {1}.
+    correctionMessage: Make sure parameters match the function annotated.
+    comment: |-
+      Parameters:
+      0: the expected number of parameters
+      1: the actual number of parameters
+  FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER:
+    problemMessage: Unexpected number of FfiNative annotation parameters. Expected {0} but has {1}. FfiNative instance method annotation must have receiver as first argument.
+    correctionMessage: Make sure parameters match the function annotated, including an extra first parameter for the receiver.
+    comment: |-
+      Parameters:
+      0: the expected number of parameters
+      1: the actual number of parameters
   FIELD_INITIALIZER_IN_STRUCT:
     problemMessage: "Constructors in subclasses of 'Struct' and 'Union' can't have field initializers."
     correctionMessage: Try removing the field initializer and marking the field as external.
diff --git a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
index a11172c..834d565 100644
--- a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
@@ -15,18 +15,6 @@
 
 @reflectiveTest
 class FfiNativeTest extends PubPackageResolutionTest {
-  test_FfiNativeAnnotationOnInstanceMethod() async {
-    await assertErrorsInCode(r'''
-import 'dart:ffi';
-class K {
-  @FfiNative<Void Function()>('DoesntMatter')
-  external void doesntMatter();
-}
-''', [
-      error(FfiCode.FFI_NATIVE_ONLY_STATIC, 31, 75),
-    ]);
-  }
-
   test_FfiNativeCanUseHandles() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
@@ -35,6 +23,27 @@
 ''', []);
   }
 
+  test_FfiNativeCanUseLeaf() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+@FfiNative<Int8 Function(Int64)>('DoesntMatter', isLeaf:true)
+external int doesntMatter(int);
+''', []);
+  }
+
+  test_FfiNativeInstanceMethodsMustHaveReceiver() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+class K {
+  @FfiNative<Void Function(Double)>('DoesntMatter')
+  external void doesntMatter(double x);
+}
+''', [
+      error(FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER,
+          31, 89),
+    ]);
+  }
+
   test_FfiNativeLeafMustNotReturnHandle() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
@@ -54,4 +63,24 @@
       error(FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE, 19, 100),
     ]);
   }
+
+  test_FfiNativeTooFewParameters() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+@FfiNative<Void Function(Double)>('DoesntMatter')
+external void doesntMatter(double x, double y);
+''', [
+      error(FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS, 19, 97),
+    ]);
+  }
+
+  test_FfiNativeTooManyParameters() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+@FfiNative<Void Function(Double, Double)>('DoesntMatter')
+external void doesntMatter(double x);
+''', [
+      error(FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS, 19, 95),
+    ]);
+  }
 }
diff --git a/pkg/analyzer_utilities/lib/tools.dart b/pkg/analyzer_utilities/lib/tools.dart
index abb3375..a072d85 100644
--- a/pkg/analyzer_utilities/lib/tools.dart
+++ b/pkg/analyzer_utilities/lib/tools.dart
@@ -10,6 +10,7 @@
 import 'package:analyzer_utilities/text_formatter.dart';
 import 'package:html/dom.dart' as dom;
 import 'package:path/path.dart';
+import 'package:test/test.dart';
 
 final RegExp trailingSpacesInLineRegExp = RegExp(r' +$', multiLine: true);
 final RegExp trailingWhitespaceRegExp = RegExp(r'[\n ]+$');
@@ -290,7 +291,7 @@
       }
       var generateScript = normalize(joinAll(posix.split(generatorPath)));
       print('  $executable$packageRoot $generateScript ${args.join(" ")}');
-      exit(1);
+      fail('Error codes need to be generated');
     } else {
       print('All generated files up to date.');
     }
diff --git a/pkg/front_end/lib/src/api_unstable/vm.dart b/pkg/front_end/lib/src/api_unstable/vm.dart
index e981bd3..141014c 100644
--- a/pkg/front_end/lib/src/api_unstable/vm.dart
+++ b/pkg/front_end/lib/src/api_unstable/vm.dart
@@ -54,8 +54,9 @@
         messageFfiExpectedConstant,
         messageFfiLeafCallMustNotReturnHandle,
         messageFfiLeafCallMustNotTakeHandle,
+        messageFfiNativeMustBeExternal,
+        messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
         messageFfiPackedAnnotationAlignment,
-        messageFfiNativeAnnotationMustAnnotateStatic,
         messageNonPositiveArrayDimensions,
         noLength,
         templateFfiDartTypeMismatch,
@@ -69,6 +70,8 @@
         templateFfiFieldInitializer,
         templateFfiFieldNoAnnotation,
         templateFfiFieldNull,
+        templateFfiNativeUnexpectedNumberOfParameters,
+        templateFfiNativeUnexpectedNumberOfParametersWithReceiver,
         templateFfiNotStatic,
         templateFfiPackedAnnotation,
         templateFfiPackedNestingNonPacked,
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index 97ad8a0..308d0a6 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -347,7 +347,10 @@
 FfiFieldNull/analyzerCode: Fail
 FfiLeafCallMustNotReturnHandle/analyzerCode: Fail
 FfiLeafCallMustNotTakeHandle/analyzerCode: Fail
-FfiNativeAnnotationMustAnnotateStatic/analyzerCode: Fail
+FfiNativeMustBeExternal/analyzerCode: Fail
+FfiNativeOnlyNativeFieldWrapperClassCanBePointer/analyzerCode: Fail
+FfiNativeUnexpectedNumberOfParameters/analyzerCode: Fail
+FfiNativeUnexpectedNumberOfParametersWithReceiver/analyzerCode: Fail
 FfiNotStatic/analyzerCode: Fail
 FfiPackedAnnotation/analyzerCode: Fail
 FfiPackedAnnotationAlignment/analyzerCode: Fail
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index bebd92b..c792862 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -4672,9 +4672,24 @@
   problemMessage: "FFI leaf call must not have Handle return type."
   external: test/ffi_test.dart
 
-FfiNativeAnnotationMustAnnotateStatic:
+FfiNativeUnexpectedNumberOfParametersWithReceiver:
   # Used by dart:ffi
-  problemMessage: "FfiNative annotations can only be used on static functions."
+  problemMessage: "Unexpected number of FfiNative annotation parameters. Expected #count but has #count2. FfiNative instance method annotation must have receiver as first argument."
+  external: test/ffi_test.dart
+
+FfiNativeUnexpectedNumberOfParameters:
+  # Used by dart:ffi
+  problemMessage: "Unexpected number of FfiNative annotation parameters. Expected #count but has #count2."
+  external: test/ffi_test.dart
+
+FfiNativeOnlyNativeFieldWrapperClassCanBePointer:
+  # Used by dart:ffi
+  problemMessage: "Only classes extending NativeFieldWrapperClass1 can be passed as Pointer."
+  external: test/ffi_test.dart
+
+FfiNativeMustBeExternal:
+  # Used by dart:ffi
+  problemMessage: "FfiNative functions must be marked external."
   external: test/ffi_test.dart
 
 SpreadTypeMismatch:
diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt
index e3c5e27..ffc3264 100644
--- a/pkg/front_end/test/spell_checking_list_messages.txt
+++ b/pkg/front_end/test/spell_checking_list_messages.txt
@@ -49,6 +49,7 @@
 name.stack
 nameokempty
 native('native
+nativefieldwrapperclass
 natively
 nativetype
 nnbd
diff --git a/pkg/vm/lib/target/vm.dart b/pkg/vm/lib/target/vm.dart
index b788180..4316715 100644
--- a/pkg/vm/lib/target/vm.dart
+++ b/pkg/vm/lib/target/vm.dart
@@ -157,10 +157,15 @@
     if (!ffiHelper.importsFfi(component, libraries)) {
       logger?.call("Skipped ffi transformation");
     } else {
-      // Transform @FfiNative(..) functions into ffi native call functions.
-      transformFfiNative.transformLibraries(
-          component, libraries, diagnosticReporter, referenceFromIndex);
+      // Transform @FfiNative(..) functions into FFI native call functions.
+      // Pass instance method receivers as implicit first argument to the static
+      // native function.
+      // Transform arguments that extend NativeFieldWrapperClass1 to Pointer if
+      // the native function expects Pointer (to avoid Handle overhead).
+      transformFfiNative.transformLibraries(component, coreTypes, hierarchy,
+          libraries, diagnosticReporter, referenceFromIndex);
       logger?.call("Transformed ffi natives");
+
       // TODO(jensj/dacoharkes): We can probably limit the transformations to
       // libraries that transitivley depend on dart:ffi.
       transformFfiDefinitions.transformLibraries(
diff --git a/pkg/vm/lib/transformations/ffi.dart b/pkg/vm/lib/transformations/ffi.dart
index 25e2ac8..63727a5 100644
--- a/pkg/vm/lib/transformations/ffi.dart
+++ b/pkg/vm/lib/transformations/ffi.dart
@@ -235,7 +235,7 @@
   final Class structClass;
   final Class unionClass;
   final Class ffiNativeClass;
-  final Class nativeFieldWrapperClass;
+  final Class nativeFieldWrapperClass1Class;
   final Class ffiStructLayoutClass;
   final Field ffiStructLayoutTypesField;
   final Field ffiStructLayoutPackingField;
@@ -291,9 +291,9 @@
   final Procedure getNativeFieldFunction;
   final Procedure reachabilityFenceFunction;
 
-  late final DartType nativeFieldWrapperClassType;
-  late final DartType voidType;
-  late final DartType pointerType;
+  late final InterfaceType nativeFieldWrapperClass1Type;
+  late final InterfaceType voidType;
+  late final InterfaceType pointerVoidType;
 
   /// Classes corresponding to [NativeType], indexed by [NativeType].
   final List<Class> nativeTypesClasses;
@@ -355,7 +355,7 @@
         structClass = index.getClass('dart:ffi', 'Struct'),
         unionClass = index.getClass('dart:ffi', 'Union'),
         ffiNativeClass = index.getClass('dart:ffi', 'FfiNative'),
-        nativeFieldWrapperClass =
+        nativeFieldWrapperClass1Class =
             index.getClass('dart:nativewrappers', 'NativeFieldWrapperClass1'),
         ffiStructLayoutClass = index.getClass('dart:ffi', '_FfiStructLayout'),
         ffiStructLayoutTypesField =
@@ -471,11 +471,11 @@
             'dart:nativewrappers', '_getNativeField'),
         reachabilityFenceFunction =
             index.getTopLevelProcedure('dart:_internal', 'reachabilityFence') {
-    nativeFieldWrapperClassType =
-        nativeFieldWrapperClass.getThisType(coreTypes, Nullability.nonNullable);
+    nativeFieldWrapperClass1Type = nativeFieldWrapperClass1Class.getThisType(
+        coreTypes, Nullability.nonNullable);
     voidType = nativeTypesClasses[NativeType.kVoid.index]
         .getThisType(coreTypes, Nullability.nonNullable);
-    pointerType =
+    pointerVoidType =
         InterfaceType(pointerClass, Nullability.nonNullable, [voidType]);
   }
 
diff --git a/pkg/vm/lib/transformations/ffi_native.dart b/pkg/vm/lib/transformations/ffi_native.dart
index 3427f28..ebd2cf6 100644
--- a/pkg/vm/lib/transformations/ffi_native.dart
+++ b/pkg/vm/lib/transformations/ffi_native.dart
@@ -2,35 +2,44 @@
 // 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:kernel/ast.dart';
-import 'package:kernel/library_index.dart' show LibraryIndex;
-import 'package:kernel/reference_from_index.dart'
-    show IndexedLibrary, ReferenceFromIndex;
-import 'package:kernel/target/targets.dart' show DiagnosticReporter;
 import 'package:front_end/src/api_unstable/vm.dart'
-    show messageFfiNativeAnnotationMustAnnotateStatic;
+    show
+        messageFfiNativeMustBeExternal,
+        messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
+        templateFfiNativeUnexpectedNumberOfParameters,
+        templateFfiNativeUnexpectedNumberOfParametersWithReceiver;
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
+import 'package:kernel/library_index.dart' show LibraryIndex;
+import 'package:kernel/reference_from_index.dart' show ReferenceFromIndex;
+import 'package:kernel/target/targets.dart' show DiagnosticReporter;
+import 'package:kernel/type_environment.dart';
+
+import 'ffi.dart' show FfiTransformer;
 
 /// Transform @FfiNative annotated functions into FFI native function pointer
 /// functions.
 void transformLibraries(
     Component component,
+    CoreTypes coreTypes,
+    ClassHierarchy hierarchy,
     List<Library> libraries,
     DiagnosticReporter diagnosticReporter,
     ReferenceFromIndex? referenceFromIndex) {
-  final index = LibraryIndex(component, ['dart:ffi']);
+  final index = LibraryIndex(component,
+      ['dart:ffi', 'dart:_internal', 'dart:typed_data', 'dart:nativewrappers']);
   // Skip if dart:ffi isn't loaded (e.g. during incremental compile).
   if (index.tryGetClass('dart:ffi', 'FfiNative') == null) {
     return;
   }
-  final transformer =
-      FfiNativeTransformer(index, diagnosticReporter, referenceFromIndex);
+  final transformer = FfiNativeTransformer(
+      index, coreTypes, hierarchy, diagnosticReporter, referenceFromIndex);
   libraries.forEach(transformer.visitLibrary);
 }
 
-class FfiNativeTransformer extends Transformer {
-  Library? currentLibrary;
-  IndexedLibrary? currentLibraryIndex;
-
+class FfiNativeTransformer extends FfiTransformer {
   final DiagnosticReporter diagnosticReporter;
   final ReferenceFromIndex? referenceFromIndex;
   final Class ffiNativeClass;
@@ -38,11 +47,17 @@
   final Field ffiNativeNameField;
   final Field ffiNativeIsLeafField;
   final Field resolverField;
-  final Procedure asFunctionProcedure;
-  final Procedure fromAddressInternal;
+
+  // VariableDeclaration names can be null or empty string, in which case
+  // they're automatically assigned a "temporary" name like `#t0`.
+  static const variableDeclarationTemporaryName = null;
 
   FfiNativeTransformer(
-      LibraryIndex index, this.diagnosticReporter, this.referenceFromIndex)
+      LibraryIndex index,
+      CoreTypes coreTypes,
+      ClassHierarchy hierarchy,
+      this.diagnosticReporter,
+      this.referenceFromIndex)
       : ffiNativeClass = index.getClass('dart:ffi', 'FfiNative'),
         nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'),
         ffiNativeNameField =
@@ -50,122 +65,396 @@
         ffiNativeIsLeafField =
             index.getField('dart:ffi', 'FfiNative', 'isLeaf'),
         resolverField = index.getTopLevelField('dart:ffi', '_ffi_resolver'),
-        asFunctionProcedure = index.getProcedure(
-            'dart:ffi', 'NativeFunctionPointer', 'asFunction'),
-        fromAddressInternal =
-            index.getTopLevelProcedure('dart:ffi', '_fromAddress') {}
+        super(index, coreTypes, hierarchy, diagnosticReporter,
+            referenceFromIndex);
 
-  @override
-  TreeNode visitLibrary(Library node) {
-    assert(currentLibrary == null);
-    currentLibrary = node;
-    currentLibraryIndex = referenceFromIndex?.lookupLibrary(node);
-    final result = super.visitLibrary(node);
-    currentLibrary = null;
-    return result;
-  }
-
-  InstanceConstant? _tryGetFfiNativeAnnotation(Member node) {
+  ConstantExpression? _tryGetFfiNativeAnnotation(Member node) {
     for (final Expression annotation in node.annotations) {
-      if (annotation is ConstantExpression) {
-        if (annotation.constant is InstanceConstant) {
-          final instConst = annotation.constant as InstanceConstant;
-          if (instConst.classNode == ffiNativeClass) {
-            return instConst;
-          }
-        }
+      if (annotation is! ConstantExpression) {
+        continue;
+      }
+      final annotationConstant = annotation.constant;
+      if (annotationConstant is! InstanceConstant) {
+        continue;
+      }
+      if (annotationConstant.classNode == ffiNativeClass) {
+        return annotation;
       }
     }
     return null;
   }
 
-  // Transform:
-  //   @FfiNative<Double Function(Double)>('Math_sqrt', isLeaf:true)
-  //   external double _square_root(double x);
+  bool _extendsNativeFieldWrapperClass1(DartType type) {
+    if (type is InterfaceType) {
+      Class? cls = type.classNode;
+      while (cls != null) {
+        if (cls == nativeFieldWrapperClass1Class) {
+          return true;
+        }
+        cls = cls.superclass;
+      }
+    }
+    return false;
+  }
+
+  // Replaces parameters with Pointer if:
+  // 1) they extend NativeFieldWrapperClass1, and
+  // 2) the corresponding native parameter is Pointer.
+  FunctionType _pointerizeFunctionType(
+      FunctionType dartType, FunctionType nativeType) {
+    final parameters = <DartType>[];
+    for (var i = 0; i < dartType.positionalParameters.length; i++) {
+      final parameter = dartType.positionalParameters[i];
+      if (parameter is InterfaceType) {
+        final nativeParameter = nativeType.positionalParameters[i];
+        if (_extendsNativeFieldWrapperClass1(parameter) &&
+            env.isSubtypeOf(nativeParameter, pointerVoidType,
+                SubtypeCheckMode.ignoringNullabilities)) {
+          parameters.add(pointerVoidType);
+          continue;
+        }
+      }
+      parameters.add(parameter);
+    }
+    return FunctionType(parameters, dartType.returnType, dartType.nullability);
+  }
+
+  // Create field holding the resolved native function pointer.
   //
-  // Into:
-  //   final _@FfiNative__square_root =
-  //       Pointer<NativeFunction<Double Function(Double)>>
-  //           .fromAddress(_ffi_resolver('dart:math', 'Math_sqrt'))
-  //           .asFunction<double Function(double)>(isLeaf:true);
-  //   double _square_root(double x) => _@FfiNative__square_root(x);
-  Statement transformFfiNative(
-      Procedure node, InstanceConstant annotationConst) {
-    assert(currentLibrary != null);
-    final params = node.function.positionalParameters;
-    final functionName = annotationConst
+  // For:
+  //   @FfiNative<IntPtr Function(Pointer<Void>)>('DoXYZ', isLeaf:true)
+  //   external int doXyz(NativeFieldWrapperClass1 obj);
+  //
+  // Create:
+  //   static final _doXyz$FfiNative$ptr =
+  //       Pointer<NativeFunction<IntPtr Function(Pointer<Void>)>>
+  //           .fromAddress(_ffi_resolver('..', 'DoXYZ', 1))
+  //           .asFunction<int Function(Pointer<Void>)>(isLeaf:true);
+  Field _createResolvedFfiNativeField(
+      InstanceConstant annotationConst,
+      String dartFunctionName,
+      FunctionType dartType,
+      FunctionType nativeType,
+      TreeNode? parent,
+      int fileOffset) {
+    final nativeFunctionName = annotationConst
         .fieldValues[ffiNativeNameField.fieldReference] as StringConstant;
     final isLeaf = annotationConst
         .fieldValues[ffiNativeIsLeafField.fieldReference] as BoolConstant;
 
-    // double Function(double)
-    final DartType dartType =
-        node.function.computeThisFunctionType(Nullability.nonNullable);
-    // Double Function(Double)
-    final nativeType = annotationConst.typeArguments[0] as FunctionType;
-    // InterfaceType(NativeFunction<Double Function(Double)>)
-    final DartType nativeInterfaceType = InterfaceType(
-        nativeFunctionClass, Nullability.nonNullable, [nativeType]);
+    // int Function(Pointer<Void>)
+    final dartTypePointerized = _pointerizeFunctionType(dartType, nativeType);
 
     // Derive number of arguments from the native function signature.
-    final args_n = nativeType.positionalParameters.length;
+    final numberNativeArgs = nativeType.positionalParameters.length;
 
-    // TODO(dartbug.com/31579): Add `..fileOffset`s once we can handle these in
-    // patch files.
-
-    // _ffi_resolver('dart:math', 'Math_sqrt', 1)
+    // _ffi_resolver('...', 'DoXYZ', 1)
     final resolverInvocation = FunctionInvocation(
         FunctionAccessKind.FunctionType,
         StaticGet(resolverField),
         Arguments([
           ConstantExpression(
-              StringConstant(currentLibrary!.importUri.toString())),
-          ConstantExpression(functionName),
-          ConstantExpression(IntConstant(args_n)),
+              StringConstant(currentLibrary.importUri.toString())),
+          ConstantExpression(nativeFunctionName),
+          ConstantExpression(IntConstant(numberNativeArgs)),
         ]),
-        functionType: resolverField.type as FunctionType);
+        functionType: resolverField.type as FunctionType)
+      ..fileOffset = fileOffset;
 
     // _fromAddress<NativeFunction<Double Function(Double)>>(...)
-    final fromAddressInvocation = StaticInvocation(fromAddressInternal,
-        Arguments([resolverInvocation], types: [nativeInterfaceType]));
+    final fromAddressInvocation = StaticInvocation(
+        fromAddressInternal,
+        Arguments([
+          resolverInvocation
+        ], types: [
+          InterfaceType(nativeFunctionClass, Nullability.legacy, [nativeType])
+        ]))
+      ..fileOffset = fileOffset;
 
     // NativeFunctionPointer.asFunction
     //     <Double Function(Double), double Function(double)>(..., isLeaf:true)
     final asFunctionInvocation = StaticInvocation(
-        asFunctionProcedure,
+        asFunctionMethod,
         Arguments([fromAddressInvocation],
-            types: [nativeType, dartType],
-            named: [NamedExpression("isLeaf", BoolLiteral(isLeaf.value))]));
+            types: [nativeType, dartTypePointerized],
+            named: [NamedExpression('isLeaf', BoolLiteral(isLeaf.value))]))
+      ..fileOffset = fileOffset;
 
-    // final _@FfiNative__square_root = ...
-    final fieldName = Name('_@FfiNative_${node.name.text}', currentLibrary);
-    final funcPtrField = Field.immutable(fieldName,
-        type: dartType,
+    // static final _doXyz$FfiNative$Ptr = ...
+    final fieldName =
+        Name('_$dartFunctionName\$FfiNative\$Ptr', currentLibrary);
+    final functionPointerField = Field.immutable(fieldName,
+        type: dartTypePointerized,
         initializer: asFunctionInvocation,
         isStatic: true,
         isFinal: true,
-        fileUri: currentLibrary!.fileUri,
+        fileUri: currentLibrary.fileUri,
         getterReference: currentLibraryIndex?.lookupGetterReference(fieldName))
-      ..fileOffset = node.fileOffset;
+      ..fileOffset = fileOffset;
+
     // Add field to the parent the FfiNative function belongs to.
-    final parent = node.parent;
     if (parent is Class) {
-      parent.addField(funcPtrField);
+      parent.addField(functionPointerField);
     } else if (parent is Library) {
-      parent.addField(funcPtrField);
+      parent.addField(functionPointerField);
     } else {
       throw 'Unexpected parent of @FfiNative function. '
           'Expected Class or Library, but found ${parent}.';
     }
 
-    // _@FfiNative__square_root(x)
-    final callFuncPtrInvocation = FunctionInvocation(
-        FunctionAccessKind.FunctionType,
-        StaticGet(funcPtrField),
-        Arguments(params.map<Expression>((p) => VariableGet(p)).toList()),
-        functionType: dartType as FunctionType);
+    return functionPointerField;
+  }
 
-    return ReturnStatement(callFuncPtrInvocation);
+  // FfiNative calls that pass objects extending NativeFieldWrapperClass1
+  // should be passed as Pointer instead so we don't have the overhead of
+  // converting Handles.
+  // If we find a NativeFieldWrapperClass1 object being passed to an FfiNative
+  // signature taking a Pointer, we automatically wrap the argument in a call to
+  // `Pointer.fromAddress(_getNativeField(obj))`.
+  //
+  // Example:
+  //   passAsPointer(ClassWithNativeField());
+  //
+  // Becomes, roughly:
+  //   {
+  //     final NativeFieldWrapperClass1#t0 = ClassWithNativeField();
+  //     final #t1 = passAsPointer(Pointer.fromAddress(_getNativeField(#t0)));
+  //     reachabilityFence(#t0);
+  //   } => #t1
+  Expression _convertArgumentsNativeFieldWrapperClass1ToPointer(
+      FunctionInvocation invocation,
+      List<DartType> ffiParameters,
+      List<VariableDeclaration> dartParameters) {
+    // Create lists of temporary variables for arguments potentially being
+    // wrapped, and the (potentially) wrapped arguments to be passed.
+    final temporariesForArguments = [];
+    final callArguments = <Expression>[];
+    final fencedArguments = [];
+    bool hasPointer = false;
+    for (int i = 0; i < invocation.arguments.positional.length; i++) {
+      if (env.isSubtypeOf(ffiParameters[i], pointerVoidType,
+              SubtypeCheckMode.ignoringNullabilities) &&
+          !env.isSubtypeOf(dartParameters[i].type, pointerVoidType,
+              SubtypeCheckMode.ignoringNullabilities)) {
+        // Only NativeFieldWrapperClass1 instances can be passed as pointer.
+        if (!_extendsNativeFieldWrapperClass1(dartParameters[i].type)) {
+          diagnosticReporter.report(
+              messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
+              dartParameters[i].fileOffset,
+              1,
+              invocation.location?.file);
+        }
+
+        hasPointer = true;
+
+        // final NativeFieldWrapperClass1 #t0 = ClassWithNativeField();
+        final argument = VariableDeclaration(variableDeclarationTemporaryName,
+            initializer: invocation.arguments.positional[i],
+            type: nativeFieldWrapperClass1Type,
+            isFinal: true);
+        temporariesForArguments.add(argument);
+        fencedArguments.add(argument);
+
+        // Pointer.fromAddress(_getNativeField(#t0))
+        final ptr = StaticInvocation(
+            fromAddressInternal,
+            Arguments([
+              StaticInvocation(
+                  getNativeFieldFunction, Arguments([VariableGet(argument)]))
+            ], types: [
+              voidType
+            ]));
+        callArguments.add(ptr);
+
+        continue;
+      }
+      // Note: We also evaluate, and assign temporaries for, non-wrapped
+      // arguments as we need to preserve the original evaluation order.
+      final argument = VariableDeclaration(variableDeclarationTemporaryName,
+          initializer: invocation.arguments.positional[i],
+          type: dartParameters[i].type,
+          isFinal: true);
+      temporariesForArguments.add(argument);
+      callArguments.add(VariableGet(argument));
+    }
+
+    // If there are no arguments to convert then we can drop the whole wrap.
+    if (!hasPointer) {
+      return invocation;
+    }
+
+    invocation.arguments = Arguments(callArguments);
+
+    // {
+    //   final NativeFieldWrapperClass1 #t0 = ClassWithNativeField();
+    //   final T #t1 = foo(Pointer.fromAddress(_getNativeField(#t0)));
+    //   reachabilityFence(#t0);
+    // } => #t1
+    final result = VariableDeclaration(variableDeclarationTemporaryName,
+        initializer: invocation,
+        type: invocation.functionType!.returnType,
+        isFinal: true);
+    return BlockExpression(
+      Block([
+        ...temporariesForArguments,
+        result,
+        for (final argument in fencedArguments)
+          ExpressionStatement(StaticInvocation(
+              reachabilityFenceFunction, Arguments([VariableGet(argument)])))
+      ]),
+      VariableGet(result),
+    );
+  }
+
+  // Transform FfiNative instance methods.
+  // Example:
+  //   class MyNativeClass extends NativeFieldWrapperClass1 {
+  //     @FfiNative<IntPtr Function(Pointer<Void>, IntPtr)>('MyClass_MyMethod')
+  //     external int myMethod(int x);
+  //   }
+  // Becomes, roughly:
+  //   ... {
+  //     static final _myMethod$FfiNative$Ptr = ...
+  //     static _myMethod$FfiNative(MyNativeClass self, int x)
+  //       => _myMethod$FfiNative$Ptr(
+  //         Pointer<Void>.fromAddress(_getNativeField(self)), x);
+  //     int myMethod(int x) => _myMethod$FfiNative(this, x);
+  //   }
+  Procedure _transformInstanceMethod(
+      Procedure node, InstanceConstant ffiConstant, int annotationOffset) {
+    final ffiSignature = ffiConstant.typeArguments[0] as FunctionType;
+
+    // The FfiNative annotation should have an extra parameter for `self`.
+    if (node.function.positionalParameters.length + 1 !=
+        ffiSignature.positionalParameters.length) {
+      diagnosticReporter.report(
+          templateFfiNativeUnexpectedNumberOfParametersWithReceiver
+              .withArguments(node.function.positionalParameters.length + 1,
+                  ffiSignature.positionalParameters.length),
+          annotationOffset,
+          1,
+          node.location?.file);
+      return node;
+    }
+
+    final cls = node.parent as Class;
+
+    final staticParameters = [
+      // Add the implicit `self` for the inner glue functions.
+      VariableDeclaration('self',
+          type: cls.getThisType(coreTypes, Nullability.nonNullable)),
+      for (final parameter in node.function.positionalParameters)
+        VariableDeclaration(parameter.name, type: parameter.type)
+    ];
+
+    final staticFunctionType = FunctionType(
+        staticParameters.map((e) => e.type).toList(),
+        node.function.returnType,
+        Nullability.nonNullable);
+
+    // static final _myMethod$FfiNative$Ptr = ..
+    final resolvedField = _createResolvedFfiNativeField(
+        ffiConstant,
+        node.name.text,
+        staticFunctionType,
+        ffiSignature,
+        node.parent,
+        node.fileOffset);
+
+    //   _myMethod$FfiNative$Ptr(self, x)
+    final functionPointerInvocation = FunctionInvocation(
+        FunctionAccessKind.FunctionType,
+        StaticGet(resolvedField),
+        Arguments(
+            [for (final parameter in staticParameters) VariableGet(parameter)]),
+        functionType: staticFunctionType)
+      ..fileOffset = node.fileOffset;
+
+    //   static _myMethod$FfiNative(MyNativeClass self, int x)
+    //     => _myMethod$FfiNative$Ptr(
+    //       Pointer<Void>.fromAddress(_getNativeField(self)), x)
+    final ffiProcedure = Procedure(
+        Name('_${node.name.text}\$FfiNative', currentLibrary),
+        ProcedureKind.Method,
+        FunctionNode(
+            ReturnStatement(_convertArgumentsNativeFieldWrapperClass1ToPointer(
+                functionPointerInvocation,
+                ffiSignature.positionalParameters,
+                staticParameters)),
+            positionalParameters: staticParameters),
+        isStatic: true,
+        fileUri: node.fileUri)
+      ..fileOffset = node.fileOffset;
+    cls.addProcedure(ffiProcedure);
+
+    //   => _myMethod$FfiNative(this, x)
+    node.function.body = ReturnStatement(StaticInvocation(
+        ffiProcedure,
+        Arguments([
+          ThisExpression(),
+          for (var parameter in node.function.positionalParameters)
+            VariableGet(parameter)
+        ])))
+      ..parent = node.function;
+
+    return node;
+  }
+
+  // Transform FfiNative static functions.
+  // Example:
+  //   @FfiNative<IntPtr Function(Pointer<Void>, IntPtr)>('MyFunction')
+  //   external int myFunction(MyNativeClass obj, int x);
+  // Becomes, roughly:
+  //   static final _myFunction$FfiNative$Ptr = ...
+  //   int myFunction(MyNativeClass obj, int x)
+  //     => myFunction$FfiNative$Ptr(
+  //       Pointer<Void>.fromAddress(_getNativeField(obj)), x);
+  Procedure _transformStaticFunction(
+      Procedure node, InstanceConstant ffiConstant, int annotationOffset) {
+    final ffiSignature = ffiConstant.typeArguments[0] as FunctionType;
+
+    if (node.function.positionalParameters.length !=
+        ffiSignature.positionalParameters.length) {
+      diagnosticReporter.report(
+          templateFfiNativeUnexpectedNumberOfParameters.withArguments(
+              node.function.positionalParameters.length,
+              ffiSignature.positionalParameters.length),
+          annotationOffset,
+          1,
+          node.location?.file);
+      return node;
+    }
+
+    // _myFunction$FfiNative$Ptr = ..
+    final resolvedField = _createResolvedFfiNativeField(
+        ffiConstant,
+        node.name.text,
+        node.function.computeThisFunctionType(Nullability.nonNullable),
+        ffiSignature,
+        node.parent,
+        node.fileOffset);
+
+    // _myFunction$FfiNative$Ptr(obj, x)
+    final functionPointerInvocation = FunctionInvocation(
+        FunctionAccessKind.FunctionType,
+        StaticGet(resolvedField),
+        Arguments([
+          for (final parameter in node.function.positionalParameters)
+            VariableGet(parameter)
+        ]),
+        functionType: resolvedField.type as FunctionType)
+      ..fileOffset = node.fileOffset;
+
+    //   => _myFunction$FfiNative$Ptr(
+    //     Pointer<Void>.fromAddress(_getNativeField(obj)), x)
+    node.function.body = ReturnStatement(
+        _convertArgumentsNativeFieldWrapperClass1ToPointer(
+            functionPointerInvocation,
+            ffiSignature.positionalParameters,
+            node.function.positionalParameters))
+      ..parent = node.function;
+
+    return node;
   }
 
   @override
@@ -173,23 +462,30 @@
     // Only transform functions that are external and have FfiNative annotation:
     //   @FfiNative<Double Function(Double)>('Math_sqrt')
     //   external double _square_root(double x);
-    if (!node.isExternal) {
-      return node;
-    }
-    InstanceConstant? ffiNativeAnnotation = _tryGetFfiNativeAnnotation(node);
+    final ffiNativeAnnotation = _tryGetFfiNativeAnnotation(node);
     if (ffiNativeAnnotation == null) {
       return node;
     }
 
+    if (!node.isExternal) {
+      diagnosticReporter.report(messageFfiNativeMustBeExternal, node.fileOffset,
+          1, node.location?.file);
+      return node;
+    }
+    node.isExternal = false;
+
+    node.annotations.remove(ffiNativeAnnotation);
+
     if (!node.isStatic) {
-      diagnosticReporter.report(messageFfiNativeAnnotationMustAnnotateStatic,
-          node.fileOffset, 1, node.location!.file);
+      return _transformInstanceMethod(
+          node,
+          ffiNativeAnnotation.constant as InstanceConstant,
+          ffiNativeAnnotation.fileOffset);
     }
 
-    node.isExternal = false;
-    node.function.body = transformFfiNative(node, ffiNativeAnnotation)
-      ..parent = node.function;
-
-    return node;
+    return _transformStaticFunction(
+        node,
+        ffiNativeAnnotation.constant as InstanceConstant,
+        ffiNativeAnnotation.fileOffset);
   }
 }
diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart
index 2dc593a..eb17eb0 100644
--- a/pkg/vm/lib/transformations/ffi_use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi_use_sites.dart
@@ -131,20 +131,6 @@
     return result;
   }
 
-  InstanceConstant? _tryGetFfiNativeAnnotation(Member node) {
-    for (final Expression annotation in node.annotations) {
-      if (annotation is ConstantExpression) {
-        if (annotation.constant is InstanceConstant) {
-          final instConst = annotation.constant as InstanceConstant;
-          if (instConst.classNode == ffiNativeClass) {
-            return instConst;
-          }
-        }
-      }
-    }
-    return null;
-  }
-
   @override
   visitStaticInvocation(StaticInvocation node) {
     super.visitStaticInvocation(node);
@@ -366,88 +352,6 @@
                   .substituteType(allocateFunctionType
                       .withoutTypeParameters) as FunctionType);
         }
-      } else if (target is Procedure) {
-        // FfiNative calls that pass objects extending NativeFieldWrapperClass1
-        // (NFWC1) should be passed as Pointer instead so we don't have the
-        // overhead of converting Handles.
-        // If we find an NFWC1 object being passed to an FfiNative signature
-        // taking a Pointer, we automatically wrap the argument in a call to
-        // `Pointer.fromAddress(_getNativeField(obj))`.
-        // Example:
-        //   passAsPointer(ClassWithNativeField());
-        // Becomes, roughly:
-        //   #t0 = PointerClassWithNativeField();
-        //   passAsPointer(Pointer.fromAddress(_getNativeField(#t0)));
-        //   reachabilityFence(#t0);
-        final ffiNativeAnn = _tryGetFfiNativeAnnotation(target);
-        if (ffiNativeAnn != null) {
-          final DartType ffiSignature = ffiNativeAnn.typeArguments[0];
-          if (ffiSignature is FunctionType) {
-            final List<DartType> ffiParams = ffiSignature.positionalParameters;
-            final List<VariableDeclaration> dartParams =
-                target.function.positionalParameters;
-
-            List<VariableDeclaration> tmpsArgs = [];
-            List<Expression> callArgs = [];
-            final origArgs = node.arguments.positional;
-            for (int i = 0; i < origArgs.length; i++) {
-              if (env.isSubtypeOf(
-                      dartParams[i].type,
-                      nativeFieldWrapperClassType,
-                      SubtypeCheckMode.ignoringNullabilities) &&
-                  env.isSubtypeOf(ffiParams[i], pointerType,
-                      SubtypeCheckMode.ignoringNullabilities)) {
-                // final NativeFieldWrapperClass1 #t1 = MyNFWC1();.
-                final tmpPtr = VariableDeclaration(null,
-                    initializer: origArgs[i],
-                    type: nativeFieldWrapperClassType,
-                    isFinal: true);
-                tmpsArgs.add(tmpPtr);
-
-                // Pointer.fromAddress(_getNativeField(#t1)).
-                final ptr = StaticInvocation(
-                    fromAddressInternal,
-                    Arguments([
-                      StaticInvocation(getNativeFieldFunction,
-                          Arguments([VariableGet(tmpPtr)]))
-                    ], types: [
-                      voidType
-                    ]));
-                callArgs.add(ptr);
-
-                continue;
-              }
-              // Note: We also evaluate, and assign temporaries for, non-wrapped
-              // arguments as we need to preserve the original evaluation order.
-              final tmpArg = VariableDeclaration(null,
-                  initializer: origArgs[i], isFinal: true);
-              tmpsArgs.add(tmpArg);
-              callArgs.add(VariableGet(tmpArg));
-            }
-
-            final targetCall = StaticInvocation(target, Arguments(callArgs));
-
-            // {
-            //   T #t0;
-            //   final NativeFieldWrapperClass1 #t1 = MyNFWC1();
-            //   #t0 = foo(Pointer.fromAddress(_getNativeField(#t1)));
-            //   reachabilityFence(#t1);
-            // } => #t0
-            final tmpResult =
-                VariableDeclaration(null, type: target.function.returnType);
-            return BlockExpression(
-              Block([
-                tmpResult,
-                ...tmpsArgs,
-                ExpressionStatement(VariableSet(tmpResult, targetCall)),
-                for (final ta in tmpsArgs)
-                  ExpressionStatement(StaticInvocation(
-                      reachabilityFenceFunction, Arguments([VariableGet(ta)])))
-              ]),
-              VariableGet(tmpResult),
-            );
-          }
-        }
       }
     } on _FfiStaticTypeError {
       // It's OK to swallow the exception because the diagnostics issued will
@@ -809,22 +713,6 @@
     return pointerType is InterfaceType ? pointerType.typeArguments[0] : null;
   }
 
-  // Replaces all NativeFieldWrapperClass1 parameters with Pointer.
-  FunctionType _pointerizeFunctionType(FunctionType dartType) {
-    List<DartType> parameters = [];
-    for (final parameter in dartType.positionalParameters) {
-      if (parameter is InterfaceType) {
-        if (env.isSubtypeOf(parameter, nativeFieldWrapperClassType,
-            SubtypeCheckMode.ignoringNullabilities)) {
-          parameters.add(pointerType);
-          continue;
-        }
-      }
-      parameters.add(parameter);
-    }
-    return FunctionType(parameters, dartType.returnType, dartType.nullability);
-  }
-
   void _ensureNativeTypeToDartType(
       DartType nativeType, DartType dartType, Expression node,
       {bool allowHandle: false}) {
@@ -837,15 +725,6 @@
         SubtypeCheckMode.ignoringNullabilities)) {
       return;
     }
-    // We do automatic argument conversion from NativeFieldWrapperClass1 to
-    // Pointer, so we specifically allow for NFWC1 to be passed as Pointer.
-    if (dartType is FunctionType) {
-      final ptrDartType = _pointerizeFunctionType(dartType);
-      if (env.isSubtypeOf(correspondingDartType, ptrDartType,
-          SubtypeCheckMode.ignoringNullabilities)) {
-        return;
-      }
-    }
     diagnosticReporter.report(
         templateFfiTypeMismatch.withArguments(dartType, correspondingDartType,
             nativeType, currentLibrary.isNonNullableByDefault),
diff --git a/pkg/vm/test/transformations/ffinative_test.dart b/pkg/vm/test/transformations/ffinative_test.dart
index 76eba47..dfcb6f4 100644
--- a/pkg/vm/test/transformations/ffinative_test.dart
+++ b/pkg/vm/test/transformations/ffinative_test.dart
@@ -5,8 +5,9 @@
 import 'dart:io';
 
 import 'package:kernel/ast.dart';
+import 'package:kernel/class_hierarchy.dart';
+import 'package:kernel/core_types.dart';
 import 'package:kernel/kernel.dart';
-import 'package:kernel/reference_from_index.dart';
 import 'package:kernel/target/targets.dart';
 import 'package:kernel/verifier.dart';
 
@@ -30,11 +31,15 @@
   Component component = await compileTestCaseToKernelProgram(source,
       target: target, experimentalFlags: ['generic-metadata']);
 
-  final ReferenceFromIndex? referenceFromIndex = null;
-  final DiagnosticReporter diagnosticReporter = TestDiagnosticReporter();
+  final coreTypes = CoreTypes(component);
 
   transformLibraries(
-      component, component.libraries, diagnosticReporter, referenceFromIndex);
+      component,
+      coreTypes,
+      ClassHierarchy(component, coreTypes),
+      component.libraries,
+      TestDiagnosticReporter(),
+      /*referenceFromIndex=*/ null);
 
   verifyComponent(component);
 
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart b/pkg/vm/testcases/transformations/ffi/ffinative.dart
index 5cb2397..c611214 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative.dart
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart
@@ -20,8 +20,30 @@
   external static int returnIntPtrStatic(int x);
 }
 
+class NativeClassy extends NativeFieldWrapperClass1 {
+  @FfiNative<Void Function(Pointer<Void>, IntPtr)>('doesntmatter')
+  external void goodHasReceiverPointer(int v);
+
+  @FfiNative<Void Function(Handle, IntPtr)>('doesntmatter')
+  external void goodHasReceiverHandle(int v);
+
+  @FfiNative<Void Function(Handle, Pointer<Void>)>('doesntmatter')
+  external void goodHasReceiverHandleAndPtr(NativeClassy v);
+
+  @FfiNative<Void Function(Handle, Handle)>('doesntmatter')
+  external void goodHasReceiverHandleAndHandle(NativeClassy v);
+
+  @FfiNative<Void Function(Pointer<Void>, Handle)>('doesntmatter')
+  external void goodHasReceiverPtrAndHandle(NativeClassy v);
+}
+
 void main() {
   returnIntPtr(13);
   returnIntPtrLeaf(37);
   Classy.returnIntPtrStatic(0xDE);
+  NativeClassy().goodHasReceiverPointer(0xAF);
+  NativeClassy().goodHasReceiverHandle(0xAF);
+  NativeClassy().goodHasReceiverHandleAndPtr(NativeClassy());
+  NativeClassy().goodHasReceiverHandleAndHandle(NativeClassy());
+  NativeClassy().goodHasReceiverPtrAndHandle(NativeClassy());
 }
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
index 09cb43b..eec7f88 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
@@ -2,54 +2,85 @@
 import self as self;
 import "dart:core" as core;
 import "dart:ffi" as ffi;
+import "dart:nativewrappers" as nat;
 import "dart:_internal" as _in;
 
 import "dart:ffi";
 import "dart:nativewrappers";
 
 class Classy extends core::Object {
-  static final field (core::int) → core::int _@FfiNative_returnIntPtrStatic = ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+  static final field (core::int) → core::int _returnIntPtrStatic$FfiNative$Ptr = ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>*>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
   synthetic constructor •() → self::Classy
     : super core::Object::•()
     ;
-  @#C5
   static method returnIntPtrStatic(core::int x) → core::int
-    return self::Classy::_@FfiNative_returnIntPtrStatic(x){(core::int) → core::int};
+    return self::Classy::_returnIntPtrStatic$FfiNative$Ptr(x){(core::int) → core::int};
 }
-static final field (core::int) → core::int _@FfiNative_returnIntPtr = ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
-static final field (core::int) → core::int _@FfiNative_returnIntPtrLeaf = ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), true)/*isLegacy*/;
-@#C5
+class NativeClassy extends nat::NativeFieldWrapperClass1 {
+  static final field (ffi::Pointer<ffi::Void>, core::int) → void _goodHasReceiverPointer$FfiNative$Ptr = ffi::_asFunctionInternal<(ffi::Pointer<ffi::Void>, core::int) → void, (ffi::Pointer<ffi::Void*>*, ffi::IntPtr*) →* ffi::Void*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::Pointer<ffi::Void*>*, ffi::IntPtr*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C4, #C5){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+  static final field (self::NativeClassy, core::int) → void _goodHasReceiverHandle$FfiNative$Ptr = ffi::_asFunctionInternal<(self::NativeClassy, core::int) → void, (ffi::Handle*, ffi::IntPtr*) →* ffi::Void*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::Handle*, ffi::IntPtr*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C4, #C5){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+  static final field (self::NativeClassy, ffi::Pointer<ffi::Void>) → void _goodHasReceiverHandleAndPtr$FfiNative$Ptr = ffi::_asFunctionInternal<(self::NativeClassy, ffi::Pointer<ffi::Void>) → void, (ffi::Handle*, ffi::Pointer<ffi::Void*>*) →* ffi::Void*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::Handle*, ffi::Pointer<ffi::Void*>*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C4, #C5){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+  static final field (self::NativeClassy, self::NativeClassy) → void _goodHasReceiverHandleAndHandle$FfiNative$Ptr = ffi::_asFunctionInternal<(self::NativeClassy, self::NativeClassy) → void, (ffi::Handle*, ffi::Handle*) →* ffi::Void*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::Handle*, ffi::Handle*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C4, #C5){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+  static final field (ffi::Pointer<ffi::Void>, self::NativeClassy) → void _goodHasReceiverPtrAndHandle$FfiNative$Ptr = ffi::_asFunctionInternal<(ffi::Pointer<ffi::Void>, self::NativeClassy) → void, (ffi::Pointer<ffi::Void*>*, ffi::Handle*) →* ffi::Void*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::Pointer<ffi::Void*>*, ffi::Handle*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C4, #C5){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+  synthetic constructor •() → self::NativeClassy
+    : super nat::NativeFieldWrapperClass1::•()
+    ;
+  method goodHasReceiverPointer(core::int v) → void
+    return self::NativeClassy::_goodHasReceiverPointer$FfiNative(this, v);
+  method goodHasReceiverHandle(core::int v) → void
+    return self::NativeClassy::_goodHasReceiverHandle$FfiNative(this, v);
+  method goodHasReceiverHandleAndPtr(self::NativeClassy v) → void
+    return self::NativeClassy::_goodHasReceiverHandleAndPtr$FfiNative(this, v);
+  method goodHasReceiverHandleAndHandle(self::NativeClassy v) → void
+    return self::NativeClassy::_goodHasReceiverHandleAndHandle$FfiNative(this, v);
+  method goodHasReceiverPtrAndHandle(self::NativeClassy v) → void
+    return self::NativeClassy::_goodHasReceiverPtrAndHandle$FfiNative(this, v);
+  static method /*isLegacy*/ _goodHasReceiverPointer$FfiNative(self::NativeClassy self, core::int v) → dynamic
+    return block {
+      final nat::NativeFieldWrapperClass1 #t1 = self;
+      final core::int #t2 = v;
+      final void #t3 = self::NativeClassy::_goodHasReceiverPointer$FfiNative$Ptr(ffi::_fromAddress<ffi::Void>(nat::_getNativeField(#t1)), #t2){(self::NativeClassy, core::int) → void};
+      _in::reachabilityFence(#t1);
+    } =>#t3;
+  static method /*isLegacy*/ _goodHasReceiverHandle$FfiNative(self::NativeClassy self, core::int v) → dynamic
+    return self::NativeClassy::_goodHasReceiverHandle$FfiNative$Ptr(self, v){(self::NativeClassy, core::int) → void};
+  static method /*isLegacy*/ _goodHasReceiverHandleAndPtr$FfiNative(self::NativeClassy self, self::NativeClassy v) → dynamic
+    return block {
+      final self::NativeClassy #t4 = self;
+      final nat::NativeFieldWrapperClass1 #t5 = v;
+      final void #t6 = self::NativeClassy::_goodHasReceiverHandleAndPtr$FfiNative$Ptr(#t4, ffi::_fromAddress<ffi::Void>(nat::_getNativeField(#t5))){(self::NativeClassy, self::NativeClassy) → void};
+      _in::reachabilityFence(#t5);
+    } =>#t6;
+  static method /*isLegacy*/ _goodHasReceiverHandleAndHandle$FfiNative(self::NativeClassy self, self::NativeClassy v) → dynamic
+    return self::NativeClassy::_goodHasReceiverHandleAndHandle$FfiNative$Ptr(self, v){(self::NativeClassy, self::NativeClassy) → void};
+  static method /*isLegacy*/ _goodHasReceiverPtrAndHandle$FfiNative(self::NativeClassy self, self::NativeClassy v) → dynamic
+    return block {
+      final nat::NativeFieldWrapperClass1 #t7 = self;
+      final self::NativeClassy #t8 = v;
+      final void #t9 = self::NativeClassy::_goodHasReceiverPtrAndHandle$FfiNative$Ptr(ffi::_fromAddress<ffi::Void>(nat::_getNativeField(#t7)), #t8){(self::NativeClassy, self::NativeClassy) → void};
+      _in::reachabilityFence(#t7);
+    } =>#t9;
+}
+static final field (core::int) → core::int _returnIntPtr$FfiNative$Ptr = ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>*>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+static final field (core::int) → core::int _returnIntPtrLeaf$FfiNative$Ptr = ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>*>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), true)/*isLegacy*/;
 static method returnIntPtr(core::int x) → core::int
-  return self::_@FfiNative_returnIntPtr(x){(core::int) → core::int};
-@#C7
+  return self::_returnIntPtr$FfiNative$Ptr(x){(core::int) → core::int};
 static method returnIntPtrLeaf(core::int x) → core::int
-  return self::_@FfiNative_returnIntPtrLeaf(x){(core::int) → core::int};
+  return self::_returnIntPtrLeaf$FfiNative$Ptr(x){(core::int) → core::int};
 static method main() → void {
-  block {
-    core::int #t1;
-    final dynamic #t2 = 13;
-    #t1 = self::returnIntPtr(#t2);
-    _in::reachabilityFence(#t2);
-  } =>#t1;
-  block {
-    core::int #t3;
-    final dynamic #t4 = 37;
-    #t3 = self::returnIntPtrLeaf(#t4);
-    _in::reachabilityFence(#t4);
-  } =>#t3;
-  block {
-    core::int #t5;
-    final dynamic #t6 = 222;
-    #t5 = self::Classy::returnIntPtrStatic(#t6);
-    _in::reachabilityFence(#t6);
-  } =>#t5;
+  self::returnIntPtr(13);
+  self::returnIntPtrLeaf(37);
+  self::Classy::returnIntPtrStatic(222);
+  new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverPointer}(175){(core::int) → void};
+  new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverHandle}(175){(core::int) → void};
+  new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverHandleAndPtr}(new self::NativeClassy::•()){(self::NativeClassy) → void};
+  new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverHandleAndHandle}(new self::NativeClassy::•()){(self::NativeClassy) → void};
+  new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverPtrAndHandle}(new self::NativeClassy::•()){(self::NativeClassy) → void};
 }
 constants  {
   #C1 = "#lib"
   #C2 = "ReturnIntPtr"
   #C3 = 1
-  #C4 = false
-  #C5 = ffi::FfiNative<(ffi::IntPtr*) →* ffi::IntPtr*> {nativeName:#C2, isLeaf:#C4}
-  #C6 = true
-  #C7 = ffi::FfiNative<(ffi::IntPtr*) →* ffi::IntPtr*> {nativeName:#C2, isLeaf:#C6}
+  #C4 = "doesntmatter"
+  #C5 = 2
 }
diff --git a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
index a705710..cf10a4e 100644
--- a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
+++ b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
@@ -1133,6 +1133,44 @@
                             DummyResourceFinalizer);
 }
 
+intptr_t AddPtrAndInt(void* self, intptr_t x) {
+  return reinterpret_cast<intptr_t>(self) + x;
+}
+
+intptr_t AddHandleFieldAndInt(Dart_Handle self, intptr_t x) {
+  intptr_t field = 0;
+  ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(self, 0, &field)));
+  return field + x;
+}
+
+intptr_t AddPtrAndPtr(void* self, void* other) {
+  return reinterpret_cast<intptr_t>(self) + reinterpret_cast<intptr_t>(other);
+}
+
+intptr_t AddHandleFieldAndPtr(Dart_Handle self, void* other) {
+  intptr_t field = 0;
+  ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(self, 0, &field)));
+  return field + reinterpret_cast<intptr_t>(other);
+}
+
+intptr_t AddHandleFieldAndHandleField(Dart_Handle self, Dart_Handle other) {
+  intptr_t field1 = 0;
+  ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(self, 0, &field1)));
+  intptr_t field2 = 0;
+  ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(other, 0, &field2)));
+  return field1 + field2;
+}
+
+intptr_t AddPtrAndHandleField(void* self, Dart_Handle other) {
+  intptr_t field = 0;
+  ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(other, 0, &field)));
+  return reinterpret_cast<intptr_t>(self) + field;
+}
+
+intptr_t ReturnIntPtrMethod(Dart_Handle self, intptr_t value) {
+  return value;
+}
+
 static void* FfiNativeResolver(const char* name, uintptr_t args_n) {
   if (strcmp(name, "Dart_SetNativeInstanceField") == 0 && args_n == 3) {
     return reinterpret_cast<void*>(Dart_SetNativeInstanceField);
@@ -1167,6 +1205,27 @@
   if (strcmp(name, "SetResourceFinalizer") == 0 && args_n == 2) {
     return reinterpret_cast<void*>(SetResourceFinalizer);
   }
+  if (strcmp(name, "AddPtrAndInt") == 0 && args_n == 2) {
+    return reinterpret_cast<void*>(AddPtrAndInt);
+  }
+  if (strcmp(name, "AddHandleFieldAndInt") == 0 && args_n == 2) {
+    return reinterpret_cast<void*>(AddHandleFieldAndInt);
+  }
+  if (strcmp(name, "AddPtrAndPtr") == 0 && args_n == 2) {
+    return reinterpret_cast<void*>(AddPtrAndPtr);
+  }
+  if (strcmp(name, "AddHandleFieldAndPtr") == 0 && args_n == 2) {
+    return reinterpret_cast<void*>(AddHandleFieldAndPtr);
+  }
+  if (strcmp(name, "AddHandleFieldAndHandleField") == 0 && args_n == 2) {
+    return reinterpret_cast<void*>(AddHandleFieldAndHandleField);
+  }
+  if (strcmp(name, "AddPtrAndHandleField") == 0 && args_n == 2) {
+    return reinterpret_cast<void*>(AddPtrAndHandleField);
+  }
+  if (strcmp(name, "ReturnIntPtrMethod") == 0 && args_n == 2) {
+    return reinterpret_cast<void*>(ReturnIntPtrMethod);
+  }
   // This should be unreachable in tests.
   ENSURE(false);
 }
diff --git a/tests/co19_2/co19_2-co19.status b/tests/co19_2/co19_2-co19.status
index f8609dc..fd3ae01e 100644
--- a/tests/co19_2/co19_2-co19.status
+++ b/tests/co19_2/co19_2-co19.status
@@ -8,6 +8,128 @@
 Language/Generics/typedef_A08_t02: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
 Language/Generics/typedef_A08_t03: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
 Language/Generics/typedef_A08_t04: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/class/static/class_typedef_l1_t03: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/class/static/class_typedef_l2_t03: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/class/static/class_typedef_l2_t07: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l1_t03: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l1_t04: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l1_t05: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l1_t08: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l1_t09: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l1_t10: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t01: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t02: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t03: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t04: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t06: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t07: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t08: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t09: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t10: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t11: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t12: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t13: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t14: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t15: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t16: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t17: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t18: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t19: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_01_t20: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t01: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t03: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t06: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t07: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t08: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t09: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t10: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t11: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t14: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t15: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t16: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t17: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t18: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t19: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_02_t20: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t02: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t04: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t06: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t07: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t08: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t09: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t12: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t13: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t14: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t15: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t16: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t17: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t18: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t19: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_03_t20: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t01: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t02: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t03: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t04: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t06: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t07: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t08: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t09: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t10: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t11: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t12: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t13: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t14: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t15: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t16: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t17: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t18: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t19: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_04_t20: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t01: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t02: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t03: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t04: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t06: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t07: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t08: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t09: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t10: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t11: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t12: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t13: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t14: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t15: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t16: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t17: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t18: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t19: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_05_t20: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t01: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t02: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t03: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t06: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t07: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t08: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t09: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t10: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t11: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t12: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t13: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t14: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t15: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t16: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t17: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t18: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t19: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_l2_06_t20: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_typedef_l1_t03: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_typedef_l1_t07: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_typedef_l1_t09: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_typedef_l1_t11: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_typedef_l1_t13: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_typedef_l1_t15: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_typedef_l1_t17: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
+LanguageFeatures/Instantiate-to-bound/typedef/static/typedef_typedef_l1_t19: SkipByDesign # https://github.com/dart-lang/sdk/issues/46483
 LibTest/io/RawDatagramSocket/*: Skip # https://github.com/dart-lang/co19/issues/195
 
 [ $compiler == dart2js || $compiler == dartdevk ]
diff --git a/tests/ffi/ffi_native_test.dart b/tests/ffi/ffi_native_test.dart
index 074e236..2874007 100644
--- a/tests/ffi/ffi_native_test.dart
+++ b/tests/ffi/ffi_native_test.dart
@@ -6,6 +6,7 @@
 // with type arguments isn't supported in that version of Dart.
 
 import 'dart:ffi';
+import 'dart:nativewrappers';
 
 // Error: FFI leaf call must not have Handle return type.
 @FfiNative<Handle Function()>("foo", isLeaf: true) //# 01: compile-time error
@@ -20,11 +21,48 @@
   @FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
   external static int returnIntPtrStatic(int x);
 
-  // Error: FfiNative annotations can only be used on static functions.
-  @FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr') //# 03: compile-time error
-  external int returnIntPtrMethod(int x); //# 03: compile-time error
+  // Error: Missing receiver in FfiNative annotation.
+  @FfiNative<Void Function(IntPtr)>('doesntmatter') //# 03: compile-time error
+  external void badMissingReceiver(int v); //# 03: compile-time error
+
+  // Error: Class doesn't extend NativeFieldWrapperClass1 - can't be converted
+  // to Pointer.
+  @FfiNative<Void Function(Pointer<Void>, IntPtr)>(//# 04: compile-time error
+      'doesntmatter') //# 04: compile-time error
+  external void badHasReceiverPointer(int v); //# 04: compile-time error
+
+  @FfiNative<Void Function(Handle, IntPtr)>('doesntmatter')
+  external void goodHasReceiverHandle(int v);
 }
 
+class NativeClassy extends NativeFieldWrapperClass1 {
+  @FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
+  external static int returnIntPtrStatic(int x);
+
+  // Error: Missing receiver in FfiNative annotation.
+  @FfiNative<Void Function(IntPtr)>('doesntmatter') //# 05: compile-time error
+  external void badMissingReceiver(int v); //# 05: compile-time error
+
+  @FfiNative<Void Function(Pointer<Void>, IntPtr)>('doesntmatter')
+  external void goodHasReceiverPointer(int v);
+
+  @FfiNative<Void Function(Handle, IntPtr)>('doesntmatter')
+  external void goodHasReceiverHandle(int v);
+}
+
+// Error: Too many FfiNative parameters.
+@FfiNative<Handle Function(IntPtr, IntPtr)>(//# 06: compile-time error
+    'doesntmatter') //# 06: compile-time error
+external Object badTooManyFfiParameter(int v); //# 06: compile-time error
+
+// Error: Too few FfiNative parameters.
+@FfiNative<Handle Function(IntPtr)>('doesntmatter') //# 07: compile-time error
+external Object badTooFewFfiParameter(int v, int v2); //# 07: compile-time error
+
+// Error: FfiNatives must be marked external (and by extension have no body).
+@FfiNative<Void Function()>('doesntmatter') //# 08: compile-time error
+void mustBeMarkedExternal() {} //# 08: compile-time error
+
 // Regression test: Ensure same-name FfiNative functions don't collide in the
 // top-level namespace, but instead live under their parent (Library, Class).
 class A {
@@ -37,4 +75,24 @@
   external static void foo();
 }
 
+class DoesNotExtend implements NativeFieldWrapperClass1 {
+  // Error: Receiver type can't be converted to Pointer since it doesn't extend
+  // NativeFieldWrapperClass1.
+  @FfiNative<IntPtr Function(Pointer<Void>, Handle)>(//# 09: compile-time error
+      'doesntmatter') //# 09: compile-time error
+  external int bad1(DoesNotExtend obj); //# 09: compile-time error
+
+  // Error: Parameter type can't be converted to Pointer since it doesn't extend
+  // NativeFieldWrapperClass1.
+  @FfiNative<IntPtr Function(Handle, Pointer<Void>)>(//# 10: compile-time error
+      'doesntmatter') //# 10: compile-time error
+  external int bad2(DoesNotExtend obj); //# 10: compile-time error
+
+  // Error: Parameter type can't be converted to Pointer since it doesn't extend
+  // NativeFieldWrapperClass1.
+  @FfiNative<IntPtr Function(Pointer<Void>)>(//# 11: compile-time error
+      'doesntmatter') //# 11: compile-time error
+  external static int bad3(DoesNotExtend obj); //# 11: compile-time error
+}
+
 void main() {/* Intentionally empty: Compile-time error tests. */}
diff --git a/tests/ffi/vmspecific_ffi_native_test.dart b/tests/ffi/vmspecific_ffi_native_test.dart
index 9caa53d..dac63ef 100644
--- a/tests/ffi/vmspecific_ffi_native_test.dart
+++ b/tests/ffi/vmspecific_ffi_native_test.dart
@@ -52,6 +52,36 @@
   ClassWithNativeField(int value) {
     setNativeInstanceField(this, 0, value);
   }
+
+  // Instance methods implicitly pass a 'self' reference as the first argument.
+  // Passed as Pointer if the native function takes that (and the class can be
+  // converted).
+  @FfiNative<IntPtr Function(Pointer<Void>, IntPtr)>('AddPtrAndInt')
+  external int addSelfPtrAndIntMethod(int x);
+
+  // Instance methods implicitly pass a 'self' reference as the first argument.
+  // Passed as Handle if the native function takes that.
+  @FfiNative<IntPtr Function(Handle, IntPtr)>('AddHandleFieldAndInt')
+  external int addSelfHandleFieldAndIntMethod(int x);
+
+  @FfiNative<IntPtr Function(Pointer<Void>, Pointer<Void>)>('AddPtrAndPtr')
+  external int addSelfPtrAndPtrMethod(ClassWithNativeField other);
+
+  @FfiNative<IntPtr Function(Handle, Pointer<Void>)>('AddHandleFieldAndPtr')
+  external int addSelfHandleFieldAndPtrMethod(ClassWithNativeField other);
+
+  @FfiNative<IntPtr Function(Handle, Handle)>('AddHandleFieldAndHandleField')
+  external int addSelfHandleFieldAndHandleFieldMethod(
+      ClassWithNativeField other);
+
+  @FfiNative<IntPtr Function(Pointer<Void>, Handle)>('AddPtrAndHandleField')
+  external int addselfPtrAndHandleFieldMethod(ClassWithNativeField other);
+}
+
+class ClassWithoutNativeField {
+  // Instance methods implicitly pass their handle as the first arg.
+  @FfiNative<IntPtr Function(Handle, IntPtr)>('ReturnIntPtrMethod')
+  external int returnIntPtrMethod(int x);
 }
 
 // Native function takes a Handle, so a Handle is passed as-is.
@@ -111,4 +141,26 @@
   state = 0;
   passAsValueAndPointer(setState(7), StateSetter(3));
   Expect.equals(3, state);
+
+  // Test transforms of instance methods.
+  Expect.equals(234, ClassWithoutNativeField().returnIntPtrMethod(234));
+  Expect.equals(1012, ClassWithNativeField(12).addSelfPtrAndIntMethod(1000));
+  Expect.equals(
+      2021, ClassWithNativeField(21).addSelfHandleFieldAndIntMethod(2000));
+  Expect.equals(
+      3031,
+      ClassWithNativeField(31)
+          .addSelfPtrAndPtrMethod(ClassWithNativeField(3000)));
+  Expect.equals(
+      4041,
+      ClassWithNativeField(41)
+          .addSelfHandleFieldAndPtrMethod(ClassWithNativeField(4000)));
+  Expect.equals(
+      5051,
+      ClassWithNativeField(51)
+          .addSelfHandleFieldAndHandleFieldMethod(ClassWithNativeField(5000)));
+  Expect.equals(
+      6061,
+      ClassWithNativeField(61)
+          .addselfPtrAndHandleFieldMethod(ClassWithNativeField(6000)));
 }
diff --git a/tools/VERSION b/tools/VERSION
index 2a7370b..d089db4 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 210
+PRERELEASE 211
 PRERELEASE_PATCH 0
\ No newline at end of file