[VM/FFI] Adds FFI leaf calls.

This CL adds FFI leaf calls by adding `lookupFunction(.., isLeaf)`
and `_asFunctionInternal(.., isLeaf)`, which generate FFI leaf calls.
These calls skip a lot of the usual frame building and generated <->
native transition overhead.

`benchmark/FfiCall/` shows a 1.1x - 4.3x speed-up between the regular
FFI calls and their leaf call counterparts (JIT, x64, release).

TEST=Adds `tests/ffi{,_2}/vmspecific_leaf_call_test.dart`. Tested FFI tests.

Closes: https://github.com/dart-lang/sdk/issues/36707
Cq-Include-Trybots: luci.dart.try:vm-precomp-ffi-qemu-linux-release-arm-try,vm-ffi-android-release-arm64-try,vm-ffi-android-release-arm-try,vm-ffi-android-product-arm64-try,vm-ffi-android-product-arm-try,vm-ffi-android-debug-arm64-try,vm-ffi-android-debug-arm-try,vm-kernel-linux-debug-ia32-try,vm-kernel-win-debug-ia32-try,vm-kernel-win-debug-x64-try,vm-kernel-win-release-x64-try,vm-kernel-mac-debug-x64-try,vm-kernel-precomp-nnbd-mac-release-simarm64-try,vm-kernel-precomp-android-release-arm64-try,vm-kernel-precomp-asan-linux-release-x64-try,vm-kernel-precomp-linux-release-simarm_x64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-precomp-ubsan-linux-release-x64-try,vm-kernel-precomp-tsan-linux-release-x64-try,vm-kernel-precomp-win-release-x64-try,vm-precomp-ffi-qemu-linux-release-arm-try,vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-reload-linux-debug-x64-try
Bug: https://github.com/dart-lang/sdk/issues/36707
Change-Id: Id8824f36b0006bf09951207bd004356fe6e9f46e
Cq-Do-Not-Cancel-Tryjobs: true
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/179768
Commit-Queue: Clement Skau <cskau@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Clement Skau 2021-05-21 11:12:02 +00:00 committed by commit-bot@chromium.org
parent f364c8bf7b
commit 4d5055805f
55 changed files with 17073 additions and 612 deletions

View file

@ -283,7 +283,10 @@ typedef Function20Object = Object Function(
//
abstract class FfiBenchmarkBase extends BenchmarkBase {
FfiBenchmarkBase(String name) : super(name);
final bool isLeaf;
FfiBenchmarkBase(String name, {this.isLeaf: false})
: super('$name${isLeaf ? 'Leaf' : ''}');
void expectEquals(actual, expected) {
if (actual != expected) {
@ -307,10 +310,13 @@ abstract class FfiBenchmarkBase extends BenchmarkBase {
class Uint8x01 extends FfiBenchmarkBase {
final Function1int f;
Uint8x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Uint8, Function1int>(
'Function1Uint8'),
super('FfiCall.Uint8x01');
Uint8x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Uint8,
Function1int>('Function1Uint8', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Uint8,
Function1int>('Function1Uint8', isLeaf: false),
super('FfiCall.Uint8x01', isLeaf: isLeaf);
@override
void run() {
@ -325,10 +331,13 @@ class Uint8x01 extends FfiBenchmarkBase {
class Uint16x01 extends FfiBenchmarkBase {
final Function1int f;
Uint16x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Uint16,
Function1int>('Function1Uint16'),
super('FfiCall.Uint16x01');
Uint16x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Uint16,
Function1int>('Function1Uint16', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Uint16,
Function1int>('Function1Uint16', isLeaf: false),
super('FfiCall.Uint16x01', isLeaf: isLeaf);
@override
void run() {
@ -343,10 +352,13 @@ class Uint16x01 extends FfiBenchmarkBase {
class Uint32x01 extends FfiBenchmarkBase {
final Function1int f;
Uint32x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Uint32,
Function1int>('Function1Uint32'),
super('FfiCall.Uint32x01');
Uint32x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Uint32,
Function1int>('Function1Uint32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Uint32,
Function1int>('Function1Uint32', isLeaf: false),
super('FfiCall.Uint32x01', isLeaf: isLeaf);
@override
void run() {
@ -361,10 +373,13 @@ class Uint32x01 extends FfiBenchmarkBase {
class Uint64x01 extends FfiBenchmarkBase {
final Function1int f;
Uint64x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Uint64,
Function1int>('Function1Uint64'),
super('FfiCall.Uint64x01');
Uint64x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Uint64,
Function1int>('Function1Uint64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Uint64,
Function1int>('Function1Uint64', isLeaf: false),
super('FfiCall.Uint64x01', isLeaf: isLeaf);
@override
void run() {
@ -379,10 +394,13 @@ class Uint64x01 extends FfiBenchmarkBase {
class Int8x01 extends FfiBenchmarkBase {
final Function1int f;
Int8x01()
: f = ffiTestFunctions
.lookupFunction<NativeFunction1Int8, Function1int>('Function1Int8'),
super('FfiCall.Int8x01');
Int8x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Int8,
Function1int>('Function1Int8', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Int8,
Function1int>('Function1Int8', isLeaf: false),
super('FfiCall.Int8x01', isLeaf: isLeaf);
@override
void run() {
@ -397,10 +415,13 @@ class Int8x01 extends FfiBenchmarkBase {
class Int16x01 extends FfiBenchmarkBase {
final Function1int f;
Int16x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Int16, Function1int>(
'Function1Int16'),
super('FfiCall.Int16x01');
Int16x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Int16,
Function1int>('Function1Int16', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Int16,
Function1int>('Function1Int16', isLeaf: false),
super('FfiCall.Int16x01', isLeaf: isLeaf);
@override
void run() {
@ -415,10 +436,13 @@ class Int16x01 extends FfiBenchmarkBase {
class Int32x01 extends FfiBenchmarkBase {
final Function1int f;
Int32x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Int32, Function1int>(
'Function1Int32'),
super('FfiCall.Int32x01');
Int32x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Int32,
Function1int>('Function1Int32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Int32,
Function1int>('Function1Int32', isLeaf: false),
super('FfiCall.Int32x01', isLeaf: isLeaf);
@override
void run() {
@ -433,10 +457,13 @@ class Int32x01 extends FfiBenchmarkBase {
class Int32x02 extends FfiBenchmarkBase {
final Function2int f;
Int32x02()
: f = ffiTestFunctions.lookupFunction<NativeFunction2Int32, Function2int>(
'Function2Int32'),
super('FfiCall.Int32x02');
Int32x02({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction2Int32,
Function2int>('Function2Int32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction2Int32,
Function2int>('Function2Int32', isLeaf: false),
super('FfiCall.Int32x02', isLeaf: isLeaf);
@override
void run() {
@ -451,10 +478,13 @@ class Int32x02 extends FfiBenchmarkBase {
class Int32x04 extends FfiBenchmarkBase {
final Function4int f;
Int32x04()
: f = ffiTestFunctions.lookupFunction<NativeFunction4Int32, Function4int>(
'Function4Int32'),
super('FfiCall.Int32x04');
Int32x04({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction4Int32,
Function4int>('Function4Int32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction4Int32,
Function4int>('Function4Int32', isLeaf: false),
super('FfiCall.Int32x04', isLeaf: isLeaf);
@override
void run() {
@ -469,10 +499,13 @@ class Int32x04 extends FfiBenchmarkBase {
class Int32x10 extends FfiBenchmarkBase {
final Function10int f;
Int32x10()
: f = ffiTestFunctions.lookupFunction<NativeFunction10Int32,
Function10int>('Function10Int32'),
super('FfiCall.Int32x10');
Int32x10({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction10Int32,
Function10int>('Function10Int32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction10Int32,
Function10int>('Function10Int32', isLeaf: false),
super('FfiCall.Int32x10', isLeaf: isLeaf);
@override
void run() {
@ -487,10 +520,13 @@ class Int32x10 extends FfiBenchmarkBase {
class Int32x20 extends FfiBenchmarkBase {
final Function20int f;
Int32x20()
: f = ffiTestFunctions.lookupFunction<NativeFunction20Int32,
Function20int>('Function20Int32'),
super('FfiCall.Int32x20');
Int32x20({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction20Int32,
Function20int>('Function20Int32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction20Int32,
Function20int>('Function20Int32', isLeaf: false),
super('FfiCall.Int32x20', isLeaf: isLeaf);
@override
void run() {
@ -505,10 +541,13 @@ class Int32x20 extends FfiBenchmarkBase {
class Int64x01 extends FfiBenchmarkBase {
final Function1int f;
Int64x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Int64, Function1int>(
'Function1Int64'),
super('FfiCall.Int64x01');
Int64x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Int64,
Function1int>('Function1Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Int64,
Function1int>('Function1Int64', isLeaf: false),
super('FfiCall.Int64x01', isLeaf: isLeaf);
@override
void run() {
@ -523,10 +562,13 @@ class Int64x01 extends FfiBenchmarkBase {
class Int64x02 extends FfiBenchmarkBase {
final Function2int f;
Int64x02()
: f = ffiTestFunctions.lookupFunction<NativeFunction2Int64, Function2int>(
'Function2Int64'),
super('FfiCall.Int64x02');
Int64x02({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction2Int64,
Function2int>('Function2Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction2Int64,
Function2int>('Function2Int64', isLeaf: false),
super('FfiCall.Int64x02', isLeaf: isLeaf);
@override
void run() {
@ -541,10 +583,13 @@ class Int64x02 extends FfiBenchmarkBase {
class Int64x04 extends FfiBenchmarkBase {
final Function4int f;
Int64x04()
: f = ffiTestFunctions.lookupFunction<NativeFunction4Int64, Function4int>(
'Function4Int64'),
super('FfiCall.Int64x04');
Int64x04({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction4Int64,
Function4int>('Function4Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction4Int64,
Function4int>('Function4Int64', isLeaf: false),
super('FfiCall.Int64x04', isLeaf: isLeaf);
@override
void run() {
@ -559,10 +604,13 @@ class Int64x04 extends FfiBenchmarkBase {
class Int64x10 extends FfiBenchmarkBase {
final Function10int f;
Int64x10()
: f = ffiTestFunctions.lookupFunction<NativeFunction10Int64,
Function10int>('Function10Int64'),
super('FfiCall.Int64x10');
Int64x10({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction10Int64,
Function10int>('Function10Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction10Int64,
Function10int>('Function10Int64', isLeaf: false),
super('FfiCall.Int64x10', isLeaf: isLeaf);
@override
void run() {
@ -577,10 +625,13 @@ class Int64x10 extends FfiBenchmarkBase {
class Int64x20 extends FfiBenchmarkBase {
final Function20int f;
Int64x20()
: f = ffiTestFunctions.lookupFunction<NativeFunction20Int64,
Function20int>('Function20Int64'),
super('FfiCall.Int64x20');
Int64x20({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction20Int64,
Function20int>('Function20Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction20Int64,
Function20int>('Function20Int64', isLeaf: false),
super('FfiCall.Int64x20', isLeaf: isLeaf);
@override
void run() {
@ -595,10 +646,13 @@ class Int64x20 extends FfiBenchmarkBase {
class Int64Mintx01 extends FfiBenchmarkBase {
final Function1int f;
Int64Mintx01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Int64, Function1int>(
'Function1Int64'),
super('FfiCall.Int64Mintx01');
Int64Mintx01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Int64,
Function1int>('Function1Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Int64,
Function1int>('Function1Int64', isLeaf: false),
super('FfiCall.Int64Mintx01', isLeaf: isLeaf);
@override
void run() {
@ -613,10 +667,13 @@ class Int64Mintx01 extends FfiBenchmarkBase {
class Floatx01 extends FfiBenchmarkBase {
final Function1double f;
Floatx01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Float,
Function1double>('Function1Float'),
super('FfiCall.Floatx01');
Floatx01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Float,
Function1double>('Function1Float', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Float,
Function1double>('Function1Float', isLeaf: false),
super('FfiCall.Floatx01', isLeaf: isLeaf);
@override
void run() {
@ -631,10 +688,13 @@ class Floatx01 extends FfiBenchmarkBase {
class Floatx02 extends FfiBenchmarkBase {
final Function2double f;
Floatx02()
: f = ffiTestFunctions.lookupFunction<NativeFunction2Float,
Function2double>('Function2Float'),
super('FfiCall.Floatx02');
Floatx02({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction2Float,
Function2double>('Function2Float', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction2Float,
Function2double>('Function2Float', isLeaf: false),
super('FfiCall.Floatx02', isLeaf: isLeaf);
@override
void run() {
@ -649,10 +709,13 @@ class Floatx02 extends FfiBenchmarkBase {
class Floatx04 extends FfiBenchmarkBase {
final Function4double f;
Floatx04()
: f = ffiTestFunctions.lookupFunction<NativeFunction4Float,
Function4double>('Function4Float'),
super('FfiCall.Floatx04');
Floatx04({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction4Float,
Function4double>('Function4Float', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction4Float,
Function4double>('Function4Float', isLeaf: false),
super('FfiCall.Floatx04', isLeaf: isLeaf);
@override
void run() {
@ -668,10 +731,13 @@ class Floatx04 extends FfiBenchmarkBase {
class Floatx10 extends FfiBenchmarkBase {
final Function10double f;
Floatx10()
: f = ffiTestFunctions.lookupFunction<NativeFunction10Float,
Function10double>('Function10Float'),
super('FfiCall.Floatx10');
Floatx10({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction10Float,
Function10double>('Function10Float', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction10Float,
Function10double>('Function10Float', isLeaf: false),
super('FfiCall.Floatx10', isLeaf: isLeaf);
@override
void run() {
@ -688,10 +754,13 @@ class Floatx10 extends FfiBenchmarkBase {
class Floatx20 extends FfiBenchmarkBase {
final Function20double f;
Floatx20()
: f = ffiTestFunctions.lookupFunction<NativeFunction20Float,
Function20double>('Function20Float'),
super('FfiCall.Floatx20');
Floatx20({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction20Float,
Function20double>('Function20Float', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction20Float,
Function20double>('Function20Float', isLeaf: false),
super('FfiCall.Floatx20', isLeaf: isLeaf);
@override
void run() {
@ -728,10 +797,13 @@ class Floatx20 extends FfiBenchmarkBase {
class Doublex01 extends FfiBenchmarkBase {
final Function1double f;
Doublex01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Double,
Function1double>('Function1Double'),
super('FfiCall.Doublex01');
Doublex01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Double,
Function1double>('Function1Double', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Double,
Function1double>('Function1Double', isLeaf: false),
super('FfiCall.Doublex01', isLeaf: isLeaf);
@override
void run() {
@ -747,10 +819,13 @@ class Doublex01 extends FfiBenchmarkBase {
class Doublex02 extends FfiBenchmarkBase {
final Function2double f;
Doublex02()
: f = ffiTestFunctions.lookupFunction<NativeFunction2Double,
Function2double>('Function2Double'),
super('FfiCall.Doublex02');
Doublex02({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction2Double,
Function2double>('Function2Double', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction2Double,
Function2double>('Function2Double', isLeaf: false),
super('FfiCall.Doublex02', isLeaf: isLeaf);
@override
void run() {
@ -766,10 +841,13 @@ class Doublex02 extends FfiBenchmarkBase {
class Doublex04 extends FfiBenchmarkBase {
final Function4double f;
Doublex04()
: f = ffiTestFunctions.lookupFunction<NativeFunction4Double,
Function4double>('Function4Double'),
super('FfiCall.Doublex04');
Doublex04({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction4Double,
Function4double>('Function4Double', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction4Double,
Function4double>('Function4Double', isLeaf: false),
super('FfiCall.Doublex04', isLeaf: isLeaf);
@override
void run() {
@ -785,10 +863,13 @@ class Doublex04 extends FfiBenchmarkBase {
class Doublex10 extends FfiBenchmarkBase {
final Function10double f;
Doublex10()
: f = ffiTestFunctions.lookupFunction<NativeFunction10Double,
Function10double>('Function10Double'),
super('FfiCall.Doublex10');
Doublex10({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction10Double,
Function10double>('Function10Double', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction10Double,
Function10double>('Function10Double', isLeaf: false),
super('FfiCall.Doublex10', isLeaf: isLeaf);
@override
void run() {
@ -805,10 +886,13 @@ class Doublex10 extends FfiBenchmarkBase {
class Doublex20 extends FfiBenchmarkBase {
final Function20double f;
Doublex20()
: f = ffiTestFunctions.lookupFunction<NativeFunction20Double,
Function20double>('Function20Double'),
super('FfiCall.Doublex20');
Doublex20({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction20Double,
Function20double>('Function20Double', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction20Double,
Function20double>('Function20Double', isLeaf: false),
super('FfiCall.Doublex20', isLeaf: isLeaf);
@override
void run() {
@ -845,10 +929,13 @@ class Doublex20 extends FfiBenchmarkBase {
class PointerUint8x01 extends FfiBenchmarkBase {
final Function1PointerUint8 f;
PointerUint8x01()
: f = ffiTestFunctions.lookupFunction<Function1PointerUint8,
Function1PointerUint8>('Function1PointerUint8'),
super('FfiCall.PointerUint8x01');
PointerUint8x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<Function1PointerUint8,
Function1PointerUint8>('Function1PointerUint8', isLeaf: true)
: ffiTestFunctions.lookupFunction<Function1PointerUint8,
Function1PointerUint8>('Function1PointerUint8', isLeaf: false),
super('FfiCall.PointerUint8x01', isLeaf: isLeaf);
Pointer<Uint8> p1 = nullptr;
@override
@ -869,10 +956,13 @@ class PointerUint8x01 extends FfiBenchmarkBase {
class PointerUint8x02 extends FfiBenchmarkBase {
final Function2PointerUint8 f;
PointerUint8x02()
: f = ffiTestFunctions.lookupFunction<Function2PointerUint8,
Function2PointerUint8>('Function2PointerUint8'),
super('FfiCall.PointerUint8x02');
PointerUint8x02({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<Function2PointerUint8,
Function2PointerUint8>('Function2PointerUint8', isLeaf: true)
: ffiTestFunctions.lookupFunction<Function2PointerUint8,
Function2PointerUint8>('Function2PointerUint8', isLeaf: false),
super('FfiCall.PointerUint8x02', isLeaf: isLeaf);
Pointer<Uint8> p1 = nullptr;
Pointer<Uint8> p2 = nullptr;
@ -901,10 +991,13 @@ class PointerUint8x02 extends FfiBenchmarkBase {
class PointerUint8x04 extends FfiBenchmarkBase {
final Function4PointerUint8 f;
PointerUint8x04()
: f = ffiTestFunctions.lookupFunction<Function4PointerUint8,
Function4PointerUint8>('Function4PointerUint8'),
super('FfiCall.PointerUint8x04');
PointerUint8x04({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<Function4PointerUint8,
Function4PointerUint8>('Function4PointerUint8', isLeaf: true)
: ffiTestFunctions.lookupFunction<Function4PointerUint8,
Function4PointerUint8>('Function4PointerUint8', isLeaf: false),
super('FfiCall.PointerUint8x04', isLeaf: isLeaf);
Pointer<Uint8> p1 = nullptr;
Pointer<Uint8> p2 = nullptr;
@ -937,10 +1030,15 @@ class PointerUint8x04 extends FfiBenchmarkBase {
class PointerUint8x10 extends FfiBenchmarkBase {
final Function10PointerUint8 f;
PointerUint8x10()
: f = ffiTestFunctions.lookupFunction<Function10PointerUint8,
Function10PointerUint8>('Function10PointerUint8'),
super('FfiCall.PointerUint8x10');
PointerUint8x10({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<Function10PointerUint8,
Function10PointerUint8>('Function10PointerUint8', isLeaf: true)
: ffiTestFunctions
.lookupFunction<Function10PointerUint8, Function10PointerUint8>(
'Function10PointerUint8',
isLeaf: false),
super('FfiCall.PointerUint8x10', isLeaf: isLeaf);
Pointer<Uint8> p1 = nullptr;
Pointer<Uint8> p2 = nullptr;
@ -985,10 +1083,15 @@ class PointerUint8x10 extends FfiBenchmarkBase {
class PointerUint8x20 extends FfiBenchmarkBase {
final Function20PointerUint8 f;
PointerUint8x20()
: f = ffiTestFunctions.lookupFunction<Function20PointerUint8,
Function20PointerUint8>('Function20PointerUint8'),
super('FfiCall.PointerUint8x20');
PointerUint8x20({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<Function20PointerUint8,
Function20PointerUint8>('Function20PointerUint8', isLeaf: true)
: ffiTestFunctions
.lookupFunction<Function20PointerUint8, Function20PointerUint8>(
'Function20PointerUint8',
isLeaf: false),
super('FfiCall.PointerUint8x20', isLeaf: isLeaf);
Pointer<Uint8> p1 = nullptr;
Pointer<Uint8> p2 = nullptr;
@ -1061,8 +1164,8 @@ class Handlex01 extends FfiBenchmarkBase {
Handlex01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Handle,
Function1Object>('Function1Handle'),
super('FfiCall.Handlex01');
Function1Object>('Function1Handle', isLeaf: false),
super('FfiCall.Handlex01', isLeaf: false);
@override
void run() {
@ -1080,8 +1183,8 @@ class Handlex02 extends FfiBenchmarkBase {
Handlex02()
: f = ffiTestFunctions.lookupFunction<NativeFunction2Handle,
Function2Object>('Function2Handle'),
super('FfiCall.Handlex02');
Function2Object>('Function2Handle', isLeaf: false),
super('FfiCall.Handlex02', isLeaf: false);
@override
void run() {
@ -1100,8 +1203,8 @@ class Handlex04 extends FfiBenchmarkBase {
Handlex04()
: f = ffiTestFunctions.lookupFunction<NativeFunction4Handle,
Function4Object>('Function4Handle'),
super('FfiCall.Handlex04');
Function4Object>('Function4Handle', isLeaf: false),
super('FfiCall.Handlex04', isLeaf: false);
@override
void run() {
@ -1122,8 +1225,8 @@ class Handlex10 extends FfiBenchmarkBase {
Handlex10()
: f = ffiTestFunctions.lookupFunction<NativeFunction10Handle,
Function10Object>('Function10Handle'),
super('FfiCall.Handlex10');
Function10Object>('Function10Handle', isLeaf: false),
super('FfiCall.Handlex10', isLeaf: false);
@override
void run() {
@ -1150,8 +1253,8 @@ class Handlex20 extends FfiBenchmarkBase {
Handlex20()
: f = ffiTestFunctions.lookupFunction<NativeFunction20Handle,
Function20Object>('Function20Handle'),
super('FfiCall.Handlex20');
Function20Object>('Function20Handle', isLeaf: false),
super('FfiCall.Handlex20', isLeaf: false);
@override
void run() {
@ -1191,10 +1294,12 @@ class Handlex20 extends FfiBenchmarkBase {
void main() {
final benchmarks = [
() => Uint8x01(),
() => Uint8x01(isLeaf: true),
() => Uint16x01(),
() => Uint32x01(),
() => Uint64x01(),
() => Int8x01(),
() => Int8x01(isLeaf: true),
() => Int16x01(),
() => Int32x01(),
() => Int32x02(),
@ -1206,22 +1311,27 @@ void main() {
() => Int64x04(),
() => Int64x10(),
() => Int64x20(),
() => Int64x20(isLeaf: true),
() => Int64Mintx01(),
() => Int64Mintx01(isLeaf: true),
() => Floatx01(),
() => Floatx02(),
() => Floatx04(),
() => Floatx10(),
() => Floatx20(),
() => Floatx20(isLeaf: true),
() => Doublex01(),
() => Doublex02(),
() => Doublex04(),
() => Doublex10(),
() => Doublex20(),
() => Doublex20(isLeaf: true),
() => PointerUint8x01(),
() => PointerUint8x02(),
() => PointerUint8x04(),
() => PointerUint8x10(),
() => PointerUint8x20(),
() => PointerUint8x20(isLeaf: true),
() => Handlex01(),
() => Handlex02(),
() => Handlex04(),

View file

@ -285,7 +285,10 @@ typedef Function20Object = Object Function(
//
abstract class FfiBenchmarkBase extends BenchmarkBase {
FfiBenchmarkBase(String name) : super(name);
final bool isLeaf;
FfiBenchmarkBase(String name, {this.isLeaf: false})
: super('$name${isLeaf ? 'Leaf' : ''}');
void expectEquals(actual, expected) {
if (actual != expected) {
@ -309,10 +312,13 @@ abstract class FfiBenchmarkBase extends BenchmarkBase {
class Uint8x01 extends FfiBenchmarkBase {
final Function1int f;
Uint8x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Uint8, Function1int>(
'Function1Uint8'),
super('FfiCall.Uint8x01');
Uint8x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Uint8,
Function1int>('Function1Uint8', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Uint8,
Function1int>('Function1Uint8', isLeaf: false),
super('FfiCall.Uint8x01', isLeaf: isLeaf);
@override
void run() {
@ -327,10 +333,13 @@ class Uint8x01 extends FfiBenchmarkBase {
class Uint16x01 extends FfiBenchmarkBase {
final Function1int f;
Uint16x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Uint16,
Function1int>('Function1Uint16'),
super('FfiCall.Uint16x01');
Uint16x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Uint16,
Function1int>('Function1Uint16', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Uint16,
Function1int>('Function1Uint16', isLeaf: false),
super('FfiCall.Uint16x01', isLeaf: isLeaf);
@override
void run() {
@ -345,10 +354,13 @@ class Uint16x01 extends FfiBenchmarkBase {
class Uint32x01 extends FfiBenchmarkBase {
final Function1int f;
Uint32x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Uint32,
Function1int>('Function1Uint32'),
super('FfiCall.Uint32x01');
Uint32x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Uint32,
Function1int>('Function1Uint32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Uint32,
Function1int>('Function1Uint32', isLeaf: false),
super('FfiCall.Uint32x01', isLeaf: isLeaf);
@override
void run() {
@ -363,10 +375,13 @@ class Uint32x01 extends FfiBenchmarkBase {
class Uint64x01 extends FfiBenchmarkBase {
final Function1int f;
Uint64x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Uint64,
Function1int>('Function1Uint64'),
super('FfiCall.Uint64x01');
Uint64x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Uint64,
Function1int>('Function1Uint64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Uint64,
Function1int>('Function1Uint64', isLeaf: false),
super('FfiCall.Uint64x01', isLeaf: isLeaf);
@override
void run() {
@ -381,10 +396,13 @@ class Uint64x01 extends FfiBenchmarkBase {
class Int8x01 extends FfiBenchmarkBase {
final Function1int f;
Int8x01()
: f = ffiTestFunctions
.lookupFunction<NativeFunction1Int8, Function1int>('Function1Int8'),
super('FfiCall.Int8x01');
Int8x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Int8,
Function1int>('Function1Int8', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Int8,
Function1int>('Function1Int8', isLeaf: false),
super('FfiCall.Int8x01', isLeaf: isLeaf);
@override
void run() {
@ -399,10 +417,13 @@ class Int8x01 extends FfiBenchmarkBase {
class Int16x01 extends FfiBenchmarkBase {
final Function1int f;
Int16x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Int16, Function1int>(
'Function1Int16'),
super('FfiCall.Int16x01');
Int16x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Int16,
Function1int>('Function1Int16', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Int16,
Function1int>('Function1Int16', isLeaf: false),
super('FfiCall.Int16x01', isLeaf: isLeaf);
@override
void run() {
@ -417,10 +438,13 @@ class Int16x01 extends FfiBenchmarkBase {
class Int32x01 extends FfiBenchmarkBase {
final Function1int f;
Int32x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Int32, Function1int>(
'Function1Int32'),
super('FfiCall.Int32x01');
Int32x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Int32,
Function1int>('Function1Int32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Int32,
Function1int>('Function1Int32', isLeaf: false),
super('FfiCall.Int32x01', isLeaf: isLeaf);
@override
void run() {
@ -435,10 +459,13 @@ class Int32x01 extends FfiBenchmarkBase {
class Int32x02 extends FfiBenchmarkBase {
final Function2int f;
Int32x02()
: f = ffiTestFunctions.lookupFunction<NativeFunction2Int32, Function2int>(
'Function2Int32'),
super('FfiCall.Int32x02');
Int32x02({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction2Int32,
Function2int>('Function2Int32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction2Int32,
Function2int>('Function2Int32', isLeaf: false),
super('FfiCall.Int32x02', isLeaf: isLeaf);
@override
void run() {
@ -453,10 +480,13 @@ class Int32x02 extends FfiBenchmarkBase {
class Int32x04 extends FfiBenchmarkBase {
final Function4int f;
Int32x04()
: f = ffiTestFunctions.lookupFunction<NativeFunction4Int32, Function4int>(
'Function4Int32'),
super('FfiCall.Int32x04');
Int32x04({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction4Int32,
Function4int>('Function4Int32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction4Int32,
Function4int>('Function4Int32', isLeaf: false),
super('FfiCall.Int32x04', isLeaf: isLeaf);
@override
void run() {
@ -471,10 +501,13 @@ class Int32x04 extends FfiBenchmarkBase {
class Int32x10 extends FfiBenchmarkBase {
final Function10int f;
Int32x10()
: f = ffiTestFunctions.lookupFunction<NativeFunction10Int32,
Function10int>('Function10Int32'),
super('FfiCall.Int32x10');
Int32x10({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction10Int32,
Function10int>('Function10Int32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction10Int32,
Function10int>('Function10Int32', isLeaf: false),
super('FfiCall.Int32x10', isLeaf: isLeaf);
@override
void run() {
@ -489,10 +522,13 @@ class Int32x10 extends FfiBenchmarkBase {
class Int32x20 extends FfiBenchmarkBase {
final Function20int f;
Int32x20()
: f = ffiTestFunctions.lookupFunction<NativeFunction20Int32,
Function20int>('Function20Int32'),
super('FfiCall.Int32x20');
Int32x20({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction20Int32,
Function20int>('Function20Int32', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction20Int32,
Function20int>('Function20Int32', isLeaf: false),
super('FfiCall.Int32x20', isLeaf: isLeaf);
@override
void run() {
@ -507,10 +543,13 @@ class Int32x20 extends FfiBenchmarkBase {
class Int64x01 extends FfiBenchmarkBase {
final Function1int f;
Int64x01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Int64, Function1int>(
'Function1Int64'),
super('FfiCall.Int64x01');
Int64x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Int64,
Function1int>('Function1Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Int64,
Function1int>('Function1Int64', isLeaf: false),
super('FfiCall.Int64x01', isLeaf: isLeaf);
@override
void run() {
@ -525,10 +564,13 @@ class Int64x01 extends FfiBenchmarkBase {
class Int64x02 extends FfiBenchmarkBase {
final Function2int f;
Int64x02()
: f = ffiTestFunctions.lookupFunction<NativeFunction2Int64, Function2int>(
'Function2Int64'),
super('FfiCall.Int64x02');
Int64x02({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction2Int64,
Function2int>('Function2Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction2Int64,
Function2int>('Function2Int64', isLeaf: false),
super('FfiCall.Int64x02', isLeaf: isLeaf);
@override
void run() {
@ -543,10 +585,13 @@ class Int64x02 extends FfiBenchmarkBase {
class Int64x04 extends FfiBenchmarkBase {
final Function4int f;
Int64x04()
: f = ffiTestFunctions.lookupFunction<NativeFunction4Int64, Function4int>(
'Function4Int64'),
super('FfiCall.Int64x04');
Int64x04({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction4Int64,
Function4int>('Function4Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction4Int64,
Function4int>('Function4Int64', isLeaf: false),
super('FfiCall.Int64x04', isLeaf: isLeaf);
@override
void run() {
@ -561,10 +606,13 @@ class Int64x04 extends FfiBenchmarkBase {
class Int64x10 extends FfiBenchmarkBase {
final Function10int f;
Int64x10()
: f = ffiTestFunctions.lookupFunction<NativeFunction10Int64,
Function10int>('Function10Int64'),
super('FfiCall.Int64x10');
Int64x10({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction10Int64,
Function10int>('Function10Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction10Int64,
Function10int>('Function10Int64', isLeaf: false),
super('FfiCall.Int64x10', isLeaf: isLeaf);
@override
void run() {
@ -579,10 +627,13 @@ class Int64x10 extends FfiBenchmarkBase {
class Int64x20 extends FfiBenchmarkBase {
final Function20int f;
Int64x20()
: f = ffiTestFunctions.lookupFunction<NativeFunction20Int64,
Function20int>('Function20Int64'),
super('FfiCall.Int64x20');
Int64x20({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction20Int64,
Function20int>('Function20Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction20Int64,
Function20int>('Function20Int64', isLeaf: false),
super('FfiCall.Int64x20', isLeaf: isLeaf);
@override
void run() {
@ -597,10 +648,13 @@ class Int64x20 extends FfiBenchmarkBase {
class Int64Mintx01 extends FfiBenchmarkBase {
final Function1int f;
Int64Mintx01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Int64, Function1int>(
'Function1Int64'),
super('FfiCall.Int64Mintx01');
Int64Mintx01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Int64,
Function1int>('Function1Int64', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Int64,
Function1int>('Function1Int64', isLeaf: false),
super('FfiCall.Int64Mintx01', isLeaf: isLeaf);
@override
void run() {
@ -615,10 +669,13 @@ class Int64Mintx01 extends FfiBenchmarkBase {
class Floatx01 extends FfiBenchmarkBase {
final Function1double f;
Floatx01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Float,
Function1double>('Function1Float'),
super('FfiCall.Floatx01');
Floatx01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Float,
Function1double>('Function1Float', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Float,
Function1double>('Function1Float', isLeaf: false),
super('FfiCall.Floatx01', isLeaf: isLeaf);
@override
void run() {
@ -633,10 +690,13 @@ class Floatx01 extends FfiBenchmarkBase {
class Floatx02 extends FfiBenchmarkBase {
final Function2double f;
Floatx02()
: f = ffiTestFunctions.lookupFunction<NativeFunction2Float,
Function2double>('Function2Float'),
super('FfiCall.Floatx02');
Floatx02({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction2Float,
Function2double>('Function2Float', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction2Float,
Function2double>('Function2Float', isLeaf: false),
super('FfiCall.Floatx02', isLeaf: isLeaf);
@override
void run() {
@ -651,10 +711,13 @@ class Floatx02 extends FfiBenchmarkBase {
class Floatx04 extends FfiBenchmarkBase {
final Function4double f;
Floatx04()
: f = ffiTestFunctions.lookupFunction<NativeFunction4Float,
Function4double>('Function4Float'),
super('FfiCall.Floatx04');
Floatx04({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction4Float,
Function4double>('Function4Float', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction4Float,
Function4double>('Function4Float', isLeaf: false),
super('FfiCall.Floatx04', isLeaf: isLeaf);
@override
void run() {
@ -670,10 +733,13 @@ class Floatx04 extends FfiBenchmarkBase {
class Floatx10 extends FfiBenchmarkBase {
final Function10double f;
Floatx10()
: f = ffiTestFunctions.lookupFunction<NativeFunction10Float,
Function10double>('Function10Float'),
super('FfiCall.Floatx10');
Floatx10({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction10Float,
Function10double>('Function10Float', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction10Float,
Function10double>('Function10Float', isLeaf: false),
super('FfiCall.Floatx10', isLeaf: isLeaf);
@override
void run() {
@ -690,10 +756,13 @@ class Floatx10 extends FfiBenchmarkBase {
class Floatx20 extends FfiBenchmarkBase {
final Function20double f;
Floatx20()
: f = ffiTestFunctions.lookupFunction<NativeFunction20Float,
Function20double>('Function20Float'),
super('FfiCall.Floatx20');
Floatx20({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction20Float,
Function20double>('Function20Float', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction20Float,
Function20double>('Function20Float', isLeaf: false),
super('FfiCall.Floatx20', isLeaf: isLeaf);
@override
void run() {
@ -730,10 +799,13 @@ class Floatx20 extends FfiBenchmarkBase {
class Doublex01 extends FfiBenchmarkBase {
final Function1double f;
Doublex01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Double,
Function1double>('Function1Double'),
super('FfiCall.Doublex01');
Doublex01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction1Double,
Function1double>('Function1Double', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction1Double,
Function1double>('Function1Double', isLeaf: false),
super('FfiCall.Doublex01', isLeaf: isLeaf);
@override
void run() {
@ -749,10 +821,13 @@ class Doublex01 extends FfiBenchmarkBase {
class Doublex02 extends FfiBenchmarkBase {
final Function2double f;
Doublex02()
: f = ffiTestFunctions.lookupFunction<NativeFunction2Double,
Function2double>('Function2Double'),
super('FfiCall.Doublex02');
Doublex02({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction2Double,
Function2double>('Function2Double', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction2Double,
Function2double>('Function2Double', isLeaf: false),
super('FfiCall.Doublex02', isLeaf: isLeaf);
@override
void run() {
@ -768,10 +843,13 @@ class Doublex02 extends FfiBenchmarkBase {
class Doublex04 extends FfiBenchmarkBase {
final Function4double f;
Doublex04()
: f = ffiTestFunctions.lookupFunction<NativeFunction4Double,
Function4double>('Function4Double'),
super('FfiCall.Doublex04');
Doublex04({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction4Double,
Function4double>('Function4Double', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction4Double,
Function4double>('Function4Double', isLeaf: false),
super('FfiCall.Doublex04', isLeaf: isLeaf);
@override
void run() {
@ -787,10 +865,13 @@ class Doublex04 extends FfiBenchmarkBase {
class Doublex10 extends FfiBenchmarkBase {
final Function10double f;
Doublex10()
: f = ffiTestFunctions.lookupFunction<NativeFunction10Double,
Function10double>('Function10Double'),
super('FfiCall.Doublex10');
Doublex10({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction10Double,
Function10double>('Function10Double', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction10Double,
Function10double>('Function10Double', isLeaf: false),
super('FfiCall.Doublex10', isLeaf: isLeaf);
@override
void run() {
@ -807,10 +888,13 @@ class Doublex10 extends FfiBenchmarkBase {
class Doublex20 extends FfiBenchmarkBase {
final Function20double f;
Doublex20()
: f = ffiTestFunctions.lookupFunction<NativeFunction20Double,
Function20double>('Function20Double'),
super('FfiCall.Doublex20');
Doublex20({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<NativeFunction20Double,
Function20double>('Function20Double', isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeFunction20Double,
Function20double>('Function20Double', isLeaf: false),
super('FfiCall.Doublex20', isLeaf: isLeaf);
@override
void run() {
@ -847,10 +931,13 @@ class Doublex20 extends FfiBenchmarkBase {
class PointerUint8x01 extends FfiBenchmarkBase {
final Function1PointerUint8 f;
PointerUint8x01()
: f = ffiTestFunctions.lookupFunction<Function1PointerUint8,
Function1PointerUint8>('Function1PointerUint8'),
super('FfiCall.PointerUint8x01');
PointerUint8x01({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<Function1PointerUint8,
Function1PointerUint8>('Function1PointerUint8', isLeaf: true)
: ffiTestFunctions.lookupFunction<Function1PointerUint8,
Function1PointerUint8>('Function1PointerUint8', isLeaf: false),
super('FfiCall.PointerUint8x01', isLeaf: isLeaf);
Pointer<Uint8> p1;
@override
@ -871,10 +958,13 @@ class PointerUint8x01 extends FfiBenchmarkBase {
class PointerUint8x02 extends FfiBenchmarkBase {
final Function2PointerUint8 f;
PointerUint8x02()
: f = ffiTestFunctions.lookupFunction<Function2PointerUint8,
Function2PointerUint8>('Function2PointerUint8'),
super('FfiCall.PointerUint8x02');
PointerUint8x02({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<Function2PointerUint8,
Function2PointerUint8>('Function2PointerUint8', isLeaf: true)
: ffiTestFunctions.lookupFunction<Function2PointerUint8,
Function2PointerUint8>('Function2PointerUint8', isLeaf: false),
super('FfiCall.PointerUint8x02', isLeaf: isLeaf);
Pointer<Uint8> p1, p2;
@ -902,10 +992,13 @@ class PointerUint8x02 extends FfiBenchmarkBase {
class PointerUint8x04 extends FfiBenchmarkBase {
final Function4PointerUint8 f;
PointerUint8x04()
: f = ffiTestFunctions.lookupFunction<Function4PointerUint8,
Function4PointerUint8>('Function4PointerUint8'),
super('FfiCall.PointerUint8x04');
PointerUint8x04({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<Function4PointerUint8,
Function4PointerUint8>('Function4PointerUint8', isLeaf: true)
: ffiTestFunctions.lookupFunction<Function4PointerUint8,
Function4PointerUint8>('Function4PointerUint8', isLeaf: false),
super('FfiCall.PointerUint8x04', isLeaf: isLeaf);
Pointer<Uint8> p1, p2, p3, p4;
@ -935,10 +1028,15 @@ class PointerUint8x04 extends FfiBenchmarkBase {
class PointerUint8x10 extends FfiBenchmarkBase {
final Function10PointerUint8 f;
PointerUint8x10()
: f = ffiTestFunctions.lookupFunction<Function10PointerUint8,
Function10PointerUint8>('Function10PointerUint8'),
super('FfiCall.PointerUint8x10');
PointerUint8x10({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<Function10PointerUint8,
Function10PointerUint8>('Function10PointerUint8', isLeaf: true)
: ffiTestFunctions
.lookupFunction<Function10PointerUint8, Function10PointerUint8>(
'Function10PointerUint8',
isLeaf: false),
super('FfiCall.PointerUint8x10', isLeaf: isLeaf);
Pointer<Uint8> p1, p2, p3, p4, p5, p6, p7, p8, p9, p10;
@ -974,10 +1072,15 @@ class PointerUint8x10 extends FfiBenchmarkBase {
class PointerUint8x20 extends FfiBenchmarkBase {
final Function20PointerUint8 f;
PointerUint8x20()
: f = ffiTestFunctions.lookupFunction<Function20PointerUint8,
Function20PointerUint8>('Function20PointerUint8'),
super('FfiCall.PointerUint8x20');
PointerUint8x20({isLeaf: false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<Function20PointerUint8,
Function20PointerUint8>('Function20PointerUint8', isLeaf: true)
: ffiTestFunctions
.lookupFunction<Function20PointerUint8, Function20PointerUint8>(
'Function20PointerUint8',
isLeaf: false),
super('FfiCall.PointerUint8x20', isLeaf: isLeaf);
Pointer<Uint8> p1,
p2,
@ -1050,8 +1153,8 @@ class Handlex01 extends FfiBenchmarkBase {
Handlex01()
: f = ffiTestFunctions.lookupFunction<NativeFunction1Handle,
Function1Object>('Function1Handle'),
super('FfiCall.Handlex01');
Function1Object>('Function1Handle', isLeaf: false),
super('FfiCall.Handlex01', isLeaf: false);
@override
void run() {
@ -1069,8 +1172,8 @@ class Handlex02 extends FfiBenchmarkBase {
Handlex02()
: f = ffiTestFunctions.lookupFunction<NativeFunction2Handle,
Function2Object>('Function2Handle'),
super('FfiCall.Handlex02');
Function2Object>('Function2Handle', isLeaf: false),
super('FfiCall.Handlex02', isLeaf: false);
@override
void run() {
@ -1089,8 +1192,8 @@ class Handlex04 extends FfiBenchmarkBase {
Handlex04()
: f = ffiTestFunctions.lookupFunction<NativeFunction4Handle,
Function4Object>('Function4Handle'),
super('FfiCall.Handlex04');
Function4Object>('Function4Handle', isLeaf: false),
super('FfiCall.Handlex04', isLeaf: false);
@override
void run() {
@ -1111,8 +1214,8 @@ class Handlex10 extends FfiBenchmarkBase {
Handlex10()
: f = ffiTestFunctions.lookupFunction<NativeFunction10Handle,
Function10Object>('Function10Handle'),
super('FfiCall.Handlex10');
Function10Object>('Function10Handle', isLeaf: false),
super('FfiCall.Handlex10', isLeaf: false);
@override
void run() {
@ -1139,8 +1242,8 @@ class Handlex20 extends FfiBenchmarkBase {
Handlex20()
: f = ffiTestFunctions.lookupFunction<NativeFunction20Handle,
Function20Object>('Function20Handle'),
super('FfiCall.Handlex20');
Function20Object>('Function20Handle', isLeaf: false),
super('FfiCall.Handlex20', isLeaf: false);
@override
void run() {
@ -1180,10 +1283,12 @@ class Handlex20 extends FfiBenchmarkBase {
void main() {
final benchmarks = [
() => Uint8x01(),
() => Uint8x01(isLeaf: true),
() => Uint16x01(),
() => Uint32x01(),
() => Uint64x01(),
() => Int8x01(),
() => Int8x01(isLeaf: true),
() => Int16x01(),
() => Int32x01(),
() => Int32x02(),
@ -1195,22 +1300,27 @@ void main() {
() => Int64x04(),
() => Int64x10(),
() => Int64x20(),
() => Int64x20(isLeaf: true),
() => Int64Mintx01(),
() => Int64Mintx01(isLeaf: true),
() => Floatx01(),
() => Floatx02(),
() => Floatx04(),
() => Floatx10(),
() => Floatx20(),
() => Floatx20(isLeaf: true),
() => Doublex01(),
() => Doublex02(),
() => Doublex04(),
() => Doublex10(),
() => Doublex20(),
() => Doublex20(isLeaf: true),
() => PointerUint8x01(),
() => PointerUint8x02(),
() => PointerUint8x04(),
() => PointerUint8x10(),
() => PointerUint8x20(),
() => PointerUint8x20(isLeaf: true),
() => Handlex01(),
() => Handlex02(),
() => Handlex04(),

View file

@ -3778,6 +3778,27 @@ const MessageCode messageFfiExpectedConstant = const MessageCode(
"FfiExpectedConstant",
message: r"""Exceptional return value must be a constant.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(String name)> templateFfiExpectedConstantArg =
const Template<Message Function(String name)>(
messageTemplate: r"""Argument '#name' must be a constant.""",
withArguments: _withArgumentsFfiExpectedConstantArg);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Message Function(String name)> codeFfiExpectedConstantArg =
const Code<Message Function(String name)>(
"FfiExpectedConstantArg",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
Message _withArgumentsFfiExpectedConstantArg(String name) {
if (name.isEmpty) throw 'No name provided';
name = demangleMixinApplicationName(name);
return new Message(codeFfiExpectedConstantArg,
message: """Argument '${name}' must be a constant.""",
arguments: {'name': name});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(String name)>
templateFfiExtendsOrImplementsSealedClass =
@ -3931,6 +3952,24 @@ Message _withArgumentsFfiFieldNull(String name) {
arguments: {'name': name});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeFfiLeafCallMustNotReturnHandle =
messageFfiLeafCallMustNotReturnHandle;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageFfiLeafCallMustNotReturnHandle = const MessageCode(
"FfiLeafCallMustNotReturnHandle",
message: r"""FFI leaf call must not have Handle return type.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeFfiLeafCallMustNotTakeHandle =
messageFfiLeafCallMustNotTakeHandle;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageFfiLeafCallMustNotTakeHandle = const MessageCode(
"FfiLeafCallMustNotTakeHandle",
message: r"""FFI leaf call must not have Handle argument types.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(String name)> templateFfiNotStatic = const Template<

View file

@ -462,6 +462,7 @@ const List<ErrorCode> errorCodeValues = [
CompileTimeErrorCode.YIELD_IN_NON_GENERATOR,
CompileTimeErrorCode.YIELD_OF_INVALID_TYPE,
FfiCode.ANNOTATION_ON_POINTER_FIELD,
FfiCode.ARGUMENT_MUST_BE_A_CONSTANT,
FfiCode.EMPTY_STRUCT,
FfiCode.EXTRA_ANNOTATION_ON_STRUCT_FIELD,
FfiCode.EXTRA_SIZE_ANNOTATION_CARRAY,
@ -470,6 +471,8 @@ const List<ErrorCode> errorCodeValues = [
FfiCode.GENERIC_STRUCT_SUBCLASS,
FfiCode.INVALID_EXCEPTION_VALUE,
FfiCode.INVALID_FIELD_TYPE_IN_STRUCT,
FfiCode.LEAF_CALL_MUST_NOT_RETURN_HANDLE,
FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE,
FfiCode.MISMATCHED_ANNOTATION_ON_STRUCT_FIELD,
FfiCode.MISSING_ANNOTATION_ON_STRUCT_FIELD,
FfiCode.MISSING_EXCEPTION_VALUE,

View file

@ -21,6 +21,15 @@ class FfiCode extends AnalyzerErrorCode {
"any annotations.",
correction: "Try removing the annotation.");
/**
* Parameters:
* 0: the name of the argument
*/
static const FfiCode ARGUMENT_MUST_BE_A_CONSTANT = FfiCode(
name: 'ARGUMENT_MUST_BE_A_CONSTANT',
message: "Argument '{0}' must be a constant.",
correction: "Try replacing the value with a literal or const.");
/**
* Parameters:
* 0: the name of the struct class
@ -103,6 +112,22 @@ class FfiCode extends AnalyzerErrorCode {
"Try using 'int', 'double', 'Array', 'Pointer', or subtype of "
"'Struct' or 'Union'.");
/**
* No parameters.
*/
static const FfiCode LEAF_CALL_MUST_NOT_RETURN_HANDLE = FfiCode(
name: 'LEAF_CALL_MUST_NOT_RETURN_HANDLE',
message: "FFI leaf call must not return a Handle.",
correction: "Try changing the return type to primitive or struct.");
/**
* No parameters.
*/
static const FfiCode LEAF_CALL_MUST_NOT_TAKE_HANDLE = FfiCode(
name: 'LEAF_CALL_MUST_NOT_TAKE_HANDLE',
message: "FFI leaf call must not take arguments of type Handle.",
correction: "Try changing the argument type to primitive or struct.");
/**
* No parameters.
*/

View file

@ -8,6 +8,7 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/error/ffi_code.dart';
@ -20,6 +21,7 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
static const _allocatorExtensionName = 'AllocatorAlloc';
static const _arrayClassName = 'Array';
static const _dartFfiLibraryName = 'dart.ffi';
static const _isLeafParamName = 'isLeaf';
static const _opaqueClassName = 'Opaque';
static const List<String> _primitiveIntegerNativeTypes = [
@ -341,6 +343,26 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
return false;
}
// Get the const bool value of `expr` if it exists.
// Return null if it isn't a const bool.
bool? _maybeGetBoolConstValue(Expression expr) {
if (expr is BooleanLiteral) {
return expr.value;
} else if (expr is Identifier) {
final staticElm = expr.staticElement;
if (staticElm is ConstVariableElement) {
return staticElm.computeConstantValue()?.toBoolValue();
}
if (staticElm is PropertyAccessorElementImpl) {
final v = staticElm.variable;
if (v is ConstVariableElement) {
return v.computeConstantValue()?.toBoolValue();
}
}
}
return null;
}
_PrimitiveDartType _primitiveNativeType(DartType nativeType) {
if (nativeType is InterfaceType) {
final element = nativeType.element;
@ -461,7 +483,9 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
_errorReporter.reportErrorForNode(
FfiCode.MUST_BE_A_SUBTYPE, node, [TPrime, F, 'asFunction']);
}
_validateFfiLeafCallUsesNoHandles(node, TPrime, node);
}
_validateIsLeafIsConst(node);
}
/// Validates that the given [nativeType] is, when native types are converted
@ -551,6 +575,37 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
}
}
void _validateFfiLeafCallUsesNoHandles(
MethodInvocation node, DartType nativeType, AstNode errorNode) {
final args = node.argumentList.arguments;
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);
}
}
}
}
}
}
}
}
}
/// Validate that the fields declared by the given [node] meet the
/// requirements for fields within a struct or union class.
void _validateFieldsInCompound(FieldDeclaration node) {
@ -654,6 +709,27 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
}
}
void _validateIsLeafIsConst(MethodInvocation node) {
// Ensure `isLeaf` is const as we need the value at compile time to know
// which trampoline to generate.
final args = node.argumentList.arguments;
if (args.isNotEmpty) {
for (final arg in args) {
if (arg is NamedExpression) {
if (arg.element?.name == _isLeafParamName) {
if (_maybeGetBoolConstValue(arg.expression) == null) {
final AstNode errorNode = node;
_errorReporter.reportErrorForNode(
FfiCode.ARGUMENT_MUST_BE_A_CONSTANT,
errorNode,
[_isLeafParamName]);
}
}
}
}
}
}
/// Validate the invocation of the instance method
/// `DynamicLibrary.lookupFunction<S, F>()`.
void _validateLookupFunction(MethodInvocation node) {
@ -678,6 +754,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
_errorReporter.reportErrorForNode(
FfiCode.MUST_BE_A_SUBTYPE, errorNode, [S, F, 'lookupFunction']);
}
_validateIsLeafIsConst(node);
_validateFfiLeafCallUsesNoHandles(node, S, typeArguments![0]);
}
/// Validate that none of the [annotations] are from `dart:ffi`.

View file

@ -620,6 +620,8 @@ class NativeType {
const NativeType();
}
class Handle extends NativeType {}
class Void extends NativeType {}
class Int8 extends NativeType {
@ -674,7 +676,7 @@ final Pointer<Never> nullptr = Pointer.fromAddress(0);
extension NativeFunctionPointer<NF extends Function>
on Pointer<NativeFunction<NF>> {
external DF asFunction<DF extends Function>();
external DF asFunction<DF extends Function>({bool isLeaf:false});
}
class _Compound extends NativeType {}
@ -689,11 +691,13 @@ class Packed {
const Packed(this.memberAlignment);
}
abstract class DynamicLibrary {}
abstract class DynamicLibrary {
external factory DynamicLibrary.open(String name);
}
extension DynamicLibraryExtension on DynamicLibrary {
external F lookupFunction<T extends Function, F extends Function>(
String symbolName);
String symbolName, {bool isLeaf:false});
}
abstract class NativeFunction<T extends Function> extends NativeType {}

View file

@ -0,0 +1,78 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/src/dart/error/ffi_code.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../dart/resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ArgumentMustBeAConstantTest);
});
}
@reflectiveTest
class ArgumentMustBeAConstantTest extends PubPackageResolutionTest {
test_AsFunctionIsLeafGlobal() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
typedef Int8UnOp = Int8 Function(Int8);
typedef IntUnOp = int Function(int);
bool isLeaf = false;
doThings() {
Pointer<NativeFunction<Int8UnOp>> p = Pointer.fromAddress(1337);
IntUnOp f = p.asFunction(isLeaf:isLeaf);
f(8);
}
''', [
error(FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, 211, 27),
]);
}
test_AsFunctionIsLeafLocal() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
typedef Int8UnOp = Int8 Function(Int8);
typedef IntUnOp = int Function(int);
doThings() {
bool isLeaf = false;
Pointer<NativeFunction<Int8UnOp>> p = Pointer.fromAddress(1337);
IntUnOp f = p.asFunction(isLeaf:isLeaf);
f(8);
}
''', [
error(FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, 213, 27),
]);
}
test_AsFunctionIsLeafParam() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
typedef Int8UnOp = Int8 Function(Int8);
typedef IntUnOp = int Function(int);
doThings(bool isLeaf) {
Pointer<NativeFunction<Int8UnOp>> p = Pointer.fromAddress(1337);
IntUnOp f = p.asFunction(isLeaf:isLeaf);
f(8);
}
''', [
error(FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, 201, 27),
]);
}
test_LookupFunctionIsLeaf() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
typedef Int8UnOp = Int8 Function(Int8);
typedef IntUnOp = int Function(int);
doThings(bool isLeaf) {
DynamicLibrary l = DynamicLibrary.open("my_lib");
l.lookupFunction<Int8UnOp, IntUnOp>("timesFour", isLeaf:isLeaf);
}
''', [
error(FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, 174, 63),
]);
}
}

View file

@ -0,0 +1,77 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/src/dart/error/ffi_code.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../dart/resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(LeafCallMustNotUseHandle);
});
}
@reflectiveTest
class LeafCallMustNotUseHandle extends PubPackageResolutionTest {
test_AsFunctionReturnsHandle() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
typedef NativeReturnsHandle = Handle Function();
typedef ReturnsHandle = Object Function();
doThings() {
Pointer<NativeFunction<NativeReturnsHandle>> p = Pointer.fromAddress(1337);
ReturnsHandle f = p.asFunction(isLeaf:true);
f();
}
''', [
error(FfiCode.LEAF_CALL_MUST_NOT_RETURN_HANDLE, 222, 25),
]);
}
test_AsFunctionTakesHandle() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
typedef NativeTakesHandle = Void Function(Handle);
typedef TakesHandle = void Function(Object);
class MyClass {}
doThings() {
Pointer<NativeFunction<NativeTakesHandle>> p = Pointer.fromAddress(1337);
TakesHandle f = p.asFunction(isLeaf:true);
f(MyClass());
}
''', [
error(FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE, 239, 25),
]);
}
test_LookupFunctionReturnsHandle() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
typedef NativeReturnsHandle = Handle Function();
typedef ReturnsHandle = Object Function();
doThings() {
DynamicLibrary l = DynamicLibrary.open("my_lib");
l.lookupFunction<NativeReturnsHandle, ReturnsHandle>("timesFour", isLeaf:true);
}
''', [
error(FfiCode.LEAF_CALL_MUST_NOT_RETURN_HANDLE, 195, 19),
]);
}
test_LookupFunctionTakesHandle() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
typedef NativeTakesHandle = Void Function(Handle);
typedef TakesHandle = void Function(Object);
class MyClass {}
doThings() {
DynamicLibrary l = DynamicLibrary.open("my_lib");
l.lookupFunction<NativeTakesHandle, TakesHandle>("timesFour", isLeaf:true);
}
''', [
error(FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE, 216, 17),
]);
}
}

View file

@ -17,6 +17,7 @@ import 'ambiguous_import_test.dart' as ambiguous_import;
import 'ambiguous_set_or_map_literal_test.dart' as ambiguous_set_or_map_literal;
import 'annotation_on_pointer_field_test.dart' as annotation_on_pointer_field;
import 'annotation_syntax_test.dart' as annotation_syntax;
import 'argument_must_be_a_constant_test.dart' as argument_must_be_a_constant;
import 'argument_type_not_assignable_test.dart' as argument_type_not_assignable;
import 'argument_type_not_assignable_to_error_handler_test.dart'
as argument_type_not_assignable_to_error_handler;
@ -188,6 +189,8 @@ import 'extra_positional_arguments_test.dart' as extra_positional_arguments;
import 'extra_size_annotation_carray_test.dart' as extra_size_annotation_carray;
import 'ffi_array_multi_non_positive_input_test.dart'
as ffi_array_multi_non_positive_input_test;
import 'ffi_leaf_call_must_not_use_handle_test.dart'
as ffi_leaf_call_must_not_use_handle;
import 'field_in_struct_with_initializer_test.dart'
as field_in_struct_with_initializer;
import 'field_initialized_by_multiple_initializers_test.dart'
@ -700,6 +703,7 @@ main() {
ambiguous_set_or_map_literal.main();
annotation_on_pointer_field.main();
annotation_syntax.main();
argument_must_be_a_constant.main();
argument_type_not_assignable.main();
argument_type_not_assignable_to_error_handler.main();
assert_in_redirecting_constructor.main();
@ -816,6 +820,7 @@ main() {
extra_positional_arguments.main();
extra_size_annotation_carray.main();
ffi_array_multi_non_positive_input_test.main();
ffi_leaf_call_must_not_use_handle.main();
field_in_struct_with_initializer.main();
field_initialized_by_multiple_initializers.main();
final_initialized_in_declaration_and_constructor.main();

View file

@ -52,11 +52,14 @@ export '../fasta/fasta_codes.dart'
LocatedMessage,
messageFfiExceptionalReturnNull,
messageFfiExpectedConstant,
messageFfiLeafCallMustNotReturnHandle,
messageFfiLeafCallMustNotTakeHandle,
messageFfiPackedAnnotationAlignment,
messageNonPositiveArrayDimensions,
noLength,
templateFfiDartTypeMismatch,
templateFfiEmptyStruct,
templateFfiExpectedConstantArg,
templateFfiExpectedExceptionalReturn,
templateFfiExpectedNoExceptionalReturn,
templateFfiExtendsOrImplementsSealedClass,

View file

@ -326,6 +326,7 @@ FfiDartTypeMismatch/analyzerCode: Fail
FfiEmptyStruct/analyzerCode: Fail
FfiExceptionalReturnNull/analyzerCode: Fail
FfiExpectedConstant/analyzerCode: Fail
FfiExpectedConstantArg/analyzerCode: Fail
FfiExpectedExceptionalReturn/analyzerCode: Fail
FfiExpectedNoExceptionalReturn/analyzerCode: Fail
FfiExtendsOrImplementsSealedClass/analyzerCode: Fail
@ -334,6 +335,8 @@ FfiFieldCyclic/analyzerCode: Fail
FfiFieldInitializer/analyzerCode: Fail
FfiFieldNoAnnotation/analyzerCode: Fail
FfiFieldNull/analyzerCode: Fail
FfiLeafCallMustNotReturnHandle/analyzerCode: Fail
FfiLeafCallMustNotTakeHandle/analyzerCode: Fail
FfiNotStatic/analyzerCode: Fail
FfiPackedAnnotation/analyzerCode: Fail
FfiPackedAnnotationAlignment/analyzerCode: Fail

View file

@ -4501,6 +4501,21 @@ FfiExceptionalReturnNull:
template: "Exceptional return value must not be null."
external: test/ffi_test.dart
FfiExpectedConstantArg:
# Used by dart:ffi
template: "Argument '#name' must be a constant."
external: test/ffi_test.dart
FfiLeafCallMustNotTakeHandle:
# Used by dart:ffi
template: "FFI leaf call must not have Handle argument types."
external: test/ffi_test.dart
FfiLeafCallMustNotReturnHandle:
# Used by dart:ffi
template: "FFI leaf call must not have Handle return type."
external: test/ffi_test.dart
SpreadTypeMismatch:
template: "Unexpected type '#type' of a spread. Expected 'dynamic' or an Iterable."
script:

View file

@ -221,6 +221,7 @@ class FfiTransformer extends Transformer {
final Library ffiLibrary;
final Class allocatorClass;
final Class nativeFunctionClass;
final Class handleClass;
final Class opaqueClass;
final Class arrayClass;
final Class arraySizeClass;
@ -324,6 +325,7 @@ class FfiTransformer extends Transformer {
ffiLibrary = index.getLibrary('dart:ffi'),
allocatorClass = index.getClass('dart:ffi', 'Allocator'),
nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'),
handleClass = index.getClass('dart:ffi', 'Handle'),
opaqueClass = index.getClass('dart:ffi', 'Opaque'),
arrayClass = index.getClass('dart:ffi', 'Array'),
arraySizeClass = index.getClass('dart:ffi', '_ArraySize'),

View file

@ -8,7 +8,10 @@ import 'package:front_end/src/api_unstable/vm.dart'
show
messageFfiExceptionalReturnNull,
messageFfiExpectedConstant,
messageFfiLeafCallMustNotReturnHandle,
messageFfiLeafCallMustNotTakeHandle,
templateFfiDartTypeMismatch,
templateFfiExpectedConstantArg,
templateFfiExpectedExceptionalReturn,
templateFfiExpectedNoExceptionalReturn,
templateFfiExtendsOrImplementsSealedClass,
@ -176,12 +179,14 @@ class _FfiUseSiteTransformer extends FfiTransformer {
}
}
} else if (target == lookupFunctionMethod) {
final DartType nativeType = InterfaceType(
final nativeType = InterfaceType(
nativeFunctionClass, Nullability.legacy, [node.arguments.types[0]]);
final DartType dartType = node.arguments.types[1];
_ensureNativeTypeValid(nativeType, node);
_ensureNativeTypeToDartType(nativeType, dartType, node);
_ensureIsLeafIsConst(node);
_ensureLeafCallDoesNotUseHandles(nativeType, node);
final replacement = _replaceLookupFunction(node);
@ -197,20 +202,27 @@ class _FfiUseSiteTransformer extends FfiTransformer {
}
return replacement;
} else if (target == asFunctionMethod) {
final DartType dartType = node.arguments.types[1];
final dartType = node.arguments.types[1];
final DartType nativeType = InterfaceType(
nativeFunctionClass, Nullability.legacy, [node.arguments.types[0]]);
_ensureNativeTypeValid(nativeType, node);
_ensureNativeTypeToDartType(nativeType, dartType, node);
_ensureIsLeafIsConst(node);
_ensureLeafCallDoesNotUseHandles(nativeType, node);
final DartType nativeSignature =
(nativeType as InterfaceType).typeArguments[0];
bool isLeaf = _getIsLeafBoolean(node);
if (isLeaf == null) {
isLeaf = false;
}
// Inline function body to make all type arguments instatiated.
final replacement = StaticInvocation(
asFunctionInternal,
Arguments([node.arguments.positional[0]],
Arguments([node.arguments.positional[0], BoolLiteral(isLeaf)],
types: [dartType, nativeSignature]));
if (dartType is FunctionType) {
@ -411,12 +423,12 @@ class _FfiUseSiteTransformer extends FfiTransformer {
Expression _replaceLookupFunction(StaticInvocation node) {
// The generated code looks like:
//
// _asFunctionInternal<DS, NS>(lookup<NativeFunction<NS>>(symbolName))
// _asFunctionInternal<DS, NS>(lookup<NativeFunction<NS>>(symbolName),
// isLeaf)
final DartType nativeSignature = node.arguments.types[0];
final DartType dartSignature = node.arguments.types[1];
final Arguments args = Arguments([
final Arguments lookupArgs = Arguments([
node.arguments.positional[1]
], types: [
InterfaceType(nativeFunctionClass, Nullability.legacy, [nativeSignature])
@ -425,11 +437,18 @@ class _FfiUseSiteTransformer extends FfiTransformer {
final Expression lookupResult = MethodInvocation(
node.arguments.positional[0],
Name("lookup"),
args,
lookupArgs,
libraryLookupMethod);
return StaticInvocation(asFunctionInternal,
Arguments([lookupResult], types: [dartSignature, nativeSignature]));
bool isLeaf = _getIsLeafBoolean(node);
if (isLeaf == null) {
isLeaf = false;
}
return StaticInvocation(
asFunctionInternal,
Arguments([lookupResult, BoolLiteral(isLeaf)],
types: [dartSignature, nativeSignature]));
}
// We need to rewrite calls to 'fromFunction' into two calls, representing the
@ -818,6 +837,72 @@ class _FfiUseSiteTransformer extends FfiTransformer {
throw _FfiStaticTypeError();
}
}
// Returns
// - `true` if leaf
// - `false` if not leaf
// - `null` if the expression is not valid (e.g. non-const bool, null)
bool _getIsLeafBoolean(StaticInvocation node) {
for (final named in node.arguments.named) {
if (named.name == 'isLeaf') {
final expr = named.value;
if (expr is BoolLiteral) {
return expr.value;
} else if (expr is ConstantExpression) {
final constant = expr.constant;
if (constant is BoolConstant) {
return constant.value;
}
}
// isLeaf is passed some invalid value.
return null;
}
}
// isLeaf defaults to false.
return false;
}
void _ensureIsLeafIsConst(StaticInvocation node) {
final isLeaf = _getIsLeafBoolean(node);
if (isLeaf == null) {
diagnosticReporter.report(
templateFfiExpectedConstantArg.withArguments('isLeaf'),
node.fileOffset,
1,
node.location.file);
// Throw so we don't get another error about not replacing
// `lookupFunction`, which will shadow the above error.
throw _FfiStaticTypeError();
}
}
void _ensureLeafCallDoesNotUseHandles(
InterfaceType nativeType, StaticInvocation node) {
// Handles are only disallowed for leaf calls.
final isLeaf = _getIsLeafBoolean(node);
if (isLeaf == null || isLeaf == false) {
return;
}
// Check if return type is Handle.
final functionType = nativeType.typeArguments[0];
if (functionType is FunctionType) {
final returnType = functionType.returnType;
if (returnType is InterfaceType) {
if (returnType.classNode == handleClass) {
diagnosticReporter.report(messageFfiLeafCallMustNotReturnHandle,
node.fileOffset, 1, node.location.file);
}
}
// Check if any of the argument types are Handle.
for (InterfaceType param in functionType.positionalParameters) {
if (param.classNode == handleClass) {
diagnosticReporter.report(messageFfiLeafCallMustNotTakeHandle,
node.fileOffset, 1, node.location.file);
}
}
}
}
}
/// Used internally for abnormal control flow to prevent cascading error

View file

@ -67,7 +67,7 @@ static method testLookupFunctionReturn() → void {
final ffi::DynamicLibrary* dylib = [@vm.inferred-type.metadata=dart.ffi::DynamicLibrary?] ffi::DynamicLibrary::executable();
final () →* self::Struct1* function1 = block {
_in::_nativeEffect(new self::Struct1::#fromTypedDataBase([@vm.inferred-type.metadata=dart.typed_data::_Uint8List] typ::Uint8List::•(#C18)));
} =>ffi::_asFunctionInternal<() →* self::Struct1*, () →* self::Struct1*>([@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup??] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] dylib.{ffi::DynamicLibrary::lookup}<ffi::NativeFunction<() →* self::Struct1*>*>("function1"));
} =>ffi::_asFunctionInternal<() →* self::Struct1*, () →* self::Struct1*>([@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup??] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] dylib.{ffi::DynamicLibrary::lookup}<ffi::NativeFunction<() →* self::Struct1*>*>("function1"), false);
final self::Struct1* struct1 = [@vm.call-site-attributes.metadata=receiverType:#lib::Struct1* Function()*] function1(){() →* self::Struct1*};
core::print(struct1);
}
@ -75,7 +75,7 @@ static method testAsFunctionReturn() → void {
final ffi::Pointer<ffi::NativeFunction<() →* self::Struct2*>*>* pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::Pointer::fromAddress<ffi::NativeFunction<() →* self::Struct2*>*>(3735928559);
final () →* self::Struct2* function2 = block {
_in::_nativeEffect(new self::Struct2::#fromTypedDataBase([@vm.inferred-type.metadata=dart.typed_data::_Uint8List] typ::Uint8List::•(#C18)));
} =>ffi::_asFunctionInternal<() →* self::Struct2*, () →* self::Struct2*>(pointer);
} =>ffi::_asFunctionInternal<() →* self::Struct2*, () →* self::Struct2*>(pointer, false);
final self::Struct2* struct2 = [@vm.call-site-attributes.metadata=receiverType:#lib::Struct2* Function()*] function2(){() →* self::Struct2*};
core::print(struct2);
}
@ -90,12 +90,12 @@ static method testFromFunctionArgument() → void {
}
static method testLookupFunctionArgument() → void {
final ffi::DynamicLibrary* dylib = [@vm.inferred-type.metadata=dart.ffi::DynamicLibrary?] ffi::DynamicLibrary::executable();
final (self::Struct5*) →* void function5 = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(self::Struct5*) →* void, (self::Struct5*) →* ffi::Void*>([@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup??] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] dylib.{ffi::DynamicLibrary::lookup}<ffi::NativeFunction<(self::Struct5*) →* ffi::Void*>*>("function5"));
final (self::Struct5*) →* void function5 = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(self::Struct5*) →* void, (self::Struct5*) →* ffi::Void*>([@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup??] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] dylib.{ffi::DynamicLibrary::lookup}<ffi::NativeFunction<(self::Struct5*) →* ffi::Void*>*>("function5"), false);
core::print(function5);
}
static method testAsFunctionArgument() → void {
final ffi::Pointer<ffi::NativeFunction<(self::Struct6*) →* ffi::Void*>*>* pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::Pointer::fromAddress<ffi::NativeFunction<(self::Struct6*) →* ffi::Void*>*>(3735928559);
final (self::Struct6*) →* void function6 = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(self::Struct6*) →* void, (self::Struct6*) →* ffi::Void*>(pointer);
final (self::Struct6*) →* void function6 = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(self::Struct6*) →* void, (self::Struct6*) →* ffi::Void*>(pointer, false);
core::print(function6);
}
static method returnStruct7() → self::Struct7* {

View file

@ -109,6 +109,13 @@ DART_EXPORT void Regress37069(uint64_t a,
Dart_ExecuteInternalCommand("gc-now", nullptr);
}
DART_EXPORT uint8_t IsThreadInGenerated() {
return Dart_ExecuteInternalCommand("is-thread-in-generated", nullptr) !=
nullptr
? 1
: 0;
}
#if !defined(HOST_OS_WINDOWS)
DART_EXPORT void* UnprotectCodeOtherThread(void* isolate,
std::condition_variable* var,
@ -276,6 +283,35 @@ DART_EXPORT intptr_t TestCallbackWrongIsolate(void (*fn)()) {
return ExpectAbort(fn);
}
DART_EXPORT intptr_t TestCallbackLeaf(void (*fn)()) {
#if defined(DEBUG)
// Calling a callback from a leaf call will crash on T->IsAtSafepoint().
return ExpectAbort(fn);
#else
// The above will only crash in debug as ASSERTS are disabled in all other
// build modes.
return 0;
#endif
}
void CallDebugName() {
Dart_DebugName();
}
DART_EXPORT intptr_t TestLeafCallApi(void (*fn)()) {
// This should be fine since it's a simple function that returns a const
// string. Though any API call should be considered unsafe from leaf calls.
Dart_VersionString();
#if defined(DEBUG)
// This will fail because it requires running in DARTSCOPE.
return ExpectAbort(&CallDebugName);
#else
// The above will only crash in debug as ASSERTS are disabled in all other
// build modes.
return 0;
#endif
}
#endif // defined(TARGET_OS_LINUX)
DART_EXPORT void IGH_MsanUnpoison(void* start, intptr_t length) {

View file

@ -62,7 +62,7 @@ DEFINE_NATIVE_ENTRY(Ffi_storePointer, 0, 3) {
}
// Static invocations to this method are translated directly in streaming FGB.
DEFINE_NATIVE_ENTRY(Ffi_asFunctionInternal, 2, 1) {
DEFINE_NATIVE_ENTRY(Ffi_asFunctionInternal, 2, 2) {
UNREACHABLE();
}

View file

@ -406,7 +406,7 @@ namespace dart {
V(Ffi_storePointer, 3) \
V(Ffi_address, 1) \
V(Ffi_fromAddress, 1) \
V(Ffi_asFunctionInternal, 1) \
V(Ffi_asFunctionInternal, 2) \
V(Ffi_nativeCallbackFunction, 2) \
V(Ffi_pointerFromFunction, 1) \
V(Ffi_dl_open, 1) \

View file

@ -6435,25 +6435,31 @@ Representation FfiCallInstr::RequiredInputRepresentation(intptr_t idx) const {
#define Z zone_
LocationSummary* FfiCallInstr::MakeLocationSummary(Zone* zone,
bool is_optimizing) const {
LocationSummary* FfiCallInstr::MakeLocationSummaryInternal(
Zone* zone,
bool is_optimizing,
const Register temp) const {
// The temporary register needs to be callee-saved and not an argument
// register.
ASSERT(((1 << CallingConventions::kFfiAnyNonAbiRegister) &
CallingConventions::kArgumentRegisters) == 0);
constexpr intptr_t kNumTemps = 2;
// TODO(dartbug.com/45468): Investigate whether we can avoid spilling
// registers across ffi leaf calls by not using `kCall` here.
LocationSummary* summary = new (zone) LocationSummary(
zone, /*num_inputs=*/InputCount(),
/*num_temps=*/temp == kNoRegister ? 2 : 3, LocationSummary::kCall);
LocationSummary* summary = new (zone)
LocationSummary(zone, /*num_inputs=*/InputCount(),
/*num_temps=*/kNumTemps, LocationSummary::kCall);
const Register temp0 = CallingConventions::kSecondNonArgumentRegister;
const Register temp1 = CallingConventions::kFfiAnyNonAbiRegister;
const Register temp0 = CallingConventions::kFfiAnyNonAbiRegister;
const Register temp1 = CallingConventions::kSecondNonArgumentRegister;
ASSERT(temp0 != temp1);
summary->set_temp(0, Location::RegisterLocation(temp0));
summary->set_temp(1, Location::RegisterLocation(temp1));
if (temp != kNoRegister) {
summary->set_temp(2, Location::RegisterLocation(temp));
}
summary->set_in(TargetAddressIndex(),
Location::RegisterLocation(
CallingConventions::kFirstNonArgumentRegister));
@ -6476,14 +6482,13 @@ LocationSummary* FfiCallInstr::MakeLocationSummary(Zone* zone,
return summary;
}
void FfiCallInstr::EmitParamMoves(FlowGraphCompiler* compiler) {
void FfiCallInstr::EmitParamMoves(FlowGraphCompiler* compiler,
const Register saved_fp,
const Register temp) {
if (compiler::Assembler::EmittingComments()) {
__ Comment("EmitParamMoves");
}
const Register saved_fp = locs()->temp(0).reg();
const Register temp = locs()->temp(1).reg();
// Moves for return pointer.
const auto& return_location =
marshaller_.Location(compiler::ffi::kResultIndex);
@ -6591,7 +6596,9 @@ void FfiCallInstr::EmitParamMoves(FlowGraphCompiler* compiler) {
}
}
void FfiCallInstr::EmitReturnMoves(FlowGraphCompiler* compiler) {
void FfiCallInstr::EmitReturnMoves(FlowGraphCompiler* compiler,
const Register temp0,
const Register temp1) {
__ Comment("EmitReturnMoves");
const auto& returnLocation =
@ -6611,16 +6618,17 @@ void FfiCallInstr::EmitReturnMoves(FlowGraphCompiler* compiler) {
ASSERT(returnLocation.payload_type().IsCompound());
ASSERT(marshaller_.PassTypedData());
const Register temp0 = TMP != kNoRegister ? TMP : locs()->temp(0).reg();
const Register temp1 = locs()->temp(1).reg();
ASSERT(temp0 != temp1);
// Get the typed data pointer which we have pinned to a stack slot.
const Location typed_data_loc = locs()->in(TypedDataIndex());
ASSERT(typed_data_loc.IsStackSlot());
ASSERT(typed_data_loc.base_reg() == FPREG);
__ LoadMemoryValue(temp0, FPREG, 0);
__ LoadMemoryValue(temp0, temp0, typed_data_loc.ToStackSlotOffset());
// If this is a leaf call there is no extra call frame to step through.
if (is_leaf_) {
__ LoadMemoryValue(temp0, FPREG, typed_data_loc.ToStackSlotOffset());
} else {
__ LoadMemoryValue(temp0, FPREG, 0);
__ LoadMemoryValue(temp0, temp0, typed_data_loc.ToStackSlotOffset());
}
__ LoadField(
temp0,
compiler::FieldAddress(

View file

@ -5106,12 +5106,14 @@ class FfiCallInstr : public Definition {
public:
FfiCallInstr(Zone* zone,
intptr_t deopt_id,
const compiler::ffi::CallMarshaller& marshaller)
const compiler::ffi::CallMarshaller& marshaller,
bool is_leaf)
: Definition(deopt_id),
zone_(zone),
marshaller_(marshaller),
inputs_(marshaller.NumDefinitions() + 1 +
(marshaller.PassTypedData() ? 1 : 0)) {
(marshaller.PassTypedData() ? 1 : 0)),
is_leaf_(is_leaf) {
inputs_.FillWith(
nullptr, 0,
marshaller.NumDefinitions() + 1 + (marshaller.PassTypedData() ? 1 : 0));
@ -5162,14 +5164,27 @@ class FfiCallInstr : public Definition {
private:
virtual void RawSetInputAt(intptr_t i, Value* value) { inputs_[i] = value; }
void EmitParamMoves(FlowGraphCompiler* compiler);
void EmitReturnMoves(FlowGraphCompiler* compiler);
LocationSummary* MakeLocationSummaryInternal(Zone* zone,
bool is_optimizing,
const Register temp) const;
// Clobbers both given registers.
// `saved_fp` is used as the frame base to rebase off of.
void EmitParamMoves(FlowGraphCompiler* compiler,
const Register saved_fp,
const Register temp);
// Clobbers both given temp registers.
void EmitReturnMoves(FlowGraphCompiler* compiler,
const Register temp0,
const Register temp1);
Zone* const zone_;
const compiler::ffi::CallMarshaller& marshaller_;
GrowableArray<Value*> inputs_;
bool is_leaf_;
DISALLOW_COPY_AND_ASSIGN(FfiCallInstr);
};

View file

@ -1402,85 +1402,114 @@ void NativeCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ Drop(ArgumentCount()); // Drop the arguments.
}
LocationSummary* FfiCallInstr::MakeLocationSummary(Zone* zone,
bool is_optimizing) const {
return MakeLocationSummaryInternal(zone, is_optimizing, R0);
}
void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
const Register saved_fp = locs()->temp(0).reg();
const Register temp = locs()->temp(1).reg();
// For regular calls, this holds the FP for rebasing the original locations
// during EmitParamMoves.
// For leaf calls, this holds the SP used to restore the pre-aligned SP after
// the call.
const Register saved_fp_or_sp = locs()->temp(0).reg();
RELEASE_ASSERT((CallingConventions::kCalleeSaveCpuRegisters &
(1 << saved_fp_or_sp)) != 0);
const Register temp1 = locs()->temp(1).reg();
const Register temp2 = locs()->temp(2).reg();
const Register branch = locs()->in(TargetAddressIndex()).reg();
// Save frame pointer because we're going to update it when we enter the exit
// frame.
__ mov(saved_fp, compiler::Operand(FPREG));
// Ensure these are callee-saved register and are preserved across the call.
ASSERT((CallingConventions::kCalleeSaveCpuRegisters &
(1 << saved_fp_or_sp)) != 0);
// temp doesn't need to be preserved.
// Make a space to put the return address.
__ PushImmediate(0);
__ mov(saved_fp_or_sp,
is_leaf_ ? compiler::Operand(SPREG) : compiler::Operand(FPREG));
// We need to create a dummy "exit frame". It will have a null code object.
__ LoadObject(CODE_REG, Object::null_object());
__ set_constant_pool_allowed(false);
__ EnterDartFrame(0, /*load_pool_pointer=*/false);
if (!is_leaf_) {
// Make a space to put the return address.
__ PushImmediate(0);
// Reserve space for arguments and align frame before entering C++ world.
// We need to create a dummy "exit frame". It will have a null code object.
__ LoadObject(CODE_REG, Object::null_object());
__ set_constant_pool_allowed(false);
__ EnterDartFrame(0, /*load_pool_pointer=*/false);
}
// Reserve space for the arguments that go on the stack (if any), then align.
__ ReserveAlignedFrameSpace(marshaller_.RequiredStackSpaceInBytes());
EmitParamMoves(compiler);
EmitParamMoves(compiler, is_leaf_ ? FPREG : saved_fp_or_sp, temp1);
if (compiler::Assembler::EmittingComments()) {
__ Comment("Call");
__ Comment(is_leaf_ ? "Leaf Call" : "Call");
}
// We need to copy the return address up into the dummy stack frame so the
// stack walker will know which safepoint to use.
__ mov(TMP, compiler::Operand(PC));
__ str(TMP, compiler::Address(FPREG, kSavedCallerPcSlotFromFp *
compiler::target::kWordSize));
// For historical reasons, the PC on ARM points 8 bytes past the current
// instruction. Therefore we emit the metadata here, 8 bytes (2 instructions)
// after the original mov.
compiler->EmitCallsiteMetadata(InstructionSource(), deopt_id(),
UntaggedPcDescriptors::Kind::kOther, locs(),
env());
// Update information in the thread object and enter a safepoint.
if (CanExecuteGeneratedCodeInSafepoint()) {
__ LoadImmediate(temp, compiler::target::Thread::exit_through_ffi());
__ TransitionGeneratedToNative(branch, FPREG, temp, saved_fp,
/*enter_safepoint=*/true);
if (is_leaf_) {
__ blx(branch);
// Update information in the thread object and leave the safepoint.
__ TransitionNativeToGenerated(saved_fp, temp, /*leave_safepoint=*/true);
} else {
// We cannot trust that this code will be executable within a safepoint.
// Therefore we delegate the responsibility of entering/exiting the
// safepoint to a stub which in the VM isolate's heap, which will never lose
// execute permission.
__ ldr(TMP,
compiler::Address(
THR, compiler::target::Thread::
call_native_through_safepoint_entry_point_offset()));
// We need to copy the return address up into the dummy stack frame so the
// stack walker will know which safepoint to use.
__ mov(temp1, compiler::Operand(PC));
__ str(temp1, compiler::Address(FPREG, kSavedCallerPcSlotFromFp *
compiler::target::kWordSize));
// Calls R8 in a safepoint and clobbers R4 and NOTFP.
ASSERT(branch == R8 && temp == R4);
static_assert((kReservedCpuRegisters & (1 << NOTFP)) != 0,
"NOTFP should be a reserved register");
__ blx(TMP);
// For historical reasons, the PC on ARM points 8 bytes past the current
// instruction. Therefore we emit the metadata here, 8 bytes
// (2 instructions) after the original mov.
compiler->EmitCallsiteMetadata(InstructionSource(), deopt_id(),
UntaggedPcDescriptors::Kind::kOther, locs(),
env());
// Update information in the thread object and enter a safepoint.
if (CanExecuteGeneratedCodeInSafepoint()) {
__ LoadImmediate(temp1, compiler::target::Thread::exit_through_ffi());
__ TransitionGeneratedToNative(branch, FPREG, temp1, saved_fp_or_sp,
/*enter_safepoint=*/true);
__ blx(branch);
// Update information in the thread object and leave the safepoint.
__ TransitionNativeToGenerated(saved_fp_or_sp, temp1,
/*leave_safepoint=*/true);
} else {
// We cannot trust that this code will be executable within a safepoint.
// Therefore we delegate the responsibility of entering/exiting the
// safepoint to a stub which in the VM isolate's heap, which will never
// lose execute permission.
__ ldr(temp1,
compiler::Address(
THR, compiler::target::Thread::
call_native_through_safepoint_entry_point_offset()));
// Calls R8 in a safepoint and clobbers R4 and NOTFP.
ASSERT(branch == R8);
static_assert((kReservedCpuRegisters & (1 << NOTFP)) != 0,
"NOTFP should be a reserved register");
__ blx(temp1);
}
// Restore the global object pool after returning from runtime (old space is
// moving, so the GOP could have been relocated).
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
__ SetupGlobalPoolAndDispatchTable();
}
}
// Restore the global object pool after returning from runtime (old space is
// moving, so the GOP could have been relocated).
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
__ SetupGlobalPoolAndDispatchTable();
EmitReturnMoves(compiler, temp1, temp2);
if (is_leaf_) {
// Restore the pre-aligned SP.
__ mov(SPREG, compiler::Operand(saved_fp_or_sp));
} else {
// Leave dummy exit frame.
__ LeaveDartFrame();
__ set_constant_pool_allowed(true);
// Instead of returning to the "fake" return address, we just pop it.
__ PopRegister(temp1);
}
EmitReturnMoves(compiler);
// Leave dummy exit frame.
__ LeaveDartFrame();
__ set_constant_pool_allowed(true);
// Instead of returning to the "fake" return address, we just pop it.
__ PopRegister(TMP);
}
// Keep in sync with NativeEntryInstr::EmitNativeCode.

View file

@ -1228,46 +1228,48 @@ void NativeCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ Drop(ArgumentCount()); // Drop the arguments.
}
LocationSummary* FfiCallInstr::MakeLocationSummary(Zone* zone,
bool is_optimizing) const {
return MakeLocationSummaryInternal(zone, is_optimizing, R11);
}
void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
const Register saved_fp = locs()->temp(0).reg();
const Register temp = locs()->temp(1).reg();
// For regular calls, this holds the FP for rebasing the original locations
// during EmitParamMoves.
// For leaf calls, this holds the SP used to restore the pre-aligned SP after
// the call.
const Register saved_fp_or_sp = locs()->temp(0).reg();
RELEASE_ASSERT((CallingConventions::kCalleeSaveCpuRegisters &
(1 << saved_fp_or_sp)) != 0);
const Register temp1 = locs()->temp(1).reg();
const Register temp2 = locs()->temp(2).reg();
const Register branch = locs()->in(TargetAddressIndex()).reg();
// Save frame pointer because we're going to update it when we enter the exit
// frame.
__ mov(saved_fp, FPREG);
// Ensure these are callee-saved register and are preserved across the call.
ASSERT((CallingConventions::kCalleeSaveCpuRegisters &
(1 << saved_fp_or_sp)) != 0);
// temps don't need to be preserved.
// We need to create a dummy "exit frame". It will share the same pool pointer
// but have a null code object.
__ LoadObject(CODE_REG, Object::null_object());
__ set_constant_pool_allowed(false);
__ EnterDartFrame(0, PP);
__ mov(saved_fp_or_sp, is_leaf_ ? SPREG : FPREG);
// Make space for arguments and align the frame.
if (!is_leaf_) {
// We need to create a dummy "exit frame". It will share the same pool
// pointer but have a null code object.
__ LoadObject(CODE_REG, Object::null_object());
__ set_constant_pool_allowed(false);
__ EnterDartFrame(0, PP);
}
// Reserve space for the arguments that go on the stack (if any), then align.
__ ReserveAlignedFrameSpace(marshaller_.RequiredStackSpaceInBytes());
EmitParamMoves(compiler);
EmitParamMoves(compiler, is_leaf_ ? FPREG : saved_fp_or_sp, temp1);
if (compiler::Assembler::EmittingComments()) {
__ Comment("Call");
__ Comment(is_leaf_ ? "Leaf Call" : "Call");
}
// We need to copy a dummy return address up into the dummy stack frame so the
// stack walker will know which safepoint to use.
//
// ADR loads relative to itself, so add kInstrSize to point to the next
// instruction.
__ adr(temp, compiler::Immediate(Instr::kInstrSize));
compiler->EmitCallsiteMetadata(
source(), deopt_id(), UntaggedPcDescriptors::Kind::kOther, locs(), env());
__ StoreToOffset(temp, FPREG, kSavedCallerPcSlotFromFp * kWordSize);
if (CanExecuteGeneratedCodeInSafepoint()) {
// Update information in the thread object and enter a safepoint.
__ LoadImmediate(temp, compiler::target::Thread::exit_through_ffi());
__ TransitionGeneratedToNative(branch, FPREG, temp,
/*enter_safepoint=*/true);
if (is_leaf_) {
// We are entering runtime code, so the C stack pointer must be restored
// from the stack limit to the top of the stack.
__ mov(R25, CSP);
@ -1278,39 +1280,75 @@ void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
// Restore the Dart stack pointer.
__ mov(SP, CSP);
__ mov(CSP, R25);
// Update information in the thread object and leave the safepoint.
__ TransitionNativeToGenerated(temp, /*leave_safepoint=*/true);
} else {
// We cannot trust that this code will be executable within a safepoint.
// Therefore we delegate the responsibility of entering/exiting the
// safepoint to a stub which in the VM isolate's heap, which will never lose
// execute permission.
__ ldr(TMP,
compiler::Address(
THR, compiler::target::Thread::
call_native_through_safepoint_entry_point_offset()));
// We need to copy a dummy return address up into the dummy stack frame so
// the stack walker will know which safepoint to use.
//
// ADR loads relative to itself, so add kInstrSize to point to the next
// instruction.
__ adr(temp1, compiler::Immediate(Instr::kInstrSize));
compiler->EmitCallsiteMetadata(source(), deopt_id(),
UntaggedPcDescriptors::Kind::kOther, locs(),
env());
// Calls R9 and clobbers R19 (along with volatile registers).
ASSERT(branch == R9 && temp == R19);
__ blr(TMP);
__ StoreToOffset(temp1, FPREG, kSavedCallerPcSlotFromFp * kWordSize);
if (CanExecuteGeneratedCodeInSafepoint()) {
// Update information in the thread object and enter a safepoint.
__ LoadImmediate(temp1, compiler::target::Thread::exit_through_ffi());
__ TransitionGeneratedToNative(branch, FPREG, temp1,
/*enter_safepoint=*/true);
// We are entering runtime code, so the C stack pointer must be restored
// from the stack limit to the top of the stack.
__ mov(R25, CSP);
__ mov(CSP, SP);
__ blr(branch);
// Restore the Dart stack pointer.
__ mov(SP, CSP);
__ mov(CSP, R25);
// Update information in the thread object and leave the safepoint.
__ TransitionNativeToGenerated(temp1, /*leave_safepoint=*/true);
} else {
// We cannot trust that this code will be executable within a safepoint.
// Therefore we delegate the responsibility of entering/exiting the
// safepoint to a stub which in the VM isolate's heap, which will never
// lose execute permission.
__ ldr(temp1,
compiler::Address(
THR, compiler::target::Thread::
call_native_through_safepoint_entry_point_offset()));
// Calls R9 and clobbers R19 (along with volatile registers).
ASSERT(branch == R9);
__ blr(temp1);
}
// Refresh pinned registers values (inc. write barrier mask and null
// object).
__ RestorePinnedRegisters();
}
// Refresh pinned registers values (inc. write barrier mask and null object).
__ RestorePinnedRegisters();
EmitReturnMoves(compiler, temp1, temp2);
EmitReturnMoves(compiler);
if (is_leaf_) {
// Restore the pre-aligned SP.
__ mov(SPREG, saved_fp_or_sp);
} else {
// Although PP is a callee-saved register, it may have been moved by the GC.
__ LeaveDartFrame(compiler::kRestoreCallerPP);
// Although PP is a callee-saved register, it may have been moved by the GC.
__ LeaveDartFrame(compiler::kRestoreCallerPP);
// Restore the global object pool after returning from runtime (old space is
// moving, so the GOP could have been relocated).
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
__ SetupGlobalPoolAndDispatchTable();
}
// Restore the global object pool after returning from runtime (old space is
// moving, so the GOP could have been relocated).
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
__ SetupGlobalPoolAndDispatchTable();
__ set_constant_pool_allowed(true);
}
__ set_constant_pool_allowed(true);
}
// Keep in sync with NativeEntryInstr::EmitNativeCode.

View file

@ -999,57 +999,88 @@ void NativeCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ Drop(ArgumentCount()); // Drop the arguments.
}
LocationSummary* FfiCallInstr::MakeLocationSummary(Zone* zone,
bool is_optimizing) const {
return MakeLocationSummaryInternal(zone, is_optimizing, kNoRegister);
}
void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
const Register saved_fp = locs()->temp(0).reg();
// For regular calls, this holds the FP for rebasing the original locations
// during EmitParamMoves.
// For leaf calls, this holds the SP used to restore the pre-aligned SP after
// the call.
const Register saved_fp_or_sp = locs()->temp(0).reg();
const Register temp = locs()->temp(1).reg();
const Register branch = locs()->in(TargetAddressIndex()).reg();
// Save frame pointer because we're going to update it when we enter the exit
// frame.
__ movl(saved_fp, FPREG);
// Ensure these are callee-saved register and are preserved across the call.
ASSERT((CallingConventions::kCalleeSaveCpuRegisters &
(1 << saved_fp_or_sp)) != 0);
// temp doesn't need to be preserved.
// Make a space to put the return address.
__ pushl(compiler::Immediate(0));
__ movl(saved_fp_or_sp, is_leaf_ ? SPREG : FPREG);
// We need to create a dummy "exit frame". It will have a null code object.
__ LoadObject(CODE_REG, Object::null_object());
__ EnterDartFrame(marshaller_.RequiredStackSpaceInBytes());
intptr_t stack_required = marshaller_.RequiredStackSpaceInBytes();
// Align frame before entering C++ world.
if (OS::ActivationFrameAlignment() > 1) {
__ andl(SPREG, compiler::Immediate(~(OS::ActivationFrameAlignment() - 1)));
if (is_leaf_) {
// For leaf calls we need to leave space at the bottom for the pre-align SP.
stack_required += compiler::target::kWordSize;
} else {
// Make a space to put the return address.
__ pushl(compiler::Immediate(0));
// We need to create a dummy "exit frame". It will have a null code object.
__ LoadObject(CODE_REG, Object::null_object());
__ EnterDartFrame(0);
}
EmitParamMoves(compiler);
// Reserve space for the arguments that go on the stack (if any), then align.
__ ReserveAlignedFrameSpace(stack_required);
EmitParamMoves(compiler, is_leaf_ ? FPREG : saved_fp_or_sp, temp);
if (is_leaf_) {
// We store the pre-align SP at a fixed offset from the final SP.
// Pushing before alignment would mean its placement would vary with how
// much the frame was unaligned.
__ movl(compiler::Address(SPREG, marshaller_.RequiredStackSpaceInBytes()),
saved_fp_or_sp);
}
if (compiler::Assembler::EmittingComments()) {
__ Comment("Call");
__ Comment(is_leaf_ ? "Leaf Call" : "Call");
}
// We need to copy a dummy return address up into the dummy stack frame so the
// stack walker will know which safepoint to use. Unlike X64, there's no
// PC-relative 'leaq' available, so we have do a trick with 'call'.
compiler::Label get_pc;
__ call(&get_pc);
compiler->EmitCallsiteMetadata(InstructionSource(), deopt_id(),
UntaggedPcDescriptors::Kind::kOther, locs(),
env());
__ Bind(&get_pc);
__ popl(temp);
__ movl(compiler::Address(FPREG, kSavedCallerPcSlotFromFp * kWordSize), temp);
ASSERT(!CanExecuteGeneratedCodeInSafepoint());
// We cannot trust that this code will be executable within a safepoint.
// Therefore we delegate the responsibility of entering/exiting the
// safepoint to a stub which in the VM isolate's heap, which will never lose
// execute permission.
__ movl(temp,
compiler::Address(
THR, compiler::target::Thread::
call_native_through_safepoint_entry_point_offset()));
if (is_leaf_) {
__ call(branch);
} else {
// We need to copy a dummy return address up into the dummy stack frame so
// the stack walker will know which safepoint to use. Unlike X64, there's no
// PC-relative 'leaq' available, so we have do a trick with 'call'.
compiler::Label get_pc;
__ call(&get_pc);
compiler->EmitCallsiteMetadata(InstructionSource(), deopt_id(),
UntaggedPcDescriptors::Kind::kOther, locs(),
env());
__ Bind(&get_pc);
__ popl(temp);
__ movl(compiler::Address(FPREG, kSavedCallerPcSlotFromFp * kWordSize),
temp);
// Calls EAX within a safepoint and clobbers EBX.
ASSERT(temp == EBX && branch == EAX);
__ call(temp);
ASSERT(!CanExecuteGeneratedCodeInSafepoint());
// We cannot trust that this code will be executable within a safepoint.
// Therefore we delegate the responsibility of entering/exiting the
// safepoint to a stub which in the VM isolate's heap, which will never lose
// execute permission.
__ movl(temp,
compiler::Address(
THR, compiler::target::Thread::
call_native_through_safepoint_entry_point_offset()));
// Calls EAX within a safepoint and clobbers EBX.
ASSERT(branch == EAX);
__ call(temp);
}
// Restore the stack when a struct by value is returned into memory pointed
// to by a pointer that is passed into the function.
@ -1061,10 +1092,10 @@ void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ subl(SPREG, compiler::Immediate(compiler::target::kWordSize));
}
// The x86 calling convention requires floating point values to be returned on
// the "floating-point stack" (aka. register ST0). We don't use the
// floating-point stack in Dart, so we need to move the return value back into
// an XMM register.
// The x86 calling convention requires floating point values to be returned
// on the "floating-point stack" (aka. register ST0). We don't use the
// floating-point stack in Dart, so we need to move the return value back
// into an XMM register.
if (representation() == kUnboxedDouble) {
__ fstpl(compiler::Address(SPREG, -kDoubleSize));
__ movsd(XMM0, compiler::Address(SPREG, -kDoubleSize));
@ -1073,13 +1104,20 @@ void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ movss(XMM0, compiler::Address(SPREG, -kFloatSize));
}
EmitReturnMoves(compiler);
// Pass both registers for use as clobbered temp registers.
EmitReturnMoves(compiler, saved_fp_or_sp, temp);
// Leave dummy exit frame.
__ LeaveFrame();
if (is_leaf_) {
// Restore pre-align SP. Was stored right before the first stack argument.
__ movl(SPREG,
compiler::Address(SPREG, marshaller_.RequiredStackSpaceInBytes()));
} else {
// Leave dummy exit frame.
__ LeaveFrame();
// Instead of returning to the "fake" return address, we just pop it.
__ popl(temp);
// Instead of returning to the "fake" return address, we just pop it.
__ popl(temp);
}
}
// Keep in sync with NativeReturnInstr::EmitNativeCode.

View file

@ -1188,82 +1188,118 @@ void NativeCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ Drop(ArgumentCount()); // Drop the arguments.
}
LocationSummary* FfiCallInstr::MakeLocationSummary(Zone* zone,
bool is_optimizing) const {
// Use R10 as a temp register. We can't use RDI, RSI, RDX, R8, R9 as they are
// arg registers, and R11 is TMP.
return MakeLocationSummaryInternal(zone, is_optimizing, R10);
}
void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
const Register saved_fp = locs()->temp(0).reg();
// For leaf calls, this holds the SP used to restore the pre-aligned SP after
// the call.
// Note: R12 doubles as CODE_REG, which gets clobbered during frame setup in
// regular calls.
const Register saved_sp = locs()->temp(0).reg();
// For regular calls, this holds the FP for rebasing the original locations
// during EmitParamMoves.
const Register saved_fp = locs()->temp(1).reg();
const Register temp = locs()->temp(2).reg();
const Register target_address = locs()->in(TargetAddressIndex()).reg();
// Save frame pointer because we're going to update it when we enter the exit
// frame.
__ movq(saved_fp, FPREG);
// Ensure these are callee-saved register and are preserved across the call.
ASSERT((CallingConventions::kCalleeSaveCpuRegisters & (1 << saved_sp)) != 0);
ASSERT((CallingConventions::kCalleeSaveCpuRegisters & (1 << saved_fp)) != 0);
// temp doesn't need to be preserved.
// Make a space to put the return address.
__ pushq(compiler::Immediate(0));
// We need to create a dummy "exit frame". It will share the same pool pointer
// but have a null code object.
__ LoadObject(CODE_REG, Object::null_object());
__ set_constant_pool_allowed(false);
__ EnterDartFrame(marshaller_.RequiredStackSpaceInBytes(), PP);
// Align frame before entering C++ world.
if (OS::ActivationFrameAlignment() > 1) {
__ andq(SPREG, compiler::Immediate(~(OS::ActivationFrameAlignment() - 1)));
}
EmitParamMoves(compiler);
// We need to copy a dummy return address up into the dummy stack frame so the
// stack walker will know which safepoint to use. RIP points to the *next*
// instruction, so 'AddressRIPRelative' loads the address of the following
// 'movq'.
__ leaq(TMP, compiler::Address::AddressRIPRelative(0));
compiler->EmitCallsiteMetadata(InstructionSource(), deopt_id(),
UntaggedPcDescriptors::Kind::kOther, locs(),
env());
__ movq(compiler::Address(FPREG, kSavedCallerPcSlotFromFp * kWordSize), TMP);
if (CanExecuteGeneratedCodeInSafepoint()) {
// Update information in the thread object and enter a safepoint.
__ movq(TMP,
compiler::Immediate(compiler::target::Thread::exit_through_ffi()));
__ TransitionGeneratedToNative(target_address, FPREG, TMP,
/*enter_safepoint=*/true);
__ CallCFunction(target_address, /*restore_rsp=*/true);
// Update information in the thread object and leave the safepoint.
__ TransitionNativeToGenerated(/*leave_safepoint=*/true);
if (is_leaf_) {
__ movq(saved_sp, SPREG);
} else {
// We cannot trust that this code will be executable within a safepoint.
// Therefore we delegate the responsibility of entering/exiting the
// safepoint to a stub which in the VM isolate's heap, which will never lose
// execute permission.
__ movq(TMP,
compiler::Address(
THR, compiler::target::Thread::
call_native_through_safepoint_entry_point_offset()));
__ movq(saved_fp, FPREG);
// Make a space to put the return address.
__ pushq(compiler::Immediate(0));
// Calls RBX within a safepoint.
ASSERT(saved_fp == RBX);
__ movq(RBX, target_address);
__ call(TMP);
// We need to create a dummy "exit frame". It will share the same pool
// pointer but have a null code object.
__ LoadObject(CODE_REG, Code::null_object());
__ set_constant_pool_allowed(false);
__ EnterDartFrame(0, PP);
}
EmitReturnMoves(compiler);
// Reserve space for the arguments that go on the stack (if any), then align.
__ ReserveAlignedFrameSpace(marshaller_.RequiredStackSpaceInBytes());
// Although PP is a callee-saved register, it may have been moved by the GC.
__ LeaveDartFrame(compiler::kRestoreCallerPP);
// Restore the global object pool after returning from runtime (old space is
// moving, so the GOP could have been relocated).
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
__ movq(PP, compiler::Address(THR, Thread::global_object_pool_offset()));
if (is_leaf_) {
EmitParamMoves(compiler, FPREG, saved_fp);
} else {
EmitParamMoves(compiler, saved_fp, saved_sp);
}
__ set_constant_pool_allowed(true);
if (compiler::Assembler::EmittingComments()) {
__ Comment(is_leaf_ ? "Leaf Call" : "Call");
}
// Instead of returning to the "fake" return address, we just pop it.
__ popq(TMP);
if (is_leaf_) {
__ CallCFunction(target_address, /*restore_rsp=*/true);
} else {
// We need to copy a dummy return address up into the dummy stack frame so
// the stack walker will know which safepoint to use. RIP points to the
// *next* instruction, so 'AddressRIPRelative' loads the address of the
// following 'movq'.
__ leaq(temp, compiler::Address::AddressRIPRelative(0));
compiler->EmitCallsiteMetadata(InstructionSource(), deopt_id(),
UntaggedPcDescriptors::Kind::kOther, locs(),
env());
__ movq(compiler::Address(FPREG, kSavedCallerPcSlotFromFp * kWordSize),
temp);
if (CanExecuteGeneratedCodeInSafepoint()) {
// Update information in the thread object and enter a safepoint.
__ movq(temp, compiler::Immediate(
compiler::target::Thread::exit_through_ffi()));
__ TransitionGeneratedToNative(target_address, FPREG, temp,
/*enter_safepoint=*/true);
__ CallCFunction(target_address, /*restore_rsp=*/true);
// Update information in the thread object and leave the safepoint.
__ TransitionNativeToGenerated(/*leave_safepoint=*/true);
} else {
// We cannot trust that this code will be executable within a safepoint.
// Therefore we delegate the responsibility of entering/exiting the
// safepoint to a stub which is in the VM isolate's heap, which will never
// lose execute permission.
__ movq(temp,
compiler::Address(
THR, compiler::target::Thread::
call_native_through_safepoint_entry_point_offset()));
// Calls RBX within a safepoint. RBX and R12 are clobbered.
__ movq(RBX, target_address);
__ call(temp);
}
}
// Pass the `saved_fp` reg. as a temp to clobber since we're done with it.
EmitReturnMoves(compiler, temp, saved_fp);
if (is_leaf_) {
// Restore the pre-aligned SP.
__ movq(SPREG, saved_sp);
} else {
// Although PP is a callee-saved register, it may have been moved by the GC.
__ LeaveDartFrame(compiler::kRestoreCallerPP);
// Restore the global object pool after returning from runtime (old space is
// moving, so the GOP could have been relocated).
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
__ movq(PP, compiler::Address(THR, Thread::global_object_pool_offset()));
}
__ set_constant_pool_allowed(true);
// Instead of returning to the "fake" return address, we just pop it.
__ popq(temp);
}
}
// Keep in sync with NativeReturnInstr::EmitNativeCode.

View file

@ -15,7 +15,8 @@ namespace ffi {
// TODO(dartbug.com/36607): Cache the trampolines.
FunctionPtr TrampolineFunction(const FunctionType& dart_signature,
const FunctionType& c_signature) {
const FunctionType& c_signature,
bool is_leaf) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
String& name = String::Handle(zone, Symbols::New(thread, "FfiTrampoline"));
@ -54,6 +55,8 @@ FunctionPtr TrampolineFunction(const FunctionType& dart_signature,
signature ^= ClassFinalizer::FinalizeType(signature);
function.set_signature(signature);
function.SetFfiIsLeaf(is_leaf);
return function.ptr();
}

View file

@ -20,7 +20,8 @@ namespace compiler {
namespace ffi {
FunctionPtr TrampolineFunction(const FunctionType& dart_signature,
const FunctionType& c_signature);
const FunctionType& c_signature,
bool is_leaf);
} // namespace ffi

View file

@ -980,7 +980,8 @@ Fragment BaseFlowGraphBuilder::Box(Representation from) {
}
Fragment BaseFlowGraphBuilder::BuildFfiAsFunctionInternalCall(
const TypeArguments& signatures) {
const TypeArguments& signatures,
bool is_leaf) {
ASSERT(signatures.IsInstantiated());
ASSERT(signatures.Length() == 2);
@ -990,7 +991,8 @@ Fragment BaseFlowGraphBuilder::BuildFfiAsFunctionInternalCall(
ASSERT(dart_type.IsFunctionType() && native_type.IsFunctionType());
const Function& target =
Function::ZoneHandle(compiler::ffi::TrampolineFunction(
FunctionType::Cast(dart_type), FunctionType::Cast(native_type)));
FunctionType::Cast(dart_type), FunctionType::Cast(native_type),
is_leaf));
Fragment code;
// Store the pointer in the context, we cannot load the untagged address

View file

@ -362,7 +362,8 @@ class BaseFlowGraphBuilder {
// Builds the graph for an invocation of '_asFunctionInternal'.
//
// 'signatures' contains the pair [<dart signature>, <native signature>].
Fragment BuildFfiAsFunctionInternalCall(const TypeArguments& signatures);
Fragment BuildFfiAsFunctionInternalCall(const TypeArguments& signatures,
bool is_leaf);
Fragment AllocateObject(TokenPosition position,
const Class& klass,

View file

@ -5667,20 +5667,32 @@ Fragment StreamingFlowGraphBuilder::BuildNativeEffect() {
Fragment StreamingFlowGraphBuilder::BuildFfiAsFunctionInternal() {
const intptr_t argc = ReadUInt(); // Read argument count.
ASSERT(argc == 1); // Pointer.
ASSERT(argc == 2); // Pointer, isLeaf.
const intptr_t list_length = ReadListLength(); // Read types list length.
ASSERT(list_length == 2); // Dart signature, then native signature.
const TypeArguments& type_arguments =
T.BuildTypeArguments(list_length); // Read types.
ASSERT(list_length == 2); // Dart signature, then native signature
// Read types.
const TypeArguments& type_arguments = T.BuildTypeArguments(list_length);
Fragment code;
const intptr_t positional_count =
ReadListLength(); // Read positional argument count.
ASSERT(positional_count == 1);
// Read positional argument count.
const intptr_t positional_count = ReadListLength();
ASSERT(positional_count == 2);
code += BuildExpression(); // Build first positional argument (pointer).
const intptr_t named_args_len =
ReadListLength(); // Skip empty named arguments list.
// The second argument, `isLeaf`, is only used internally and dictates whether
// we can do a lightweight leaf function call.
bool is_leaf = false;
Fragment frag = BuildExpression();
ASSERT(frag.entry->IsConstant());
if (frag.entry->AsConstant()->value().ptr() == Object::bool_true().ptr()) {
is_leaf = true;
}
Pop();
// Skip (empty) named arguments list.
const intptr_t named_args_len = ReadListLength();
ASSERT(named_args_len == 0);
code += B->BuildFfiAsFunctionInternalCall(type_arguments);
code += B->BuildFfiAsFunctionInternalCall(type_arguments, is_leaf);
return code;
}

View file

@ -384,7 +384,8 @@ Fragment FlowGraphBuilder::FfiCall(
Fragment body;
FfiCallInstr* const call =
new (Z) FfiCallInstr(Z, GetNextDeoptId(), marshaller);
new (Z) FfiCallInstr(Z, GetNextDeoptId(), marshaller,
parsed_function_->function().FfiIsLeaf());
for (intptr_t i = call->InputCount() - 1; i >= 0; --i) {
call->SetInputAt(i, Pop());

View file

@ -164,7 +164,7 @@ namespace dart {
V(_WeakProperty, set:value, WeakProperty_setValue, 0x8b2bafab) \
V(::, _classRangeCheck, ClassRangeCheck, 0x00269620) \
V(::, _abi, FfiAbi, 0x7c4ab775) \
V(::, _asFunctionInternal, FfiAsFunctionInternal, 0xbbcb235a) \
V(::, _asFunctionInternal, FfiAsFunctionInternal, 0x92ae104f) \
V(::, _nativeCallbackFunction, FfiNativeCallbackFunction, 0x3ff5ae9c) \
V(::, _nativeEffect, NativeEffect, 0x61e00b59) \
V(::, _loadInt8, FfiLoadInt8, 0x0f04dfd6) \

View file

@ -489,7 +489,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word ExternalTypedData_InstanceSize =
12;
static constexpr dart::compiler::target::word FfiTrampolineData_InstanceSize =
24;
28;
static constexpr dart::compiler::target::word Field_InstanceSize = 60;
static constexpr dart::compiler::target::word Float32x4_InstanceSize = 24;
static constexpr dart::compiler::target::word Float64x2_InstanceSize = 24;
@ -1569,7 +1569,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word ExternalTypedData_InstanceSize =
12;
static constexpr dart::compiler::target::word FfiTrampolineData_InstanceSize =
24;
28;
static constexpr dart::compiler::target::word Field_InstanceSize = 60;
static constexpr dart::compiler::target::word Float32x4_InstanceSize = 24;
static constexpr dart::compiler::target::word Float64x2_InstanceSize = 24;
@ -3738,7 +3738,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word ExternalTypedData_InstanceSize =
12;
static constexpr dart::compiler::target::word FfiTrampolineData_InstanceSize =
24;
28;
static constexpr dart::compiler::target::word Field_InstanceSize = 60;
static constexpr dart::compiler::target::word Float32x4_InstanceSize = 24;
static constexpr dart::compiler::target::word Float64x2_InstanceSize = 24;
@ -4806,7 +4806,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word ExternalTypedData_InstanceSize =
12;
static constexpr dart::compiler::target::word FfiTrampolineData_InstanceSize =
24;
28;
static constexpr dart::compiler::target::word Field_InstanceSize = 60;
static constexpr dart::compiler::target::word Float32x4_InstanceSize = 24;
static constexpr dart::compiler::target::word Float64x2_InstanceSize = 24;
@ -7018,7 +7018,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
AOT_ExternalTypedData_InstanceSize = 12;
static constexpr dart::compiler::target::word
AOT_FfiTrampolineData_InstanceSize = 24;
AOT_FfiTrampolineData_InstanceSize = 28;
static constexpr dart::compiler::target::word AOT_Field_InstanceSize = 48;
static constexpr dart::compiler::target::word AOT_Float32x4_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_Float64x2_InstanceSize = 24;
@ -10049,7 +10049,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
AOT_ExternalTypedData_InstanceSize = 12;
static constexpr dart::compiler::target::word
AOT_FfiTrampolineData_InstanceSize = 24;
AOT_FfiTrampolineData_InstanceSize = 28;
static constexpr dart::compiler::target::word AOT_Field_InstanceSize = 48;
static constexpr dart::compiler::target::word AOT_Float32x4_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_Float64x2_InstanceSize = 24;

View file

@ -246,6 +246,12 @@ DART_EXPORT void* Dart_ExecuteInternalCommand(const char* command, void* arg) {
IsolateGroup::Current()->heap()->CollectAllGarbage();
return nullptr;
} else if (strcmp(command, "is-thread-in-generated") == 0) {
if (Thread::Current()->execution_state() == Thread::kThreadInGenerated) {
return reinterpret_cast<void*>(1);
}
return nullptr;
} else if (strcmp(command, "is-mutator-in-native") == 0) {
Isolate* const isolate = reinterpret_cast<Isolate*>(arg);
if (isolate->mutator_thread()->execution_state_cross_thread_for_testing() ==

View file

@ -7535,6 +7535,20 @@ void Function::SetFfiCallbackId(int32_t value) const {
FfiTrampolineData::Cast(obj).set_callback_id(value);
}
bool Function::FfiIsLeaf() const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(untag()->data());
ASSERT(!obj.IsNull());
return FfiTrampolineData::Cast(obj).is_leaf();
}
void Function::SetFfiIsLeaf(bool is_leaf) const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(untag()->data());
ASSERT(!obj.IsNull());
FfiTrampolineData::Cast(obj).set_is_leaf(is_leaf);
}
FunctionPtr Function::FfiCallbackTarget() const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(data());
@ -10295,6 +10309,10 @@ void FfiTrampolineData::set_callback_id(int32_t callback_id) const {
StoreNonPointer(&untag()->callback_id_, callback_id);
}
void FfiTrampolineData::set_is_leaf(bool is_leaf) const {
StoreNonPointer(&untag()->is_leaf_, is_leaf);
}
void FfiTrampolineData::set_callback_exceptional_return(
const Instance& value) const {
untag()->set_callback_exceptional_return(value.ptr());
@ -10307,6 +10325,7 @@ FfiTrampolineDataPtr FfiTrampolineData::New() {
Heap::kOld, FfiTrampolineData::ContainsCompressedPointers());
FfiTrampolineDataPtr data = static_cast<FfiTrampolineDataPtr>(raw);
data->untag()->callback_id_ = 0;
data->untag()->is_leaf_ = false;
return data;
}

View file

@ -2554,6 +2554,12 @@ class Function : public Object {
// Can only be called on FFI trampolines.
void SetFfiCallbackId(int32_t value) const;
// Can only be called on FFI trampolines.
bool FfiIsLeaf() const;
// Can only be called on FFI trampolines.
void SetFfiIsLeaf(bool is_leaf) const;
// Can only be called on FFI trampolines.
// Null for Dart -> native calls.
FunctionPtr FfiCallbackTarget() const;
@ -3869,6 +3875,9 @@ class FfiTrampolineData : public Object {
int32_t callback_id() const { return untag()->callback_id_; }
void set_callback_id(int32_t value) const;
bool is_leaf() const { return untag()->is_leaf_; }
void set_is_leaf(bool value) const;
static FfiTrampolineDataPtr New();
FINAL_HEAP_OBJECT_IMPLEMENTATION(FfiTrampolineData, Object);

View file

@ -1463,6 +1463,9 @@ class UntaggedFfiTrampolineData : public UntaggedObject {
// Will be 0 for non-callbacks. Check 'callback_target_' to determine if this
// is a callback or not.
uint32_t callback_id_;
// Whether this is a leaf call - i.e. one that doesn't call back into Dart.
bool is_leaf_;
};
class UntaggedField : public UntaggedObject {

View file

@ -90,6 +90,16 @@ main() {
print(result.runtimeType);
}
{
// As a leaf call.
BinaryOp sumPlus42Leaf = ffiTestFunctions
.lookupFunction<NativeBinaryOp, BinaryOp>("SumPlus42", isLeaf: true);
final result = sumPlus42Leaf(3, 17);
print(result);
print(result.runtimeType);
}
{
// Various size arguments.
QuadOp intComputation = ffiTestFunctions

View file

@ -92,6 +92,16 @@ main() {
print(result.runtimeType);
}
{
// As a leaf call.
BinaryOp sumPlus42Leaf = ffiTestFunctions
.lookupFunction<NativeBinaryOp, BinaryOp>("SumPlus42", isLeaf: true);
final result = sumPlus42Leaf(3, 17);
print(result);
print(result.runtimeType);
}
{
// Various size arguments.
QuadOp intComputation = ffiTestFunctions

View file

@ -52,7 +52,7 @@ class DynamicLibrary {
extension DynamicLibraryExtension on DynamicLibrary {
@patch
DS lookupFunction<NS extends Function, DS extends Function>(
String symbolName) =>
DS lookupFunction<NS extends Function, DS extends Function>(String symbolName,
{bool isLeaf: false}) =>
throw UnsupportedError("The body is inlined in the frontend.");
}

View file

@ -38,7 +38,8 @@ Pointer<T> _fromAddress<T extends NativeType>(int ptr) native "Ffi_fromAddress";
// this function.
@pragma("vm:recognized", "other")
DS _asFunctionInternal<DS extends Function, NS extends Function>(
Pointer<NativeFunction<NS>> ptr) native "Ffi_asFunctionInternal";
Pointer<NativeFunction<NS>> ptr,
bool isLeaf) native "Ffi_asFunctionInternal";
dynamic _asExternalTypedData(Pointer ptr, int count)
native "Ffi_asExternalTypedData";
@ -336,7 +337,7 @@ Pointer<Pointer<S>> _elementAtPointer<S extends NativeType>(
extension NativeFunctionPointer<NF extends Function>
on Pointer<NativeFunction<NF>> {
@patch
DF asFunction<DF extends Function>() =>
DF asFunction<DF extends Function>({bool isLeaf: false}) =>
throw UnsupportedError("The body is inlined in the frontend.");
}

View file

@ -76,5 +76,6 @@ extension DynamicLibraryExtension on DynamicLibrary {
/// int Function(int, int)>('add');
/// ```
external F lookupFunction<T extends Function, F extends Function>(
String symbolName);
String symbolName,
{bool isLeaf: false});
}

View file

@ -149,7 +149,8 @@ extension NativeFunctionPointer<NF extends Function>
on Pointer<NativeFunction<NF>> {
/// Convert to Dart function, automatically marshalling the arguments
/// and return value.
external DF asFunction<@DartRepresentationOf("NF") DF extends Function>();
external DF asFunction<@DartRepresentationOf("NF") DF extends Function>(
{bool isLeaf: false});
}
//

View file

@ -17,13 +17,19 @@ class CallbackTest {
final String name;
final Pointer callback;
final void Function() afterCallbackChecks;
final bool isLeaf;
CallbackTest(this.name, this.callback) : afterCallbackChecks = noChecks {}
CallbackTest.withCheck(this.name, this.callback, this.afterCallbackChecks) {}
CallbackTest(this.name, this.callback, {this.isLeaf: false})
: afterCallbackChecks = noChecks {}
CallbackTest.withCheck(this.name, this.callback, this.afterCallbackChecks,
{this.isLeaf: false}) {}
void run() {
final NativeCallbackTestFn tester = ffiTestFunctions
.lookupFunction<NativeCallbackTest, NativeCallbackTestFn>("Test$name");
final NativeCallbackTestFn tester = isLeaf
? ffiTestFunctions.lookupFunction<NativeCallbackTest,
NativeCallbackTestFn>("Test$name", isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeCallbackTest,
NativeCallbackTestFn>("Test$name", isLeaf: false);
final int testCode = tester(callback);

File diff suppressed because it is too large Load diff

View file

@ -19,18 +19,22 @@ import 'very_large_struct.dart';
typedef NativeCoordinateOp = Pointer<Coordinate> Function(Pointer<Coordinate>);
void main() {
testFunctionWithStruct();
testFunctionWithStructArray();
testFunctionWithVeryLargeStruct();
for (final isLeaf in [false, true]) {
testFunctionWithStruct(isLeaf: isLeaf);
testFunctionWithStructArray(isLeaf: isLeaf);
testFunctionWithVeryLargeStruct(isLeaf: isLeaf);
}
}
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
/// pass a struct to a c function and get a struct as return value
void testFunctionWithStruct() {
void testFunctionWithStruct({bool isLeaf: false}) {
Pointer<NativeFunction<NativeCoordinateOp>> p1 =
ffiTestFunctions.lookup("TransposeCoordinate");
NativeCoordinateOp f1 = p1.asFunction();
NativeCoordinateOp f1 =
(isLeaf ? p1.asFunction(isLeaf: true) : p1.asFunction(isLeaf: false));
;
final c1 = calloc<Coordinate>()
..ref.x = 10.0
@ -54,10 +58,12 @@ void testFunctionWithStruct() {
}
/// pass an array of structs to a c funtion
void testFunctionWithStructArray() {
void testFunctionWithStructArray({bool isLeaf: false}) {
Pointer<NativeFunction<NativeCoordinateOp>> p1 =
ffiTestFunctions.lookup("CoordinateElemAt1");
NativeCoordinateOp f1 = p1.asFunction();
NativeCoordinateOp f1 =
(isLeaf ? p1.asFunction(isLeaf: true) : p1.asFunction(isLeaf: false));
;
final coordinateArray = calloc<Coordinate>(3);
Coordinate c1 = coordinateArray[0];
@ -83,10 +89,12 @@ void testFunctionWithStructArray() {
typedef VeryLargeStructSum = int Function(Pointer<VeryLargeStruct>);
typedef NativeVeryLargeStructSum = Int64 Function(Pointer<VeryLargeStruct>);
void testFunctionWithVeryLargeStruct() {
void testFunctionWithVeryLargeStruct({bool isLeaf: false}) {
Pointer<NativeFunction<NativeVeryLargeStructSum>> p1 =
ffiTestFunctions.lookup("SumVeryLargeStruct");
VeryLargeStructSum f = p1.asFunction();
VeryLargeStructSum f =
(isLeaf ? p1.asFunction(isLeaf: true) : p1.asFunction(isLeaf: false));
;
final vlsArray = calloc<VeryLargeStruct>(2);
VeryLargeStruct vls1 = vlsArray[0];

View file

@ -537,7 +537,7 @@ extension on CompositeType {
}
extension on FunctionType {
String get dartCallCode {
String dartCallCode({bool isLeaf: false}) {
final a = ArgumentValueAssigner();
final assignValues = arguments.assignValueStatements(a);
final argumentFrees = arguments.dartFreeStatements();
@ -561,17 +561,19 @@ extension on FunctionType {
break;
}
final namePostfix = isLeaf ? "Leaf" : "";
return """
final $dartName =
ffiTestFunctions.lookupFunction<$dartCType, $dartType>("$cName");
final $dartName$namePostfix =
ffiTestFunctions.lookupFunction<$dartCType, $dartType>(
"$cName"${isLeaf ? ", isLeaf:true" : ""});
${reason.makeDartDocComment()}
void $dartTestName() {
void $dartTestName$namePostfix() {
${arguments.dartAllocateStatements()}
${assignValues}
final result = $dartName($argumentNames);
final result = $dartName$namePostfix($argumentNames);
print("result = \$result");
@ -886,11 +888,13 @@ void writeDartCallTest() {
void main() {
for (int i = 0; i < 10; ++i) {
${functions.map((e) => "${e.dartTestName}();").join("\n")}
${functions.map((e) => "${e.dartTestName}Leaf();").join("\n")}
}
}
""");
buffer.writeAll(compounds.map((e) => e.dartClass(nnbd)));
buffer.writeAll(functions.map((e) => e.dartCallCode));
buffer.writeAll(functions.map((e) => e.dartCallCode(isLeaf: false)));
buffer.writeAll(functions.map((e) => e.dartCallCode(isLeaf: true)));
final path = callTestPath(nnbd);
File(path).writeAsStringSync(buffer.toString());

View file

@ -0,0 +1,61 @@
// 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
import 'dart:ffi';
import 'dart:io';
import 'package:expect/expect.dart';
import 'dylib_utils.dart';
import 'ffi_test_helpers.dart';
import 'callback_tests_utils.dart';
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
testLeafCall() {
// Note: This test currently fails on Windows AOT: https://dartbug.com/40579
// Regular calls should transition generated -> native.
final isThreadInGenerated = ffiTestFunctions.lookupFunction<
Int8 Function(), int Function()>("IsThreadInGenerated");
Expect.equals(0, isThreadInGenerated());
// Leaf calls should remain in generated state.
final isThreadInGeneratedLeaf = ffiTestFunctions.lookupFunction<
Int8 Function(), int Function()>("IsThreadInGenerated", isLeaf: true);
Expect.equals(1, isThreadInGeneratedLeaf());
}
testLeafCallApi() {
// Note: This will only crash as expected in debug build mode. In other modes
// it's effectively skip.
final f = ffiTestFunctions.lookupFunction<
Void Function(), void Function()>("TestLeafCallApi", isLeaf: true);
// Calling Dart_.. API is unsafe from leaf calls since we explicitly haven't
// made the generated -> native transition.
f();
}
void nop() {}
testCallbackLeaf() {
// This should crash with "expected: T->IsAtSafepoint()", since it's unsafe to
// do callbacks from leaf calls (otherwise they wouldn't be leaf calls).
// Note: This will only crash as expected in debug build mode. In other modes
// it's effectively skip.
CallbackTest("CallbackLeaf", Pointer.fromFunction<Void Function()>(nop),
isLeaf:true).run();
}
main() {
testLeafCall(); //# 01: ok
// These tests terminate the process after successful completion, so we have
// to run them separately.
//
// Since they use signal handlers they only run on Linux.
if (Platform.isLinux && !const bool.fromEnvironment("dart.vm.product")) {
testLeafCallApi(); //# 02: ok
testCallbackLeaf(); //# 03: ok
}
}

View file

@ -61,6 +61,12 @@ void main() {
testSizeOfHandle();
testElementAtGeneric();
testElementAtNativeType();
testLookupFunctionIsLeafMustBeConst();
testAsFunctionIsLeafMustBeConst();
testLookupFunctionTakesHandle();
testAsFunctionTakesHandle();
testLookupFunctionReturnsHandle();
testAsFunctionReturnsHandle();
}
typedef Int8UnOp = Int8 Function(Int8);
@ -705,6 +711,44 @@ class TestStruct1405 extends Struct {
external Pointer<Uint8> notEmpty;
}
void testLookupFunctionIsLeafMustBeConst() {
bool notAConst = false;
DynamicLibrary l = dlopenPlatformSpecific("ffi_test_dynamic_library");
l.lookupFunction<NativeDoubleUnOp, DoubleUnOp>("timesFour", isLeaf:notAConst); //# 1500: compile-time error
}
void testAsFunctionIsLeafMustBeConst() {
bool notAConst = false;
Pointer<NativeFunction<Int8UnOp>> p = Pointer.fromAddress(1337);
IntUnOp f = p.asFunction(isLeaf:notAConst); //# 1501: compile-time error
}
typedef NativeTakesHandle = Void Function(Handle);
typedef TakesHandle = void Function(Object);
void testLookupFunctionTakesHandle() {
DynamicLibrary l = dlopenPlatformSpecific("ffi_test_dynamic_library");
l.lookupFunction<NativeTakesHandle, TakesHandle>("takesHandle", isLeaf:true); //# 1502: compile-time error
}
void testAsFunctionTakesHandle() {
Pointer<NativeFunction<NativeTakesHandle>> p = Pointer.fromAddress(1337); //# 1503: compile-time error
TakesHandle f = p.asFunction(isLeaf:true); //# 1503: compile-time error
}
typedef NativeReturnsHandle = Handle Function();
typedef ReturnsHandle = Object Function();
void testLookupFunctionReturnsHandle() {
DynamicLibrary l = dlopenPlatformSpecific("ffi_test_dynamic_library");
l.lookupFunction<NativeReturnsHandle, ReturnsHandle>("returnsHandle", isLeaf:true); //# 1504: compile-time error
}
void testAsFunctionReturnsHandle() {
Pointer<NativeFunction<NativeReturnsHandle>> p = Pointer.fromAddress(1337); //# 1505: compile-time error
ReturnsHandle f = p.asFunction(isLeaf:true); //# 1505: compile-time error
}
@Packed(1)
class TestStruct1600 extends Struct {
external Pointer<Uint8> notEmpty;

View file

@ -19,13 +19,19 @@ class CallbackTest {
final String name;
final Pointer callback;
final void Function() afterCallbackChecks;
final bool isLeaf;
CallbackTest(this.name, this.callback) : afterCallbackChecks = noChecks {}
CallbackTest.withCheck(this.name, this.callback, this.afterCallbackChecks) {}
CallbackTest(this.name, this.callback, {this.isLeaf: false})
: afterCallbackChecks = noChecks {}
CallbackTest.withCheck(this.name, this.callback, this.afterCallbackChecks,
{this.isLeaf: false}) {}
void run() {
final NativeCallbackTestFn tester = ffiTestFunctions
.lookupFunction<NativeCallbackTest, NativeCallbackTestFn>("Test$name");
final NativeCallbackTestFn tester = isLeaf
? ffiTestFunctions.lookupFunction<NativeCallbackTest,
NativeCallbackTestFn>("Test$name", isLeaf: true)
: ffiTestFunctions.lookupFunction<NativeCallbackTest,
NativeCallbackTestFn>("Test$name", isLeaf: false);
final int testCode = tester(callback);

File diff suppressed because it is too large Load diff

View file

@ -21,18 +21,21 @@ import 'very_large_struct.dart';
typedef NativeCoordinateOp = Pointer<Coordinate> Function(Pointer<Coordinate>);
void main() {
testFunctionWithStruct();
testFunctionWithStructArray();
testFunctionWithVeryLargeStruct();
for (final isLeaf in [false, true]) {
testFunctionWithStruct(isLeaf: isLeaf);
testFunctionWithStructArray(isLeaf: isLeaf);
testFunctionWithVeryLargeStruct(isLeaf: isLeaf);
}
}
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
/// pass a struct to a c function and get a struct as return value
void testFunctionWithStruct() {
void testFunctionWithStruct({bool isLeaf: false}) {
Pointer<NativeFunction<NativeCoordinateOp>> p1 =
ffiTestFunctions.lookup("TransposeCoordinate");
NativeCoordinateOp f1 = p1.asFunction();
NativeCoordinateOp f1 =
(isLeaf ? p1.asFunction(isLeaf: true) : p1.asFunction(isLeaf: false));
final c1 = calloc<Coordinate>()
..ref.x = 10.0
@ -56,10 +59,11 @@ void testFunctionWithStruct() {
}
/// pass an array of structs to a c funtion
void testFunctionWithStructArray() {
void testFunctionWithStructArray({bool isLeaf: false}) {
Pointer<NativeFunction<NativeCoordinateOp>> p1 =
ffiTestFunctions.lookup("CoordinateElemAt1");
NativeCoordinateOp f1 = p1.asFunction();
NativeCoordinateOp f1 =
(isLeaf ? p1.asFunction(isLeaf: true) : p1.asFunction(isLeaf: false));
final coordinateArray = calloc<Coordinate>(3);
Coordinate c1 = coordinateArray[0];
@ -85,10 +89,11 @@ void testFunctionWithStructArray() {
typedef VeryLargeStructSum = int Function(Pointer<VeryLargeStruct>);
typedef NativeVeryLargeStructSum = Int64 Function(Pointer<VeryLargeStruct>);
void testFunctionWithVeryLargeStruct() {
void testFunctionWithVeryLargeStruct({bool isLeaf: false}) {
Pointer<NativeFunction<NativeVeryLargeStructSum>> p1 =
ffiTestFunctions.lookup("SumVeryLargeStruct");
VeryLargeStructSum f = p1.asFunction();
VeryLargeStructSum f =
(isLeaf ? p1.asFunction(isLeaf: true) : p1.asFunction(isLeaf: false));
final vlsArray = calloc<VeryLargeStruct>(2);
VeryLargeStruct vls1 = vlsArray[0];

View file

@ -539,7 +539,7 @@ extension on CompositeType {
}
extension on FunctionType {
String get dartCallCode {
String dartCallCode({bool isLeaf: false}) {
final a = ArgumentValueAssigner();
final assignValues = arguments.assignValueStatements(a);
final argumentFrees = arguments.dartFreeStatements();
@ -563,17 +563,19 @@ extension on FunctionType {
break;
}
final namePostfix = isLeaf ? "Leaf" : "";
return """
final $dartName =
ffiTestFunctions.lookupFunction<$dartCType, $dartType>("$cName");
final ${dartName}$namePostfix =
ffiTestFunctions.lookupFunction<$dartCType, $dartType>(
"$cName"${isLeaf ? ", isLeaf:true" : ""});
${reason.makeDartDocComment()}
void $dartTestName() {
void ${dartTestName}$namePostfix() {
${arguments.dartAllocateStatements()}
${assignValues}
final result = $dartName($argumentNames);
final result = ${dartName}$namePostfix($argumentNames);
print("result = \$result");
@ -888,11 +890,13 @@ void writeDartCallTest() {
void main() {
for (int i = 0; i < 10; ++i) {
${functions.map((e) => "${e.dartTestName}();").join("\n")}
${functions.map((e) => "${e.dartTestName}Leaf();").join("\n")}
}
}
""");
buffer.writeAll(compounds.map((e) => e.dartClass(nnbd)));
buffer.writeAll(functions.map((e) => e.dartCallCode));
buffer.writeAll(functions.map((e) => e.dartCallCode(isLeaf: false)));
buffer.writeAll(functions.map((e) => e.dartCallCode(isLeaf: true)));
final path = callTestPath(nnbd);
File(path).writeAsStringSync(buffer.toString());

View file

@ -0,0 +1,60 @@
// 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
import 'dart:ffi';
import 'dart:io';
import 'package:expect/expect.dart';
import 'dylib_utils.dart';
import 'ffi_test_helpers.dart';
import 'callback_tests_utils.dart';
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
testLeafCall() {
// Regular calls should transition generated -> native.
final isThreadInGenerated = ffiTestFunctions.lookupFunction<
Int8 Function(), int Function()>("IsThreadInGenerated");
Expect.equals(0, isThreadInGenerated());
// Leaf calls should remain in generated state.
final isThreadInGeneratedLeaf = ffiTestFunctions.lookupFunction<
Int8 Function(), int Function()>("IsThreadInGenerated", isLeaf: true);
Expect.equals(1, isThreadInGeneratedLeaf());
}
testLeafCallApi() {
// Note: This will only crash as expected in debug build mode. In other modes
// it's effectively skip.
final f = ffiTestFunctions.lookupFunction<
Void Function(), void Function()>("TestLeafCallApi", isLeaf: true);
// Calling Dart_.. API is unsafe from leaf calls since we explicitly haven't
// made the generated -> native transition.
f();
}
void nop() {}
testCallbackLeaf() {
// This should crash with "expected: T->IsAtSafepoint()", since it's unsafe to
// do callbacks from leaf calls (otherwise they wouldn't be leaf calls).
// Note: This will only crash as expected in debug build mode. In other modes
// it's effectively skip.
CallbackTest("CallbackLeaf", Pointer.fromFunction<Void Function()>(nop),
isLeaf:true).run();
}
main() {
testLeafCall(); //# 01: ok
// These tests terminate the process after successful completion, so we have
// to run them separately.
//
// Since they use signal handlers they only run on Linux.
if (Platform.isLinux && !const bool.fromEnvironment("dart.vm.product")) {
testLeafCallApi(); //# 02: ok
testCallbackLeaf(); //# 03: ok
}
}

View file

@ -63,6 +63,12 @@ void main() {
testSizeOfHandle();
testElementAtGeneric();
testElementAtNativeType();
testLookupFunctionIsLeafMustBeConst();
testAsFunctionIsLeafMustBeConst();
testLookupFunctionTakesHandle();
testAsFunctionTakesHandle();
testLookupFunctionReturnsHandle();
testAsFunctionReturnsHandle();
}
typedef Int8UnOp = Int8 Function(Int8);
@ -705,6 +711,44 @@ class TestStruct1405 extends Struct {
Pointer<Uint8> notEmpty;
}
void testLookupFunctionIsLeafMustBeConst() {
bool notAConst = false;
DynamicLibrary l = dlopenPlatformSpecific("ffi_test_dynamic_library");
l.lookupFunction<NativeDoubleUnOp, DoubleUnOp>("timesFour", isLeaf:notAConst); //# 1500: compile-time error
}
void testAsFunctionIsLeafMustBeConst() {
bool notAConst = false;
Pointer<NativeFunction<Int8UnOp>> p = Pointer.fromAddress(1337);
IntUnOp f = p.asFunction(isLeaf:notAConst); //# 1501: compile-time error
}
typedef NativeTakesHandle = Void Function(Handle);
typedef TakesHandle = void Function(Object);
void testLookupFunctionTakesHandle() {
DynamicLibrary l = dlopenPlatformSpecific("ffi_test_dynamic_library");
l.lookupFunction<NativeTakesHandle, TakesHandle>("takesHandle", isLeaf:true); //# 1502: compile-time error
}
void testAsFunctionTakesHandle() {
Pointer<NativeFunction<NativeTakesHandle>> p = Pointer.fromAddress(1337); //# 1503: compile-time error
TakesHandle f = p.asFunction(isLeaf:true); //# 1503: compile-time error
}
typedef NativeReturnsHandle = Handle Function();
typedef ReturnsHandle = Object Function();
void testLookupFunctionReturnsHandle() {
DynamicLibrary l = dlopenPlatformSpecific("ffi_test_dynamic_library");
l.lookupFunction<NativeReturnsHandle, ReturnsHandle>("returnsHandle", isLeaf:true); //# 1504: compile-time error
}
void testAsFunctionReturnsHandle() {
Pointer<NativeFunction<NativeReturnsHandle>> p = Pointer.fromAddress(1337); //# 1505: compile-time error
ReturnsHandle f = p.asFunction(isLeaf:true); //# 1505: compile-time error
}
@Packed(1)
class TestStruct1600 extends Struct {
Pointer<Uint8> notEmpty;