[vm/ffi] Adds FFI transform for Handle -> Pointer.

This will automatically desugar FfiNative calls passing
NativeFieldWrapperClass1 handles to use Pointer when
the underlying native function expects a pointer.
E.g.:

```
class ClassWithNativeField extends NativeFieldWrapperClass1 {}

@FfiNative<IntPtr Function(Pointer<Void>)>('PassAsPointer')
external int passAsPointer(ClassWithNativeField obj);

passAsPointer(ClassWithNativeField());
// Becomes roughly:
// #t0 = PointerClassWithNativeField();
// passAsPointer(Pointer.fromAddress(getNativeField(#t0)));
// reachabilityFence(#t0);
```

TEST=Adds new tests in tests/ffi/ffi_native_test.dart.
Change-Id: I4460f9249803054f12be5d5ff34dbdf7c96567fd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/213260
Commit-Queue: Clement Skau <cskau@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
diff --git a/pkg/vm/lib/transformations/ffi.dart b/pkg/vm/lib/transformations/ffi.dart
index 88edf51..58aae19 100644
--- a/pkg/vm/lib/transformations/ffi.dart
+++ b/pkg/vm/lib/transformations/ffi.dart
@@ -234,6 +234,8 @@
   final Class compoundClass;
   final Class structClass;
   final Class unionClass;
+  final Class ffiNativeClass;
+  final Class nativeFieldWrapperClass;
   final Class ffiStructLayoutClass;
   final Field ffiStructLayoutTypesField;
   final Field ffiStructLayoutPackingField;
@@ -286,6 +288,12 @@
   final Procedure allocationTearoff;
   final Procedure asFunctionTearoff;
   final Procedure lookupFunctionTearoff;
+  final Procedure getNativeFieldFunction;
+  final Procedure reachabilityFenceFunction;
+
+  late final DartType nativeFieldWrapperClassType;
+  late final DartType voidType;
+  late final DartType pointerType;
 
   /// Classes corresponding to [NativeType], indexed by [NativeType].
   final List<Class> nativeTypesClasses;
@@ -297,7 +305,7 @@
 
   FfiTransformer(this.index, this.coreTypes, this.hierarchy,
       this.diagnosticReporter, this.referenceFromIndex)
-      : env = new TypeEnvironment(coreTypes, hierarchy),
+      : env = TypeEnvironment(coreTypes, hierarchy),
         objectClass = coreTypes.objectClass,
         intClass = coreTypes.intClass,
         doubleClass = coreTypes.doubleClass,
@@ -346,6 +354,9 @@
         compoundClass = index.getClass('dart:ffi', '_Compound'),
         structClass = index.getClass('dart:ffi', 'Struct'),
         unionClass = index.getClass('dart:ffi', 'Union'),
+        ffiNativeClass = index.getClass('dart:ffi', 'FfiNative'),
+        nativeFieldWrapperClass =
+            index.getClass('dart:nativewrappers', 'NativeFieldWrapperClass1'),
         ffiStructLayoutClass = index.getClass('dart:ffi', '_FfiStructLayout'),
         ffiStructLayoutTypesField =
             index.getField('dart:ffi', '_FfiStructLayout', 'fieldTypes'),
@@ -455,7 +466,18 @@
         lookupFunctionTearoff = index.getProcedure(
             'dart:ffi',
             'DynamicLibraryExtension',
-            LibraryIndex.tearoffPrefix + 'lookupFunction');
+            LibraryIndex.tearoffPrefix + 'lookupFunction'),
+        getNativeFieldFunction =
+            index.getTopLevelProcedure('dart:nativewrappers', 'getNativeField'),
+        reachabilityFenceFunction =
+            index.getTopLevelProcedure('dart:_internal', 'reachabilityFence') {
+    nativeFieldWrapperClassType =
+        nativeFieldWrapperClass.getThisType(coreTypes, Nullability.nonNullable);
+    voidType = nativeTypesClasses[NativeType.kVoid.index]
+        .getThisType(coreTypes, Nullability.nonNullable);
+    pointerType =
+        InterfaceType(pointerClass, Nullability.nonNullable, [voidType]);
+  }
 
   @override
   TreeNode visitLibrary(Library node) {
diff --git a/pkg/vm/lib/transformations/ffi_definitions.dart b/pkg/vm/lib/transformations/ffi_definitions.dart
index 2238251..7157845 100644
--- a/pkg/vm/lib/transformations/ffi_definitions.dart
+++ b/pkg/vm/lib/transformations/ffi_definitions.dart
@@ -69,9 +69,14 @@
     DiagnosticReporter diagnosticReporter,
     ReferenceFromIndex? referenceFromIndex,
     ChangedStructureNotifier? changedStructureNotifier) {
-  final LibraryIndex index = LibraryIndex(component,
-      const ["dart:core", "dart:ffi", "dart:_internal", "dart:typed_data"]);
-  if (!index.containsLibrary("dart:ffi")) {
+  final LibraryIndex index = LibraryIndex(component, const [
+    'dart:core',
+    'dart:ffi',
+    'dart:_internal',
+    'dart:typed_data',
+    'dart:nativewrappers'
+  ]);
+  if (!index.containsLibrary('dart:ffi')) {
     // TODO: This check doesn't make sense: "dart:ffi" is always loaded/created
     // for the VM target.
     // If dart:ffi is not loaded, do not do the transformation.
diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart
index 49281ed..312c1d6 100644
--- a/pkg/vm/lib/transformations/ffi_use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi_use_sites.dart
@@ -43,8 +43,8 @@
     List<Library> libraries,
     DiagnosticReporter diagnosticReporter,
     ReferenceFromIndex? referenceFromIndex) {
-  final index = new LibraryIndex(
-      component, ["dart:ffi", "dart:_internal", "dart:typed_data"]);
+  final index = LibraryIndex(component,
+      ["dart:ffi", "dart:_internal", "dart:typed_data", "dart:nativewrappers"]);
   if (!index.containsLibrary("dart:ffi")) {
     // TODO: This check doesn't make sense: "dart:ffi" is always loaded/created
     // for the VM target.
@@ -77,7 +77,7 @@
       DiagnosticReporter diagnosticReporter,
       ReferenceFromIndex? referenceFromIndex)
       : super(index, coreTypes, hierarchy, diagnosticReporter,
-            referenceFromIndex) {}
+            referenceFromIndex);
 
   @override
   TreeNode visitLibrary(Library node) {
@@ -131,6 +131,20 @@
     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);
@@ -352,6 +366,88 @@
                   .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('',
+                    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('',
+                  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('', 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
@@ -713,6 +809,22 @@
     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}) {
@@ -725,6 +837,15 @@
         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/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
index affbe3a..524fcb3 100644
--- a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
+++ b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
@@ -1094,13 +1094,69 @@
   return x;
 }
 
+intptr_t PassAsHandle(Dart_Handle handle) {
+  intptr_t result = 0;
+  ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(handle, 0, &result)));
+  return result;
+}
+
+intptr_t PassAsPointer(void* ptr) {
+  return reinterpret_cast<intptr_t>(ptr);
+}
+
+intptr_t PassAsPointerAndValue(void* ptr, intptr_t value) {
+  return reinterpret_cast<intptr_t>(value);
+}
+
+intptr_t PassAsValueAndPointer(intptr_t value, void* ptr) {
+  return reinterpret_cast<intptr_t>(value);
+}
+
+// We're using this to keep track of whether the finalizer has been called.
+static intptr_t shared_resource = 0;
+
+void DummyResourceFinalizer(void* isolate_peer, void* peer) {
+  shared_resource = 0;
+}
+
+void SetSharedResource(Dart_Handle handle, intptr_t value) {
+  Dart_NewFinalizableHandle(handle, nullptr, sizeof(Dart_FinalizableHandle),
+                            DummyResourceFinalizer);
+  shared_resource = value;
+}
+
+intptr_t GetSharedResource() {
+  return shared_resource;
+}
+
 static void* FfiNativeResolver(const char* name, uintptr_t args_n) {
-  if (strcmp(name, "ReturnIntPtr") == 0 && args_n == 1) {
-    return reinterpret_cast<void*>(ReturnIntPtr);
+  if (strcmp(name, "Dart_SetNativeInstanceField") == 0 && args_n == 3) {
+    return reinterpret_cast<void*>(Dart_SetNativeInstanceField);
   }
   if (strcmp(name, "IsThreadInGenerated") == 0 && args_n == 0) {
     return reinterpret_cast<void*>(IsThreadInGenerated);
   }
+  if (strcmp(name, "ReturnIntPtr") == 0 && args_n == 1) {
+    return reinterpret_cast<void*>(ReturnIntPtr);
+  }
+  if (strcmp(name, "PassAsHandle") == 0 && args_n == 1) {
+    return reinterpret_cast<void*>(PassAsHandle);
+  }
+  if (strcmp(name, "PassAsPointer") == 0 && args_n == 1) {
+    return reinterpret_cast<void*>(PassAsPointer);
+  }
+  if (strcmp(name, "PassAsPointerAndValue") == 0 && args_n == 2) {
+    return reinterpret_cast<void*>(PassAsPointerAndValue);
+  }
+  if (strcmp(name, "PassAsValueAndPointer") == 0 && args_n == 2) {
+    return reinterpret_cast<void*>(PassAsValueAndPointer);
+  }
+  if (strcmp(name, "SetSharedResource") == 0 && args_n == 2) {
+    return reinterpret_cast<void*>(SetSharedResource);
+  }
+  if (strcmp(name, "GetSharedResource") == 0 && args_n == 0) {
+    return reinterpret_cast<void*>(GetSharedResource);
+  }
   // This should be unreachable in tests.
   ENSURE(false);
 }
diff --git a/sdk/lib/_internal/vm/lib/internal_patch.dart b/sdk/lib/_internal/vm/lib/internal_patch.dart
index e762a8b..421fb4d 100644
--- a/sdk/lib/_internal/vm/lib/internal_patch.dart
+++ b/sdk/lib/_internal/vm/lib/internal_patch.dart
@@ -165,7 +165,7 @@
 @pragma("vm:external-name", "Internal_unsafeCast")
 external T unsafeCast<T>(Object? v);
 
-// This function can be used to keep an object alive til that point.
+// This function can be used to keep an object alive till that point.
 @pragma("vm:recognized", "other")
 @pragma('vm:prefer-inline')
 @pragma("vm:external-name", "Internal_reachabilityFence")
diff --git a/tests/ffi/ffi_native_test.dart b/tests/ffi/ffi_native_test.dart
index 12ca0dd..34be137 100644
--- a/tests/ffi/ffi_native_test.dart
+++ b/tests/ffi/ffi_native_test.dart
@@ -1,36 +1,15 @@
 // Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-//
-// SharedObjects=ffi_test_functions
 
 // NOTE: There is no `test/ffi_2/...` version of this test since annotations
 // with type arguments isn't supported in that version of Dart.
 
 import 'dart:ffi';
+import 'dart:nativewrappers';
 
 import 'package:expect/expect.dart';
 
-import 'dylib_utils.dart';
-
-final nativeLib = dlopenPlatformSpecific('ffi_test_functions');
-final getRootLibraryUrl = nativeLib
-    .lookupFunction<Handle Function(), Object Function()>('GetRootLibraryUrl');
-final setFfiNativeResolverForTest = nativeLib
-    .lookupFunction<Void Function(Handle), void Function(Object)>('SetFfiNativeResolverForTest');
-
-@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
-external int returnIntPtr(int x);
-
-@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr', isLeaf: true)
-external int returnIntPtrLeaf(int x);
-
-@FfiNative<IntPtr Function()>('IsThreadInGenerated')
-external int isThreadInGenerated();
-
-@FfiNative<IntPtr Function()>('IsThreadInGenerated', isLeaf: true)
-external int isThreadInGeneratedLeaf();
-
 // Error: FFI leaf call must not have Handle return type.
 @FfiNative<Handle Function()>("foo", isLeaf: true)  //# 01: compile-time error
 external Object foo();  //# 01: compile-time error
@@ -59,19 +38,4 @@
   external static void foo();
 }
 
-void main() {
-  // Register test resolver for top-level functions above.
-  final root_lib_url = getRootLibraryUrl();
-  setFfiNativeResolverForTest(root_lib_url);
-
-  // Test we can call FfiNative functions.
-  Expect.equals(123, returnIntPtr(123));
-  Expect.equals(123, returnIntPtrLeaf(123));
-  Expect.equals(123, Classy.returnIntPtrStatic(123));
-
-  // Test FfiNative leaf calls remain in generated code.
-  // Regular calls should transition generated -> native.
-  Expect.equals(0, isThreadInGenerated());
-  // Leaf calls should remain in generated state.
-  Expect.equals(1, isThreadInGeneratedLeaf());
-}
+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
new file mode 100644
index 0000000..5264901
--- /dev/null
+++ b/tests/ffi/vmspecific_ffi_native_test.dart
@@ -0,0 +1,143 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// SharedObjects=ffi_test_functions
+
+// NOTE: There is no `test/ffi_2/...` version of this test since annotations
+// with type arguments isn't supported in that version of Dart.
+
+import 'dart:ffi';
+import 'dart:nativewrappers';
+
+import 'package:expect/expect.dart';
+
+import 'dylib_utils.dart';
+
+final nativeLib = dlopenPlatformSpecific('ffi_test_functions');
+
+final getRootLibraryUrl = nativeLib
+    .lookupFunction<Handle Function(), Object Function()>('GetRootLibraryUrl');
+
+final setFfiNativeResolverForTest =
+    nativeLib.lookupFunction<Void Function(Handle), void Function(Object)>(
+        'SetFfiNativeResolverForTest');
+
+final triggerGC = nativeLib
+    .lookupFunction<Void Function(IntPtr), void Function(int)>('TriggerGC');
+
+@FfiNative<Handle Function(Handle, IntPtr, IntPtr)>(
+    'Dart_SetNativeInstanceField')
+external Object setNativeInstanceField(Object obj, int index, int ptr);
+
+// Basic FfiNative test functions.
+
+@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
+external int returnIntPtr(int x);
+
+@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr', isLeaf: true)
+external int returnIntPtrLeaf(int x);
+
+@FfiNative<IntPtr Function()>('IsThreadInGenerated')
+external int isThreadInGenerated();
+
+@FfiNative<IntPtr Function()>('IsThreadInGenerated', isLeaf: true)
+external int isThreadInGeneratedLeaf();
+
+class Classy {
+  @FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
+  external static int returnIntPtrStatic(int x);
+}
+
+// For automatic transform of NativeFieldWrapperClass1 to Pointer.
+
+// Sets the native (dummy) resource and the object's finalizer.
+@FfiNative<Void Function(Handle, IntPtr)>('SetSharedResource')
+external void setSharedResource(NativeFieldWrapperClass1 obj, int value);
+// Return the native (dummy) resource.
+@FfiNative<IntPtr Function()>('GetSharedResource')
+external int getSharedResource();
+
+class ClassWithNativeField extends NativeFieldWrapperClass1 {
+  ClassWithNativeField(int value) {
+    setNativeInstanceField(this, 0, value);
+    setSharedResource(this, value);
+  }
+}
+
+// Native function takes a Handle, so a Handle is passed as-is.
+@FfiNative<IntPtr Function(Handle)>('PassAsHandle')
+external int passAsHandle(NativeFieldWrapperClass1 obj);
+// FFI signature takes Pointer, Dart signature takes NativeFieldWrapperClass1.
+// This implies automatic conversion.
+@FfiNative<IntPtr Function(Pointer<Void>)>('PassAsPointer')
+external int passAsPointer(NativeFieldWrapperClass1 obj);
+// Pass Pointer automatically, and return value.
+@FfiNative<IntPtr Function(Pointer<Void>, IntPtr)>('PassAsPointerAndValue')
+external int passAsPointerAndValue(NativeFieldWrapperClass1 obj, int value);
+// Pass Pointer automatically, and return value.
+@FfiNative<IntPtr Function(IntPtr, Pointer<Void>)>('PassAsValueAndPointer')
+external int passAsValueAndPointer(int value, NativeFieldWrapperClass1 obj);
+
+// Helper to embed triggerGC(..) as an expression.
+int triggerGCWrap() {
+  triggerGC(0);
+  return 0;
+}
+
+// Helpers for testing argumnent evaluation order is preserved.
+int state = 0;
+int setState(int value) {
+  state = value;
+  return 0;
+}
+
+class StateSetter extends NativeFieldWrapperClass1 {
+  StateSetter(int value) {
+    setNativeInstanceField(this, 0, 0);
+    state = value;
+  }
+}
+
+void main() {
+  // Register test resolver for top-level functions above.
+  setFfiNativeResolverForTest(getRootLibraryUrl());
+
+  // Test we can call FfiNative functions.
+  Expect.equals(123, returnIntPtr(123));
+  Expect.equals(123, returnIntPtrLeaf(123));
+  Expect.equals(123, Classy.returnIntPtrStatic(123));
+
+  // Test FfiNative leaf calls remain in generated code.
+  // Regular calls should transition generated -> native.
+  Expect.equals(0, isThreadInGenerated());
+  // Leaf calls should remain in generated state.
+  Expect.equals(1, isThreadInGeneratedLeaf());
+
+  // Test that objects extending NativeFieldWrapperClass1 can be passed to
+  // FfiNative functions that take Pointer.
+  // Such objects should automatically be converted and pass as Pointer.
+  final cwnf = ClassWithNativeField(123456);
+  Expect.equals(123456, passAsHandle(cwnf));
+  Expect.equals(123456, passAsPointer(cwnf));
+
+  // Test that the transform to wrap NativeFieldWrapperClass1 objects in
+  // getNativeField(..) doesn't violate the original argument's liveness.
+  Expect.equals(
+      314159,
+      passAsPointerAndValue(
+          // 1: Locally alloc. instance.
+          // If this gets wrapped in another call the instance does not live
+          // past the return of the wrapper call.
+          ClassWithNativeField(314159),
+          // 2: Force GC, to collect the above if it isn't being kept alive.
+          // 3: Check that the underlying (dummy) resource hasn't been
+          // "collected" (i.e. reset to 0).
+          triggerGCWrap() + getSharedResource()));
+
+  // Test that the order of argument evaluation is being preserved through the
+  // transform wrapping NativeFieldWrapperClass1 objects.
+  state = 0;
+  passAsValueAndPointer(setState(7), StateSetter(3));
+  Expect.equals(3, state);
+}