Version 2.15.0-110.0.dev
Merge commit 'cd7c9922eba7265218196e15c73c167a88bd2a0c' into 'dev'
diff --git a/pkg/analyzer/tool/messages/generate.dart b/pkg/analyzer/tool/messages/generate.dart
index 8740d46..2e1c1a7 100644
--- a/pkg/analyzer/tool/messages/generate.dart
+++ b/pkg/analyzer/tool/messages/generate.dart
@@ -25,35 +25,10 @@
import 'package:yaml/yaml.dart' show loadYaml;
main() async {
- String analyzerPkgPath = normalize(join(pkg_root.packageRoot, 'analyzer'));
- String frontEndPkgPath = normalize(join(pkg_root.packageRoot, 'front_end'));
- String frontEndSharedPkgPath =
- normalize(join(pkg_root.packageRoot, '_fe_analyzer_shared'));
+ await GeneratedContent.generateAll(analyzerPkgPath, allTargets);
- Map<dynamic, dynamic> messagesYaml =
- loadYaml(File(join(frontEndPkgPath, 'messages.yaml')).readAsStringSync());
- String errorConverterSource = File(join(analyzerPkgPath,
- joinAll(posix.split('lib/src/fasta/error_converter.dart'))))
- .readAsStringSync();
- String syntacticErrorsSource = File(join(analyzerPkgPath,
- joinAll(posix.split('lib/src/dart/error/syntactic_errors.dart'))))
- .readAsStringSync();
- String parserSource = File(join(frontEndSharedPkgPath,
- joinAll(posix.split('lib/src/parser/parser.dart'))))
- .readAsStringSync();
-
- final codeGenerator = _SyntacticErrorGenerator(
- messagesYaml, errorConverterSource, syntacticErrorsSource, parserSource);
-
- await GeneratedContent.generateAll(analyzerPkgPath, <GeneratedContent>[
- GeneratedFile('lib/src/dart/error/syntactic_errors.g.dart',
- (String pkgPath) async {
- codeGenerator.generateFormatCode();
- return codeGenerator.out.toString();
- }),
- ]);
-
- codeGenerator
+ _SyntacticErrorGenerator()
+ ..generateFormatCode()
..checkForManualChanges()
..printSummary();
}
@@ -70,6 +45,21 @@
pkg/front_end/tool/fasta generate-messages
""";
+/// A list of all targets generated by this code generator.
+final List<GeneratedContent> allTargets = <GeneratedContent>[
+ GeneratedFile('lib/src/dart/error/syntactic_errors.g.dart',
+ (String pkgPath) async {
+ final codeGenerator = _SyntacticErrorGenerator();
+
+ codeGenerator.generateFormatCode();
+ return codeGenerator.out.toString();
+ }),
+];
+
+/// The path to the `analyzer` package.
+final String analyzerPkgPath =
+ normalize(join(pkg_root.packageRoot, 'analyzer'));
+
/// Return an entry containing 2 strings,
/// the name of the class containing the error and the name of the error,
/// or throw an exception if 'analyzerCode:' field is invalid.
@@ -106,7 +96,28 @@
''');
- _SyntacticErrorGenerator(this.messagesYaml, this.errorConverterSource,
+ factory _SyntacticErrorGenerator() {
+ String frontEndPkgPath = normalize(join(pkg_root.packageRoot, 'front_end'));
+ String frontEndSharedPkgPath =
+ normalize(join(pkg_root.packageRoot, '_fe_analyzer_shared'));
+
+ Map<dynamic, dynamic> messagesYaml = loadYaml(
+ File(join(frontEndPkgPath, 'messages.yaml')).readAsStringSync());
+ String errorConverterSource = File(join(analyzerPkgPath,
+ joinAll(posix.split('lib/src/fasta/error_converter.dart'))))
+ .readAsStringSync();
+ String syntacticErrorsSource = File(join(analyzerPkgPath,
+ joinAll(posix.split('lib/src/dart/error/syntactic_errors.dart'))))
+ .readAsStringSync();
+ String parserSource = File(join(frontEndSharedPkgPath,
+ joinAll(posix.split('lib/src/parser/parser.dart'))))
+ .readAsStringSync();
+
+ return _SyntacticErrorGenerator._(messagesYaml, errorConverterSource,
+ syntacticErrorsSource, parserSource);
+ }
+
+ _SyntacticErrorGenerator._(this.messagesYaml, this.errorConverterSource,
this.syntacticErrorsSource, this.parserSource);
void checkForManualChanges() {
diff --git a/pkg/analyzer/tool/messages/generate_test.dart b/pkg/analyzer/tool/messages/generate_test.dart
new file mode 100644
index 0000000..462df57
--- /dev/null
+++ b/pkg/analyzer/tool/messages/generate_test.dart
@@ -0,0 +1,16 @@
+// 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.
+
+// This test verifies that all files generated by `generate.dart` are up to
+// date.
+
+import 'package:analyzer_utilities/tools.dart';
+import 'package:path/path.dart';
+
+import 'generate.dart';
+
+main() async {
+ await GeneratedContent.checkAll(analyzerPkgPath,
+ join(analyzerPkgPath, 'tool', 'messages', 'generate.dart'), allTargets);
+}
diff --git a/pkg/vm/lib/transformations/ffi.dart b/pkg/vm/lib/transformations/ffi.dart
index 88edf51..58aae19 100644
--- a/pkg/vm/lib/transformations/ffi.dart
+++ b/pkg/vm/lib/transformations/ffi.dart
@@ -234,6 +234,8 @@
final Class compoundClass;
final Class structClass;
final Class unionClass;
+ final Class ffiNativeClass;
+ final Class nativeFieldWrapperClass;
final Class ffiStructLayoutClass;
final Field ffiStructLayoutTypesField;
final Field ffiStructLayoutPackingField;
@@ -286,6 +288,12 @@
final Procedure allocationTearoff;
final Procedure asFunctionTearoff;
final Procedure lookupFunctionTearoff;
+ final Procedure getNativeFieldFunction;
+ final Procedure reachabilityFenceFunction;
+
+ late final DartType nativeFieldWrapperClassType;
+ late final DartType voidType;
+ late final DartType pointerType;
/// Classes corresponding to [NativeType], indexed by [NativeType].
final List<Class> nativeTypesClasses;
@@ -297,7 +305,7 @@
FfiTransformer(this.index, this.coreTypes, this.hierarchy,
this.diagnosticReporter, this.referenceFromIndex)
- : env = new TypeEnvironment(coreTypes, hierarchy),
+ : env = TypeEnvironment(coreTypes, hierarchy),
objectClass = coreTypes.objectClass,
intClass = coreTypes.intClass,
doubleClass = coreTypes.doubleClass,
@@ -346,6 +354,9 @@
compoundClass = index.getClass('dart:ffi', '_Compound'),
structClass = index.getClass('dart:ffi', 'Struct'),
unionClass = index.getClass('dart:ffi', 'Union'),
+ ffiNativeClass = index.getClass('dart:ffi', 'FfiNative'),
+ nativeFieldWrapperClass =
+ index.getClass('dart:nativewrappers', 'NativeFieldWrapperClass1'),
ffiStructLayoutClass = index.getClass('dart:ffi', '_FfiStructLayout'),
ffiStructLayoutTypesField =
index.getField('dart:ffi', '_FfiStructLayout', 'fieldTypes'),
@@ -455,7 +466,18 @@
lookupFunctionTearoff = index.getProcedure(
'dart:ffi',
'DynamicLibraryExtension',
- LibraryIndex.tearoffPrefix + 'lookupFunction');
+ LibraryIndex.tearoffPrefix + 'lookupFunction'),
+ getNativeFieldFunction =
+ index.getTopLevelProcedure('dart:nativewrappers', 'getNativeField'),
+ reachabilityFenceFunction =
+ index.getTopLevelProcedure('dart:_internal', 'reachabilityFence') {
+ nativeFieldWrapperClassType =
+ nativeFieldWrapperClass.getThisType(coreTypes, Nullability.nonNullable);
+ voidType = nativeTypesClasses[NativeType.kVoid.index]
+ .getThisType(coreTypes, Nullability.nonNullable);
+ pointerType =
+ InterfaceType(pointerClass, Nullability.nonNullable, [voidType]);
+ }
@override
TreeNode visitLibrary(Library node) {
diff --git a/pkg/vm/lib/transformations/ffi_definitions.dart b/pkg/vm/lib/transformations/ffi_definitions.dart
index 2238251..7157845 100644
--- a/pkg/vm/lib/transformations/ffi_definitions.dart
+++ b/pkg/vm/lib/transformations/ffi_definitions.dart
@@ -69,9 +69,14 @@
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex? referenceFromIndex,
ChangedStructureNotifier? changedStructureNotifier) {
- final LibraryIndex index = LibraryIndex(component,
- const ["dart:core", "dart:ffi", "dart:_internal", "dart:typed_data"]);
- if (!index.containsLibrary("dart:ffi")) {
+ final LibraryIndex index = LibraryIndex(component, const [
+ 'dart:core',
+ 'dart:ffi',
+ 'dart:_internal',
+ 'dart:typed_data',
+ 'dart:nativewrappers'
+ ]);
+ if (!index.containsLibrary('dart:ffi')) {
// TODO: This check doesn't make sense: "dart:ffi" is always loaded/created
// for the VM target.
// If dart:ffi is not loaded, do not do the transformation.
diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart
index 49281ed..312c1d6 100644
--- a/pkg/vm/lib/transformations/ffi_use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi_use_sites.dart
@@ -43,8 +43,8 @@
List<Library> libraries,
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex? referenceFromIndex) {
- final index = new LibraryIndex(
- component, ["dart:ffi", "dart:_internal", "dart:typed_data"]);
+ final index = LibraryIndex(component,
+ ["dart:ffi", "dart:_internal", "dart:typed_data", "dart:nativewrappers"]);
if (!index.containsLibrary("dart:ffi")) {
// TODO: This check doesn't make sense: "dart:ffi" is always loaded/created
// for the VM target.
@@ -77,7 +77,7 @@
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex? referenceFromIndex)
: super(index, coreTypes, hierarchy, diagnosticReporter,
- referenceFromIndex) {}
+ referenceFromIndex);
@override
TreeNode visitLibrary(Library node) {
@@ -131,6 +131,20 @@
return result;
}
+ InstanceConstant? _tryGetFfiNativeAnnotation(Member node) {
+ for (final Expression annotation in node.annotations) {
+ if (annotation is ConstantExpression) {
+ if (annotation.constant is InstanceConstant) {
+ final instConst = annotation.constant as InstanceConstant;
+ if (instConst.classNode == ffiNativeClass) {
+ return instConst;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
@override
visitStaticInvocation(StaticInvocation node) {
super.visitStaticInvocation(node);
@@ -352,6 +366,88 @@
.substituteType(allocateFunctionType
.withoutTypeParameters) as FunctionType);
}
+ } else if (target is Procedure) {
+ // FfiNative calls that pass objects extending NativeFieldWrapperClass1
+ // (NFWC1) should be passed as Pointer instead so we don't have the
+ // overhead of converting Handles.
+ // If we find an NFWC1 object being passed to an FfiNative signature
+ // taking a Pointer, we automatically wrap the argument in a call to
+ // `Pointer.fromAddress(getNativeField(obj))`.
+ // Example:
+ // passAsPointer(ClassWithNativeField());
+ // Becomes, roughly:
+ // #t0 = PointerClassWithNativeField();
+ // passAsPointer(Pointer.fromAddress(getNativeField(#t0)));
+ // reachabilityFence(#t0);
+ final ffiNativeAnn = _tryGetFfiNativeAnnotation(target);
+ if (ffiNativeAnn != null) {
+ final DartType ffiSignature = ffiNativeAnn.typeArguments[0];
+ if (ffiSignature is FunctionType) {
+ final List<DartType> ffiParams = ffiSignature.positionalParameters;
+ final List<VariableDeclaration> dartParams =
+ target.function.positionalParameters;
+
+ List<VariableDeclaration> tmpsArgs = [];
+ List<Expression> callArgs = [];
+ final origArgs = node.arguments.positional;
+ for (int i = 0; i < origArgs.length; i++) {
+ if (env.isSubtypeOf(
+ dartParams[i].type,
+ nativeFieldWrapperClassType,
+ SubtypeCheckMode.ignoringNullabilities) &&
+ env.isSubtypeOf(ffiParams[i], pointerType,
+ SubtypeCheckMode.ignoringNullabilities)) {
+ // final NativeFieldWrapperClass1 #t1 = MyNFWC1();.
+ final tmpPtr = VariableDeclaration('',
+ initializer: origArgs[i],
+ type: nativeFieldWrapperClassType,
+ isFinal: true);
+ tmpsArgs.add(tmpPtr);
+
+ // Pointer.fromAddress(getNativeField(#t1)).
+ final ptr = StaticInvocation(
+ fromAddressInternal,
+ Arguments([
+ StaticInvocation(getNativeFieldFunction,
+ Arguments([VariableGet(tmpPtr)]))
+ ], types: [
+ voidType
+ ]));
+ callArgs.add(ptr);
+
+ continue;
+ }
+ // Note: We also evaluate, and assign temporaries for, non-wrapped
+ // arguments as we need to preserve the original evaluation order.
+ final tmpArg = VariableDeclaration('',
+ initializer: origArgs[i], isFinal: true);
+ tmpsArgs.add(tmpArg);
+ callArgs.add(VariableGet(tmpArg));
+ }
+
+ final targetCall = StaticInvocation(target, Arguments(callArgs));
+
+ // {
+ // T #t0;
+ // final NativeFieldWrapperClass1 #t1 = MyNFWC1();
+ // #t0 = foo(Pointer.fromAddress(getNativeField(#t1)));
+ // reachabilityFence(#t1);
+ // } => #t0
+ final tmpResult =
+ VariableDeclaration('', type: target.function.returnType);
+ return BlockExpression(
+ Block([
+ tmpResult,
+ ...tmpsArgs,
+ ExpressionStatement(VariableSet(tmpResult, targetCall)),
+ for (final ta in tmpsArgs)
+ ExpressionStatement(StaticInvocation(
+ reachabilityFenceFunction, Arguments([VariableGet(ta)])))
+ ]),
+ VariableGet(tmpResult),
+ );
+ }
+ }
}
} on _FfiStaticTypeError {
// It's OK to swallow the exception because the diagnostics issued will
@@ -713,6 +809,22 @@
return pointerType is InterfaceType ? pointerType.typeArguments[0] : null;
}
+ // Replaces all NativeFieldWrapperClass1 parameters with Pointer.
+ FunctionType _pointerizeFunctionType(FunctionType dartType) {
+ List<DartType> parameters = [];
+ for (final parameter in dartType.positionalParameters) {
+ if (parameter is InterfaceType) {
+ if (env.isSubtypeOf(parameter, nativeFieldWrapperClassType,
+ SubtypeCheckMode.ignoringNullabilities)) {
+ parameters.add(pointerType);
+ continue;
+ }
+ }
+ parameters.add(parameter);
+ }
+ return FunctionType(parameters, dartType.returnType, dartType.nullability);
+ }
+
void _ensureNativeTypeToDartType(
DartType nativeType, DartType dartType, Expression node,
{bool allowHandle: false}) {
@@ -725,6 +837,15 @@
SubtypeCheckMode.ignoringNullabilities)) {
return;
}
+ // We do automatic argument conversion from NativeFieldWrapperClass1 to
+ // Pointer, so we specifically allow for NFWC1 to be passed as Pointer.
+ if (dartType is FunctionType) {
+ final ptrDartType = _pointerizeFunctionType(dartType);
+ if (env.isSubtypeOf(correspondingDartType, ptrDartType,
+ SubtypeCheckMode.ignoringNullabilities)) {
+ return;
+ }
+ }
diagnosticReporter.report(
templateFfiTypeMismatch.withArguments(dartType, correspondingDartType,
nativeType, currentLibrary.isNonNullableByDefault),
diff --git a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
index affbe3a..524fcb3 100644
--- a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
+++ b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
@@ -1094,13 +1094,69 @@
return x;
}
+intptr_t PassAsHandle(Dart_Handle handle) {
+ intptr_t result = 0;
+ ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(handle, 0, &result)));
+ return result;
+}
+
+intptr_t PassAsPointer(void* ptr) {
+ return reinterpret_cast<intptr_t>(ptr);
+}
+
+intptr_t PassAsPointerAndValue(void* ptr, intptr_t value) {
+ return reinterpret_cast<intptr_t>(value);
+}
+
+intptr_t PassAsValueAndPointer(intptr_t value, void* ptr) {
+ return reinterpret_cast<intptr_t>(value);
+}
+
+// We're using this to keep track of whether the finalizer has been called.
+static intptr_t shared_resource = 0;
+
+void DummyResourceFinalizer(void* isolate_peer, void* peer) {
+ shared_resource = 0;
+}
+
+void SetSharedResource(Dart_Handle handle, intptr_t value) {
+ Dart_NewFinalizableHandle(handle, nullptr, sizeof(Dart_FinalizableHandle),
+ DummyResourceFinalizer);
+ shared_resource = value;
+}
+
+intptr_t GetSharedResource() {
+ return shared_resource;
+}
+
static void* FfiNativeResolver(const char* name, uintptr_t args_n) {
- if (strcmp(name, "ReturnIntPtr") == 0 && args_n == 1) {
- return reinterpret_cast<void*>(ReturnIntPtr);
+ if (strcmp(name, "Dart_SetNativeInstanceField") == 0 && args_n == 3) {
+ return reinterpret_cast<void*>(Dart_SetNativeInstanceField);
}
if (strcmp(name, "IsThreadInGenerated") == 0 && args_n == 0) {
return reinterpret_cast<void*>(IsThreadInGenerated);
}
+ if (strcmp(name, "ReturnIntPtr") == 0 && args_n == 1) {
+ return reinterpret_cast<void*>(ReturnIntPtr);
+ }
+ if (strcmp(name, "PassAsHandle") == 0 && args_n == 1) {
+ return reinterpret_cast<void*>(PassAsHandle);
+ }
+ if (strcmp(name, "PassAsPointer") == 0 && args_n == 1) {
+ return reinterpret_cast<void*>(PassAsPointer);
+ }
+ if (strcmp(name, "PassAsPointerAndValue") == 0 && args_n == 2) {
+ return reinterpret_cast<void*>(PassAsPointerAndValue);
+ }
+ if (strcmp(name, "PassAsValueAndPointer") == 0 && args_n == 2) {
+ return reinterpret_cast<void*>(PassAsValueAndPointer);
+ }
+ if (strcmp(name, "SetSharedResource") == 0 && args_n == 2) {
+ return reinterpret_cast<void*>(SetSharedResource);
+ }
+ if (strcmp(name, "GetSharedResource") == 0 && args_n == 0) {
+ return reinterpret_cast<void*>(GetSharedResource);
+ }
// This should be unreachable in tests.
ENSURE(false);
}
diff --git a/sdk/lib/_internal/vm/lib/internal_patch.dart b/sdk/lib/_internal/vm/lib/internal_patch.dart
index e762a8b..421fb4d 100644
--- a/sdk/lib/_internal/vm/lib/internal_patch.dart
+++ b/sdk/lib/_internal/vm/lib/internal_patch.dart
@@ -165,7 +165,7 @@
@pragma("vm:external-name", "Internal_unsafeCast")
external T unsafeCast<T>(Object? v);
-// This function can be used to keep an object alive til that point.
+// This function can be used to keep an object alive till that point.
@pragma("vm:recognized", "other")
@pragma('vm:prefer-inline')
@pragma("vm:external-name", "Internal_reachabilityFence")
diff --git a/tests/ffi/ffi_native_test.dart b/tests/ffi/ffi_native_test.dart
index 12ca0dd..34be137 100644
--- a/tests/ffi/ffi_native_test.dart
+++ b/tests/ffi/ffi_native_test.dart
@@ -1,36 +1,15 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-//
-// SharedObjects=ffi_test_functions
// NOTE: There is no `test/ffi_2/...` version of this test since annotations
// with type arguments isn't supported in that version of Dart.
import 'dart:ffi';
+import 'dart:nativewrappers';
import 'package:expect/expect.dart';
-import 'dylib_utils.dart';
-
-final nativeLib = dlopenPlatformSpecific('ffi_test_functions');
-final getRootLibraryUrl = nativeLib
- .lookupFunction<Handle Function(), Object Function()>('GetRootLibraryUrl');
-final setFfiNativeResolverForTest = nativeLib
- .lookupFunction<Void Function(Handle), void Function(Object)>('SetFfiNativeResolverForTest');
-
-@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
-external int returnIntPtr(int x);
-
-@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr', isLeaf: true)
-external int returnIntPtrLeaf(int x);
-
-@FfiNative<IntPtr Function()>('IsThreadInGenerated')
-external int isThreadInGenerated();
-
-@FfiNative<IntPtr Function()>('IsThreadInGenerated', isLeaf: true)
-external int isThreadInGeneratedLeaf();
-
// Error: FFI leaf call must not have Handle return type.
@FfiNative<Handle Function()>("foo", isLeaf: true) //# 01: compile-time error
external Object foo(); //# 01: compile-time error
@@ -59,19 +38,4 @@
external static void foo();
}
-void main() {
- // Register test resolver for top-level functions above.
- final root_lib_url = getRootLibraryUrl();
- setFfiNativeResolverForTest(root_lib_url);
-
- // Test we can call FfiNative functions.
- Expect.equals(123, returnIntPtr(123));
- Expect.equals(123, returnIntPtrLeaf(123));
- Expect.equals(123, Classy.returnIntPtrStatic(123));
-
- // Test FfiNative leaf calls remain in generated code.
- // Regular calls should transition generated -> native.
- Expect.equals(0, isThreadInGenerated());
- // Leaf calls should remain in generated state.
- Expect.equals(1, isThreadInGeneratedLeaf());
-}
+void main() { /* Intentionally empty: Compile-time error tests. */ }
diff --git a/tests/ffi/vmspecific_ffi_native_test.dart b/tests/ffi/vmspecific_ffi_native_test.dart
new file mode 100644
index 0000000..5264901
--- /dev/null
+++ b/tests/ffi/vmspecific_ffi_native_test.dart
@@ -0,0 +1,143 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// SharedObjects=ffi_test_functions
+
+// NOTE: There is no `test/ffi_2/...` version of this test since annotations
+// with type arguments isn't supported in that version of Dart.
+
+import 'dart:ffi';
+import 'dart:nativewrappers';
+
+import 'package:expect/expect.dart';
+
+import 'dylib_utils.dart';
+
+final nativeLib = dlopenPlatformSpecific('ffi_test_functions');
+
+final getRootLibraryUrl = nativeLib
+ .lookupFunction<Handle Function(), Object Function()>('GetRootLibraryUrl');
+
+final setFfiNativeResolverForTest =
+ nativeLib.lookupFunction<Void Function(Handle), void Function(Object)>(
+ 'SetFfiNativeResolverForTest');
+
+final triggerGC = nativeLib
+ .lookupFunction<Void Function(IntPtr), void Function(int)>('TriggerGC');
+
+@FfiNative<Handle Function(Handle, IntPtr, IntPtr)>(
+ 'Dart_SetNativeInstanceField')
+external Object setNativeInstanceField(Object obj, int index, int ptr);
+
+// Basic FfiNative test functions.
+
+@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
+external int returnIntPtr(int x);
+
+@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr', isLeaf: true)
+external int returnIntPtrLeaf(int x);
+
+@FfiNative<IntPtr Function()>('IsThreadInGenerated')
+external int isThreadInGenerated();
+
+@FfiNative<IntPtr Function()>('IsThreadInGenerated', isLeaf: true)
+external int isThreadInGeneratedLeaf();
+
+class Classy {
+ @FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
+ external static int returnIntPtrStatic(int x);
+}
+
+// For automatic transform of NativeFieldWrapperClass1 to Pointer.
+
+// Sets the native (dummy) resource and the object's finalizer.
+@FfiNative<Void Function(Handle, IntPtr)>('SetSharedResource')
+external void setSharedResource(NativeFieldWrapperClass1 obj, int value);
+// Return the native (dummy) resource.
+@FfiNative<IntPtr Function()>('GetSharedResource')
+external int getSharedResource();
+
+class ClassWithNativeField extends NativeFieldWrapperClass1 {
+ ClassWithNativeField(int value) {
+ setNativeInstanceField(this, 0, value);
+ setSharedResource(this, value);
+ }
+}
+
+// Native function takes a Handle, so a Handle is passed as-is.
+@FfiNative<IntPtr Function(Handle)>('PassAsHandle')
+external int passAsHandle(NativeFieldWrapperClass1 obj);
+// FFI signature takes Pointer, Dart signature takes NativeFieldWrapperClass1.
+// This implies automatic conversion.
+@FfiNative<IntPtr Function(Pointer<Void>)>('PassAsPointer')
+external int passAsPointer(NativeFieldWrapperClass1 obj);
+// Pass Pointer automatically, and return value.
+@FfiNative<IntPtr Function(Pointer<Void>, IntPtr)>('PassAsPointerAndValue')
+external int passAsPointerAndValue(NativeFieldWrapperClass1 obj, int value);
+// Pass Pointer automatically, and return value.
+@FfiNative<IntPtr Function(IntPtr, Pointer<Void>)>('PassAsValueAndPointer')
+external int passAsValueAndPointer(int value, NativeFieldWrapperClass1 obj);
+
+// Helper to embed triggerGC(..) as an expression.
+int triggerGCWrap() {
+ triggerGC(0);
+ return 0;
+}
+
+// Helpers for testing argumnent evaluation order is preserved.
+int state = 0;
+int setState(int value) {
+ state = value;
+ return 0;
+}
+
+class StateSetter extends NativeFieldWrapperClass1 {
+ StateSetter(int value) {
+ setNativeInstanceField(this, 0, 0);
+ state = value;
+ }
+}
+
+void main() {
+ // Register test resolver for top-level functions above.
+ setFfiNativeResolverForTest(getRootLibraryUrl());
+
+ // Test we can call FfiNative functions.
+ Expect.equals(123, returnIntPtr(123));
+ Expect.equals(123, returnIntPtrLeaf(123));
+ Expect.equals(123, Classy.returnIntPtrStatic(123));
+
+ // Test FfiNative leaf calls remain in generated code.
+ // Regular calls should transition generated -> native.
+ Expect.equals(0, isThreadInGenerated());
+ // Leaf calls should remain in generated state.
+ Expect.equals(1, isThreadInGeneratedLeaf());
+
+ // Test that objects extending NativeFieldWrapperClass1 can be passed to
+ // FfiNative functions that take Pointer.
+ // Such objects should automatically be converted and pass as Pointer.
+ final cwnf = ClassWithNativeField(123456);
+ Expect.equals(123456, passAsHandle(cwnf));
+ Expect.equals(123456, passAsPointer(cwnf));
+
+ // Test that the transform to wrap NativeFieldWrapperClass1 objects in
+ // getNativeField(..) doesn't violate the original argument's liveness.
+ Expect.equals(
+ 314159,
+ passAsPointerAndValue(
+ // 1: Locally alloc. instance.
+ // If this gets wrapped in another call the instance does not live
+ // past the return of the wrapper call.
+ ClassWithNativeField(314159),
+ // 2: Force GC, to collect the above if it isn't being kept alive.
+ // 3: Check that the underlying (dummy) resource hasn't been
+ // "collected" (i.e. reset to 0).
+ triggerGCWrap() + getSharedResource()));
+
+ // Test that the order of argument evaluation is being preserved through the
+ // transform wrapping NativeFieldWrapperClass1 objects.
+ state = 0;
+ passAsValueAndPointer(setState(7), StateSetter(3));
+ Expect.equals(3, state);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 6470b37..a787f08 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 15
PATCH 0
-PRERELEASE 109
+PRERELEASE 110
PRERELEASE_PATCH 0
\ No newline at end of file