[vm] Minor improvements to FFI function tests.

Most importantly, we move resolution of symbols in function_test to the toplevel
so the same function object is re-used on each invocation. This makes it possible to
tests repeated invocations with the optimization counter enabled.

Also added some comments and new tests.

Change-Id: I3772a1f15d1ad9924c8583112ddf3b1349fb6c09
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96083
Reviewed-by: Daco Harkes <dacoharkes@google.com>
diff --git a/runtime/bin/ffi_test_functions.cc b/runtime/bin/ffi_test_functions.cc
index 28687ca..cfc8d7b 100644
--- a/runtime/bin/ffi_test_functions.cc
+++ b/runtime/bin/ffi_test_functions.cc
@@ -33,6 +33,11 @@
   return retval;
 }
 
+// Test 32-bit (int32_t) -> 64-bit (Dart int) sign extension and truncation.
+DART_EXPORT int32_t TestExtension() {
+  return 1UL << 31;
+}
+
 // Performs some computation on various sized signed ints.
 // Used for testing value ranges for signed ints.
 DART_EXPORT int64_t IntComputation(int8_t a, int16_t b, int32_t c, int64_t d) {
@@ -410,6 +415,10 @@
   return reinterpret_cast<void*>(-0x80000000L);
 }
 
+DART_EXPORT void* LargePointer() {
+  return reinterpret_cast<void*>(-0x8000000000000000L);
+}
+
 #if !defined(_WIN32)
 DART_EXPORT int RedirectStderr() {
   char filename[256];
diff --git a/tests/standalone_2/ffi/function_stress_test.dart b/tests/standalone_2/ffi/function_stress_test.dart
index 46e716f..fd3f0ec 100644
--- a/tests/standalone_2/ffi/function_stress_test.dart
+++ b/tests/standalone_2/ffi/function_stress_test.dart
@@ -2,10 +2,12 @@
 // 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.
 //
-// Dart test program for stress-testing boxing and GC.
 // VMOptions=--deterministic --optimization-counter-threshold=500 --verbose-gc
 // VMOptions=--deterministic --optimization-counter-threshold=-1 --verbose-gc
 //
+// Dart test program for stress-testing boxing and GC in return paths from FFI
+// trampolines.
+//
 // NOTE: This test does not produce useful stderr when it fails because the
 // stderr is redirected to a file for reflection.
 
@@ -39,7 +41,8 @@
     // Smi.
     await test(watcher, testBoxInt32, mustTriggerGC: false);
     await test(watcher, testBoxDouble);
-    await test(watcher, testBoxPointer);
+    await test(watcher, testBoxSmallPointer);
+    await test(watcher, testBoxLargePointer);
   } finally {
     watcher.dispose();
   }
@@ -56,9 +59,12 @@
 typedef NullaryOpDbl = double Function();
 typedef NullaryOpPtr = ffi.Pointer<ffi.Void> Function();
 
+//// These functions return values that require boxing into different types.
+
 final minInt64 =
     ffiTestFunctions.lookupFunction<NativeNullaryOp64, NullaryOp>("MinInt64");
 
+// Forces boxing into Mint on all platforms.
 void testBoxInt64() {
   Expect.equals(0x8000000000000000, minInt64());
 }
@@ -66,13 +72,15 @@
 NullaryOp minInt32 =
     ffiTestFunctions.lookupFunction<NativeNullaryOp32, NullaryOp>("MinInt32");
 
+// Forces boxing into Mint on 32-bit platforms only.
 void testBoxInt32() {
-  Expect.equals(0x80000000, minInt32());
+  Expect.equals(-0x80000000, minInt32());
 }
 
 final smallDouble = ffiTestFunctions
     .lookupFunction<NativeNullaryOpDouble, NullaryOpDbl>("SmallDouble");
 
+// Forces boxing into Double.
 void testBoxDouble() {
   Expect.equals(0x80000000 * -1.0, smallDouble());
 }
@@ -80,6 +88,16 @@
 final smallPointer = ffiTestFunctions
     .lookupFunction<NativeNullaryOpPtr, NullaryOpPtr>("SmallPointer");
 
-void testBoxPointer() {
+// Forces boxing into ffi.Pointer. On 32-bit platforms, also forces boxing into
+// Mint inside of ffi.Pointer.
+void testBoxSmallPointer() {
   Expect.equals(-0x80000000, smallPointer().address);
 }
+
+final largePointer = ffiTestFunctions
+    .lookupFunction<NativeNullaryOpPtr, NullaryOpPtr>("LargePointer");
+
+// Forces boxing into ffi.Pointer and ffi.Mint on all platforms.
+void testBoxLargePointer() {
+  Expect.equals(-0x8000000000000000, largePointer().address);
+}
diff --git a/tests/standalone_2/ffi/function_test.dart b/tests/standalone_2/ffi/function_test.dart
index c2019f7..b7e8129 100644
--- a/tests/standalone_2/ffi/function_test.dart
+++ b/tests/standalone_2/ffi/function_test.dart
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 //
 // Dart test program for testing dart:ffi function pointers.
+//
+// VMOptions=
+// VMOptions=--deterministic --optimization-counter-threshold=10
 
 library FfiTest;
 
@@ -13,23 +16,26 @@
 import "package:expect/expect.dart";
 
 void main() {
-  testNativeFunctionFromCast();
-  testNativeFunctionFromLookup();
-  test64bitInterpretations();
-  testTruncation();
-  testNativeFunctionDoubles();
-  testNativeFunctionFloats();
-  testNativeFunctionManyArguments1();
-  testNativeFunctionManyArguments2();
-  testNativeFunctionManyArguments3();
-  testNativeFunctionPointer();
-  testNullInt();
-  testNullDouble();
-  testNullManyArgs();
-  testNullPointers();
-  testFloatRounding();
-  testVoidReturn();
-  testNoArgs();
+  for (int i = 0; i < 100; ++i) {
+    testNativeFunctionFromCast();
+    testNativeFunctionFromLookup();
+    test64bitInterpretations();
+    //  TODO(36122): testExtension();
+    testTruncation();
+    testNativeFunctionDoubles();
+    testNativeFunctionFloats();
+    testNativeFunctionManyArguments1();
+    testNativeFunctionManyArguments2();
+    testNativeFunctionManyArguments3();
+    testNativeFunctionPointer();
+    testNullInt();
+    testNullDouble();
+    testNullManyArgs();
+    testNullPointers();
+    testFloatRounding();
+    testVoidReturn();
+    testNoArgs();
+  }
 }
 
 ffi.DynamicLibrary ffiTestFunctions =
@@ -43,8 +49,8 @@
 void testNativeFunctionFromCast() {
   ffi.Pointer<ffi.IntPtr> p1 = ffi.allocate();
   ffi.Pointer<ffi.NativeFunction<NativeBinaryOp>> p2 = p1.cast();
-  BinaryOp f = p2.asFunction<BinaryOp>();
-  BinaryOp f2 = p2.asFunction<GenericBinaryOp<int>>();
+  p2.asFunction<BinaryOp>();
+  p2.asFunction<GenericBinaryOp<int>>();
   p1.free();
 }
 
@@ -54,13 +60,15 @@
 typedef NativeQuadOpUnsigned = ffi.Uint64 Function(
     ffi.Uint64, ffi.Uint32, ffi.Uint16, ffi.Uint8);
 
+BinaryOp sumPlus42 =
+    ffiTestFunctions.lookupFunction<NativeBinaryOp, BinaryOp>("SumPlus42");
+
+QuadOp intComputation = ffiTestFunctions
+    .lookupFunction<NativeQuadOpSigned, QuadOp>("IntComputation");
+
 void testNativeFunctionFromLookup() {
-  BinaryOp sumPlus42 =
-      ffiTestFunctions.lookupFunction<NativeBinaryOp, BinaryOp>("SumPlus42");
   Expect.equals(49, sumPlus42(3, 4));
 
-  QuadOp intComputation = ffiTestFunctions
-      .lookupFunction<NativeQuadOpSigned, QuadOp>("IntComputation");
   Expect.equals(625, intComputation(125, 250, 500, 1000));
 
   Expect.equals(
@@ -69,10 +77,29 @@
       -0x8000000000000000, intComputation(0, 0, 0, -0x8000000000000000));
 }
 
-void test64bitInterpretations() {
-  QuadOp uintComputation = ffiTestFunctions
-      .lookupFunction<NativeQuadOpUnsigned, QuadOp>("UintComputation");
+typedef NativeNullaryOpSigned = ffi.Int32 Function();
+typedef NativeNullaryOpUnsigned = ffi.Uint32 Function();
 
+int Function() unsignedOp = ffiTestFunctions
+    .lookup("TestExtension")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeNullaryOpUnsigned>>>()
+    .asFunction();
+
+int Function() signedOp = ffiTestFunctions
+    .lookup("TestExtension")
+    .cast<ffi.Pointer<ffi.NativeFunction<NativeNullaryOpSigned>>>()
+    .asFunction();
+
+// Test 32-bit (int32_t) -> 64-bit (Dart int) sign extension and truncation.
+void testExtension() {
+  Expect.equals(unsignedOp(), 0x80000000);
+  Expect.equals(signedOp(), 0xffffffff80000000);
+}
+
+QuadOp uintComputation = ffiTestFunctions
+    .lookupFunction<NativeQuadOpUnsigned, QuadOp>("UintComputation");
+
+void test64bitInterpretations() {
   // 2 ^ 63 - 1
   Expect.equals(
       0x7FFFFFFFFFFFFFFF, uintComputation(0, 0, 0, 0x7FFFFFFFFFFFFFFF));
@@ -87,10 +114,10 @@
     ffi.Int8, ffi.Int16, ffi.Int32, ffi.Uint8, ffi.Uint16, ffi.Uint32);
 typedef SenaryOp = int Function(int, int, int, int, int, int);
 
-void testTruncation() {
-  SenaryOp sumSmallNumbers = ffiTestFunctions
-      .lookupFunction<NativeSenaryOp, SenaryOp>("SumSmallNumbers");
+SenaryOp sumSmallNumbers = ffiTestFunctions
+    .lookupFunction<NativeSenaryOp, SenaryOp>("SumSmallNumbers");
 
+void testTruncation() {
   // TODO(dacoharkes): implement truncation and sign extension in trampolines
   // for values smaller than 32 bits.
   sumSmallNumbers(128, 0, 0, 0, 0, 0);
@@ -113,17 +140,19 @@
 typedef NativeDoubleUnaryOp = ffi.Double Function(ffi.Double);
 typedef DoubleUnaryOp = double Function(double);
 
+DoubleUnaryOp times1_337Double = ffiTestFunctions
+    .lookupFunction<NativeDoubleUnaryOp, DoubleUnaryOp>("Times1_337Double");
+
 void testNativeFunctionDoubles() {
-  DoubleUnaryOp times1_337Double = ffiTestFunctions
-      .lookupFunction<NativeDoubleUnaryOp, DoubleUnaryOp>("Times1_337Double");
   Expect.approxEquals(2.0 * 1.337, times1_337Double(2.0));
 }
 
 typedef NativeFloatUnaryOp = ffi.Float Function(ffi.Float);
 
+DoubleUnaryOp times1_337Float = ffiTestFunctions
+    .lookupFunction<NativeFloatUnaryOp, DoubleUnaryOp>("Times1_337Float");
+
 void testNativeFunctionFloats() {
-  DoubleUnaryOp times1_337Float = ffiTestFunctions
-      .lookupFunction<NativeFloatUnaryOp, DoubleUnaryOp>("Times1_337Float");
   Expect.approxEquals(1337.0, times1_337Float(1000.0));
 }
 
@@ -141,9 +170,10 @@
 typedef OctenaryOp = int Function(
     int, int, int, int, int, int, int, int, int, int);
 
+OctenaryOp sumManyInts = ffiTestFunctions
+    .lookupFunction<NativeOctenaryOp, OctenaryOp>("SumManyInts");
+
 void testNativeFunctionManyArguments1() {
-  OctenaryOp sumManyInts = ffiTestFunctions
-      .lookupFunction<NativeOctenaryOp, OctenaryOp>("SumManyInts");
   Expect.equals(55, sumManyInts(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
 }
 
@@ -161,10 +191,10 @@
 typedef DoubleOctenaryOp = double Function(double, double, double, double,
     double, double, double, double, double, double);
 
+DoubleOctenaryOp sumManyDoubles = ffiTestFunctions
+    .lookupFunction<NativeDoubleOctenaryOp, DoubleOctenaryOp>("SumManyDoubles");
+
 void testNativeFunctionManyArguments2() {
-  DoubleOctenaryOp sumManyDoubles =
-      ffiTestFunctions.lookupFunction<NativeDoubleOctenaryOp, DoubleOctenaryOp>(
-          "SumManyDoubles");
   Expect.approxEquals(
       55.0, sumManyDoubles(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0));
 }
@@ -212,9 +242,10 @@
     int,
     double);
 
+VigesimalOp sumManyNumbers = ffiTestFunctions
+    .lookupFunction<NativeVigesimalOp, VigesimalOp>("SumManyNumbers");
+
 void testNativeFunctionManyArguments3() {
-  VigesimalOp sumManyNumbers = ffiTestFunctions
-      .lookupFunction<NativeVigesimalOp, VigesimalOp>("SumManyNumbers");
   Expect.approxEquals(
       210.0,
       sumManyNumbers(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9, 10.0, 11, 12.0, 13,
@@ -224,9 +255,10 @@
 typedef Int64PointerUnOp = ffi.Pointer<ffi.Int64> Function(
     ffi.Pointer<ffi.Int64>);
 
+Int64PointerUnOp assign1337Index1 = ffiTestFunctions
+    .lookupFunction<Int64PointerUnOp, Int64PointerUnOp>("Assign1337Index1");
+
 void testNativeFunctionPointer() {
-  Int64PointerUnOp assign1337Index1 = ffiTestFunctions
-      .lookupFunction<Int64PointerUnOp, Int64PointerUnOp>("Assign1337Index1");
   ffi.Pointer<ffi.Int64> p2 = ffi.allocate(count: 2);
   p2.store(42);
   p2.elementAt(1).store(1000);
@@ -245,23 +277,18 @@
 }
 
 void testNullDouble() {
-  DoubleUnaryOp times1_337Double = ffiTestFunctions
-      .lookupFunction<NativeDoubleUnaryOp, DoubleUnaryOp>("Times1_337Double");
   Expect.throws(() => times1_337Double(null));
 }
 
 void testNullManyArgs() {
-  VigesimalOp sumManyNumbers = ffiTestFunctions
-      .lookupFunction<NativeVigesimalOp, VigesimalOp>("SumManyNumbers");
   Expect.throws(() => sumManyNumbers(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9, 10.0,
       11, 12.0, 13, 14.0, 15, 16.0, 17, 18.0, null, 20.0));
 }
 
-void testNullPointers() {
-  Int64PointerUnOp nullableInt64ElemAt1 =
-      ffiTestFunctions.lookupFunction<Int64PointerUnOp, Int64PointerUnOp>(
-          "NullableInt64ElemAt1");
+Int64PointerUnOp nullableInt64ElemAt1 = ffiTestFunctions
+    .lookupFunction<Int64PointerUnOp, Int64PointerUnOp>("NullableInt64ElemAt1");
 
+void testNullPointers() {
   ffi.Pointer<ffi.Int64> result = nullableInt64ElemAt1(null);
   Expect.isNull(result);
 
@@ -274,10 +301,10 @@
 typedef NativeFloatPointerToBool = ffi.Uint8 Function(ffi.Pointer<ffi.Float>);
 typedef FloatPointerToBool = int Function(ffi.Pointer<ffi.Float>);
 
-void testFloatRounding() {
-  FloatPointerToBool isRoughly1337 = ffiTestFunctions.lookupFunction<
-      NativeFloatPointerToBool, FloatPointerToBool>("IsRoughly1337");
+FloatPointerToBool isRoughly1337 = ffiTestFunctions.lookupFunction<
+    NativeFloatPointerToBool, FloatPointerToBool>("IsRoughly1337");
 
+void testFloatRounding() {
   ffi.Pointer<ffi.Float> p2 = ffi.allocate();
   p2.store(1337.0);
 
@@ -290,10 +317,10 @@
 typedef NativeFloatToVoid = ffi.Void Function(ffi.Float);
 typedef DoubleToVoid = void Function(double);
 
-void testVoidReturn() {
-  DoubleToVoid devNullFloat = ffiTestFunctions
-      .lookupFunction<NativeFloatToVoid, DoubleToVoid>("DevNullFloat");
+DoubleToVoid devNullFloat = ffiTestFunctions
+    .lookupFunction<NativeFloatToVoid, DoubleToVoid>("DevNullFloat");
 
+void testVoidReturn() {
   devNullFloat(1337.0);
 
   dynamic loseSignature = devNullFloat;
@@ -304,10 +331,10 @@
 typedef NativeVoidToFloat = ffi.Float Function();
 typedef VoidToDouble = double Function();
 
-void testNoArgs() {
-  VoidToDouble inventFloatValue = ffiTestFunctions
-      .lookupFunction<NativeVoidToFloat, VoidToDouble>("InventFloatValue");
+VoidToDouble inventFloatValue = ffiTestFunctions
+    .lookupFunction<NativeVoidToFloat, VoidToDouble>("InventFloatValue");
 
+void testNoArgs() {
   double result = inventFloatValue();
   Expect.approxEquals(1337.0, result);
 }
diff --git a/tests/standalone_2/ffi/negative_function_test.dart b/tests/standalone_2/ffi/negative_function_test.dart
index f716ca5..a529d67 100644
--- a/tests/standalone_2/ffi/negative_function_test.dart
+++ b/tests/standalone_2/ffi/negative_function_test.dart
@@ -19,8 +19,8 @@
 typedef NativeBinaryOp = ffi.Int32 Function(ffi.Int32, ffi.Int32);
 typedef BinaryOp = int Function(int, int);
 
-typedef NativeUnaryOp = ffi.Int64 Function(ffi.Int64);
-typedef UnaryOp = int Function(int);
+typedef NativeUnaryOp = ffi.Int64 Function(ffi.Pointer<ffi.Int64>);
+typedef UnaryOp = int Function(ffi.Pointer<ffi.Int64>);
 
 void testWrongArity() {
   {
@@ -44,18 +44,18 @@
   {
     dynamic sumPlus42 =
         ffiTestFunctions.lookupFunction<NativeBinaryOp, BinaryOp>("SumPlus42");
-    Expect.throwsNoSuchMethodError(() => sumPlus42("abc", "def"));
+    Expect.throwsTypeError(() => sumPlus42("abc", "def"));
   }
 
   {
     Function sumPlus42 =
         ffiTestFunctions.lookupFunction<NativeBinaryOp, BinaryOp>("SumPlus42");
-    Expect.throwsNoSuchMethodError(() => sumPlus42("abc", "def"));
+    Expect.throwsTypeError(() => sumPlus42("abc", "def"));
   }
 
   {
     dynamic pointerOp = ffiTestFunctions
         .lookupFunction<NativeUnaryOp, UnaryOp>("Assign1337Index1");
-    Expect.throwsNoSuchMethodError(() => pointerOp(0));
+    Expect.throwsTypeError(() => pointerOp(0));
   }
 }