[XL] Change in dart/sdk[main]: [vm/ffi] Support `@Native` fields

23 views
Skip to first unread message

Simon Binder (Gerrit)

unread,
Nov 23, 2023, 5:49:06 PM11/23/23
to Daco Harkes, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Daco Harkes.

Simon Binder would like Daco Harkes to review this change.

View Change

[vm/ffi] Support `@Native` fields

Allow annotating top-level or static fields with `@Native` to create
fields backed by native memory.
By using the `_addressOf` operator implemented in the VM, these fields
can be implemented in the CFE by replacing them with accessors looking
up the pointer and then using existing methods to load and store the
value.

Closes https://github.com/dart-lang/sdk/issues/50551

TEST=tests/ffi/native_assets/asset_*_test.dart
TEST=pkg/analyzer/test/src/diagnostics/ffi_native_test.dart

Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
---
M pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
M pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
M pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
M pkg/analyzer/lib/src/error/error_code_values.g.dart
M pkg/analyzer/lib/src/generated/ffi_verifier.dart
M pkg/analyzer/messages.yaml
M pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
M pkg/front_end/lib/src/api_unstable/vm.dart
M pkg/front_end/messages.yaml
M pkg/vm/lib/transformations/ffi/native.dart
M pkg/vm/lib/transformations/ffi/use_sites.dart
M pkg/vm/testcases/transformations/ffi/ffinative.dart
M pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
M pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
M pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart
M pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.aot.expect
M pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.expect
M runtime/bin/ffi_test/ffi_test_functions.cc
M runtime/vm/compiler/frontend/kernel_to_il.cc
M tests/ffi/native_assets/asset_absolute_test.dart
M tests/ffi/native_assets/asset_library_annotation_test.dart
M tests/ffi/native_assets/asset_relative_test.dart
M tests/ffi/vmspecific_static_checks_ffinative_test.dart
23 files changed, 913 insertions(+), 212 deletions(-)

diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index b206c51..bb289eb 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -5120,12 +5120,33 @@
problemMessage: r"""FFI leaf call must not have Handle argument types.""");

// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeFfiNativeFieldMustBeStatic =
+ messageFfiNativeFieldMustBeStatic;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageFfiNativeFieldMustBeStatic = const MessageCode(
+ "FfiNativeFieldMustBeStatic",
+ analyzerCodes: <String>["NATIVE_FIELD_NOT_STATIC"],
+ problemMessage: r"""Native fields must be static.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeFfiNativeFieldType = messageFfiNativeFieldType;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageFfiNativeFieldType = const MessageCode(
+ "FfiNativeFieldType",
+ analyzerCodes: <String>["NATIVE_FIELD_TYPE"],
+ problemMessage:
+ r"""Unsupported type for native fields. Native fields only support pointers or numeric and compound types.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeFfiNativeMustBeExternal = messageFfiNativeMustBeExternal;

// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageFfiNativeMustBeExternal = const MessageCode(
"FfiNativeMustBeExternal",
- problemMessage: r"""Native functions must be marked external.""");
+ problemMessage:
+ r"""Native functions and fields must be marked external.""");

// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeFfiNativeOnlyNativeFieldWrapperClassCanBePointer =
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 1b7c263..ed1311b 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -1730,6 +1730,10 @@
status: noFix
FfiCode.ARGUMENT_MUST_BE_NATIVE:
status: needsEvaluation
+FfiCode.NATIVE_FIELD_TYPE:
+ status: needsEvaluation
+FfiCode.NATIVE_FIELD_NOT_STATIC:
+ status: needsEvaluation
FfiCode.COMPOUND_IMPLEMENTS_FINALIZABLE:
status: noFix
FfiCode.CREATION_OF_STRUCT_OR_UNION:
diff --git a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
index 6aa967f..6981cda 100644
--- a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
+++ b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
@@ -339,6 +339,23 @@
hasPublishedDocs: true,
);

+ /// No parameters
+ static const FfiCode NATIVE_FIELD_NOT_STATIC = FfiCode(
+ 'NATIVE_FIELD_NOT_STATIC',
+ "Native fields must be static.",
+ correctionMessage: "Try adding a static modifier to this field.",
+ );
+
+ /// Parameters:
+ /// 0: The invalid type.
+ static const FfiCode NATIVE_FIELD_TYPE = FfiCode(
+ 'NATIVE_FIELD_TYPE',
+ "'{0}' is an invalid type for fields annotated with `@Native`.",
+ correctionMessage:
+ "Try changing the type in the `@Native` annotation to a numeric FFI "
+ "type, a pointer, or a compound class.",
+ );
+
/// Parameters:
/// 0: the name of the function, method, or constructor having type arguments
static const FfiCode NON_CONSTANT_TYPE_ARGUMENT = FfiCode(
diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart
index 2109858..8568bca 100644
--- a/pkg/analyzer/lib/src/error/error_code_values.g.dart
+++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart
@@ -606,6 +606,8 @@
FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
FfiCode.MUST_BE_A_SUBTYPE,
FfiCode.MUST_RETURN_VOID,
+ FfiCode.NATIVE_FIELD_NOT_STATIC,
+ FfiCode.NATIVE_FIELD_TYPE,
FfiCode.NON_CONSTANT_TYPE_ARGUMENT,
FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER,
FfiCode.NON_POSITIVE_ARRAY_DIMENSION,
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index 7b990d2..b3acbfa 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -179,6 +179,20 @@
if (inCompound) {
_validateFieldsInCompound(node);
}
+
+ for (final declared in node.fields.variables) {
+ final declaredElement = declared.declaredElement;
+ if (declaredElement != null) {
+ _checkFfiNative(
+ errorNode: declared,
+ annotations: node.metadata,
+ declarationElement: declaredElement,
+ formalParameterList: null,
+ isExternal: node.externalKeyword != null,
+ );
+ }
+ }
+
super.visitFieldDeclaration(node);
}

@@ -189,6 +203,7 @@
annotations: node.metadata,
declarationElement: node.declaredElement!,
formalParameterList: node.functionExpression.parameters,
+ isExternal: node.externalKeyword != null,
);
super.visitFunctionDeclaration(node);
}
@@ -239,10 +254,12 @@
@override
void visitMethodDeclaration(MethodDeclaration node) {
_checkFfiNative(
- errorNode: node,
- annotations: node.metadata,
- declarationElement: node.declaredElement!,
- formalParameterList: node.parameters);
+ errorNode: node,
+ annotations: node.metadata,
+ declarationElement: node.declaredElement!,
+ formalParameterList: node.parameters,
+ isExternal: node.externalKeyword != null,
+ );
super.visitMethodDeclaration(node);
}

@@ -311,10 +328,29 @@
super.visitPropertyAccess(node);
}

+ @override
+ void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
+ for (final declared in node.variables.variables) {
+ final declaredElement = declared.declaredElement;
+ if (declaredElement != null) {
+ _checkFfiNative(
+ errorNode: declared,
+ annotations: node.metadata,
+ declarationElement: declaredElement,
+ formalParameterList: null,
+ isExternal: node.externalKeyword != null,
+ );
+ }
+ }
+
+ super.visitTopLevelVariableDeclaration(node);
+ }
+
void _checkFfiNative({
required Declaration errorNode,
required List<Annotation> annotations,
- required ExecutableElement declarationElement,
+ required Element declarationElement,
+ required bool isExternal,
required FormalParameterList? formalParameterList,
}) {
final formalParameters =
@@ -333,95 +369,135 @@
return;
}

- final ffiSignature = typeArguments[0].type! as FunctionType;
-
- // Leaf call FFI Natives can't use Handles.
- _validateFfiLeafCallUsesNoHandles(arguments, ffiSignature, errorNode);
-
- if (!declarationElement.isExternal) {
+ if (!isExternal) {
_errorReporter.reportErrorForNode(
FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL, errorNode);
}

- var ffiParameterTypes =
- ffiSignature.normalParameterTypes.flattenVarArgs();
- var ffiParameters = ffiSignature.parameters;
-
- if ((declarationElement is MethodElement ||
- declarationElement is PropertyAccessorElementImpl) &&
- !declarationElement.isStatic) {
- // Instance methods must have the receiver as an extra parameter in the
- // Native annotation.
- if (formalParameters.length + 1 != ffiParameterTypes.length) {
- _errorReporter.reportErrorForNode(
- FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER,
- errorNode,
- [formalParameters.length + 1, ffiParameterTypes.length]);
- return;
- }
-
- // Receiver can only be Pointer if the class extends
- // NativeFieldWrapperClass1.
- if (ffiSignature.normalParameterTypes[0].isPointer) {
- final cls = declarationElement.enclosingElement as InterfaceElement;
- if (!_extendsNativeFieldWrapperClass1(cls.thisType)) {
- _errorReporter.reportErrorForNode(
- FfiCode
- .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
- errorNode);
- }
- }
-
- ffiParameterTypes = ffiParameterTypes.sublist(1);
- ffiParameters = ffiParameters.sublist(1);
+ final ffiType = typeArguments[0].type!;
+ if (ffiType is FunctionType && declarationElement is ExecutableElement) {
+ _checkFfiNativeFunction(
+ errorNode,
+ declarationElement,
+ ffiType,
+ arguments,
+ formalParameters,
+ );
} else {
- // Number of parameters in the Native annotation must match the
- // annotated declaration.
- if (formalParameters.length != ffiParameterTypes.length) {
+ DartType type;
+
+ if (declarationElement is FieldElement) {
+ if (!declarationElement.isStatic) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.NATIVE_FIELD_NOT_STATIC, errorNode);
+ }
+
+ type = declarationElement.type;
+ } else if (declarationElement is TopLevelVariableElement) {
+ type = declarationElement.type;
+ } else {
_errorReporter.reportErrorForNode(
- FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS,
- errorNode,
- [ffiParameterTypes.length, formalParameters.length]);
+ FfiCode.NATIVE_FIELD_NOT_STATIC, errorNode);
return;
}
+
+ if (!_validateCompatibleNativeType(type, ffiType)) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.MUST_BE_A_SUBTYPE, errorNode, [type, ffiType, 'Native']);
+ }
+
+ if (ffiType.isArray || ffiType.isHandle || ffiType.isNativeFunction) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.NATIVE_FIELD_TYPE, errorNode, [type, ffiType]);
+ }
+ }
+ }
+ }
+
+ void _checkFfiNativeFunction(
+ Declaration errorNode,
+ ExecutableElement declarationElement,
+ FunctionType ffiSignature,
+ NodeList<Expression>? arguments,
+ List<FormalParameter> formalParameters,
+ ) {
+ // Leaf call FFI Natives can't use Handles.
+ _validateFfiLeafCallUsesNoHandles(arguments, ffiSignature, errorNode);
+
+ var ffiParameterTypes = ffiSignature.normalParameterTypes.flattenVarArgs();
+ var ffiParameters = ffiSignature.parameters;
+
+ if ((declarationElement is MethodElement ||
+ declarationElement is PropertyAccessorElementImpl) &&
+ !declarationElement.isStatic) {
+ // Instance methods must have the receiver as an extra parameter in the
+ // Native annotation.
+ if (formalParameters.length + 1 != ffiParameterTypes.length) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER,
+ errorNode,
+ [formalParameters.length + 1, ffiParameterTypes.length]);
+ return;
}

- // Arguments can only be Pointer if the class extends
- // Pointer or NativeFieldWrapperClass1.
- for (var i = 0; i < formalParameters.length; i++) {
- if (ffiParameterTypes[i].isPointer) {
- final type =
- formalParameters[i].declaredElement!.type as InterfaceType;
- if (!type.isPointer && !_extendsNativeFieldWrapperClass1(type)) {
- _errorReporter.reportErrorForNode(
- FfiCode
- .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
- errorNode);
- }
+ // Receiver can only be Pointer if the class extends
+ // NativeFieldWrapperClass1.
+ if (ffiSignature.normalParameterTypes[0].isPointer) {
+ final cls = declarationElement.enclosingElement as InterfaceElement;
+ if (!_extendsNativeFieldWrapperClass1(cls.thisType)) {
+ _errorReporter.reportErrorForNode(
+ FfiCode
+ .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
+ errorNode);
}
}

- final dartType = declarationElement.type;
- final nativeType = FunctionTypeImpl(
- typeFormals: ffiSignature.typeFormals,
- parameters: ffiParameters,
- returnType: ffiSignature.returnType,
- nullabilitySuffix: ffiSignature.nullabilitySuffix,
- );
- if (!_isValidFfiNativeFunctionType(nativeType)) {
+ ffiParameterTypes = ffiParameterTypes.sublist(1);
+ ffiParameters = ffiParameters.sublist(1);
+ } else {
+ // Number of parameters in the Native annotation must match the
+ // annotated declaration.
+ if (formalParameters.length != ffiParameterTypes.length) {
_errorReporter.reportErrorForNode(
- FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
+ FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS,
errorNode,
- [nativeType, 'Native']);
+ [ffiParameterTypes.length, formalParameters.length]);
return;
}
- if (!_validateCompatibleFunctionTypes(dartType, nativeType,
- nativeFieldWrappersAsPointer: true, allowStricterReturn: true)) {
- _errorReporter.reportErrorForNode(FfiCode.MUST_BE_A_SUBTYPE, errorNode,
- [nativeType, dartType, 'Native']);
- return;
+ }
+
+ // Arguments can only be Pointer if the class extends
+ // Pointer or NativeFieldWrapperClass1.
+ for (var i = 0; i < formalParameters.length; i++) {
+ if (ffiParameterTypes[i].isPointer) {
+ final type = formalParameters[i].declaredElement!.type as InterfaceType;
+ if (!type.isPointer && !_extendsNativeFieldWrapperClass1(type)) {
+ _errorReporter.reportErrorForNode(
+ FfiCode
+ .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
+ errorNode);
+ }
}
}
+
+ final dartType = declarationElement.type;
+ final nativeType = FunctionTypeImpl(
+ typeFormals: ffiSignature.typeFormals,
+ parameters: ffiParameters,
+ returnType: ffiSignature.returnType,
+ nullabilitySuffix: ffiSignature.nullabilitySuffix,
+ );
+ if (!_isValidFfiNativeFunctionType(nativeType)) {
+ _errorReporter.reportErrorForNode(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
+ errorNode, [nativeType, 'Native']);
+ return;
+ }
+ if (!_validateCompatibleFunctionTypes(dartType, nativeType,
+ nativeFieldWrappersAsPointer: true, allowStricterReturn: true)) {
+ _errorReporter.reportErrorForNode(FfiCode.MUST_BE_A_SUBTYPE, errorNode,
+ [nativeType, dartType, 'Native']);
+ return;
+ }
}

bool _extendsNativeFieldWrapperClass1(InterfaceType? type) {
@@ -1166,8 +1242,8 @@
final targetType = typeArguments[0];
var validTarget = false;

- final referencedElement = switch (argument) {
- Identifier() => argument.staticElement,
+ var referencedElement = switch (argument) {
+ Identifier() => argument.staticElement?.nonSynthetic,
_ => null,
};

@@ -1176,25 +1252,36 @@
final value = annotation.computeConstantValue();

if (value?.type?.element.isNative == true) {
- final functionType = (value!.type as InterfaceType).typeArguments[0];
+ final nativeType = (value!.type as InterfaceType).typeArguments[0];

- // When referencing a function, the target type must be a
- // `NativeFunction<T>` so that `T` matches the type from the
- // annotation.
- if (!targetType.isNativeFunction) {
- _errorReporter.reportErrorForNode(
- FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
- node,
- [targetType, _nativeAddressOf],
- );
+ if (nativeType is FunctionType) {
+ // If a native function is being referenced, the target type must be
+ // a `NativeFunction<T>` with a matching `T` as well.
+ if (!targetType.isNativeFunction) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
+ node,
+ [targetType, _nativeAddressOf],
+ );
+ } else {
+ final targetFunctionType =
+ (targetType as InterfaceType).typeArguments[0];
+ if (!typeSystem.isAssignableTo(nativeType, targetFunctionType)) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.MUST_BE_A_SUBTYPE,
+ node,
+ [nativeType, targetFunctionType, _nativeAddressOf],
+ );
+ }
+ }
} else {
- final targetFunctionType =
- (targetType as InterfaceType).typeArguments[0];
- if (!typeSystem.isAssignableTo(functionType, targetFunctionType)) {
+ // A native field is being referenced, this doesn't require a
+ // NativeFunction wrapper.
+ if (!typeSystem.isAssignableTo(nativeType, targetType)) {
_errorReporter.reportErrorForNode(
FfiCode.MUST_BE_A_SUBTYPE,
node,
- [functionType, targetFunctionType, _nativeAddressOf],
+ [nativeType, targetType, _nativeAddressOf],
);
}
}
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 8033707..a19ba30 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -18229,6 +18229,16 @@
comment: |-
Parameters:
0: the name of the argument
+ NATIVE_FIELD_TYPE:
+ problemMessage: "'{0}' is an invalid type for fields annotated with `@Native`."
+ correctionMessage: "Try changing the type in the `@Native` annotation to a numeric FFI type, a pointer, or a compound class."
+ comment: |-
+ Parameters:
+ 0: The invalid type.
+ NATIVE_FIELD_NOT_STATIC:
+ problemMessage: "Native fields must be static."
+ correctionMessage: "Try adding a static modifier to this field."
+ comment: "No parameters"
COMPOUND_IMPLEMENTS_FINALIZABLE:
problemMessage: "The class '{0}' can't implement Finalizable."
correctionMessage: "Try removing the implements clause from '{0}'."
diff --git a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
index 9760629..030325a 100644
--- a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
@@ -28,6 +28,21 @@
]);
}

+ test_invalid_MismatchingFieldType() async {
+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native<Int32>()
+external int foo;
+
+void main() {
+ print(Native.addressOf<Double>(foo));
+}
+''', [
+ error(FfiCode.MUST_BE_A_SUBTYPE, 78, 29),
+ ]);
+ }
+
test_invalid_MismatchingType() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
@@ -97,6 +112,19 @@
}
''');
}
+
+ test_valid_field() async {
+ await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native<Int32>()
+external int foo;
+
+void main() {
+ print(Native.addressOf<Int32>(foo));
+}
+''');
+ }
}

@reflectiveTest
@@ -298,6 +326,68 @@
]);
}

+ test_Native_FieldHandle() async {
+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native<Handle>()
+external Object foo;
+''', [
+ error(FfiCode.NATIVE_FIELD_TYPE, 54, 3),
+ ]);
+ }
+
+ test_Native_FieldMismatchingType() async {
+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native<Int32>()
+external double foo;
+''', [
+ error(FfiCode.MUST_BE_A_SUBTYPE, 53, 3),
+ ]);
+ }
+
+ test_Native_FieldNativeFunction() async {
+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native<NativeFunction<Void Function()>>()
+external NativeFunction<Void Function()> foo;
+''', [
+ error(FfiCode.NATIVE_FIELD_TYPE, 104, 3, contextMessages: [
+ message('/sdk/lib/ffi/ffi.dart', 2256, 14),
+ message('/sdk/lib/ffi/ffi.dart', 225, 4),
+ message('/sdk/lib/ffi/ffi.dart', 2256, 14),
+ message('/sdk/lib/ffi/ffi.dart', 225, 4)
+ ]),
+ ]);
+ }
+
+ test_Native_FieldNotStatic() async {
+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+class MyClass {
+ @Native<Int32>()
+ external int foo;
+}
+''', [
+ error(FfiCode.NATIVE_FIELD_NOT_STATIC, 70, 3),
+ ]);
+ }
+
+ test_Native_FieldValid() async {
+ await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+class MyClass {
+ @Native<Int32>()
+ external static int foo;
+}
+''');
+ }
+
test_NativeCanUseHandles() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
diff --git a/pkg/front_end/lib/src/api_unstable/vm.dart b/pkg/front_end/lib/src/api_unstable/vm.dart
index 0c4b48c..8234e55 100644
--- a/pkg/front_end/lib/src/api_unstable/vm.dart
+++ b/pkg/front_end/lib/src/api_unstable/vm.dart
@@ -67,6 +67,8 @@
messageFfiLeafCallMustNotReturnHandle,
messageFfiLeafCallMustNotTakeHandle,
messageFfiNativeMustBeExternal,
+ messageFfiNativeFieldMustBeStatic,
+ messageFfiNativeFieldType,
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
messageFfiPackedAnnotationAlignment,
messageNonPositiveArrayDimensions,
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index a8d5d6d..91ac367 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -5164,7 +5164,19 @@

FfiNativeMustBeExternal:
# Used by dart:ffi
- problemMessage: "Native functions must be marked external."
+ problemMessage: "Native functions and fields must be marked external."
+ external: test/ffi_test.dart
+
+FfiNativeFieldMustBeStatic:
+ # Used by dart:ffi
+ problemMessage: "Native fields must be static."
+ analyzerCode: NATIVE_FIELD_NOT_STATIC
+ external: test/ffi_test.dart
+
+FfiNativeFieldType:
+ # Used by dart:ffi
+ problemMessage: "Unsupported type for native fields. Native fields only support pointers or numeric and compound types."
+ analyzerCode: NATIVE_FIELD_TYPE
external: test/ffi_test.dart

FfiAddressOfMustBeNative:
diff --git a/pkg/vm/lib/transformations/ffi/native.dart b/pkg/vm/lib/transformations/ffi/native.dart
index 87e8bbf..8274e93 100644
--- a/pkg/vm/lib/transformations/ffi/native.dart
+++ b/pkg/vm/lib/transformations/ffi/native.dart
@@ -4,12 +4,15 @@

import 'package:front_end/src/api_unstable/vm.dart'
show
+ messageFfiNativeFieldMustBeStatic,
+ messageFfiNativeFieldType,
messageFfiNativeMustBeExternal,
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
templateCantHaveNamedParameters,
templateCantHaveOptionalParameters,
templateFfiNativeUnexpectedNumberOfParameters,
- templateFfiNativeUnexpectedNumberOfParametersWithReceiver;
+ templateFfiNativeUnexpectedNumberOfParametersWithReceiver,
+ templateFfiTypeInvalid;

import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
@@ -19,7 +22,7 @@
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
import 'package:kernel/type_environment.dart';

-import 'common.dart' show FfiStaticTypeError, FfiTransformer;
+import 'common.dart' show FfiStaticTypeError, FfiTransformer, NativeType;

/// Transform @Native annotated functions into FFI native function pointer
/// functions.
@@ -59,6 +62,7 @@
final Field resolverField;

StringConstant? currentAsset;
+ List<(Field, InterfaceType, InstanceConstant)>? _fieldsToReplace;

// VariableDeclaration names can be null or empty string, in which case
// they're automatically assigned a "temporary" name like `#t0`.
@@ -89,11 +93,33 @@
currentAsset = (annotation.constant as InstanceConstant)
.fieldValues[assetAssetField.fieldReference] as StringConstant;
}
+
+ final nativeFields = _fieldsToReplace = [];
final result = super.visitLibrary(node);
+
+ for (final (field, type, native) in nativeFields) {
+ _replaceNativeField(
+ field, type, native, node.addProcedure, node.fields.remove);
+ }
currentAsset = null;
return result;
}

+ @override
+ TreeNode visitClass(Class node) {
+ final savedFields = _fieldsToReplace;
+ final nativeFieldsInClass = _fieldsToReplace = [];
+
+ TreeNode result = super.visitClass(node);
+ for (final (field, type, native) in nativeFieldsInClass) {
+ _replaceNativeField(
+ field, type, native, node.addProcedure, node.fields.remove);
+ }
+ _fieldsToReplace = savedFields;
+
+ return result;
+ }
+
ConstantExpression? tryGetAnnotation(
Annotatable node, List<Class> instanceOf) {
for (final Expression annotation in node.annotations) {
@@ -130,6 +156,26 @@
return false;
}

+ StringConstant _resolveNativeSymbolName(
+ Member member, InstanceConstant native) {
+ final nativeFunctionConst =
+ native.fieldValues[nativeSymbolField.fieldReference];
+ return nativeFunctionConst is StringConstant
+ ? nativeFunctionConst
+ : StringConstant(member.name.text);
+ }
+
+ StringConstant? _resolveAssetName(InstanceConstant native) {
+ final assetConstant = native.fieldValues[nativeAssetField.fieldReference];
+ return assetConstant is StringConstant ? assetConstant : currentAsset;
+ }
+
+ bool _isLeaf(InstanceConstant native) {
+ return (native.fieldValues[nativeIsLeafField.fieldReference]
+ as BoolConstant)
+ .value;
+ }
+
// Replaces parameters with Pointer if:
// 1) they extend NativeFieldWrapperClass1, and
// 2) the corresponding FFI parameter is Pointer.
@@ -402,6 +448,29 @@
}

static const vmFfiNative = 'vm:ffi:native';
+ static const vmFfiNativeField = 'vm:ffi:native-field';
+
+ /// Creates a `Native` constant with all fields resolved.
+ ///
+ /// We can't re-use the constant from the `@Native` annotation because the
+ /// asset may be inferred from the library.
+ InstanceConstant _createResolvedNativeConstant({
+ required DartType nativeType,
+ required StringConstant nativeName,
+ required bool isLeaf,
+ StringConstant? assetName,
+ }) {
+ return InstanceConstant(
+ nativeClass.reference,
+ [nativeType],
+ {
+ nativeSymbolField.fieldReference: nativeName,
+ nativeAssetField.fieldReference:
+ assetName ?? StringConstant(currentLibrary.importUri.toString()),
+ nativeIsLeafField.fieldReference: BoolConstant(isLeaf),
+ },
+ );
+ }

Procedure _transformProcedure(
Procedure node,
@@ -427,16 +496,12 @@
final pragmaConstant = ConstantExpression(
InstanceConstant(pragmaClass.reference, [], {
pragmaName.fieldReference: StringConstant(vmFfiNative),
- pragmaOptions.fieldReference: InstanceConstant(
- nativeClass.reference,
- [ffiFunctionType],
- {
- nativeSymbolField.fieldReference: nativeFunctionName,
- nativeAssetField.fieldReference: assetName ??
- StringConstant(currentLibrary.importUri.toString()),
- nativeIsLeafField.fieldReference: BoolConstant(isLeaf),
- },
- )
+ pragmaOptions.fieldReference: _createResolvedNativeConstant(
+ nativeType: ffiFunctionType,
+ nativeName: nativeFunctionName,
+ isLeaf: isLeaf,
+ assetName: assetName,
+ ),
}),
InterfaceType(
pragmaClass,
@@ -655,36 +720,283 @@

final ffiConstant = ffiNativeAnnotation.constant as InstanceConstant;
final nativeType = ffiConstant.typeArguments[0];
- try {
- final nativeFunctionType = InterfaceType(
- nativeFunctionClass, Nullability.nonNullable, [nativeType]);
- ensureNativeTypeValid(nativeFunctionType, ffiNativeAnnotation,
- allowCompounds: true, allowHandle: true);
- } on FfiStaticTypeError {
- // We've already reported an error.
+ final isFunction = nativeType is FunctionType;
+
+ final nativeName = _resolveNativeSymbolName(node, ffiConstant);
+ final assetName = _resolveAssetName(ffiConstant);
+ final isLeaf = _isLeaf(ffiConstant);
+
+ if (isFunction) {
+ try {
+ final nativeFunctionType = InterfaceType(
+ nativeFunctionClass, Nullability.nonNullable, [nativeType]);
+ ensureNativeTypeValid(nativeFunctionType, ffiNativeAnnotation,
+ allowCompounds: true, allowHandle: true);
+ } on FfiStaticTypeError {
+ // We've already reported an error.
+ return node;
+ }
+ final ffiFunctionType = ffiConstant.typeArguments[0] as FunctionType;
+
+ if (!node.isStatic) {
+ return _transformInstanceMethod(node, ffiFunctionType, nativeName,
+ assetName, isLeaf, ffiNativeAnnotation.fileOffset);
+ }
+
+ return _transformStaticFunction(node, ffiFunctionType, nativeName,
+ assetName, isLeaf, ffiNativeAnnotation.fileOffset);
+ } else {
+ if (node.kind == ProcedureKind.Getter ||
+ node.kind == ProcedureKind.Setter) {
+ try {
+ final type = node.kind == ProcedureKind.Getter
+ ? node.function.returnType
+ : node.function.positionalParameters[0].type;
+
+ _checkValidNativeField(node, node.isStatic, nativeType, type);
+ } on FfiStaticTypeError {
+ return node;
+ }
+
+ final resolved = _createResolvedNativeConstant(
+ nativeType: nativeType,
+ nativeName: nativeName,
+ isLeaf: isLeaf,
+ assetName: assetName,
+ );
+
+ node.isExternal = false;
+
+ if (node.kind == ProcedureKind.Getter) {
+ node.function.body = ReturnStatement(
+ _loadField(node, nativeType as InterfaceType, resolved));
+ node.annotations.add(ConstantExpression(
+ InstanceConstant(pragmaClass.reference, [], {
+ pragmaName.fieldReference: StringConstant(vmFfiNativeField),
+ pragmaOptions.fieldReference: resolved,
+ }),
+ InterfaceType(
+ pragmaClass,
+ Nullability.nonNullable,
+ [],
+ ),
+ ));
+ } else {
+ node.function.body = ExpressionStatement(_storeField(
+ node,
+ nativeType as InterfaceType,
+ resolved,
+ node.function.positionalParameters[0],
+ ));
+ }
+ } else {
+ // There's a function not annotated with a native function type, which
+ // is invalid.
+ diagnosticReporter.report(
+ templateFfiTypeInvalid.withArguments(
+ nativeType, currentLibrary.isNonNullableByDefault),
+ node.fileOffset,
+ 1,
+ node.location?.file);
+ }
+
return node;
}
- final ffiFunctionType = ffiConstant.typeArguments[0] as FunctionType;
- final nativeFunctionConst =
- ffiConstant.fieldValues[nativeSymbolField.fieldReference];
- final nativeFunctionName = nativeFunctionConst is StringConstant
- ? nativeFunctionConst
- : StringConstant(node.name.text);
- final assetConstant =
- ffiConstant.fieldValues[nativeAssetField.fieldReference];
- final assetName =
- assetConstant is StringConstant ? assetConstant : currentAsset;
- final isLeaf = (ffiConstant.fieldValues[nativeIsLeafField.fieldReference]
- as BoolConstant)
- .value;
+ }

- if (!node.isStatic) {
- return _transformInstanceMethod(node, ffiFunctionType, nativeFunctionName,
- assetName, isLeaf, ffiNativeAnnotation.fileOffset);
+ void _checkValidNativeField(
+ Member node, bool static, DartType ffiType, DartType dartType) {
+ if (!static) {
+ diagnosticReporter.report(messageFfiNativeFieldMustBeStatic,
+ node.fileOffset, 1, node.location?.file);
}

- return _transformStaticFunction(node, ffiFunctionType, nativeFunctionName,
- assetName, isLeaf, ffiNativeAnnotation.fileOffset);
+ ensureNativeTypeValid(ffiType, node, allowCompounds: true);
+ ensureNativeTypeToDartType(ffiType, dartType, node);
+
+ // Only allow compound, pointer and numeric types.
+ if (isCompoundSubtype(ffiType)) return;
+
+ final type = switch (ffiType) {
+ InterfaceType(:var classNode) => getType(classNode),
+ _ => null,
+ };
+
+ if (type == null || type == NativeType.kNativeFunction) {
+ diagnosticReporter.report(
+ messageFfiNativeFieldType, node.fileOffset, 1, node.location?.file);
+ throw FfiStaticTypeError();
+ }
+ }
+
+ @override
+ TreeNode visitField(Field node) {
+ final nativeAnnotation = tryGetNativeAnnotation(node);
+ if (nativeAnnotation == null) {
+ return node;
+ }
+
+ // @Native fields must be external and static.
+ if (!node.isExternal) {
+ diagnosticReporter.report(messageFfiNativeMustBeExternal, node.fileOffset,
+ 1, node.location?.file);
+ return node;
+ }
+
+ node.annotations.remove(nativeAnnotation);
+ final annotationValue = nativeAnnotation.constant as InstanceConstant;
+
+ // The type of the field must match the type argument of the annotation.
+ final nativeType = annotationValue.typeArguments[0];
+ final dartType = node.type;
+ try {
+ _checkValidNativeField(node, node.isStatic, nativeType, dartType);
+ } on FfiStaticTypeError {
+ // An error has been reported already.
+ return node;
+ }
+
+ final resolvedValue = _createResolvedNativeConstant(
+ nativeType: nativeType,
+ nativeName: _resolveNativeSymbolName(node, annotationValue),
+ isLeaf: _isLeaf(annotationValue),
+ assetName: _resolveAssetName(annotationValue),
+ );
+ if (nativeType is InterfaceType) {
+ _fieldsToReplace?.add((node, nativeType, resolvedValue));
+ }
+
+ return node;
+ }
+
+ Expression _addressOfField(
+ Member node, InterfaceType ffiType, InstanceConstant native) {
+ return StaticInvocation(
+ nativePrivateAddressOf,
+ Arguments([ConstantExpression(native)], types: [ffiType]),
+ )..fileOffset = node.fileOffset;
+ }
+
+ Expression _loadField(
+ Member node, InterfaceType ffiType, InstanceConstant native) {
+ final ptr = _addressOfField(node, ffiType, native);
+
+ if (getType(ffiType.classNode) case NativeType nt) {
+ return StaticInvocation(
+ loadMethods[nt]!,
+ Arguments([
+ ptr,
+ ConstantExpression(IntConstant(0)),
+ ]),
+ )..fileOffset = node.fileOffset;
+ } else if (isAbiSpecificIntegerSubtype(ffiType)) {
+ return StaticInvocation(
+ abiSpecificIntegerPointerGetValue, Arguments([ptr], types: [ffiType]))
+ ..fileOffset = node.fileEndOffset;
+ } else {
+ assert(isCompoundSubtype(ffiType));
+
+ return StaticInvocation(
+ structPointerGetRef, Arguments([ptr], types: [ffiType]))
+ ..fileOffset = node.fileEndOffset;
+ }
+ }
+
+ Expression _storeField(Member node, InterfaceType ffiType,
+ InstanceConstant native, VariableDeclaration arg) {
+ final ptr = _addressOfField(node, ffiType, native);
+ final value = VariableGet(arg);
+
+ if (getType(ffiType.classNode) case NativeType nt) {
+ return StaticInvocation(
+ storeMethods[nt]!,
+ Arguments([
+ ptr,
+ ConstantExpression(IntConstant(0)),
+ value,
+ ]),
+ )..fileOffset = node.fileOffset;
+ } else if (isAbiSpecificIntegerSubtype(ffiType)) {
+ assert(isAbiSpecificIntegerSubtype(ffiType));
+
+ return StaticInvocation(
+ abiSpecificIntegerPointerSetValue,
+ Arguments([
+ ptr,
+ value,
+ ], types: [
+ ffiType
+ ]))
+ ..fileOffset = node.fileEndOffset;
+ } else {
+ assert(isCompoundSubtype(ffiType));
+
+ return StaticInvocation(
+ structPointerSetRef,
+ Arguments([
+ ptr,
+ value,
+ ], types: [
+ ffiType
+ ]))
+ ..fileOffset = node.fileEndOffset;
+ }
+ }
+
+ void _replaceNativeField(
+ Field node,
+ InterfaceType ffiType,
+ InstanceConstant native,
+ void Function(Procedure) addProcedure,
+ void Function(Field) removeField,
+ ) {
+ final getter = Procedure(
+ node.name,
+ ProcedureKind.Getter,
+ FunctionNode(
+ ReturnStatement(_loadField(node, ffiType, native)),
+ returnType: node.type,
+ ),
+ fileUri: node.fileUri,
+ reference: node.getterReference,
+ )
+ ..fileOffset = node.fileOffset
+ ..isNonNullableByDefault = node.isNonNullableByDefault
+ ..annotations.add(ConstantExpression(
+ InstanceConstant(pragmaClass.reference, [], {
+ pragmaName.fieldReference: StringConstant(vmFfiNativeField),
+ pragmaOptions.fieldReference: native,
+ }),
+ InterfaceType(
+ pragmaClass,
+ Nullability.nonNullable,
+ [],
+ ),
+ ));
+ addProcedure(getter);
+
+ if (node.setterReference case Reference setter) {
+ final argument =
+ VariableDeclaration('#v', type: node.type, isSynthesized: true)
+ ..fileOffset = node.fileOffset;
+
+ addProcedure(Procedure(
+ node.name,
+ ProcedureKind.Setter,
+ FunctionNode(
+ ExpressionStatement(_storeField(node, ffiType, native, argument))
+ ..fileOffset = node.fileOffset,
+ returnType: VoidType(),
+ positionalParameters: [argument],
+ ),
+ fileUri: node.fileUri,
+ reference: setter,
+ )
+ ..fileOffset = node.fileOffset
+ ..isNonNullableByDefault = node.isNonNullableByDefault);
+ }
+
+ removeField(node);
}

/// Checks whether the FFI function type is valid and reports any errors.
diff --git a/pkg/vm/lib/transformations/ffi/use_sites.dart b/pkg/vm/lib/transformations/ffi/use_sites.dart
index 3eacd39..c23b6db 100644
--- a/pkg/vm/lib/transformations/ffi/use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi/use_sites.dart
@@ -1048,12 +1048,13 @@
final arg = node.arguments.positional.single;
final nativeType = node.arguments.types.single;

- // `x` must be a method annotated with `@Native`, so referencing it makes
- // it a tear-off.
+ Constant? nativeAnnotation;
+
+ // `x` must either be a method annoted with `@Native` (making the reference
+ // a tear off) or a field (making the reference a getter invocation).
if (arg case ConstantExpression(constant: StaticTearOffConstant method)) {
// The method must have the `vm:ffi:native` pragma added by the native
// transformer.
- Constant? nativeAnnotation;
for (final annotation in method.target.annotations) {
if (annotation
case ConstantExpression(constant: final InstanceConstant c)) {
@@ -1077,16 +1078,41 @@

ensureNativeTypeValid(nativeType, node);
ensureNativeTypeToDartType(nativeType, arg.type, node);
+ } else if (arg case StaticGet(:var targetReference)) {
+ for (final annotation in targetReference.asMember.annotations) {
+ if (annotation
+ case ConstantExpression(constant: final InstanceConstant c)) {
+ if (c.classNode == coreTypes.pragmaClass) {
+ final name = c.fieldValues[coreTypes.pragmaName.fieldReference];
+ if (name is StringConstant &&
+ name.value == FfiNativeTransformer.vmFfiNativeField) {
+ nativeAnnotation =
+ c.fieldValues[coreTypes.pragmaOptions.fieldReference]!;
+ break;
+ }
+ }
+ }
+ }

- return StaticInvocation(
- nativePrivateAddressOf,
- Arguments([ConstantExpression(nativeAnnotation)], types: [nativeType]),
- )..fileOffset = arg.fileOffset;
+ if (nativeAnnotation == null) {
+ diagnosticReporter.report(messageFfiAddressOfMustBeNative,
+ arg.fileOffset, 1, node.location?.file);
+ return node;
+ }
+
+ ensureNativeTypeValid(nativeType, node);
+ ensureNativeTypeToDartType(
+ nativeType, arg.getStaticType(staticTypeContext!), node);
} else {
diagnosticReporter.report(messageFfiAddressOfMustBeNative, arg.fileOffset,
1, node.location?.file);
return node;
}
+
+ return StaticInvocation(
+ nativePrivateAddressOf,
+ Arguments([ConstantExpression(nativeAnnotation)], types: [nativeType]),
+ )..fileOffset = arg.fileOffset;
}
}

diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart b/pkg/vm/testcases/transformations/ffi/ffinative.dart
index 313780c..0475574 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative.dart
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart
@@ -53,8 +53,14 @@
@Native<Void Function(Pointer<Void>, Bool)>(
symbol: 'doesntmatter', isLeaf: true)
external set myField(bool value);
+
+ @Native<Double>()
+ external static double nativeDouble;
}

+@Native<Int>()
+external final int myAbiSpecificField;
+
void main() {
returnIntPtr(13);
returnIntPtrLeaf(37);
@@ -70,4 +76,5 @@
NativeClassy().myField = !b;

Native.addressOf<NativeFunction<IntPtr Function(IntPtr)>>(returnIntPtr);
+ print('native fields: $myAbiSpecificField, ${NativeClassy.nativeDouble}');
}
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
index 8c85998..b22bf48 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
@@ -112,29 +112,33 @@
} =>#pointerAddress), #t18);
_in::reachabilityFence(#t17);
} =>#t19;
-[@vm.unboxing-info.metadata=(b,i)->b] @#C16
- external static method _goodHasReceiverPointer$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 175)] core::int #t1) → void;
+[@vm.unboxing-info.metadata=()->d] static get nativeDouble() → core::double
+ return [@vm.inferred-type.metadata=dart.core::_Double] ffi::_loadDouble([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Double>(#C15), #C11);
[@vm.unboxing-info.metadata=(b,i)->b] @#C18
+ external static method _goodHasReceiverPointer$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 175)] core::int #t1) → void;
+[@vm.unboxing-info.metadata=(b,i)->b] @#C20
external static method _goodHasReceiverHandle$Method$FfiNative([@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t0, [@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 175)] core::int #t1) → void;
- @#C20
- external static method _goodHasReceiverHandleAndPtr$Method$FfiNative([@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t0, [@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t1) → void;
@#C22
- external static method _goodHasReceiverHandleAndHandle$Method$FfiNative([@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t0, [@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t1) → void;
+ external static method _goodHasReceiverHandleAndPtr$Method$FfiNative([@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t0, [@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t1) → void;
@#C24
- external static method _goodHasReceiverPtrAndHandle$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t1) → void;
+ external static method _goodHasReceiverHandleAndHandle$Method$FfiNative([@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t0, [@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t1) → void;
@#C26
- external static method _meh$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=dart.core::bool (value: true)] core::bool #t1) → core::Object?;
+ external static method _goodHasReceiverPtrAndHandle$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t1) → void;
@#C28
- external static method _blah$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0) → core::bool;
+ external static method _meh$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=dart.core::bool (value: true)] core::bool #t1) → core::Object?;
@#C30
- external static method _myField$Getter$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0) → core::bool;
+ external static method _blah$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0) → core::bool;
@#C32
+ external static method _myField$Getter$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0) → core::bool;
+ @#C34
external static method _myField$Setter$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=dart.core::bool] core::bool #t1) → void;
}
[@vm.unboxing-info.metadata=(i)->i]@#C6
external static method returnIntPtr([@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 13)] core::int x) → core::int;
-[@vm.unboxing-info.metadata=(i)->i]@#C34
+[@vm.unboxing-info.metadata=(i)->i]@#C36
external static method returnIntPtrLeaf([@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 37)] core::int x) → core::int;
+[@vm.unboxing-info.metadata=()->i]static get myAbiSpecificField() → core::int
+ return [@vm.inferred-type.metadata=int] ffi::_loadAbiSpecificInt<ffi::Int>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Int>(#C38), #C11);
static method main() → void {
self::returnIntPtr(13);
self::returnIntPtrLeaf(37);
@@ -149,6 +153,7 @@
final core::bool b = [@vm.direct-call.metadata=#lib::NativeClassy.myField] [@vm.inferred-type.metadata=dart.core::bool] new self::NativeClassy::•().{self::NativeClassy::myField}{core::bool};
[@vm.direct-call.metadata=#lib::NativeClassy.myField] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::myField} = !b;
ffi::Native::_addressOf<ffi::NativeFunction<(ffi::IntPtr) → ffi::IntPtr>>(#C5);
+ core::print("native fields: ${self::myAbiSpecificField}, ${self::NativeClassy::nativeDouble}");
}
constants {
#C1 = "vm:ffi:native"
@@ -164,25 +169,29 @@
#C11 = 0
#C12 = "A Dart object attempted to access a native peer, but the native peer has been collected (nullptr). This is usually the result of calling methods on a native-backed object when the native resources have already been disposed."
#C13 = true
- #C14 = "doesntmatter"
- #C15 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::IntPtr) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
- #C16 = core::pragma {name:#C1, options:#C15}
- #C17 = ffi::Native<(ffi::Handle, ffi::IntPtr) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+ #C14 = "nativeDouble"
+ #C15 = ffi::Native<ffi::Double> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+ #C16 = "doesntmatter"
+ #C17 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::IntPtr) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C18 = core::pragma {name:#C1, options:#C17}
- #C19 = ffi::Native<(ffi::Handle, ffi::Pointer<ffi::Void>) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+ #C19 = ffi::Native<(ffi::Handle, ffi::IntPtr) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C20 = core::pragma {name:#C1, options:#C19}
- #C21 = ffi::Native<(ffi::Handle, ffi::Handle) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+ #C21 = ffi::Native<(ffi::Handle, ffi::Pointer<ffi::Void>) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C22 = core::pragma {name:#C1, options:#C21}
- #C23 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Handle) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+ #C23 = ffi::Native<(ffi::Handle, ffi::Handle) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C24 = core::pragma {name:#C1, options:#C23}
- #C25 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Handle> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+ #C25 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Handle) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C26 = core::pragma {name:#C1, options:#C25}
- #C27 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+ #C27 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Handle> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C28 = core::pragma {name:#C1, options:#C27}
- #C29 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C14, assetId:#C3, isLeaf:#C13}
+ #C29 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C30 = core::pragma {name:#C1, options:#C29}
- #C31 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C13}
+ #C31 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C16, assetId:#C3, isLeaf:#C13}
#C32 = core::pragma {name:#C1, options:#C31}
- #C33 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C13}
+ #C33 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C13}
#C34 = core::pragma {name:#C1, options:#C33}
+ #C35 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C13}
+ #C36 = core::pragma {name:#C1, options:#C35}
+ #C37 = "myAbiSpecificField"
+ #C38 = ffi::Native<ffi::Int> {symbol:#C37, assetId:#C3, isLeaf:#C4}
}
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
index ac08c034..f95f372 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
@@ -115,31 +115,37 @@
} =>#pointerAddress), #t18);
_in::reachabilityFence(#t17);
} =>#t19;
- @#C14
- external static method _goodHasReceiverPointer$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, core::int #t1) → void;
+ static get nativeDouble() → core::double
+ return ffi::_loadDouble(ffi::Native::_addressOf<ffi::Double>(#C13), #C10);
+ static set nativeDouble(core::double #externalFieldValue) → void
+ ffi::_storeDouble(ffi::Native::_addressOf<ffi::Double>(#C13), #C10, #externalFieldValue);
@#C16
- external static method _goodHasReceiverHandle$Method$FfiNative(self::NativeClassy #t0, core::int #t1) → void;
+ external static method _goodHasReceiverPointer$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, core::int #t1) → void;
@#C18
- external static method _goodHasReceiverHandleAndPtr$Method$FfiNative(self::NativeClassy #t0, ffi::Pointer<ffi::Void> #t1) → void;
+ external static method _goodHasReceiverHandle$Method$FfiNative(self::NativeClassy #t0, core::int #t1) → void;
@#C20
- external static method _goodHasReceiverHandleAndHandle$Method$FfiNative(self::NativeClassy #t0, self::NativeClassy #t1) → void;
+ external static method _goodHasReceiverHandleAndPtr$Method$FfiNative(self::NativeClassy #t0, ffi::Pointer<ffi::Void> #t1) → void;
@#C22
- external static method _goodHasReceiverPtrAndHandle$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, self::NativeClassy #t1) → void;
+ external static method _goodHasReceiverHandleAndHandle$Method$FfiNative(self::NativeClassy #t0, self::NativeClassy #t1) → void;
@#C24
- external static method _meh$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, core::bool #t1) → core::Object?;
+ external static method _goodHasReceiverPtrAndHandle$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, self::NativeClassy #t1) → void;
@#C26
+ external static method _meh$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, core::bool #t1) → core::Object?;
+ @#C28
external static method _blah$Method$FfiNative(ffi::Pointer<ffi::Void> #t0) → core::bool;
- @#C29
- external static method _myField$Getter$FfiNative(ffi::Pointer<ffi::Void> #t0) → core::bool;
@#C31
+ external static method _myField$Getter$FfiNative(ffi::Pointer<ffi::Void> #t0) → core::bool;
+ @#C33
external static method _myField$Setter$FfiNative(ffi::Pointer<ffi::Void> #t0, core::bool #t1) → void;
}
@#C6
external static method returnIntPtr(core::int x) → core::int;
-@#C33
+@#C35
external static method returnIntPtrLeaf(core::int x) → core::int;
-@#C36
+@#C38
external static method returnNativeIntPtrLeaf(core::int x) → core::int;
+static get myAbiSpecificField() → core::int
+ return ffi::_loadAbiSpecificInt<ffi::Int>(ffi::Native::_addressOf<ffi::Int>(#C40), #C10);
static method main() → void {
self::returnIntPtr(13);
self::returnIntPtrLeaf(37);
@@ -154,6 +160,7 @@
final core::bool b = new self::NativeClassy::•().{self::NativeClassy::myField}{core::bool};
new self::NativeClassy::•().{self::NativeClassy::myField} = !b;
ffi::Native::_addressOf<ffi::NativeFunction<(ffi::IntPtr) → ffi::IntPtr>>(#C5);
+ core::print("native fields: ${self::myAbiSpecificField}, ${self::NativeClassy::nativeDouble}");
}
constants {
#C1 = "vm:ffi:native"
@@ -167,29 +174,33 @@
#C9 = core::pragma {name:#C7, options:#C8}
#C10 = 0
#C11 = "A Dart object attempted to access a native peer, but the native peer has been collected (nullptr). This is usually the result of calling methods on a native-backed object when the native resources have already been disposed."
- #C12 = "doesntmatter"
- #C13 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::IntPtr) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C4}
- #C14 = core::pragma {name:#C1, options:#C13}
- #C15 = ffi::Native<(ffi::Handle, ffi::IntPtr) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+ #C12 = "nativeDouble"
+ #C13 = ffi::Native<ffi::Double> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+ #C14 = "doesntmatter"
+ #C15 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::IntPtr) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
#C16 = core::pragma {name:#C1, options:#C15}
- #C17 = ffi::Native<(ffi::Handle, ffi::Pointer<ffi::Void>) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+ #C17 = ffi::Native<(ffi::Handle, ffi::IntPtr) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
#C18 = core::pragma {name:#C1, options:#C17}
- #C19 = ffi::Native<(ffi::Handle, ffi::Handle) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+ #C19 = ffi::Native<(ffi::Handle, ffi::Pointer<ffi::Void>) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
#C20 = core::pragma {name:#C1, options:#C19}
- #C21 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Handle) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+ #C21 = ffi::Native<(ffi::Handle, ffi::Handle) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
#C22 = core::pragma {name:#C1, options:#C21}
- #C23 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Handle> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+ #C23 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Handle) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
#C24 = core::pragma {name:#C1, options:#C23}
- #C25 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+ #C25 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Handle> {symbol:#C14, assetId:#C3, isLeaf:#C4}
#C26 = core::pragma {name:#C1, options:#C25}
- #C27 = true
- #C28 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C12, assetId:#C3, isLeaf:#C27}
- #C29 = core::pragma {name:#C1, options:#C28}
- #C30 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C27}
+ #C27 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+ #C28 = core::pragma {name:#C1, options:#C27}
+ #C29 = true
+ #C30 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C14, assetId:#C3, isLeaf:#C29}
#C31 = core::pragma {name:#C1, options:#C30}
- #C32 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C27}
+ #C32 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C29}
#C33 = core::pragma {name:#C1, options:#C32}
- #C34 = "returnNativeIntPtrLeaf"
- #C35 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C34, assetId:#C3, isLeaf:#C27}
- #C36 = core::pragma {name:#C1, options:#C35}
+ #C34 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C29}
+ #C35 = core::pragma {name:#C1, options:#C34}
+ #C36 = "returnNativeIntPtrLeaf"
+ #C37 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C36, assetId:#C3, isLeaf:#C29}
+ #C38 = core::pragma {name:#C1, options:#C37}
+ #C39 = "myAbiSpecificField"
+ #C40 = ffi::Native<ffi::Int> {symbol:#C39, assetId:#C3, isLeaf:#C4}
}
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart
index c66f2e5..5a5ea99 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart
+++ b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart
@@ -9,13 +9,16 @@

void main() {
final result = returnStruct1ByteIntNative(-1);
- print("result = $result");
+ print("result = $result, from field = $nativeField");
}

// ignore: sdk_version_since
@Native<Struct1ByteInt Function(Int8)>(symbol: 'ReturnStruct1ByteInt')
external Struct1ByteInt returnStruct1ByteIntNative(int a0);

+@Native<Struct1ByteInt>()
+external Struct1ByteInt nativeField;
+
final class Struct1ByteInt extends Struct {
@Int8()
external int a0;
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.aot.expect
index a5b3fc6..bafdf4c 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.aot.expect
+++ b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.aot.expect
@@ -9,24 +9,26 @@

@#C6
final class Struct1ByteInt extends ffi::Struct {
- constructor #fromTypedDataBase([@vm.inferred-arg-type.metadata=dart.typed_data::_Uint8List] synthesized core::Object #typedDataBase) → self::Struct1ByteInt
+ constructor #fromTypedDataBase([@vm.inferred-arg-type.metadata=!] synthesized core::Object #typedDataBase) → self::Struct1ByteInt
: super ffi::Struct::_fromTypedDataBase(#typedDataBase)
;
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1] [@vm.unboxing-info.metadata=()->i] get a0() → core::int
- return [@vm.inferred-type.metadata=int] ffi::_loadInt8([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.typed_data::_Uint8List] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C8.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
+ return [@vm.inferred-type.metadata=int] ffi::_loadInt8([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=!] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C8.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:2,getterSelectorId:3] method toString() → core::String
return "(${[@vm.direct-call.metadata=#lib::Struct1ByteInt.a0] this.{self::Struct1ByteInt::a0}{core::int}})";
}
static method main() → void {
final self::Struct1ByteInt result = self::returnStruct1ByteIntNative([@vm.direct-call.metadata=dart.core::_IntegerImplementation.unary-] [@vm.inferred-type.metadata=int (skip check)] 1.{core::int::unary-}(){() → core::int});
- core::print("result = ${result}");
+ core::print("result = ${result}, from field = ${self::nativeField}");
}
[@vm.unboxing-info.metadata=(i)->b]@#C10
static method returnStruct1ByteIntNative([@vm.inferred-arg-type.metadata=int] core::int a0) → self::Struct1ByteInt
return block {
_in::_nativeEffect(new self::Struct1ByteInt::#fromTypedDataBase([@vm.inferred-type.metadata=dart.typed_data::_Uint8List] typ::Uint8List::•(#C11)));
} =>[@vm.inferred-type.metadata=#lib::Struct1ByteInt] self::_returnStruct1ByteIntNative$Method$FfiNative(a0);
-[@vm.unboxing-info.metadata=(i)->b]@#C17
+static get nativeField() → self::Struct1ByteInt
+ return new self::Struct1ByteInt::#fromTypedDataBase(_in::unsafeCast<ffi::Pointer<self::Struct1ByteInt>>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<self::Struct1ByteInt>(#C15)));
+[@vm.unboxing-info.metadata=(i)->b]@#C19
external static method _returnStruct1ByteIntNative$Method$FfiNative([@vm.inferred-arg-type.metadata=int] core::int #t0) → self::Struct1ByteInt;
constants {
#C1 = "vm:ffi:struct-fields"
@@ -40,10 +42,12 @@
#C9 = "vm:prefer-inline"
#C10 = core::pragma {name:#C9, options:#C4}
#C11 = 1
- #C12 = "vm:ffi:native"
- #C13 = "ReturnStruct1ByteInt"
- #C14 = "#lib"
- #C15 = false
- #C16 = ffi::Native<(ffi::Int8) → self::Struct1ByteInt> {symbol:#C13, assetId:#C14, isLeaf:#C15}
- #C17 = core::pragma {name:#C12, options:#C16}
+ #C12 = "nativeField"
+ #C13 = "#lib"
+ #C14 = false
+ #C15 = ffi::Native<self::Struct1ByteInt> {symbol:#C12, assetId:#C13, isLeaf:#C14}
+ #C16 = "vm:ffi:native"
+ #C17 = "ReturnStruct1ByteInt"
+ #C18 = ffi::Native<(ffi::Int8) → self::Struct1ByteInt> {symbol:#C17, assetId:#C13, isLeaf:#C14}
+ #C19 = core::pragma {name:#C16, options:#C18}
}
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.expect b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.expect
index 7471888..efa3dcf 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.expect
+++ b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.expect
@@ -29,14 +29,18 @@
}
static method main() → void {
final self::Struct1ByteInt result = self::returnStruct1ByteIntNative(1.{core::int::unary-}(){() → core::int});
- core::print("result = ${result}");
+ core::print("result = ${result}, from field = ${self::nativeField}");
}
@#C11
static method returnStruct1ByteIntNative(core::int a0) → self::Struct1ByteInt
return block {
_in::_nativeEffect(new self::Struct1ByteInt::#fromTypedDataBase(typ::Uint8List::•(#C12)));
} =>self::_returnStruct1ByteIntNative$Method$FfiNative(a0);
-@#C19
+static get nativeField() → self::Struct1ByteInt
+ return new self::Struct1ByteInt::#fromTypedDataBase(ffi::Native::_addressOf<self::Struct1ByteInt>(#C17)!);
+static set nativeField(self::Struct1ByteInt #externalFieldValue) → void
+ ffi::_memCopy(ffi::Native::_addressOf<self::Struct1ByteInt>(#C17), #C8, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #C8, self::Struct1ByteInt::#sizeOf);
+@#C21
external static method _returnStruct1ByteIntNative$Method$FfiNative(core::int #t0) → self::Struct1ByteInt;
constants {
#C1 = "vm:ffi:struct-fields"
@@ -52,10 +56,12 @@
#C11 = core::pragma {name:#C10, options:#C4}
#C12 = 1
#C13 = <core::int*>[#C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12]
- #C14 = "vm:ffi:native"
- #C15 = "ReturnStruct1ByteInt"
- #C16 = "#lib"
- #C17 = false
- #C18 = ffi::Native<(ffi::Int8) → self::Struct1ByteInt> {symbol:#C15, assetId:#C16, isLeaf:#C17}
- #C19 = core::pragma {name:#C14, options:#C18}
+ #C14 = "nativeField"
+ #C15 = "#lib"
+ #C16 = false
+ #C17 = ffi::Native<self::Struct1ByteInt> {symbol:#C14, assetId:#C15, isLeaf:#C16}
+ #C18 = "vm:ffi:native"
+ #C19 = "ReturnStruct1ByteInt"
+ #C20 = ffi::Native<(ffi::Int8) → self::Struct1ByteInt> {symbol:#C19, assetId:#C15, isLeaf:#C16}
+ #C21 = core::pragma {name:#C18, options:#C20}
}
diff --git a/runtime/bin/ffi_test/ffi_test_functions.cc b/runtime/bin/ffi_test/ffi_test_functions.cc
index 7bc707a..5916acd 100644
--- a/runtime/bin/ffi_test/ffi_test_functions.cc
+++ b/runtime/bin/ffi_test/ffi_test_functions.cc
@@ -25,9 +25,11 @@

#if defined(_WIN32)
#define DART_EXPORT extern "C" __declspec(dllexport)
+#define DART_EXPORT_FIELD DART_EXPORT
#else
#define DART_EXPORT \
extern "C" __attribute__((visibility("default"))) __attribute((used))
+#define DART_EXPORT_FIELD __attribute__((visibility("default")))
#endif

namespace dart {
@@ -44,9 +46,11 @@
// Tests for Dart -> native calls.
//
// Note: If this interface is changed please also update
-// sdk/runtime/tools/dartfuzz/ffiapi.dart
+// sdk/runtime/tools/dartfuzz/dartfuzz_ffi_api.dart

-int32_t globalVar;
+extern "C" {
+DART_EXPORT_FIELD int32_t globalVar;
+}

DART_EXPORT void InduceACrash() {
*reinterpret_cast<int*>(InduceACrash) = 123;
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index a0525d1..206aa88 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -5056,10 +5056,17 @@
String::ZoneHandle(Z, String::RawCast(native.GetField(asset_id_field)));
const auto& type_args = TypeArguments::Handle(Z, native.GetTypeArguments());
ASSERT(type_args.Length() == 1);
- const auto& native_type =
- FunctionType::Cast(AbstractType::ZoneHandle(Z, type_args.TypeAt(0)));
- const intptr_t arg_n =
- native_type.NumParameters() - native_type.num_implicit_parameters();
+ const auto& native_type = AbstractType::ZoneHandle(Z, type_args.TypeAt(0));
+ intptr_t arg_n;
+ if (native_type.IsFunctionType()) {
+ const auto& native_function_type = FunctionType::Cast(native_type);
+ arg_n = native_function_type.NumParameters() -
+ native_function_type.num_implicit_parameters();
+ } else {
+ // We're looking up the address of a native field.
+ arg_n = 0;
+ }
+
const auto& ffi_resolver =
Function::ZoneHandle(Z, IG->object_store()->ffi_resolver_function());

diff --git a/tests/ffi/native_assets/asset_absolute_test.dart b/tests/ffi/native_assets/asset_absolute_test.dart
index 6cd3595..eb6217e 100644
--- a/tests/ffi/native_assets/asset_absolute_test.dart
+++ b/tests/ffi/native_assets/asset_absolute_test.dart
@@ -65,6 +65,7 @@

Future<void> runTests() async {
testFfiTestfunctionsDll();
+ testFfiTestFieldsDll();
testNonExistingFunction();
}

@@ -80,3 +81,20 @@
.asFunction<int Function(int, int)>();
Expect.equals(2 + 3 + 42, viaAddressOf(2, 3));
}
+
+@Native<Int32>(symbol: 'globalVar')
+external int globalVar;
+
+@Native<Void Function(Int32)>()
+external void SetGlobalVar(int value);
+
+@Native<Int32 Function()>()
+external int GetGlobalVar();
+
+void testFfiTestFieldsDll() {
+ SetGlobalVar(42);
+ Expect.equals(globalVar, 42);
+
+ globalVar = 13;
+ Expect.equals(GetGlobalVar(), 13);
+}
diff --git a/tests/ffi/native_assets/asset_library_annotation_test.dart b/tests/ffi/native_assets/asset_library_annotation_test.dart
index 84b5759..f393b84 100644
--- a/tests/ffi/native_assets/asset_library_annotation_test.dart
+++ b/tests/ffi/native_assets/asset_library_annotation_test.dart
@@ -63,6 +63,7 @@

Future<void> runTests() async {
testFfiTestfunctionsDll();
+ testFfiTestFieldsDll();
}

@Native<Int32 Function(Int32, Int32)>()
@@ -77,3 +78,20 @@
final function = ptr.asFunction<int Function(int, int)>();
Expect.equals(2 + 3 + 42, function(2, 3));
}
+
+@Native<Int32>(symbol: 'globalVar')
+external int globalVar;
+
+@Native<Void Function(Int32)>()
+external void SetGlobalVar(int value);
+
+@Native<Int32 Function()>()
+external int GetGlobalVar();
+
+void testFfiTestFieldsDll() {
+ SetGlobalVar(42);
+ Expect.equals(globalVar, 42);
+
+ globalVar = 13;
+ Expect.equals(GetGlobalVar(), 13);
+}
diff --git a/tests/ffi/native_assets/asset_relative_test.dart b/tests/ffi/native_assets/asset_relative_test.dart
index d70f30c..4529626 100644
--- a/tests/ffi/native_assets/asset_relative_test.dart
+++ b/tests/ffi/native_assets/asset_relative_test.dart
@@ -119,6 +119,7 @@

Future<void> runTests() async {
testFfiTestfunctionsDll();
+ testFfiTestFieldsDll();
testNonExistingFunction();
}

@@ -139,3 +140,20 @@
Expect.equals(2 + 3 + 42, namedSumViaAddress(2, 3));
Expect.equals(2 + 3 + 42, unnamedSumViaAddress(2, 3));
}
+
+@Native<Int32>(symbol: 'globalVar')
+external int globalVar;
+
+@Native<Void Function(Int32)>()
+external void SetGlobalVar(int value);
+
+@Native<Int32 Function()>()
+external int GetGlobalVar();
+
+void testFfiTestFieldsDll() {
+ SetGlobalVar(42);
+ Expect.equals(globalVar, 42);
+
+ globalVar = 13;
+ Expect.equals(GetGlobalVar(), 13);
+}
diff --git a/tests/ffi/vmspecific_static_checks_ffinative_test.dart b/tests/ffi/vmspecific_static_checks_ffinative_test.dart
index 0323fe7..489a62d 100644
--- a/tests/ffi/vmspecific_static_checks_ffinative_test.dart
+++ b/tests/ffi/vmspecific_static_checks_ffinative_test.dart
@@ -73,6 +73,19 @@
@Native<double Function(IntPtr)>(symbol: 'doesntmatter') //# 17: compile-time error
external double nonFfiReturnType(int v); //# 17: compile-time error

+// Error: Unsupported type for native fields. Native fields only support pointers or numeric and compound types.
+@Native<NativeFunction<Void Function()>>() //# 20: compile-time error
+external void Function() nativeFunction; //# 20: compile-time error
+
+@Native<Handle>() //# 21: compile-time error
+external Object nativeObject; //# 21: compile-time error
+
+@Native<Opaque>() //# 22: compile-time error
+external Opaque nativeOpaque; //# 22: compile-time error
+
+@Native<Int32>() //# 23: compile-time error
+external double nativeDouble; // #23: compile-time error
+
@Native<Void Function()>()
external void _valid();


To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 3
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Daco Harkes <dacoh...@google.com>

Simon Binder (Gerrit)

unread,
Nov 23, 2023, 5:49:06 PM11/23/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Daco Harkes, Alexander Markov, Paul Berry

Attention is currently required from: Daco Harkes.

View Change

1 comment:

  • File pkg/vm/lib/transformations/ffi/native.dart:

    • Patch Set #3, Line 833: TreeNode visitField(Field node) {

      It looks like this doesn't actually run (whenever I declare an external field it just turns it into a getter/setter), but I don't know if I'm missing something or if this really is impossible.

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 3
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Daco Harkes <dacoh...@google.com>
Gerrit-Comment-Date: Thu, 23 Nov 2023 22:49:01 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No

Daco Harkes (Gerrit)

unread,
Nov 24, 2023, 12:23:32 PM11/24/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Alexander Markov, Paul Berry

Attention is currently required from: Simon Binder.

View Change

5 comments:

  • Patchset:

    • Patch Set #3:

      🔥

      Initial pass over the API.
      (Ignoring most of the implementation details for now, because the parent CL is still in flux.)

  • File pkg/analyzer/messages.yaml:

    • Patch Set #3, Line 18233: problemMessage: "'{0}' is an invalid type for fields annotated with `@Native`."

      Consistency with error message in CFE.

  • File pkg/vm/lib/transformations/ffi/native.dart:

    • It looks like this doesn't actually run (whenever I declare an external field it just turns it into […]

      There are transformations _before_ this FFI transformation runs. So it might be transformed before.

      And yes, `external` fields are defined as an external getter and setter pair in the language spec. "external field" as a field makes no sense from the concept of a Dart object. So that's why it's an external getter setter pair.

  • File pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart:

  • File tests/ffi/native_assets/asset_absolute_test.dart:

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 3
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Comment-Date: Fri, 24 Nov 2023 17:23:26 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Simon Binder <o...@simonbinder.eu>

Daco Harkes (Gerrit)

unread,
Nov 24, 2023, 12:23:57 PM11/24/23
to Brian Wilkerson, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Simon Binder

Attention is currently required from: Brian Wilkerson, Simon Binder.

Daco Harkes would like Brian Wilkerson to review this change authored by Simon Binder.

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 3
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Attention: Brian Wilkerson <brianwi...@google.com>

Daco Harkes (Gerrit)

unread,
Nov 24, 2023, 12:23:59 PM11/24/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Brian Wilkerson, Alexander Markov, Paul Berry

Attention is currently required from: Brian Wilkerson, Simon Binder.

View Change

1 comment:

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 3
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Attention: Brian Wilkerson <brianwi...@google.com>
Gerrit-Comment-Date: Fri, 24 Nov 2023 17:23:52 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No

Simon Binder (Gerrit)

unread,
Nov 28, 2023, 3:15:22 PM11/28/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Brian Wilkerson, Simon Binder.

Simon Binder uploaded patch set #4 to this change.

View Change

23 files changed, 890 insertions(+), 210 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 4

Brian Wilkerson (Gerrit)

unread,
Nov 29, 2023, 11:48:02 AM11/29/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Brian Wilkerson, Daco Harkes, Alexander Markov, Paul Berry

Attention is currently required from: Simon Binder.

Patch set 4:Code-Review +1

View Change

2 comments:

  • Patchset:

    • Patch Set #4:

      The analyzer and server pieces lgtm. I didn't look at the rest of the changes.

  • File pkg/analyzer/messages.yaml:

    • Patch Set #4, Line 18240: a static modifier

      Consider "adding the modifier 'static' to". As written, the word "static" could be misread as an adjective for "modifier".

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 4
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Comment-Date: Wed, 29 Nov 2023 16:47:56 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: Yes

Simon Binder (Gerrit)

unread,
Nov 30, 2023, 8:16:43 AM11/30/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Simon Binder.

Simon Binder uploaded patch set #5 to this change.

View Change

M sdk/lib/ffi/ffi.dart

M tests/ffi/native_assets/asset_absolute_test.dart
M tests/ffi/native_assets/asset_library_annotation_test.dart
M tests/ffi/native_assets/asset_relative_test.dart
M tests/ffi/vmspecific_static_checks_ffinative_test.dart
24 files changed, 894 insertions(+), 212 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 5

Simon Binder (Gerrit)

unread,
Nov 30, 2023, 8:23:06 AM11/30/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Simon Binder.

Simon Binder uploaded patch set #6 to this change.

Gerrit-PatchSet: 6

Simon Binder (Gerrit)

unread,
Nov 30, 2023, 8:40:04 AM11/30/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Simon Binder.

Simon Binder uploaded patch set #7 to this change.

View Change

M tests/ffi/vmspecific_static_checks_native_test.dart
25 files changed, 921 insertions(+), 212 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 7

Simon Binder (Gerrit)

unread,
Nov 30, 2023, 8:40:36 AM11/30/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Brian Wilkerson, Daco Harkes, Alexander Markov, Paul Berry

Attention is currently required from: Daco Harkes.

View Change

4 comments:

  • File pkg/analyzer/messages.yaml:

    • Patch Set #3, Line 18233: problemMessage: "'{0}' is an invalid type for fields annotated with `@Native`."

      Consistency with error message in CFE.

    • Done

  • File pkg/analyzer/messages.yaml:

    • Consider "adding the modifier 'static' to". […]

      Done

  • File pkg/vm/lib/transformations/ffi/native.dart:

    • There are transformations _before_ this FFI transformation runs. So it might be transformed before. […]

      That makes sense, thanks. I've changed `visitField` to report a compilation error if a `@Native` annotation is present, since that indicates it's not external.

  • File tests/ffi/native_assets/asset_absolute_test.dart:

    • Done

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 6
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Daco Harkes <dacoh...@google.com>
Gerrit-Comment-Date: Thu, 30 Nov 2023 13:40:29 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Daco Harkes <dacoh...@google.com>
Comment-In-Reply-To: Simon Binder <o...@simonbinder.eu>
Comment-In-Reply-To: Brian Wilkerson <brianwi...@google.com>

Daco Harkes (Gerrit)

unread,
Nov 30, 2023, 2:12:26 PM11/30/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Brian Wilkerson, Alexander Markov, Paul Berry

Attention is currently required from: Simon Binder.

View Change

3 comments:

  • Patchset:

  • File pkg/analyzer/test/src/diagnostics/ffi_native_test.dart:

    • Patch Set #7, Line 333: @Native<Handle>()

      Add a test with `@Native<Pointer>`.

      Actually, for pointers we don't need a type argument to `Native`. It would be the same as struct fields, there nested structs, inline arrays and pointers don't need annotations.

      ```
      @Native()
      external Pointer<Int8> foo;


      @Native()
      external Pointer<MyStruct> bar;
      ```

    • Patch Set #7, Line 333: @Native<Handle>()

      Ditto with classes extending struct.

      One should be able to set/get a full struct.

      And, I don't think we'd need a type argument either:

      ```
      @Native()
      external MyStruct mystruct;
      ```

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 7
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Comment-Date: Thu, 30 Nov 2023 17:08:38 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No

Simon Binder (Gerrit)

unread,
Dec 4, 2023, 6:38:36 PM12/4/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Brian Wilkerson, Daco Harkes, Alexander Markov, Paul Berry

Attention is currently required from: Daco Harkes.

View Change

2 comments:

  • Patchset:

    • Patch Set #7:

      I'll get back to this after the parent CL is complete - the implementation has changed a bit and I want to avoid rebasing it more often than necessary. I have a question about inferring the native type though.

  • File pkg/analyzer/test/src/diagnostics/ffi_native_test.dart:

    • Add a test with `@Native<Pointer>`. […]

      A difference is that struct fields don't need an annotation, whereas native fields do. Struct fields don't have a dedicated `@Field<NativeType>` annotation at all, they just use the type as an annotation where necessary. So there, we're inferring something not expressed in source code.

      When we try to infer a suitable native type for fields, it seems like we'd be misinterpreting the original source in a way because there already is a type argument on the annotation. For instance, consider the difference between `@Native()` and `@Native<Object?>()`. Inferring a suitable type seems reasonable for the former, but (IMO) the latter should be an error. But at least in the CFE, we can't tell them apart because the constant expression we get will be instantiated to bounds.

      One could make the same point about native functions, since there is nothing ambiguous about say

      ```dart
      @Native()
      external void free(Pointer<Void>);
      ```

      And yet, we're not doing it. I can implement this inference if that's what we want, but then it should probably be consistent across functions and fields. That leads to more problems though, since we then can't tell zero-argument C functions exposed as getters apart from a const global in C - they could both be `@Native() external MyStruct get foo;`.

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 7
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Daco Harkes <dacoh...@google.com>
Gerrit-Comment-Date: Mon, 04 Dec 2023 23:38:31 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Daco Harkes <dacoh...@google.com>

Daco Harkes (Gerrit)

unread,
Dec 5, 2023, 6:09:43 AM12/5/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Johnni Winther, Jens Johansen, Simon Binder, Brian Wilkerson

Attention is currently required from: Jens Johansen, Johnni Winther, Simon Binder.

Simon Binder has uploaded this change for review.

diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index c4b2ab8..2f311d0 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -5145,12 +5145,33 @@
index a2eab97..8072be1 100644
--- a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
+++ b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
@@ -338,6 +338,24 @@

hasPublishedDocs: true,
);

+ /// No parameters
+ static const FfiCode NATIVE_FIELD_NOT_STATIC = FfiCode(
+ 'NATIVE_FIELD_NOT_STATIC',
+ "Native fields must be static.",
+    correctionMessage: "Try adding the modifier 'static' to this field.",

+ );
+
+ /// Parameters:
+ /// 0: The invalid type.
+ static const FfiCode NATIVE_FIELD_TYPE = FfiCode(
+ 'NATIVE_FIELD_TYPE',
+    "'{0}' is an unsupported type for native fields. Native fields only "
+ "support pointers or numeric and compound types.",

+ correctionMessage:
+ "Try changing the type in the `@Native` annotation to a numeric FFI "
+ "type, a pointer, or a compound class.",
+ );
+
/// Parameters:
/// 0: the name of the function, method, or constructor having type arguments
static const FfiCode NON_CONSTANT_TYPE_ARGUMENT = FfiCode(
diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart
index 2109858..8568bca 100644
--- a/pkg/analyzer/lib/src/error/error_code_values.g.dart
+++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart
@@ -606,6 +606,8 @@
FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
FfiCode.MUST_BE_A_SUBTYPE,
FfiCode.MUST_RETURN_VOID,
+ FfiCode.NATIVE_FIELD_NOT_STATIC,
+ FfiCode.NATIVE_FIELD_TYPE,
FfiCode.NON_CONSTANT_TYPE_ARGUMENT,
FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER,
FfiCode.NON_POSITIVE_ARRAY_DIMENSION,
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index fc0e70d..e23af54 100644

--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -179,6 +179,20 @@
if (inCompound) {
_validateFieldsInCompound(node);
}
+
+    for (var declared in node.fields.variables) {
+ var declaredElement = declared.declaredElement;
+    for (var declared in node.variables.variables) {
+ var declaredElement = declared.declaredElement;
+      var ffiType = typeArguments[0].type!;
@@ -1167,7 +1243,7 @@
var validTarget = false;


var referencedElement = switch (argument) {
- Identifier() => argument.staticElement,
+ Identifier() => argument.staticElement?.nonSynthetic,
_ => null,
};

@@ -1178,25 +1254,36 @@

if (annotationType is InterfaceType &&
annotationType.element.isNative) {
- var functionType = (value!.type as InterfaceType).typeArguments[0];
+ var nativeType = annotationType.typeArguments[0];


- // When referencing a function, the target type must be a
- // `NativeFunction<T>` so that `T` matches the type from the
- // annotation.
- if (!targetType.isNativeFunction) {
- _errorReporter.reportErrorForNode(
- FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
- node,
- [targetType, _nativeAddressOf],
- );
+ if (nativeType is FunctionType) {
+ // If a native function is being referenced, the target type must be
+ // a `NativeFunction<T>` with a matching `T` as well.
+ if (!targetType.isNativeFunction) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
+ node,
+ [targetType, _nativeAddressOf],
+ );
+ } else {
+              var targetFunctionType =

+ (targetType as InterfaceType).typeArguments[0];
+ if (!typeSystem.isAssignableTo(nativeType, targetFunctionType)) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.MUST_BE_A_SUBTYPE,
+ node,
+ [nativeType, targetFunctionType, _nativeAddressOf],
+ );
+ }
+ }
} else {
-            var targetFunctionType =

- (targetType as InterfaceType).typeArguments[0];
- if (!typeSystem.isAssignableTo(functionType, targetFunctionType)) {
+ // A native field is being referenced, this doesn't require a
+ // NativeFunction wrapper.
+ if (!typeSystem.isAssignableTo(nativeType, targetType)) {
_errorReporter.reportErrorForNode(
FfiCode.MUST_BE_A_SUBTYPE,
node,
- [functionType, targetFunctionType, _nativeAddressOf],
+ [nativeType, targetType, _nativeAddressOf],
);
}
}
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 79244fd..e9be1fb 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -18655,6 +18655,16 @@
problemMessage: "Argument to 'Native.addressOf' must be annotated with @Native"
correctionMessage: "Try passing a static function annotated with '@Native'"
comment: No parameters
+ NATIVE_FIELD_TYPE:
+ problemMessage: "'{0}' is an unsupported type for native fields. Native fields only support pointers or numeric and compound types."

+ correctionMessage: "Try changing the type in the `@Native` annotation to a numeric FFI type, a pointer, or a compound class."
+ comment: |-
+ Parameters:
+ 0: The invalid type.
+ NATIVE_FIELD_NOT_STATIC:
+ problemMessage: "Native fields must be static."
+    correctionMessage: "Try adding the modifier 'static' to this field."
index eb492c6..7bd0f1f 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -5196,7 +5196,19 @@


FfiNativeMustBeExternal:
# Used by dart:ffi
- problemMessage: "Native functions must be marked external."
+ problemMessage: "Native functions and fields must be marked external."
+ external: test/ffi_test.dart
+
+FfiNativeFieldMustBeStatic:
+ # Used by dart:ffi
+ problemMessage: "Native fields must be static."
+ analyzerCode: NATIVE_FIELD_NOT_STATIC
+ external: test/ffi_test.dart
+
+FfiNativeFieldType:
+ # Used by dart:ffi
+ problemMessage: "Unsupported type for native fields. Native fields only support pointers or numeric and compound types."
+ analyzerCode: NATIVE_FIELD_TYPE
external: test/ffi_test.dart

FfiAddressOfMustBeNative:
diff --git a/pkg/vm/lib/transformations/ffi/native.dart b/pkg/vm/lib/transformations/ffi/native.dart
index 87e8bbf..b89db74 100644

--- a/pkg/vm/lib/transformations/ffi/native.dart
+++ b/pkg/vm/lib/transformations/ffi/native.dart
@@ -4,12 +4,15 @@

import 'package:front_end/src/api_unstable/vm.dart'
show
+ messageFfiNativeFieldMustBeStatic,
+ messageFfiNativeFieldType,
messageFfiNativeMustBeExternal,
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
templateCantHaveNamedParameters,
templateCantHaveOptionalParameters,
templateFfiNativeUnexpectedNumberOfParameters,
- templateFfiNativeUnexpectedNumberOfParametersWithReceiver;
+ templateFfiNativeUnexpectedNumberOfParametersWithReceiver,
+ templateFfiTypeInvalid;

import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
@@ -19,7 +22,7 @@
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
import 'package:kernel/type_environment.dart';

-import 'common.dart' show FfiStaticTypeError, FfiTransformer;
+import 'common.dart' show FfiStaticTypeError, FfiTransformer, NativeType;

/// Transform @Native annotated functions into FFI native function pointer
/// functions.
@@ -89,6 +92,7 @@

currentAsset = (annotation.constant as InstanceConstant)
.fieldValues[assetAssetField.fieldReference] as StringConstant;
}
+
     final result = super.visitLibrary(node);

currentAsset = null;
return result;
@@ -130,6 +134,26 @@

return false;
}

+ StringConstant _resolveNativeSymbolName(
+ Member member, InstanceConstant native) {
+ final nativeFunctionConst =
+ native.fieldValues[nativeSymbolField.fieldReference];
+ return nativeFunctionConst is StringConstant
+ ? nativeFunctionConst
+ : StringConstant(member.name.text);
+ }
+
+ StringConstant? _resolveAssetName(InstanceConstant native) {
+ final assetConstant = native.fieldValues[nativeAssetField.fieldReference];
+ return assetConstant is StringConstant ? assetConstant : currentAsset;
+ }
+
+ bool _isLeaf(InstanceConstant native) {
+ return (native.fieldValues[nativeIsLeafField.fieldReference]
+ as BoolConstant)
+ .value;
+ }
+
// Replaces parameters with Pointer if:
// 1) they extend NativeFieldWrapperClass1, and
// 2) the corresponding FFI parameter is Pointer.
@@ -402,6 +426,29 @@
@@ -427,16 +474,12 @@

final pragmaConstant = ConstantExpression(
InstanceConstant(pragmaClass.reference, [], {
pragmaName.fieldReference: StringConstant(vmFfiNative),
- pragmaOptions.fieldReference: InstanceConstant(
- nativeClass.reference,
- [ffiFunctionType],
- {
- nativeSymbolField.fieldReference: nativeFunctionName,
- nativeAssetField.fieldReference: assetName ??
- StringConstant(currentLibrary.importUri.toString()),
- nativeIsLeafField.fieldReference: BoolConstant(isLeaf),
- },
- )
+ pragmaOptions.fieldReference: _createResolvedNativeConstant(
+ nativeType: ffiFunctionType,
+ nativeName: nativeFunctionName,
+ isLeaf: isLeaf,
+ assetName: assetName,
+ ),
}),
InterfaceType(
pragmaClass,
@@ -655,36 +698,206 @@
+    if (isCompoundSubtype(ffiType) || isAbiSpecificIntegerSubtype(ffiType)) {
+ return;
+ }

+
+ final type = switch (ffiType) {
+ InterfaceType(:var classNode) => getType(classNode),
+ _ => null,
+ };
+
+ if (type == null || type == NativeType.kNativeFunction) {
+ diagnosticReporter.report(
+ messageFfiNativeFieldType, node.fileOffset, 1, node.location?.file);
+ throw FfiStaticTypeError();
+ }
+ }
+
+ @override
+ TreeNode visitField(Field node) {
+ final nativeAnnotation = tryGetNativeAnnotation(node);
+ if (nativeAnnotation == null) {
+ return node;
+ }
+
+    // @Native annotations on fields are valid if the field is external. But
+ // external fields are represented as a getter/setter pair in Kernel, so we
+ // only visit fields to verify that no native annotation is present.
+ assert(!node.isExternal);
+

+ diagnosticReporter.report(messageFfiNativeMustBeExternal, node.fileOffset,
+ 1, node.location?.file);
+ return node;
+ }
+
index 313780c..d868e62 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative.dart
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart
@@ -53,8 +53,20 @@

@Native<Void Function(Pointer<Void>, Bool)>(
symbol: 'doesntmatter', isLeaf: true)
external set myField(bool value);
+
+ @Native<Double>()
+ external static double nativeDouble;
}

+@Native<Int>()
+external final int myAbiSpecificField;
+
+@Native<Int32>()
+external int get nativeFieldAsProcedures;
+
+@Native<Int32>()
+external set nativeFieldAsProcedures(int value);

+
void main() {
returnIntPtr(13);
returnIntPtrLeaf(37);
@@ -70,4 +82,5 @@

NativeClassy().myField = !b;

Native.addressOf<NativeFunction<IntPtr Function(IntPtr)>>(returnIntPtr);
+ print('native fields: $myAbiSpecificField, ${NativeClassy.nativeDouble}');
}
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
index 8c85998..eba76c3 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
@@ -112,29 +112,35 @@

} =>#pointerAddress), #t18);
_in::reachabilityFence(#t17);
} =>#t19;
-[@vm.unboxing-info.metadata=(b,i)->b] @#C16
+[@vm.unboxing-info.metadata=()->d]  @#C17

+ static get nativeDouble() → core::double
+    return [@vm.inferred-type.metadata=dart.core::_Double] ffi::_loadDouble([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Double>(#C16), #C11);
+[@vm.unboxing-info.metadata=(b,i)->b] @#C20

external static method _goodHasReceiverPointer$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 175)] core::int #t1) → void;
-[@vm.unboxing-info.metadata=(b,i)->b]  @#C18
+[@vm.unboxing-info.metadata=(b,i)->b] @#C22

external static method _goodHasReceiverHandle$Method$FfiNative([@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t0, [@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 175)] core::int #t1) → void;
- @#C20
- external static method _goodHasReceiverHandleAndPtr$Method$FfiNative([@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t0, [@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t1) → void;
-  @#C22

- external static method _goodHasReceiverHandleAndHandle$Method$FfiNative([@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t0, [@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t1) → void;
   @#C24
- external static method _goodHasReceiverPtrAndHandle$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t1) → void;
+  external static method _goodHasReceiverHandleAndPtr$Method$FfiNative([@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t0, [@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t1) → void;

@#C26
- external static method _meh$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=dart.core::bool (value: true)] core::bool #t1) → core::Object?;
+  external static method _goodHasReceiverHandleAndHandle$Method$FfiNative([@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t0, [@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t1) → void;

@#C28
- external static method _blah$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0) → core::bool;
+  external static method _goodHasReceiverPtrAndHandle$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=#lib::NativeClassy] self::NativeClassy #t1) → void;

@#C30
- external static method _myField$Getter$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0) → core::bool;
+  external static method _meh$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=dart.core::bool (value: true)] core::bool #t1) → core::Object?;
@#C32
+ external static method _blah$Method$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0) → core::bool;
+ @#C34

+ external static method _myField$Getter$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0) → core::bool;
+  @#C36

external static method _myField$Setter$FfiNative([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] ffi::Pointer<ffi::Void> #t0, [@vm.inferred-arg-type.metadata=dart.core::bool] core::bool #t1) → void;
}
[@vm.unboxing-info.metadata=(i)->i]@#C6
external static method returnIntPtr([@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 13)] core::int x) → core::int;
-[@vm.unboxing-info.metadata=(i)->i]@#C34
+[@vm.unboxing-info.metadata=(i)->i]@#C38

external static method returnIntPtrLeaf([@vm.inferred-arg-type.metadata=dart.core::_Smi (value: 37)] core::int x) → core::int;
+[@vm.unboxing-info.metadata=()->i]@#C41

+static get myAbiSpecificField() → core::int
+  return [@vm.inferred-type.metadata=int] ffi::_loadAbiSpecificInt<ffi::Int>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Int>(#C40), #C11);

static method main() → void {
self::returnIntPtr(13);
self::returnIntPtrLeaf(37);
@@ -149,6 +155,7 @@

final core::bool b = [@vm.direct-call.metadata=#lib::NativeClassy.myField] [@vm.inferred-type.metadata=dart.core::bool] new self::NativeClassy::•().{self::NativeClassy::myField}{core::bool};
[@vm.direct-call.metadata=#lib::NativeClassy.myField] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::myField} = !b;
ffi::Native::_addressOf<ffi::NativeFunction<(ffi::IntPtr) → ffi::IntPtr>>(#C5);
+ core::print("native fields: ${self::myAbiSpecificField}, ${self::NativeClassy::nativeDouble}");
}
constants {
#C1 = "vm:ffi:native"
@@ -164,25 +171,32 @@

#C11 = 0
#C12 = "A Dart object attempted to access a native peer, but the native peer has been collected (nullptr). This is usually the result of calling methods on a native-backed object when the native resources have already been disposed."
#C13 = true
- #C14 = "doesntmatter"
- #C15 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::IntPtr) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
- #C16 = core::pragma {name:#C1, options:#C15}
- #C17 = ffi::Native<(ffi::Handle, ffi::IntPtr) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
-  #C18 = core::pragma {name:#C1, options:#C17}

- #C19 = ffi::Native<(ffi::Handle, ffi::Pointer<ffi::Void>) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+  #C14 = "vm:ffi:native-field"
+ #C15 = "nativeDouble"
+ #C16 = ffi::Native<ffi::Double> {symbol:#C15, assetId:#C3, isLeaf:#C4}
+ #C17 = core::pragma {name:#C14, options:#C16}
+ #C18 = "doesntmatter"
+ #C19 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::IntPtr) → ffi::Void> {symbol:#C18, assetId:#C3, isLeaf:#C4}

#C20 = core::pragma {name:#C1, options:#C19}
- #C21 = ffi::Native<(ffi::Handle, ffi::Handle) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+  #C21 = ffi::Native<(ffi::Handle, ffi::IntPtr) → ffi::Void> {symbol:#C18, assetId:#C3, isLeaf:#C4}

#C22 = core::pragma {name:#C1, options:#C21}
- #C23 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Handle) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+  #C23 = ffi::Native<(ffi::Handle, ffi::Pointer<ffi::Void>) → ffi::Void> {symbol:#C18, assetId:#C3, isLeaf:#C4}

#C24 = core::pragma {name:#C1, options:#C23}
- #C25 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Handle> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+  #C25 = ffi::Native<(ffi::Handle, ffi::Handle) → ffi::Void> {symbol:#C18, assetId:#C3, isLeaf:#C4}

#C26 = core::pragma {name:#C1, options:#C25}
- #C27 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C14, assetId:#C3, isLeaf:#C4}
+  #C27 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Handle) → ffi::Void> {symbol:#C18, assetId:#C3, isLeaf:#C4}

#C28 = core::pragma {name:#C1, options:#C27}
- #C29 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C14, assetId:#C3, isLeaf:#C13}
+  #C29 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Handle> {symbol:#C18, assetId:#C3, isLeaf:#C4}

#C30 = core::pragma {name:#C1, options:#C29}
- #C31 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Void> {symbol:#C14, assetId:#C3, isLeaf:#C13}
+  #C31 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C18, assetId:#C3, isLeaf:#C4}

#C32 = core::pragma {name:#C1, options:#C31}
- #C33 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C13}
+  #C33 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C18, assetId:#C3, isLeaf:#C13}

#C34 = core::pragma {name:#C1, options:#C33}
+  #C35 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Void> {symbol:#C18, assetId:#C3, isLeaf:#C13}

+ #C36 = core::pragma {name:#C1, options:#C35}
+  #C37 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C13}

+ #C38 = core::pragma {name:#C1, options:#C37}
+ #C39 = "myAbiSpecificField"
+ #C40 = ffi::Native<ffi::Int> {symbol:#C39, assetId:#C3, isLeaf:#C4}
+  #C41 = core::pragma {name:#C14, options:#C40}
}
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
index ac08c034..cddca0f 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
@@ -115,31 +115,44 @@

} =>#pointerAddress), #t18);
_in::reachabilityFence(#t17);
} =>#t19;
- @#C14
- external static method _goodHasReceiverPointer$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, core::int #t1) → void;
-  @#C16

- external static method _goodHasReceiverHandle$Method$FfiNative(self::NativeClassy #t0, core::int #t1) → void;
+  @#C15

+ static get nativeDouble() → core::double
+    return ffi::_loadDouble(ffi::Native::_addressOf<ffi::Double>(#C14), #C10);

+ static set nativeDouble(core::double #externalFieldValue) → void
+    ffi::_storeDouble(ffi::Native::_addressOf<ffi::Double>(#C14), #C10, #externalFieldValue);

@#C18
- external static method _goodHasReceiverHandleAndPtr$Method$FfiNative(self::NativeClassy #t0, ffi::Pointer<ffi::Void> #t1) → void;
+  external static method _goodHasReceiverPointer$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, core::int #t1) → void;

@#C20
- external static method _goodHasReceiverHandleAndHandle$Method$FfiNative(self::NativeClassy #t0, self::NativeClassy #t1) → void;
+  external static method _goodHasReceiverHandle$Method$FfiNative(self::NativeClassy #t0, core::int #t1) → void;

@#C22
- external static method _goodHasReceiverPtrAndHandle$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, self::NativeClassy #t1) → void;
+  external static method _goodHasReceiverHandleAndPtr$Method$FfiNative(self::NativeClassy #t0, ffi::Pointer<ffi::Void> #t1) → void;

@#C24
- external static method _meh$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, core::bool #t1) → core::Object?;
+  external static method _goodHasReceiverHandleAndHandle$Method$FfiNative(self::NativeClassy #t0, self::NativeClassy #t1) → void;
@#C26
+ external static method _goodHasReceiverPtrAndHandle$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, self::NativeClassy #t1) → void;
+ @#C28

+ external static method _meh$Method$FfiNative(ffi::Pointer<ffi::Void> #t0, core::bool #t1) → core::Object?;
+  @#C30

external static method _blah$Method$FfiNative(ffi::Pointer<ffi::Void> #t0) → core::bool;
- @#C29
+  @#C33

external static method _myField$Getter$FfiNative(ffi::Pointer<ffi::Void> #t0) → core::bool;
-  @#C31
+ @#C35

external static method _myField$Setter$FfiNative(ffi::Pointer<ffi::Void> #t0, core::bool #t1) → void;
}
@#C6
external static method returnIntPtr(core::int x) → core::int;
-@#C33
+@#C37

external static method returnIntPtrLeaf(core::int x) → core::int;
-@#C36
+@#C40

external static method returnNativeIntPtrLeaf(core::int x) → core::int;
+@#C43

+static get myAbiSpecificField() → core::int
+  return ffi::_loadAbiSpecificInt<ffi::Int>(ffi::Native::_addressOf<ffi::Int>(#C42), #C10);
+@#C46
+static get nativeFieldAsProcedures() → core::int
+ return ffi::_loadInt32(ffi::Native::_addressOf<ffi::Int32>(#C45), #C10);
+static set nativeFieldAsProcedures(core::int value) → void
+ ffi::_storeInt32(ffi::Native::_addressOf<ffi::Int32>(#C45), #C10, value);

static method main() → void {
self::returnIntPtr(13);
self::returnIntPtrLeaf(37);
@@ -154,6 +167,7 @@

final core::bool b = new self::NativeClassy::•().{self::NativeClassy::myField}{core::bool};
new self::NativeClassy::•().{self::NativeClassy::myField} = !b;
ffi::Native::_addressOf<ffi::NativeFunction<(ffi::IntPtr) → ffi::IntPtr>>(#C5);
+ core::print("native fields: ${self::myAbiSpecificField}, ${self::NativeClassy::nativeDouble}");
}
constants {
#C1 = "vm:ffi:native"
@@ -167,29 +181,39 @@

#C9 = core::pragma {name:#C7, options:#C8}
#C10 = 0
#C11 = "A Dart object attempted to access a native peer, but the native peer has been collected (nullptr). This is usually the result of calling methods on a native-backed object when the native resources have already been disposed."
- #C12 = "doesntmatter"
- #C13 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::IntPtr) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C4}
- #C14 = core::pragma {name:#C1, options:#C13}
- #C15 = ffi::Native<(ffi::Handle, ffi::IntPtr) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C4}
-  #C16 = core::pragma {name:#C1, options:#C15}
-  #C17 = ffi::Native<(ffi::Handle, ffi::Pointer<ffi::Void>) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+  #C12 = "vm:ffi:native-field"
+ #C13 = "nativeDouble"
+ #C14 = ffi::Native<ffi::Double> {symbol:#C13, assetId:#C3, isLeaf:#C4}
+ #C15 = core::pragma {name:#C12, options:#C14}

+ #C16 = "doesntmatter"
+ #C17 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::IntPtr) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C18 = core::pragma {name:#C1, options:#C17}
-  #C19 = ffi::Native<(ffi::Handle, ffi::Handle) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+  #C19 = ffi::Native<(ffi::Handle, ffi::IntPtr) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C20 = core::pragma {name:#C1, options:#C19}
-  #C21 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Handle) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+  #C21 = ffi::Native<(ffi::Handle, ffi::Pointer<ffi::Void>) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C22 = core::pragma {name:#C1, options:#C21}
-  #C23 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Handle> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+  #C23 = ffi::Native<(ffi::Handle, ffi::Handle) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C24 = core::pragma {name:#C1, options:#C23}
-  #C25 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C12, assetId:#C3, isLeaf:#C4}
+  #C25 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Handle) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C4}
#C26 = core::pragma {name:#C1, options:#C25}
-  #C27 = true
- #C28 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C12, assetId:#C3, isLeaf:#C27}
- #C29 = core::pragma {name:#C1, options:#C28}
- #C30 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Void> {symbol:#C12, assetId:#C3, isLeaf:#C27}
-  #C31 = core::pragma {name:#C1, options:#C30}

- #C32 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C27}
+  #C27 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Handle> {symbol:#C16, assetId:#C3, isLeaf:#C4}
+  #C28 = core::pragma {name:#C1, options:#C27}
+  #C29 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C16, assetId:#C3, isLeaf:#C4}
+  #C30 = core::pragma {name:#C1, options:#C29}
+ #C31 = true
+ #C32 = ffi::Native<(ffi::Pointer<ffi::Void>) → ffi::Bool> {symbol:#C16, assetId:#C3, isLeaf:#C31}

#C33 = core::pragma {name:#C1, options:#C32}
- #C34 = "returnNativeIntPtrLeaf"
- #C35 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C34, assetId:#C3, isLeaf:#C27}
- #C36 = core::pragma {name:#C1, options:#C35}
+  #C34 = ffi::Native<(ffi::Pointer<ffi::Void>, ffi::Bool) → ffi::Void> {symbol:#C16, assetId:#C3, isLeaf:#C31}

+ #C35 = core::pragma {name:#C1, options:#C34}
+  #C36 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C31}
+ #C37 = core::pragma {name:#C1, options:#C36}
+ #C38 = "returnNativeIntPtrLeaf"
+ #C39 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C38, assetId:#C3, isLeaf:#C31}
+ #C40 = core::pragma {name:#C1, options:#C39}
+ #C41 = "myAbiSpecificField"
+ #C42 = ffi::Native<ffi::Int> {symbol:#C41, assetId:#C3, isLeaf:#C4}
+ #C43 = core::pragma {name:#C12, options:#C42}
+ #C44 = "nativeFieldAsProcedures"
+ #C45 = ffi::Native<ffi::Int32> {symbol:#C44, assetId:#C3, isLeaf:#C4}
+ #C46 = core::pragma {name:#C12, options:#C45}

}
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart
index c66f2e5..5a5ea99 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart
+++ b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart
@@ -9,13 +9,16 @@

void main() {
final result = returnStruct1ByteIntNative(-1);
- print("result = $result");
+ print("result = $result, from field = $nativeField");
}

// ignore: sdk_version_since
@Native<Struct1ByteInt Function(Int8)>(symbol: 'ReturnStruct1ByteInt')
external Struct1ByteInt returnStruct1ByteIntNative(int a0);

+@Native<Struct1ByteInt>()
+external Struct1ByteInt nativeField;
+
final class Struct1ByteInt extends Struct {
@Int8()
external int a0;
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.aot.expect
index a5b3fc6..dea2335 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.aot.expect
+++ b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.aot.expect
@@ -9,24 +9,27 @@


@#C6
final class Struct1ByteInt extends ffi::Struct {
- constructor #fromTypedDataBase([@vm.inferred-arg-type.metadata=dart.typed_data::_Uint8List] synthesized core::Object #typedDataBase) → self::Struct1ByteInt
+ constructor #fromTypedDataBase([@vm.inferred-arg-type.metadata=!] synthesized core::Object #typedDataBase) → self::Struct1ByteInt
: super ffi::Struct::_fromTypedDataBase(#typedDataBase)
;
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1] [@vm.unboxing-info.metadata=()->i] get a0() → core::int
- return [@vm.inferred-type.metadata=int] ffi::_loadInt8([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.typed_data::_Uint8List] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C8.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
+ return [@vm.inferred-type.metadata=int] ffi::_loadInt8([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=!] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C8.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:2,getterSelectorId:3] method toString() → core::String
return "(${[@vm.direct-call.metadata=#lib::Struct1ByteInt.a0] this.{self::Struct1ByteInt::a0}{core::int}})";
}
static method main() → void {
final self::Struct1ByteInt result = self::returnStruct1ByteIntNative([@vm.direct-call.metadata=dart.core::_IntegerImplementation.unary-] [@vm.inferred-type.metadata=int (skip check)] 1.{core::int::unary-}(){() → core::int});
- core::print("result = ${result}");
+ core::print("result = ${result}, from field = ${self::nativeField}");
}
[@vm.unboxing-info.metadata=(i)->b]@#C10
static method returnStruct1ByteIntNative([@vm.inferred-arg-type.metadata=int] core::int a0) → self::Struct1ByteInt
return block {
_in::_nativeEffect(new self::Struct1ByteInt::#fromTypedDataBase([@vm.inferred-type.metadata=dart.typed_data::_Uint8List] typ::Uint8List::•(#C11)));
} =>[@vm.inferred-type.metadata=#lib::Struct1ByteInt] self::_returnStruct1ByteIntNative$Method$FfiNative(a0);
-[@vm.unboxing-info.metadata=(i)->b]@#C17
+@#C17

+static get nativeField() → self::Struct1ByteInt
+  return new self::Struct1ByteInt::#fromTypedDataBase(_in::unsafeCast<ffi::Pointer<self::Struct1ByteInt>>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<self::Struct1ByteInt>(#C16)));
+[@vm.unboxing-info.metadata=(i)->b]@#C21

external static method _returnStruct1ByteIntNative$Method$FfiNative([@vm.inferred-arg-type.metadata=int] core::int #t0) → self::Struct1ByteInt;
constants {
#C1 = "vm:ffi:struct-fields"
@@ -40,10 +43,14 @@

#C9 = "vm:prefer-inline"
#C10 = core::pragma {name:#C9, options:#C4}
#C11 = 1
- #C12 = "vm:ffi:native"
- #C13 = "ReturnStruct1ByteInt"
+  #C12 = "vm:ffi:native-field"
+ #C13 = "nativeField"
#C14 = "#lib"

#C15 = false
- #C16 = ffi::Native<(ffi::Int8) → self::Struct1ByteInt> {symbol:#C13, assetId:#C14, isLeaf:#C15}
+  #C16 = ffi::Native<self::Struct1ByteInt> {symbol:#C13, assetId:#C14, isLeaf:#C15}

#C17 = core::pragma {name:#C12, options:#C16}
+  #C18 = "vm:ffi:native"
+ #C19 = "ReturnStruct1ByteInt"
+  #C20 = ffi::Native<(ffi::Int8) → self::Struct1ByteInt> {symbol:#C19, assetId:#C14, isLeaf:#C15}

+ #C21 = core::pragma {name:#C18, options:#C20}
}
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.expect b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.expect
index 7471888..65f83a0 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.expect
+++ b/pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart.expect
@@ -29,7 +29,7 @@

}
static method main() → void {
final self::Struct1ByteInt result = self::returnStruct1ByteIntNative(1.{core::int::unary-}(){() → core::int});
- core::print("result = ${result}");
+ core::print("result = ${result}, from field = ${self::nativeField}");
}
@#C11
static method returnStruct1ByteIntNative(core::int a0) → self::Struct1ByteInt
@@ -37,6 +37,11 @@

_in::_nativeEffect(new self::Struct1ByteInt::#fromTypedDataBase(typ::Uint8List::•(#C12)));
} =>self::_returnStruct1ByteIntNative$Method$FfiNative(a0);
 @#C19
+static get nativeField() → self::Struct1ByteInt
+  return new self::Struct1ByteInt::#fromTypedDataBase(ffi::Native::_addressOf<self::Struct1ByteInt>(#C18)!);

+static set nativeField(self::Struct1ByteInt #externalFieldValue) → void
+  ffi::_memCopy(ffi::Native::_addressOf<self::Struct1ByteInt>(#C18), #C8, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #C8, self::Struct1ByteInt::#sizeOf);
+@#C23

external static method _returnStruct1ByteIntNative$Method$FfiNative(core::int #t0) → self::Struct1ByteInt;
constants {
#C1 = "vm:ffi:struct-fields"
@@ -52,10 +57,14 @@

#C11 = core::pragma {name:#C10, options:#C4}
#C12 = 1
#C13 = <core::int*>[#C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12]
- #C14 = "vm:ffi:native"
- #C15 = "ReturnStruct1ByteInt"
+  #C14 = "vm:ffi:native-field"
+ #C15 = "nativeField"
#C16 = "#lib"

#C17 = false
- #C18 = ffi::Native<(ffi::Int8) → self::Struct1ByteInt> {symbol:#C15, assetId:#C16, isLeaf:#C17}
+  #C18 = ffi::Native<self::Struct1ByteInt> {symbol:#C15, assetId:#C16, isLeaf:#C17}

#C19 = core::pragma {name:#C14, options:#C18}
+  #C20 = "vm:ffi:native"
+ #C21 = "ReturnStruct1ByteInt"
+ #C22 = ffi::Native<(ffi::Int8) → self::Struct1ByteInt> {symbol:#C21, assetId:#C16, isLeaf:#C17}
+ #C23 = core::pragma {name:#C20, options:#C22}
index f365ab8..dbfd67d 100644

--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -5056,10 +5056,17 @@
String::ZoneHandle(Z, String::RawCast(native.GetField(asset_id_field)));
const auto& type_args = TypeArguments::Handle(Z, native.GetTypeArguments());
ASSERT(type_args.Length() == 1);
- const auto& native_type =
- FunctionType::Cast(AbstractType::ZoneHandle(Z, type_args.TypeAt(0)));
- const intptr_t arg_n =
- native_type.NumParameters() - native_type.num_implicit_parameters();
+ const auto& native_type = AbstractType::ZoneHandle(Z, type_args.TypeAt(0));
+ intptr_t arg_n;
+ if (native_type.IsFunctionType()) {
+ const auto& native_function_type = FunctionType::Cast(native_type);
+ arg_n = native_function_type.NumParameters() -
+ native_function_type.num_implicit_parameters();
+ } else {
+ // We're looking up the address of a native field.
+ arg_n = 0;
+ }
+
const auto& ffi_resolver =
Function::ZoneHandle(Z, IG->object_store()->ffi_resolver_function());

diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart
index 7707cfa..41d833f 100644
--- a/sdk/lib/ffi/ffi.dart
+++ b/sdk/lib/ffi/ffi.dart
@@ -1246,8 +1246,10 @@

/// The native address of [native].
///
- /// [native] must be a reference to a method annotated with `@Native` and [T]
- /// must be a [NativeFunction] compatible to the signature of [native].
+ /// [native] must be a direct reference to a method annotated with `@Native`.
+ /// [addressOf] does not support non-constant arguments.
+ /// Further, [T] must be a [NativeFunction] compatible to the signature of
+ /// [native].
///
/// Example:
/// ```dart
@@ -1259,7 +1261,7 @@
/// external int sum(int a, int b);
///
/// void main() {
- /// final address = Native.addressOf<NativeFunction<NativeAdd>>(sum);
+ /// final pointer = Native.addressOf<NativeFunction<NativeAdd>>(sum);
/// }
/// ```
@Since('3.3')
diff --git a/tests/ffi/native_assets/asset_absolute_test.dart b/tests/ffi/native_assets/asset_absolute_test.dart
index 6cd3595..f648e2e 100644

--- a/tests/ffi/native_assets/asset_absolute_test.dart
+++ b/tests/ffi/native_assets/asset_absolute_test.dart
@@ -65,6 +65,7 @@

Future<void> runTests() async {
testFfiTestfunctionsDll();
+ testFfiTestFieldsDll();
testNonExistingFunction();
}

@@ -80,3 +81,30 @@

.asFunction<int Function(int, int)>();
Expect.equals(2 + 3 + 42, viaAddressOf(2, 3));
}
+
+@Native<Int32>()

+external int globalVar;
+
+@Native<Int32>(symbol: 'globalVar')
+external int get globalVarProcedure;

+
+@Native<Int32>(symbol: 'globalVar')
+external set globalVarProcedure(int value);

+
+@Native<Void Function(Int32)>()
+external void SetGlobalVar(int value);
+
+@Native<Int32 Function()>()
+external int GetGlobalVar();
+
+void testFfiTestFieldsDll() {
+ SetGlobalVar(42);
+ Expect.equals(globalVar, 42);
+  Expect.equals(globalVarProcedure, 42);

+
+ globalVar = 13;
+ Expect.equals(GetGlobalVar(), 13);
+
+  globalVarProcedure = 26;
+ Expect.equals(GetGlobalVar(), 26);

+}
diff --git a/tests/ffi/native_assets/asset_library_annotation_test.dart b/tests/ffi/native_assets/asset_library_annotation_test.dart
index 84b5759..e41296b 100644

--- a/tests/ffi/native_assets/asset_library_annotation_test.dart
+++ b/tests/ffi/native_assets/asset_library_annotation_test.dart
@@ -63,6 +63,7 @@

Future<void> runTests() async {
testFfiTestfunctionsDll();
+ testFfiTestFieldsDll();
}

@Native<Int32 Function(Int32, Int32)>()
@@ -77,3 +78,30 @@

final function = ptr.asFunction<int Function(int, int)>();
Expect.equals(2 + 3 + 42, function(2, 3));
}
+
+@Native<Int32>(symbol: 'globalVar')
+external int globalVar;
+
+@Native<Int32>(symbol: 'globalVar')
+external int get globalVarProcedure;

+
+@Native<Int32>(symbol: 'globalVar')
+external set globalVarProcedure(int value);

+
+@Native<Void Function(Int32)>()
+external void SetGlobalVar(int value);
+
+@Native<Int32 Function()>()
+external int GetGlobalVar();
+
+void testFfiTestFieldsDll() {
+ SetGlobalVar(42);
+ Expect.equals(globalVar, 42);
+  Expect.equals(globalVarProcedure, 42);

+
+ globalVar = 13;
+ Expect.equals(GetGlobalVar(), 13);
+
+  globalVarProcedure = 26;
+ Expect.equals(GetGlobalVar(), 26);

+}
diff --git a/tests/ffi/native_assets/asset_relative_test.dart b/tests/ffi/native_assets/asset_relative_test.dart
index ea0bef2..a9b10f04 100644

--- a/tests/ffi/native_assets/asset_relative_test.dart
+++ b/tests/ffi/native_assets/asset_relative_test.dart
@@ -119,6 +119,7 @@

Future<void> runTests() async {
testFfiTestfunctionsDll();
+ testFfiTestFieldsDll();
testNonExistingFunction();
}

@@ -135,3 +136,30 @@


Expect.equals(2 + 3 + 42, viaAddressOf(2, 3));
}
+
+@Native<Int32>(symbol: 'globalVar')
+external int globalVar;
+
+@Native<Int32>(symbol: 'globalVar')
+external int get globalVarProcedure;

+
+@Native<Int32>(symbol: 'globalVar')
+external set globalVarProcedure(int value);

+
+@Native<Void Function(Int32)>()
+external void SetGlobalVar(int value);
+
+@Native<Int32 Function()>()
+external int GetGlobalVar();
+
+void testFfiTestFieldsDll() {
+ SetGlobalVar(42);
+ Expect.equals(globalVar, 42);
+  Expect.equals(globalVarProcedure, 42);

+
+ globalVar = 13;
+ Expect.equals(GetGlobalVar(), 13);
+
+  globalVarProcedure = 26;
+ Expect.equals(GetGlobalVar(), 26);

+}
diff --git a/tests/ffi/vmspecific_static_checks_ffinative_test.dart b/tests/ffi/vmspecific_static_checks_ffinative_test.dart
index 7120a13..489a62d 100644
--- a/tests/ffi/vmspecific_static_checks_ffinative_test.dart
+++ b/tests/ffi/vmspecific_static_checks_ffinative_test.dart
@@ -72,3 +72,33 @@


@Native<double Function(IntPtr)>(symbol: 'doesntmatter') //# 17: compile-time error
external double nonFfiReturnType(int v); //# 17: compile-time error
+
+// Error: Unsupported type for native fields. Native fields only support pointers or numeric and compound types.
+@Native<NativeFunction<Void Function()>>() //# 20: compile-time error
+external void Function() nativeFunction; //# 20: compile-time error
+
+@Native<Handle>() //# 21: compile-time error
+external Object nativeObject; //# 21: compile-time error
+
+@Native<Opaque>() //# 22: compile-time error
+external Opaque nativeOpaque; //# 22: compile-time error
+
+@Native<Int32>() //# 23: compile-time error
+external double nativeDouble; // #23: compile-time error
+
+@Native<Void Function()>()
+external void _valid();
+
+@Native<Void Function()>()
+external void _valid2();
+
+void invalidNotAConstant() {
+ var boolean = 1 == 2;
+ // Error: Argument to 'Native.addressOf' must be annotated with @Native.
+ Native.addressOf<NativeFunction<Void Function()>>(boolean ? _valid : _valid2); //# 18: compile-time error
+}
+
+void invalidWrongType() {
+ // Error: Expected type 'void Function()' to be 'void Function(int)', which is the Dart type corresponding to 'NativeFunction<Void Function(Int)>'.
+ Native.addressOf<NativeFunction<Void Function(Int)>>(_valid); //# 19: compile-time error
+}
diff --git a/tests/ffi/vmspecific_static_checks_native_test.dart b/tests/ffi/vmspecific_static_checks_native_test.dart
index 2ddaf56..f56ba14 100644
--- a/tests/ffi/vmspecific_static_checks_native_test.dart
+++ b/tests/ffi/vmspecific_static_checks_native_test.dart
@@ -11,6 +11,27 @@
@Native<Void Function()>()
external void _valid2();

+@Native<Int32>()
+external int nativeFieldValid;
+
+@Native<Pointer<Char>>()
+external String nativeFieldTypeMismatch;
+// ^^^^^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
+// [cfe] Expected type 'String' to be 'Pointer<Char>', which is the Dart type corresponding to 'Pointer<Char>'.
+
+@Native<Handle>()
+external Object nativeFieldUnsupportedType;
+// ^^^^^^^^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.NATIVE_FIELD_TYPE
+// [cfe] Expected type 'Handle' to be a valid and instantiated subtype of 'NativeType'.
+
+@Native<Int32>()
+int nativeFieldNotExternal = 0;
+// ^^^^^^^^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.FFI_NATIVE_MUST_BE_EXTERNAL
+// [cfe] Native functions and fields must be marked external.
+
void invalidNotAConstant() {
var boolean = 1 == 2;
Native.addressOf<NativeFunction<Void Function()>>(boolean ? _valid : _valid2);
@@ -26,4 +47,10 @@
// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
// ^
// [cfe] Expected type 'void Function()' to be 'void Function(int)', which is the Dart type corresponding to 'NativeFunction<Void Function(Int)>'.
+
+ Native.addressOf<Double>(nativeFieldValid);
+//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
+ // ^
+ // [cfe] Expected type 'int' to be 'double', which is the Dart type corresponding to 'Double'.
}

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 7
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Johnni Winther <johnni...@google.com>
Gerrit-Attention: Jens Johansen <je...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>

Daco Harkes (Gerrit)

unread,
Dec 5, 2023, 6:09:44 AM12/5/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

Attention is currently required from: Jens Johansen, Johnni Winther, Simon Binder.

View Change

1 comment:

  • File pkg/analyzer/test/src/diagnostics/ffi_native_test.dart:

    • For instance, consider the difference between `@Native()` and `@Native<Object?>`(). Inferring a suitable type seems reasonable for the former, but (IMO) the latter should be an error.

      @johnni...@google.com @je...@google.com Is there a way to get access to the source type instead of the specified bound?

      If we can't tell them apart, I have no problem with interpreting the user writing `Native<Object?>()` being treated as if the user wrote `Native()`.

    • One could make the same point about native functions

    • I guess we could. But the inference would only be allowed for the subset of argument / parameter types that are unambigious. In contrast to `Pointer` and structs the type would _not_ be equal. E.g. `void` corresponds to `Void`.

    • That leads to more problems though, since we then can't tell zero-argument C functions exposed as getters apart from a const global in C - they could both be `@Native() external MyStruct get foo;`.

    • Right. I guess we could default to one when unspecified and then use the specified if specified. But you're right that there would be no unique solution.

      Maybe we should just stick with that one must specify the function types. Because it's already a bit funky if you must specify on some function parameter types but not on other.

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 7
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Johnni Winther <johnni...@google.com>
Gerrit-Attention: Jens Johansen <je...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Comment-Date: Tue, 05 Dec 2023 11:09:37 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Daco Harkes <dacoh...@google.com>
Comment-In-Reply-To: Simon Binder <o...@simonbinder.eu>

Jens Johansen (Gerrit)

unread,
Dec 5, 2023, 6:23:28 AM12/5/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Johnni Winther, Brian Wilkerson, Daco Harkes, Alexander Markov, Paul Berry

Attention is currently required from: Daco Harkes, Johnni Winther, Simon Binder.

View Change

1 comment:

  • File pkg/analyzer/test/src/diagnostics/ffi_native_test.dart:

    • > For instance, consider the difference between `@Native()` and `@Native<Object?>`(). […]

      This dart code:

      ```
      @Foo()
      @Foo<dynamic>()
      @Foo<Object>()
      @Foo<Object?>()
      @Foo<int>()
      main() {
      print("hello");
      }
      ```

      compiles to

      ```
      @#C1
      @#C1
      @#C2
      @#C3
      @#C4
      static method main() → dynamic {
      core::print("hello");
      }
      constants {
      #C1 = self::Foo<dynamic> {}
      #C2 = self::Foo<core::Object*> {}
      #C3 = self::Foo<core::Object?> {}
      #C4 = self::Foo<core::int*> {}
      }
      ```

      So `@Foo()` and `@Foo<dynamic>()` are the same, but others (e.g. `@Foo<Object?>()`) are different.

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 7
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Daco Harkes <dacoh...@google.com>
Gerrit-Attention: Johnni Winther <johnni...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Comment-Date: Tue, 05 Dec 2023 11:23:21 +0000

Daco Harkes (Gerrit)

unread,
Dec 5, 2023, 6:31:46 AM12/5/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

Attention is currently required from: Jens Johansen, Johnni Winther, Simon Binder.

View Change

2 comments:

  • Patchset:

  • File pkg/analyzer/test/src/diagnostics/ffi_native_test.dart:

    • This dart code: […]

      That would solve our issue. Just to double check:

      Is this without a bound?

      ```
      class Foo<T> {

      }
      ```

      What about with a bound?

      ```
      class Foo<T extends Object?> {

      }
      ```

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 7
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Johnni Winther <johnni...@google.com>
Gerrit-Attention: Jens Johansen <je...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Comment-Date: Tue, 05 Dec 2023 11:31:40 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Daco Harkes <dacoh...@google.com>
Comment-In-Reply-To: Jens Johansen <je...@google.com>
Comment-In-Reply-To: Simon Binder <o...@simonbinder.eu>

Jens Johansen (Gerrit)

unread,
Dec 5, 2023, 6:48:00 AM12/5/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Johnni Winther, Brian Wilkerson, Daco Harkes, Alexander Markov, Paul Berry

Attention is currently required from: Daco Harkes, Johnni Winther, Simon Binder.

View Change

1 comment:

  • File pkg/analyzer/test/src/diagnostics/ffi_native_test.dart:

    • That would solve our issue. Just to double check: […]

      ```
      $ cat t.dart
      class Foo<T> {
      final int i;
      const Foo(this.i);
      }
      class Bar<T extends Object?> {
      final int i;
      const Bar(this.i);
      }

      @Foo(1)
      @Foo<dynamic>(2)
      @Foo<Object>(3)
      @Foo<Object?>(4)
      @Foo<int>(5)
      withFoo() {}

      @Bar(1)
      @Bar<dynamic>(2)
      @Bar<Object>(3)
      @Bar<Object?>(4)
      @Bar<int>(5)
      withBar() {}

      $ pkg/front_end/tool/fasta compile --dump-ir --omit-platform t.dart
      library;
      import self as self;
      import "dart:core" as core;

      class Foo<T extends core::Object? = dynamic> extends core::Object /*hasConstConstructor*/  {
      final field core::int i;
      const constructor •(core::int i) → self::Foo<self::Foo::T%>
      : self::Foo::i = i, super core::Object::•()
      ;
      }
      class Bar<T extends core::Object?> extends core::Object /*hasConstConstructor*/ {
      final field core::int i;
      const constructor •(core::int i) → self::Bar<self::Bar::T%>
      : self::Bar::i = i, super core::Object::•()
      ;
      }
      @#C2
      @#C4
      @#C6
      @#C8
      @#C10
      static method withFoo() → dynamic {}
      @#C11
      @#C12
      @#C13
      @#C14
      @#C15
      static method withBar() → dynamic {}
      constants {
      #C1 = 1
      #C2 = self::Foo<dynamic> {i:#C1}
      #C3 = 2
      #C4 = self::Foo<dynamic> {i:#C3}
      #C5 = 3
      #C6 = self::Foo<core::Object*> {i:#C5}
      #C7 = 4
      #C8 = self::Foo<core::Object?> {i:#C7}
      #C9 = 5
      #C10 = self::Foo<core::int*> {i:#C9}
      #C11 = self::Bar<core::Object?> {i:#C1}
      #C12 = self::Bar<dynamic> {i:#C3}
      #C13 = self::Bar<core::Object*> {i:#C5}
      #C14 = self::Bar<core::Object?> {i:#C7}
      #C15 = self::Bar<core::int*> {i:#C9}
      }
      ```

      which edited for some sort of clarity becomes

      ```
      @#C2 // `@Foo(1)` becomes `self::Foo<dynamic> {i: 1}`
      @#C4 // `@Foo<dynamic>(2)` becomes `self::Foo<dynamic> {i: 2}`
      @#C6 // `@Foo<Object>(3)` becomes `self::Foo<core::Object*> {i: 3}`
      @#C8 // `@Foo<Object?>(4)` becomes `self::Foo<core::Object?> {i: 4}`
      @#C10 // `@Foo<int>(5)` becomes `self::Foo<core::int*> {i: 5}`
      static method withFoo() → dynamic {}

      @#C11 // `@Bar(1)` becomes `self::Bar<core::Object?> {i: 1}`
      @#C12 // `@Bar<dynamic>(2)` becomes `self::Bar<dynamic> {i: 2}`
      @#C13 // `@Bar<Object>(3)` becomes `self::Bar<core::Object*> {i: 3}`
      @#C14 // `@Bar<Object?>(4)` becomes `self::Bar<core::Object?> {i: 4}`
      @#C15 // `@Bar<int>(5)` becomes `self::Bar<core::int*> {i: 5}`
      static method withBar() → dynamic {}
      ```

      So with no bound `@Foo(` and `@Foo<dynamic>(` looks the same.
      With an `Object?` bound `@Bar(` and `@Bar<Object?>(` looks the same.

      Maybe @johnni...@google.com has input though --- is it important / how important is it to know the difference?

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 7
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Daco Harkes <dacoh...@google.com>
Gerrit-Attention: Johnni Winther <johnni...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Comment-Date: Tue, 05 Dec 2023 11:47:53 +0000

Simon Binder (Gerrit)

unread,
Dec 8, 2023, 9:59:59 AM12/8/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Daco Harkes, Johnni Winther, Simon Binder.

Simon Binder uploaded patch set #8 to this change.

View Change

[vm/ffi] Support `@Native` fields

Allow annotating top-level or static fields with `@Native` to create
fields backed by native memory.
By using the `_addressOf` operator implemented in the VM, these fields
can be implemented in the CFE by replacing them with accessors looking
up the pointer and then using existing methods to load and store the
value.

Closes https://github.com/dart-lang/sdk/issues/50551

TEST=tests/ffi/native_assets/asset_*_test.dart
TEST=pkg/analyzer/test/src/diagnostics/ffi_native_test.dart

Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
---
M pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
M pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
M pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
M pkg/analyzer/lib/src/error/error_code_values.g.dart
M pkg/analyzer/lib/src/generated/ffi_verifier.dart
M pkg/analyzer/messages.yaml
M pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
M pkg/front_end/lib/src/api_unstable/vm.dart
M pkg/front_end/messages.yaml
M pkg/vm/lib/transformations/ffi/common.dart
M pkg/vm/lib/transformations/ffi/definitions.dart
M pkg/vm/lib/transformations/ffi/native.dart
M pkg/vm/lib/transformations/ffi/use_sites.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect
A pkg/vm/testcases/transformations/ffi/native_fields.dart.expect

M runtime/bin/ffi_test/ffi_test_functions.cc
M runtime/vm/compiler/frontend/kernel_to_il.cc
M sdk/lib/ffi/ffi.dart
M tests/ffi/native_assets/asset_absolute_test.dart
M tests/ffi/native_assets/asset_library_annotation_test.dart
M tests/ffi/native_assets/asset_relative_test.dart
M tests/ffi/vmspecific_static_checks_native_test.dart
23 files changed, 1,241 insertions(+), 194 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 8

Simon Binder (Gerrit)

unread,
Dec 9, 2023, 2:14:11 PM12/9/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Daco Harkes, Johnni Winther, Simon Binder.

Simon Binder uploaded patch set #9 to this change.

View Change

23 files changed, 1,376 insertions(+), 201 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 9

Simon Binder (Gerrit)

unread,
Dec 9, 2023, 2:36:33 PM12/9/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Daco Harkes, Johnni Winther, Simon Binder.

Simon Binder uploaded patch set #10 to this change.

View Change

[vm/ffi] Support `@Native` fields

Allow annotating top-level or static fields with `@Native` to create
fields backed by native memory.
By using the `_addressOf` operator implemented in the VM, these fields
can be implemented in the CFE by replacing them with accessors looking
up the pointer and then using existing methods to load and store the
value.

Closes https://github.com/dart-lang/sdk/issues/50551

TEST=tests/ffi/native_assets/asset_*_test.dart
TEST=pkg/analyzer/test/src/diagnostics/ffi_native_test.dart

Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
---
M CHANGELOG.md

M pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
M pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
M pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
M pkg/analyzer/lib/src/error/error_code_values.g.dart
M pkg/analyzer/lib/src/generated/ffi_verifier.dart
M pkg/analyzer/messages.yaml
M pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
M pkg/front_end/lib/src/api_unstable/vm.dart
M pkg/front_end/messages.yaml
M pkg/vm/lib/transformations/ffi/common.dart
M pkg/vm/lib/transformations/ffi/definitions.dart
M pkg/vm/lib/transformations/ffi/native.dart
M pkg/vm/lib/transformations/ffi/use_sites.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect
A pkg/vm/testcases/transformations/ffi/native_fields.dart.expect
M runtime/bin/ffi_test/ffi_test_functions.cc
M runtime/vm/compiler/frontend/kernel_to_il.cc
M sdk/lib/ffi/ffi.dart
M tests/ffi/native_assets/asset_absolute_test.dart
M tests/ffi/native_assets/asset_library_annotation_test.dart
M tests/ffi/native_assets/asset_relative_test.dart
M tests/ffi/vmspecific_static_checks_native_test.dart
24 files changed, 1,382 insertions(+), 201 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 10

Simon Binder (Gerrit)

unread,
Dec 9, 2023, 2:40:05 PM12/9/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Johnni Winther, Jens Johansen, Brian Wilkerson, Daco Harkes, Alexander Markov, Paul Berry

Attention is currently required from: Daco Harkes.

View Change

2 comments:

  • File pkg/analyzer/test/src/diagnostics/ffi_native_test.dart:

    • Ditto with classes extending struct. […]

      Done, I've added struct tests to this file and to the static checks test. I've also added runtime tests for getting and setting individual fields / whole structs.

    • ``` […]

      Thanks! I've made the CFE and the analyzer infer the type when the one given is `dynamic`.

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 10
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Daco Harkes <dacoh...@google.com>
Gerrit-Comment-Date: Sat, 09 Dec 2023 19:40:00 +0000

Daco Harkes (Gerrit)

unread,
Dec 11, 2023, 12:38:24 PM12/11/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

Attention is currently required from: Lasse Nielsen, Simon Binder.

View Change

32 comments:

  • Patchset:

    • Patch Set #10:

      Great work Simon!

      Here are a million comments. 🤓 Most of them are nits, the only bigger thing is if we can reuse the code generation in pkg/vm/lib/transformations/ffi/native_type_cfe.dart

  • File pkg/analyzer/messages.yaml:

    • Patch Set #10, Line 18668: 0: The invalid type.

      Could you expand the documentation here with examples?

      ```
      hasPublishedDocs: true
      documentation: |-
      ...
      ```
  • File pkg/vm/lib/transformations/ffi/common.dart:

    • Patch Set #10, Line 689: DartType? canonicalNativeTypeForDartType(DartType dartType) {

      Add a comment that this is the inverse of `ensureNativeTypeToDartType`, and add a comment there that it's the inverse of this.

      Maybe also move this method next to `ensureNativeTypeToDartType`.

      Also, probably rename this to `dartTypeToNativeType`, so that the relation is clear.

    • Patch Set #10, Line 699: bool canInferNativeTypeFromDartType(DartType dartType) {

      Maybe use a case on `canonicalNativeTypeForDartType` the two call sites instead?

      Currently both call sites rely on `dartType` being equal to `canonicalNativeTypeForDartType(dartType)`, but maybe we would have some other inferred type in the future.

      Or, alternatively refactor `canonicalNativeTypeForDartType` to `bool nativeTypeAndDartTypeEqual(...)` so that it's clear we're never returning a different type.

  • File pkg/vm/lib/transformations/ffi/native.dart:

    • Patch Set #10, Line 617: /// We can't re-use the constant from the `@Native` annotation because the

      Here and in other places, avoid 'we'. (Sorry I'm guilty of this myself as well.)

      (Inspired from: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#use-the-passive-voice-recommend-do-not-require-never-say-things-are-simple )

    • Patch Set #10, Line 619: InstanceConstant _createResolvedNativeConstant({

      I think in other files we use `generate` instead of `create` for methods generating/creating AST nodes.

    • Patch Set #10, Line 742: Expression _addressOfField(

      nit: maybe start method name with `_generate` so that it's clear we're creating an expression here.

      The same for the methods below.

    • Patch Set #10, Line 750: Expression _loadField(

      I think this can be implemented in pkg/vm/lib/transformations/ffi/native_type_cfe.dart See for example `AbiSpecificNativeTypeCfe.generateGetterStatement`

      `transformer.getCompoundTypedDataBaseField` (will be `nativeAddressOf` in the new case) and offset (will be always 0 in the new case) should be factored out then.

      That will avoid the branching on type here.

      And means we're reusing all the 'generate' logic.

      Please give it a shot and see if that reduces the amount of code duplication between here and that file for reading/writing the different FFI types.

    • Patch Set #10, Line 841: // We don't currently allow handles and arrays, but we can provide a more

      Nit: 'We'

      (And yes, this is the correct way to do it to provide better error messages.)

    • Patch Set #10, Line 909: if (isFunction) {

      nit: if isFunction is inlined, we have type promotion for nativeType in the then branch.

    • Patch Set #10, Line 929: if (node.kind == ProcedureKind.Getter ||

      else if?

    • Patch Set #10, Line 974: // There's a function not annotated with a native function type, which

      Nit: "There is" is pointing to somewhere, but nothing specific.

      Rephrase to: This function is not ...

  • File pkg/vm/testcases/transformations/ffi/native_fields.dart.expect:

    • Patch Set #10, Line 44: return ffi::_loadAbiSpecificInt<ffi::Int>(ffi::Native::_addressOf<ffi::Int>(#C26), #C8);

      Yep, makes sense.

  • File runtime/bin/ffi_test/ffi_test_functions.cc:

    • Patch Set #10, Line 49: // sdk/runtime/tools/dartfuzz/dartfuzz_ffi_api.dart

      Did we update the fuzzer? I don't see any changes to the fuzzer.

      Do these changes break the fuzzer?

      If these changes are unrelated to the fuzzer, maybe move them down under a different header in this file?

      (Thanks for fixing the reference!)

  • File sdk/lib/ffi/ffi.dart:

    • Patch Set #10, Line 1120: /// Annotation specifying how to bind an external function or global field to

      @l...@google.com Would you please be so kind to also take a look at the API?

    • Patch Set #10, Line 1123: /// The annotation applies only to `external` function and field declarations.

      I don't think we use `declaration` and `definition` in Dart terminology like in C++.

      So maybe this should be: "The annotation applies only to `external` functions and fields."

    • Patch Set #10, Line 1127: /// Similarly, a [Native]-annotated `external` field is implemented by reading

      Maybe use "native memory"

      ```
      /// A [Native]-annotated `external` field is implemented by reading
      /// from or writing to native memory.
      ```

    • Patch Set #10, Line 1133: /// against the native library, to find a native function or a native variable,

      "native variable" -> "native global variable"

    • Patch Set #10, Line 1141: /// When this annotation is used on a function, the type argument [T] to the

      When -> If ?

    • Patch Set #10, Line 1146: /// When an external field is annotated with [Native], the type argument [T]

      When -> If ?

    • Patch Set #10, Line 1147: /// must be a compatible native type. An [int] field could be annotated with

      Maybe start the sentence with "For example," instead of ending with it.

    • Patch Set #10, Line 1148: /// [Int32], for example. Some Dart types, namely [Pointer]s, [Compound]

      Maybe rephrase to: "If the Dart type is equal to the native type, the type argument on the `@Native` annotation can be omitted."

      (That avoids us from having to enumerate all types for which this is the case, as that list might change.)

    • Patch Set #10, Line 1151: ///

      Maybe mention that const global variables in C++ would only need a getter.

    • Patch Set #10, Line 1162: /// external Pointer<Char> aGlobalString;

      Use an external getter here instead of an external field. (Assuming this is a global const string.)

    • Patch Set #10, Line 1165: /// Calling such function will try to resolve the [symbol] in (in that order)

      Should mention reading/writing to the field.

    • Patch Set #10, Line 1268: /// [native] must be a reference to a method or field annotated with

      an external method or field

    • Patch Set #10, Line 1276: /// For example, consider a native C library exposing a function and a global

      For example, for a native C library ...

    • Patch Set #10, Line 1289: /// ```dart

      Maybe split into two separate examples, one for field one for function. That way the examples individually are smaller and the documentation reader has to keep less paged in their memory. And it makes it clear that the field and method are unrelated.

  • File tests/ffi/native_assets/asset_absolute_test.dart:

    • Patch Set #10, Line 114: external static Coord copy(Pointer<Coord> ptr);

      This is a bit of a weird API. Usually native code passes a type only by pointer or only by value.

      For a copy, it would need to return a `Pointer<Coord>` and malloc it.

      (Returning the struct by value, it should more likely be called `CoordinateRead` or `CoordinateByValue`. Because we're not keeping a copy in native memory at all, it's being returned to the caller by value.)

  • File tests/ffi/native_assets/asset_relative_test.dart:

  • File tests/ffi/vmspecific_static_checks_native_test.dart:

    • Patch Set #10, Line 51: @Native<Pointer<MyStruct>>()

      nit: newline

    • Patch Set #10, Line 79: // [cfe] Unsupported type for native fields. Native fields only support pointers or numeric and compound types.

      or and

      Maybe rewrite as: Native fields only support pointers, compounds, and numeric types.

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 10
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Lasse Nielsen <l...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Comment-Date: Mon, 11 Dec 2023 17:38:17 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No

Daco Harkes (Gerrit)

unread,
Dec 11, 2023, 12:38:24 PM12/11/23
to Lasse Nielsen, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Simon Binder, Brian Wilkerson

Attention is currently required from: Lasse Nielsen, Simon Binder.

Daco Harkes would like Lasse Nielsen to review this change authored by Simon Binder.

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad2a1b0..2cc74ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,12 @@
- `String.fromCharCodes` now allow `start` and `end` to be after the end of
the `Iterable` argument, just like `skip` and `take` does on an `Iterable`.

+#### `dart:ffi`
+
+- In addition to functions, `@Native` can now be used on fields.
+- Allow taking the address of native functions and fields via
+ `Native.addressOf`.
+
#### `dart:nativewrappers`

- **Breaking Change** [#51896][]: The NativeWrapperClasses are marked `base` so
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index 3d51157..089f82e 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -5198,7 +5198,38 @@
"FfiNativeDuplicateAnnotations",
analyzerCodes: <String>["FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS"],
problemMessage:
- r"""Native functions must not have more than @Native annotation.""");
+ r"""Native functions and fields must not have more than @Native annotation.""");

+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeFfiNativeFieldMissingType =
+ messageFfiNativeFieldMissingType;

+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageFfiNativeFieldMissingType = const MessageCode(
+ "FfiNativeFieldMissingType",
+ analyzerCodes: <String>["NATIVE_FIELD_MISSING_TYPE"],
+ problemMessage:
+ r"""The native type of this field could not be inferred and must be specified in the annotation.""");

+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeFfiNativeFieldMustBeStatic =
+ messageFfiNativeFieldMustBeStatic;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageFfiNativeFieldMustBeStatic = const MessageCode(
+ "FfiNativeFieldMustBeStatic",
+ analyzerCodes: <String>["NATIVE_FIELD_NOT_STATIC"],
+ problemMessage: r"""Native fields must be static.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeFfiNativeFieldType = messageFfiNativeFieldType;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageFfiNativeFieldType = const MessageCode(
+ "FfiNativeFieldType",
+    analyzerCodes: <String>["NATIVE_FIELD_INVALID_TYPE"],
+ problemMessage:
+ r"""Unsupported type for native fields. Native fields only support pointers or numeric and compound types.""");


// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeFfiNativeMustBeExternal = messageFfiNativeMustBeExternal;
@@ -5206,7 +5237,8 @@

// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageFfiNativeMustBeExternal = const MessageCode(
"FfiNativeMustBeExternal",
- problemMessage: r"""Native functions must be marked external.""");
+ problemMessage:
+ r"""Native functions and fields must be marked external.""");

// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeFfiNativeOnlyNativeFieldWrapperClassCanBePointer =
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 9d2ad38..8ac433c 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -1792,6 +1792,12 @@
status: noFix
FfiCode.MUST_RETURN_VOID:
status: noFix
+FfiCode.NATIVE_FIELD_INVALID_TYPE:
+ status: needsEvaluation
+FfiCode.NATIVE_FIELD_MISSING_TYPE:

+ status: needsEvaluation
+FfiCode.NATIVE_FIELD_NOT_STATIC:
+ status: needsEvaluation
 FfiCode.NON_CONSTANT_TYPE_ARGUMENT:
status: noFix
FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER:
diff --git a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
index 667e640..89a1206 100644
--- a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
+++ b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
@@ -87,7 +87,8 @@
static const FfiCode ARGUMENT_MUST_BE_NATIVE = FfiCode(
'ARGUMENT_MUST_BE_NATIVE',
"Argument to 'Native.addressOf' must be annotated with @Native",
- correctionMessage: "Try passing a static function annotated with '@Native'",
+ correctionMessage:
+ "Try passing a static function or field annotated with '@Native'",
);

/// Parameters:
@@ -147,7 +148,7 @@
/// No parameters
static const FfiCode FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS = FfiCode(
'FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS',
- "Native functions must have exactly one `@Native` annotation.",
+ "Native functions and fields must have exactly one `@Native` annotation.",
correctionMessage: "Try removing the extra annotation.",
);

@@ -353,6 +354,33 @@
);


/// Parameters:
+ /// 0: The invalid type.
+  static const FfiCode NATIVE_FIELD_INVALID_TYPE = FfiCode(
+ 'NATIVE_FIELD_INVALID_TYPE',

+ "'{0}' is an unsupported type for native fields. Native fields only "
+ "support pointers or numeric and compound types.",
+ correctionMessage:
+ "Try changing the type in the `@Native` annotation to a numeric FFI "
+ "type, a pointer, or a compound class.",
+ );
+
+  static const FfiCode NATIVE_FIELD_MISSING_TYPE = FfiCode(
+ 'NATIVE_FIELD_MISSING_TYPE',
+ "The native type of this field could not be inferred and must be specified "
+ "in the annotation.",
+ correctionMessage:
+ "Try adding a type parameter extending `NativeType` to the `@Native` "
+ "annotation.",

+ );
+
+ /// No parameters
+ static const FfiCode NATIVE_FIELD_NOT_STATIC = FfiCode(
+ 'NATIVE_FIELD_NOT_STATIC',
+ "Native fields must be static.",
+ correctionMessage: "Try adding the modifier 'static' to this field.",
+ );
+
+ /// Parameters:
   ///  0: the name of the function, method, or constructor having type arguments
static const FfiCode NON_CONSTANT_TYPE_ARGUMENT = FfiCode(
     'NON_CONSTANT_TYPE_ARGUMENT',
diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart
index f157b10..a652c1a 100644
--- a/pkg/analyzer/lib/src/error/error_code_values.g.dart
+++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart
@@ -609,6 +609,9 @@
FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
FfiCode.MUST_BE_A_SUBTYPE,
FfiCode.MUST_RETURN_VOID,
+ FfiCode.NATIVE_FIELD_INVALID_TYPE,
+ FfiCode.NATIVE_FIELD_MISSING_TYPE,
+ FfiCode.NATIVE_FIELD_NOT_STATIC,

FfiCode.NON_CONSTANT_TYPE_ARGUMENT,
FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER,
FfiCode.NON_POSITIVE_ARRAY_DIMENSION,
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index 34ddd42..d7b84e7 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -180,6 +180,19 @@

if (inCompound) {
_validateFieldsInCompound(node);
}
+
+ for (var declared in node.fields.variables) {
+ var declaredElement = declared.declaredElement;
+ if (declaredElement != null) {
+ _checkFfiNative(
+ errorNode: declared,
+          declarationElement: declaredElement,
+ formalParameterList: null,
+ isExternal: node.externalKeyword != null,
+ );
+ }
+ }
+
super.visitFieldDeclaration(node);
}

@@ -187,9 +200,9 @@
void visitFunctionDeclaration(FunctionDeclaration node) {
_checkFfiNative(
errorNode: node,
- annotations: node.declaredElement!.metadata,

declarationElement: node.declaredElement!,
formalParameterList: node.functionExpression.parameters,
+ isExternal: node.externalKeyword != null,
);
super.visitFunctionDeclaration(node);
}
@@ -263,10 +276,11 @@

@override
void visitMethodDeclaration(MethodDeclaration node) {
_checkFfiNative(
- errorNode: node,
-        annotations: node.declaredElement!.metadata,

- declarationElement: node.declaredElement!,
- formalParameterList: node.parameters);
+ errorNode: node,
+      declarationElement: node.declaredElement!,
+ formalParameterList: node.parameters,
+ isExternal: node.externalKeyword != null,
+ );
super.visitMethodDeclaration(node);
}

@@ -335,17 +349,41 @@

super.visitPropertyAccess(node);
}

+ @override
+ void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
+ for (var declared in node.variables.variables) {
+ var declaredElement = declared.declaredElement;
+ if (declaredElement != null) {
+ _checkFfiNative(
+ errorNode: declared,
+          declarationElement: declaredElement,
+ formalParameterList: null,
+ isExternal: node.externalKeyword != null,
+ );
+ }
+ }
+    super.visitTopLevelVariableDeclaration(node);
+ }
+
+ DartType? _canonicalFfiTypeForDartType(DartType dartType) {
+ if (dartType.isPointer || dartType.isCompoundSubtype || dartType.isArray) {
+ return dartType;
+ } else {
+ return null;
+ }

+ }
+
void _checkFfiNative({
required Declaration errorNode,
-    required List<ElementAnnotation> annotations,

- required ExecutableElement declarationElement,
+ required Element declarationElement,
     required FormalParameterList? formalParameterList,
+ required bool isExternal,
}) {
final formalParameters =
formalParameterList?.parameters ?? <FormalParameter>[];
var hadNativeAnnotation = false;

- for (var annotation in annotations) {
+ for (var annotation in declarationElement.metadata) {
var annotationValue = annotation.computeConstantValue();
var annotationType = annotationValue?.type; // Native<T>

@@ -359,108 +397,186 @@
var name = (annotation as ElementAnnotationImpl).annotationAst.name;
_errorReporter.reportErrorForNode(
FfiCode.FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS, name, []);
+ break;
}

hadNativeAnnotation = true;

- var ffiSignature = annotationType.typeArguments[0]; // The T in @Native<T>
-
- if (ffiSignature is! FunctionType) {
- _errorReporter.reportErrorForNode(
- FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, errorNode, ['T', 'Native']);
- return;
- }
-

- // Leaf call FFI Natives can't use Handles.
-      var isLeaf = annotationValue.getField(_isLeafParamName)?.toBoolValue();
- if (isLeaf == true) {
- _validateFfiLeafCallUsesNoHandles(ffiSignature, errorNode);
- }

-
- if (!declarationElement.isExternal) {
+ if (!isExternal) {
_errorReporter.reportErrorForNode(
FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL, errorNode);
}

- var ffiParameterTypes =
- ffiSignature.normalParameterTypes.flattenVarArgs();
- var ffiParameters = ffiSignature.parameters;
+      var ffiSignature = annotationType.typeArguments[0]; // The T in @Native<T>
-      } else {

- // Number of parameters in the Native annotation must match the
- // annotated declaration.
- if (formalParameters.length != ffiParameterTypes.length) {
-          _errorReporter.reportErrorForNode(

- FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS,
- errorNode,
- [ffiParameterTypes.length, formalParameters.length]);
-          return;
- }
- }
-
-      // Arguments can only be Pointer if the class extends
- // Pointer or NativeFieldWrapperClass1.
- for (var i = 0; i < formalParameters.length; i++) {
- if (ffiParameterTypes[i].isPointer) {
- final type =
- formalParameters[i].declaredElement!.type as InterfaceType;
- if (!type.isPointer && !_extendsNativeFieldWrapperClass1(type)) {
- _errorReporter.reportErrorForNode(
- FfiCode
- .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
- errorNode);
- }
-        }
- }
-

- final dartType = declarationElement.type;
- final nativeType = FunctionTypeImpl(
- typeFormals: ffiSignature.typeFormals,
- parameters: ffiParameters,
- returnType: ffiSignature.returnType,
- nullabilitySuffix: ffiSignature.nullabilitySuffix,
- );
- if (!_isValidFfiNativeFunctionType(nativeType)) {
-        _errorReporter.reportErrorForNode(
- FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
+ if (ffiSignature is FunctionType) {
+ if (declarationElement is ExecutableElement) {
+ _checkFfiNativeFunction(

errorNode,
- [nativeType, 'Native']);
+            declarationElement,
+ ffiSignature,
+ annotationValue,
+ formalParameters,
+ );
+ } else {
+ // Field annotated with a function type, that can't work.
+ _errorReporter.reportErrorForNode(
+ FfiCode.NATIVE_FIELD_INVALID_TYPE, errorNode, [ffiSignature]);
+ }
+ } else {
+ if (declarationElement is MethodElement ||
+ declarationElement is FunctionElement) {
+ // Function annotated with something that isn't a function type.
+ _errorReporter.reportErrorForNode(
+ FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
+ errorNode,
+ ['T', 'Native']);
+ } else {
+ _checkFfiNativeField(
+ errorNode, declarationElement, ffiSignature, annotationValue);
+ }
+ }
+
+ if (ffiSignature is FunctionType &&
+ declarationElement is ExecutableElement) {}
+ }
+ }
+
+ void _checkFfiNativeField(
+ Declaration errorNode,
+ Element declarationElement,
+ DartType ffiSignature,
+ DartObject annotationValue,
+ ) {

+ DartType type;
+
+ if (declarationElement is FieldElement) {
+ if (!declarationElement.isStatic) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.NATIVE_FIELD_NOT_STATIC, errorNode);
+ }
+ type = declarationElement.type;
+ } else if (declarationElement is TopLevelVariableElement) {
+ type = declarationElement.type;
+    } else if (declarationElement is PropertyAccessorElement) {
+ type = declarationElement.variable.type;
+ } else {

+ _errorReporter.reportErrorForNode(
+ FfiCode.NATIVE_FIELD_NOT_STATIC, errorNode);
+      return;
+ }
+
+ if (ffiSignature is DynamicType) {
+ // Attempt to infer the native type from the Dart type.
+ final canonical = _canonicalFfiTypeForDartType(type);
+
+ if (canonical == null) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.NATIVE_FIELD_MISSING_TYPE, errorNode, [type, ffiSignature]);
+ return;
+ } else {
+ ffiSignature = canonical;
+ }
+ }
+
+ if (!_validateCompatibleNativeType(type, ffiSignature)) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.MUST_BE_A_SUBTYPE, errorNode, [type, ffiSignature, 'Native']);
+ } else if (ffiSignature.isArray ||
+ ffiSignature.isHandle ||
+ ffiSignature.isNativeFunction) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.NATIVE_FIELD_INVALID_TYPE, errorNode, [type, ffiSignature]);
+ }
+ }
+

+ void _checkFfiNativeFunction(
+ Declaration errorNode,
+ ExecutableElement declarationElement,
+ FunctionType ffiSignature,
+    DartObject annotationValue,

+ List<FormalParameter> formalParameters,
+ ) {
+ // Leaf call FFI Natives can't use Handles.
+    var isLeaf = annotationValue.getField(_isLeafParamName)?.toBoolValue();
+ if (isLeaf == true) {
+ _validateFfiLeafCallUsesNoHandles(ffiSignature, errorNode);
+ }

+
+ var ffiParameterTypes = ffiSignature.normalParameterTypes.flattenVarArgs();
+ var ffiParameters = ffiSignature.parameters;
+
+ if ((declarationElement is MethodElement ||
+ declarationElement is PropertyAccessorElementImpl) &&
+ !declarationElement.isStatic) {
+ // Instance methods must have the receiver as an extra parameter in the
+ // Native annotation.
+ if (formalParameters.length + 1 != ffiParameterTypes.length) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER,
+ errorNode,
+ [formalParameters.length + 1, ffiParameterTypes.length]);
         return;
}
- if (!_validateCompatibleFunctionTypes(dartType, nativeType,
- nativeFieldWrappersAsPointer: true, allowStricterReturn: true)) {
- _errorReporter.reportErrorForNode(FfiCode.MUST_BE_A_SUBTYPE, errorNode,
- [nativeType, dartType, 'Native']);
+

+ // Receiver can only be Pointer if the class extends
+ // NativeFieldWrapperClass1.
+ if (ffiSignature.normalParameterTypes[0].isPointer) {
+ final cls = declarationElement.enclosingElement as InterfaceElement;
+ if (!_extendsNativeFieldWrapperClass1(cls.thisType)) {
+ _errorReporter.reportErrorForNode(
+ FfiCode
+ .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
+ errorNode);
+        }
+ }
+

+ ffiParameterTypes = ffiParameterTypes.sublist(1);
+ ffiParameters = ffiParameters.sublist(1);
+ } else {
+ // Number of parameters in the Native annotation must match the
+ // annotated declaration.
+ if (formalParameters.length != ffiParameterTypes.length) {
+        _errorReporter.reportErrorForNode(
+ FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS,
+ errorNode,

+ [ffiParameterTypes.length, formalParameters.length]);
return;
}
}
@@ -1234,7 +1350,7 @@

var validTarget = false;

var referencedElement = switch (argument) {
- Identifier() => argument.staticElement,
+ Identifier() => argument.staticElement?.nonSynthetic,
_ => null,
};

@@ -1245,25 +1361,50 @@


if (annotationType is InterfaceType &&
annotationType.element.isNative) {
-          var functionType = annotationType.typeArguments[0];

+ var nativeType = annotationType.typeArguments[0];

- // When referencing a function, the target type must be a
- // `NativeFunction<T>` so that `T` matches the type from the
- // annotation.
- if (!targetType.isNativeFunction) {
- _errorReporter.reportErrorForNode(
- FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
- node,
- [targetType, _nativeAddressOf],
- );
+ if (nativeType is FunctionType) {
+            // When referencing a function, the target type must be a
+ // `NativeFunction<T>` so that `T` matches the type from the
+ // annotation.

+ if (!targetType.isNativeFunction) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
+ node,
+ [targetType, _nativeAddressOf],
+ );
+ } else {
+ var targetFunctionType =
+ (targetType as InterfaceType).typeArguments[0];
+ if (!typeSystem.isAssignableTo(nativeType, targetFunctionType)) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.MUST_BE_A_SUBTYPE,
+ node,
+ [nativeType, targetFunctionType, _nativeAddressOf],
+ );
+ }
+ }
} else {
- var targetFunctionType =
- (targetType as InterfaceType).typeArguments[0];
- if (!typeSystem.isAssignableTo(functionType, targetFunctionType)) {
+ // A native field is being referenced, this doesn't require a
+            // NativeFunction wrapper. However, we can't read the native type
+ // from the annotation directly because it might be inferred if none
+ // was given.
+ if (nativeType is DynamicType) {
+ final staticType = argument.staticType;
+ if (staticType != null) {
+ final canonical = _canonicalFfiTypeForDartType(staticType);
+
+ if (canonical != null) {
+ nativeType = canonical;
+ }
+ }
+ }
+

+ if (!typeSystem.isAssignableTo(nativeType, targetType)) {
_errorReporter.reportErrorForNode(
FfiCode.MUST_BE_A_SUBTYPE,
node,
- [functionType, targetFunctionType, _nativeAddressOf],
+ [nativeType, targetType, _nativeAddressOf],
);
}
}
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 01ee657..7d8faad 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -18658,8 +18658,22 @@
```
ARGUMENT_MUST_BE_NATIVE:

problemMessage: "Argument to 'Native.addressOf' must be annotated with @Native"
-    correctionMessage: "Try passing a static function annotated with '@Native'"
+ correctionMessage: "Try passing a static function or field annotated with '@Native'"
comment: No parameters
+ NATIVE_FIELD_INVALID_TYPE:

+ problemMessage: "'{0}' is an unsupported type for native fields. Native fields only support pointers or numeric and compound types."
+ correctionMessage: "Try changing the type in the `@Native` annotation to a numeric FFI type, a pointer, or a compound class."
+ comment: |-
+ Parameters:
+ 0: The invalid type.
+  NATIVE_FIELD_MISSING_TYPE:
+ problemMessage: "The native type of this field could not be inferred and must be specified in the annotation."
+ correctionMessage: "Try adding a type parameter extending `NativeType` to the `@Native` annotation."
+ pcomment: "No parameters"

+ NATIVE_FIELD_NOT_STATIC:
+ problemMessage: "Native fields must be static."
+ correctionMessage: "Try adding the modifier 'static' to this field."
+ comment: "No parameters"
COMPOUND_IMPLEMENTS_FINALIZABLE:
problemMessage: "The class '{0}' can't implement Finalizable."
correctionMessage: "Try removing the implements clause from '{0}'."
@@ -18915,7 +18929,7 @@
correctionMessage: "Try removing the extra annotation."
comment: No parameters
FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS:
- problemMessage: "Native functions must have exactly one `@Native` annotation."
+ problemMessage: "Native functions and fields must have exactly one `@Native` annotation."
correctionMessage: "Try removing the extra annotation."
comment: No parameters
FIELD_INITIALIZER_IN_STRUCT:
diff --git a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
index 28ba4d8..c86b49d 100644
--- a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
@@ -13,6 +13,7 @@
defineReflectiveTests(AddressOfTest);
defineReflectiveTests(DefaultAssetTest);
defineReflectiveTests(FfiNativeTest);
+ defineReflectiveTests(NativeFieldTest);
defineReflectiveTests(NativeTest);
});
}
@@ -86,6 +87,19 @@
]);
}

+ test_invalid_MismatchedInferredType() async {

+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external Pointer<IntPtr> global;
+
+void main() => print(Native.addressOf<Pointer<Double>>(global));
+''', [
+ error(FfiCode.MUST_BE_A_SUBTYPE, 85, 41),
+ ]);
+ }
+
test_valid() async {
await assertNoErrorsInCode(r'''
import 'dart:ffi';
@@ -93,8 +107,12 @@
@Native<Void Function()>()
external void foo();

+@Native()
+external Pointer<IntPtr> global;
+
void main() {
print(Native.addressOf<NativeFunction<Void Function()>>(foo));
+ print(Native.addressOf<Pointer<IntPtr>>(global));
}
''');
}
@@ -324,7 +342,147 @@
}

@reflectiveTest
+class NativeFieldTest extends PubPackageResolutionTest {
+ test_Accessors() async {

+ await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native<IntPtr>()
+external int get foo;
+
+@Native<IntPtr>()
+external set foo(int value);
+''');
+ }
+
+ test_Infer() async {

+ await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+final class MyStruct extends Struct {
+ external Pointer<MyStruct> next;
+}
+
+@Native()
+external MyStruct first;
+
+@Native()
+external Pointer<MyStruct> last;
+''');
+ }
+
+ test_InvalidFunctionType() async {

+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+@Native<IntPtr Function(IntPtr)>()
+external int field;
+''', [
+ error(FfiCode.NATIVE_FIELD_INVALID_TYPE, 67, 5),
+ ]);
+ }
+
+ test_InvalidInstanceMember() async {

+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+class Foo {
+ @Native<IntPtr>()
+ external int field;
+}
+''', [
+ error(FfiCode.NATIVE_FIELD_NOT_STATIC, 67, 5),
+ ]);
+ }
+
+ test_MissingType() async {

+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external int invalid;
+
+@Native()
+external Pointer<IntPtr> valid;
+''', [
+ error(FfiCode.NATIVE_FIELD_MISSING_TYPE, 43, 7),
+ ]);
+ }
+
+ test_InvalidNotExternal() async {

+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native<IntPtr>()
+int field;
+''', [
+ error(CompileTimeErrorCode.NOT_INITIALIZED_NON_NULLABLE_VARIABLE, 42, 5),
+ error(FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL, 42, 5),
+ ]);
+ }
+
+ test_MismatchingType() async {

+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native<Double>()
+external int field;
+''', [
+ error(FfiCode.MUST_BE_A_SUBTYPE, 51, 5),
+ ]);
+ }
+
+ test_Unsupported_Array() async {

+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external Array<IntPtr> field;
+''', [
+ error(FfiCode.NATIVE_FIELD_INVALID_TYPE, 53, 5, contextMessages: [
+ message('/sdk/lib/ffi/ffi.dart', 2500, 5),
+ message('/sdk/lib/ffi/ffi.dart', 861, 6),
+ message('/sdk/lib/ffi/ffi.dart', 2500, 5),
+ message('/sdk/lib/ffi/ffi.dart', 861, 6)

+ ]),
+ ]);
+ }
+
+  test_Unsupported_Function() async {

+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native<NativeFunction<Void Function()>>()
+external void Function() field;
+''', [
+ error(FfiCode.MUST_BE_A_SUBTYPE, 88, 5),
+ ]);
+ }
+
+ test_Unsupported_Handle() async {

+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native<Handle>()
+external Object field;
+''', [
+ error(FfiCode.NATIVE_FIELD_INVALID_TYPE, 54, 5),

+ ]);
+ }
+}
+
+@reflectiveTest
class NativeTest extends PubPackageResolutionTest {
+ test_annotation_InvalidFieldType() async {

+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native<IntPtr>()
+external int foo();
+''', [
+ error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 20, 37),
+ ]);
+ }
+
test_annotation_MissingType() async {

await assertErrorsInCode(r'''
import 'dart:ffi';
diff --git a/pkg/front_end/lib/src/api_unstable/vm.dart b/pkg/front_end/lib/src/api_unstable/vm.dart
index 5d95fba..9f24c4a 100644
--- a/pkg/front_end/lib/src/api_unstable/vm.dart
+++ b/pkg/front_end/lib/src/api_unstable/vm.dart
@@ -68,6 +68,9 @@
messageFfiLeafCallMustNotReturnHandle,
messageFfiLeafCallMustNotTakeHandle,
messageFfiNativeDuplicateAnnotations,
+ messageFfiNativeFieldMissingType,

+ messageFfiNativeFieldMustBeStatic,
+ messageFfiNativeFieldType,
messageFfiNativeMustBeExternal,
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
         messageFfiPackedAnnotationAlignment,
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index f680817..4a522bd 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -5202,15 +5202,33 @@


FfiNativeMustBeExternal:
# Used by dart:ffi
- problemMessage: "Native functions must be marked external."
+ problemMessage: "Native functions and fields must be marked external."
   external: test/ffi_test.dart

FfiNativeDuplicateAnnotations:

# Used by dart:ffi
-  problemMessage: "Native functions must not have more than @Native annotation."
+ problemMessage: "Native functions and fields must not have more than @Native annotation."
external: test/ffi_test.dart
analyzerCode: FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS


+FfiNativeFieldMustBeStatic:
+ # Used by dart:ffi
+ problemMessage: "Native fields must be static."
+ analyzerCode: NATIVE_FIELD_NOT_STATIC
+ external: test/ffi_test.dart
+
+FfiNativeFieldMissingType:

+ # Used by dart:ffi
+  problemMessage: "The native type of this field could not be inferred and must be specified in the annotation."
+ analyzerCode: NATIVE_FIELD_MISSING_TYPE

+ external: test/ffi_test.dart
+
+FfiNativeFieldType:
+ # Used by dart:ffi
+  problemMessage: "Unsupported type for native fields. Native fields only support pointers or numeric and compound types."
+ analyzerCode: NATIVE_FIELD_INVALID_TYPE
+ external: test/ffi_test.dart
+
FfiAddressOfMustBeNative:

# Used by dart:ffi
   problemMessage: "Argument to 'Native.addressOf' must be annotated with @Native."
diff --git a/pkg/vm/lib/transformations/ffi/common.dart b/pkg/vm/lib/transformations/ffi/common.dart
index 90521be..55cd1f5 100644
--- a/pkg/vm/lib/transformations/ffi/common.dart
+++ b/pkg/vm/lib/transformations/ffi/common.dart
@@ -686,6 +686,20 @@
return FunctionType(argumentTypes, returnType, Nullability.legacy);
}

+ DartType? canonicalNativeTypeForDartType(DartType dartType) {
+ if (isPointerType(dartType) ||
+ isCompoundSubtype(dartType) ||
+ isArrayType(dartType)) {
+ return dartType;
+ } else {
+ return null;
+ }
+ }
+
+ bool canInferNativeTypeFromDartType(DartType dartType) {
+ return canonicalNativeTypeForDartType(dartType) != null;
+ }
+
/// Removes the VarArgs from a DartType list.
///
/// ```
@@ -1253,12 +1267,14 @@
DartType dartType,
TreeNode reportErrorOn, {
bool allowHandle = false,
+ bool allowArray = false,
allowVoid = false,
}) {
final DartType correspondingDartType = convertNativeTypeToDartType(
nativeType,
allowCompounds: true,
allowHandle: allowHandle,
+ allowInlineArray: allowArray,
allowVoid: allowVoid,
)!;
if (dartType == correspondingDartType) return;
diff --git a/pkg/vm/lib/transformations/ffi/definitions.dart b/pkg/vm/lib/transformations/ffi/definitions.dart
index 3627f53..36441e3 100644
--- a/pkg/vm/lib/transformations/ffi/definitions.dart
+++ b/pkg/vm/lib/transformations/ffi/definitions.dart
@@ -429,9 +429,7 @@
f.fileUri);
// This class is invalid, but continue reporting other errors on it.
success = false;
- } else if (isPointerType(type) ||
- isCompoundSubtype(type) ||
- isArrayType(type)) {
+ } else if (canInferNativeTypeFromDartType(type)) {
if (nativeTypeAnnos.isNotEmpty) {
diagnosticReporter.report(
templateFfiFieldNoAnnotation.withArguments(f.name.text),
diff --git a/pkg/vm/lib/transformations/ffi/native.dart b/pkg/vm/lib/transformations/ffi/native.dart
index a976eb4..9ba0b79 100644
--- a/pkg/vm/lib/transformations/ffi/native.dart
+++ b/pkg/vm/lib/transformations/ffi/native.dart
@@ -6,12 +6,16 @@
show
messageFfiDefaultAssetDuplicate,
messageFfiNativeDuplicateAnnotations,
+ messageFfiNativeFieldMissingType,

+ messageFfiNativeFieldMustBeStatic,
+ messageFfiNativeFieldType,
messageFfiNativeMustBeExternal,
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
templateCantHaveNamedParameters,
templateCantHaveOptionalParameters,
templateFfiNativeUnexpectedNumberOfParameters,
- templateFfiNativeUnexpectedNumberOfParametersWithReceiver;
+ templateFfiNativeUnexpectedNumberOfParametersWithReceiver,
+ templateFfiTypeInvalid;

import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
@@ -21,7 +25,7 @@

import 'package:kernel/target/targets.dart' show DiagnosticReporter;
import 'package:kernel/type_environment.dart';

-import 'common.dart' show FfiStaticTypeError, FfiTransformer;
+import 'common.dart' show FfiStaticTypeError, FfiTransformer, NativeType;

/// Transform @Native annotated functions into FFI native function pointer
/// functions.
@@ -163,6 +167,26 @@

return false;
}

+ StringConstant _resolveNativeSymbolName(
+ Member member, InstanceConstant native) {
+ final nativeFunctionConst =
+ native.fieldValues[nativeSymbolField.fieldReference];
+ return nativeFunctionConst is StringConstant
+ ? nativeFunctionConst
+ : StringConstant(member.name.text);
+ }
+
+ StringConstant? _resolveAssetName(InstanceConstant native) {
+ final assetConstant = native.fieldValues[nativeAssetField.fieldReference];
+ return assetConstant is StringConstant ? assetConstant : currentAsset;
+ }
+
+ bool _isLeaf(InstanceConstant native) {
+ return (native.fieldValues[nativeIsLeafField.fieldReference]
+ as BoolConstant)
+ .value;
+ }
+
// Replaces parameters with Pointer if:
// 1) they extend NativeFieldWrapperClass1, and
// 2) the corresponding FFI parameter is Pointer.
@@ -475,7 +499,11 @@

final pragmaConstant = ConstantExpression(
InstanceConstant(pragmaClass.reference, [], {
         pragmaName.fieldReference: StringConstant(_vmFfiNative),
- pragmaOptions.fieldReference: resolvedNative,

+ pragmaOptions.fieldReference: _createResolvedNativeConstant(
+ nativeType: ffiFunctionType,
+ nativeName: nativeFunctionName,
+ isLeaf: isLeaf,
+        ),
}),
InterfaceType(
pragmaClass,
@@ -584,6 +612,28 @@
return node;

}

+ /// Creates a `Native` constant with all fields resolved.
+ ///
+ /// We can't re-use the constant from the `@Native` annotation because the
+ /// asset may be inferred from the library.
+ InstanceConstant _createResolvedNativeConstant({
+ required DartType nativeType,
+ required StringConstant nativeName,
+ required bool isLeaf,
+ StringConstant? assetName,
+ }) {
+ return InstanceConstant(
+ nativeClass.reference,
+ [nativeType],
+ {
+ nativeSymbolField.fieldReference: nativeName,
+ nativeAssetField.fieldReference:
+ assetName ?? StringConstant(currentLibrary.importUri.toString()),
+ nativeIsLeafField.fieldReference: BoolConstant(isLeaf),
+ },
+ );
+ }
+
// Transform Native instance methods.
//
// Example:
@@ -689,6 +739,147 @@
);

}

+ Expression _addressOfField(
+ Member node, InterfaceType ffiType, InstanceConstant native) {
+ return StaticInvocation(
+ nativePrivateAddressOf,
+ Arguments([ConstantExpression(native)], types: [ffiType]),
+ )..fileOffset = node.fileOffset;
+ }
+
+ Expression _loadField(
+ Member node, InterfaceType ffiType, InstanceConstant native) {
+ final ptr = _addressOfField(node, ffiType, native);
+ if (getType(ffiType.classNode) case NativeType nt) {
+ return StaticInvocation(
+ loadMethods[nt]!,
+ Arguments(
+          [
+ ptr,
+ ConstantExpression(IntConstant(0)),
+ ],
+ types: [
+ // _loadPointer is generic
+ if (nt == NativeType.kPointer) ffiType.typeArguments[0],
+ ],
+ ),

+ )..fileOffset = node.fileOffset;
+ } else if (isAbiSpecificIntegerSubtype(ffiType)) {
+ return StaticInvocation(
+ abiSpecificIntegerPointerGetValue, Arguments([ptr], types: [ffiType]))
+ ..fileOffset = node.fileEndOffset;
+ } else {
+ assert(isCompoundSubtype(ffiType));
+ return StaticInvocation(
+ structPointerGetRef, Arguments([ptr], types: [ffiType]))
+ ..fileOffset = node.fileEndOffset;
+ }
+ }
+
+ Expression _storeField(Member node, InterfaceType ffiType,
+ InstanceConstant native, VariableDeclaration arg) {
+ final ptr = _addressOfField(node, ffiType, native);
+ final value = VariableGet(arg);
+ if (getType(ffiType.classNode) case NativeType nt) {
+ return StaticInvocation(
+ storeMethods[nt]!,
+ Arguments(
+          [

+ ptr,
+ ConstantExpression(IntConstant(0)),
+ value,
+          ],
+ types: [
+ // _storePointer is generic
+ if (nt == NativeType.kPointer) ffiType.typeArguments[0],
+ ],
+ ),

+ )..fileOffset = node.fileOffset;
+ } else if (isAbiSpecificIntegerSubtype(ffiType)) {
+ assert(isAbiSpecificIntegerSubtype(ffiType));
+ return StaticInvocation(
+ abiSpecificIntegerPointerSetValue,
+ Arguments([
+ ptr,
+ value,
+ ], types: [
+ ffiType
+ ]))
+ ..fileOffset = node.fileEndOffset;
+ } else {
+ assert(isCompoundSubtype(ffiType));
+ return StaticInvocation(
+ structPointerSetRef,
+ Arguments([
+ ptr,
+ value,
+ ], types: [
+ ffiType
+ ]))
+ ..fileOffset = node.fileEndOffset;
+ }
+  }
+
+ DartType _validateOrInferNativeFieldType(
+ Member node, DartType ffiType, DartType dartType) {
+ if (ffiType is DynamicType) {
+ // If no type argument is given on the @Native annotation, we can try to
+ // infer it.
+ if (canInferNativeTypeFromDartType(dartType)) {
+ ffiType = dartType;
+ } else {
+ diagnosticReporter.report(messageFfiNativeFieldMissingType,
+ node.fileOffset, 1, node.location?.file);

+ throw FfiStaticTypeError();
+ }
+ }
+
+    ensureNativeTypeValid(
+ ffiType,
+ node,
+ allowCompounds: true,
+ // We don't currently allow handles and arrays, but we can provide a more
+ // specific error message for them which is why we allow them here.
+ allowHandle: true,
+ allowInlineArray: true,
+ );
+ ensureNativeTypeToDartType(ffiType, dartType, node,
+ allowHandle: true, allowArray: true);

+ // Only allow compound, pointer and numeric types.
+ if (isCompoundSubtype(ffiType) || isAbiSpecificIntegerSubtype(ffiType)) {
+      return ffiType;

+ }
+ final type = switch (ffiType) {
+ InterfaceType(:var classNode) => getType(classNode),
+ _ => null,
+ };
+    if (type == null ||
+ type == NativeType.kNativeFunction ||
+ type == NativeType.kHandle ||
+ isArrayType(ffiType)) {

+ diagnosticReporter.report(
+ messageFfiNativeFieldType, node.fileOffset, 1, node.location?.file);
+ throw FfiStaticTypeError();
+ }
+
+    return ffiType;

+ }
+
+ @override
+ TreeNode visitField(Field node) {
+    final nativeAnnotation = tryGetNativeAnnotationOrWarnOnDuplicates(node);

+ if (nativeAnnotation == null) {
+ return node;
+ }
+ // @Native annotations on fields are valid if the field is external. But
+ // external fields are represented as a getter/setter pair in Kernel, so we
+ // only visit fields to verify that no native annotation is present.
+ assert(!node.isExternal);
+ diagnosticReporter.report(messageFfiNativeMustBeExternal, node.fileOffset,
+ 1, node.location?.file);
+ return node;
+ }
+
   @override
visitProcedure(Procedure node) {
// Only transform functions that are external and have Native annotation:
@@ -708,37 +899,90 @@
node.annotations.remove(ffiNativeAnnotation);


final ffiConstant = ffiNativeAnnotation.constant as InstanceConstant;
-    final nativeType = ffiConstant.typeArguments[0];

- try {
- final nativeFunctionType = InterfaceType(
- nativeFunctionClass, Nullability.nonNullable, [nativeType]);
- ensureNativeTypeValid(nativeFunctionType, ffiNativeAnnotation,
- allowCompounds: true, allowHandle: true);
- } on FfiStaticTypeError {
- // We've already reported an error.
-      return node;
- }

- final ffiFunctionType = ffiConstant.typeArguments[0] as FunctionType;
- final nativeFunctionConst =
- ffiConstant.fieldValues[nativeSymbolField.fieldReference];
- final nativeFunctionName = nativeFunctionConst is StringConstant
- ? nativeFunctionConst
- : StringConstant(node.name.text);
- final assetConstant =
- ffiConstant.fieldValues[nativeAssetField.fieldReference];
- final assetName =
- assetConstant is StringConstant ? assetConstant : currentAsset;
- final isLeaf = (ffiConstant.fieldValues[nativeIsLeafField.fieldReference]
- as BoolConstant)
- .value;
+    var nativeType = ffiConstant.typeArguments[0];

+ final isFunction = nativeType is FunctionType;

-    if (!node.isStatic) {
- return _transformInstanceMethod(node, ffiFunctionType, nativeFunctionName,
+    final nativeName = _resolveNativeSymbolName(node, ffiConstant);
+ final assetName = _resolveAssetName(ffiConstant);
+ final isLeaf = _isLeaf(ffiConstant);
+
+ if (isFunction) {
+ try {
+ final nativeFunctionType = InterfaceType(
+ nativeFunctionClass, Nullability.nonNullable, [nativeType]);
+ ensureNativeTypeValid(nativeFunctionType, ffiNativeAnnotation,
+ allowCompounds: true, allowHandle: true);
+ } on FfiStaticTypeError {
+ // We've already reported an error.
+ return node;
+ }
+ final ffiFunctionType = ffiConstant.typeArguments[0] as FunctionType;
+
+ if (!node.isStatic) {
+ return _transformInstanceMethod(node, ffiFunctionType, nativeName,
+ assetName, isLeaf, ffiNativeAnnotation.fileOffset);
+ }
+
+ return _transformStaticFunction(node, ffiFunctionType, nativeName,
           assetName, isLeaf, ffiNativeAnnotation.fileOffset);
+ } else {
+ if (node.kind == ProcedureKind.Getter ||
+ node.kind == ProcedureKind.Setter) {
+        if (!node.isStatic) {

+ diagnosticReporter.report(messageFfiNativeFieldMustBeStatic,
+ node.fileOffset, 1, node.location?.file);
+        }
+

+ try {
+ final type = node.kind == ProcedureKind.Getter
+ ? node.function.returnType
+ : node.function.positionalParameters[0].type;
+          nativeType = _validateOrInferNativeFieldType(node, nativeType, type);

+ } on FfiStaticTypeError {
+ return node;
+ }
+ final resolved = _createResolvedNativeConstant(
+ nativeType: nativeType,
+ nativeName: nativeName,
+ isLeaf: isLeaf,
+ assetName: assetName,
+ );
+        node.isExternal = false;

+ if (node.kind == ProcedureKind.Getter) {
+ node.function.body = ReturnStatement(
+ _loadField(node, nativeType as InterfaceType, resolved));
+ node.annotations.add(ConstantExpression(
+ InstanceConstant(pragmaClass.reference, [], {
+              pragmaName.fieldReference: StringConstant(nativeMarker),
-    return _transformStaticFunction(node, ffiFunctionType, nativeFunctionName,
- assetName, isLeaf, ffiNativeAnnotation.fileOffset);
+    return node;

}

/// Checks whether the FFI function type is valid and reports any errors.
diff --git a/pkg/vm/lib/transformations/ffi/use_sites.dart b/pkg/vm/lib/transformations/ffi/use_sites.dart
index a98913f..875f3ef 100644
--- a/pkg/vm/lib/transformations/ffi/use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi/use_sites.dart
@@ -1059,13 +1059,16 @@

final arg = node.arguments.positional.single;
final nativeType = node.arguments.types.single;

- // `x` must be a method annotated with `@Native`, so referencing it makes
- // it a tear-off.
-    if (arg case ConstantExpression(constant: StaticTearOffConstant method)) {
- // The method must have the `vm:ffi:native` pragma added by the native
- // transformer.
- Constant? nativeAnnotation;
- for (final annotation in method.target.annotations) {
+ final potentiallyNativeTarget = switch (arg) {
+ ConstantExpression(constant: StaticTearOffConstant method) =>
+ method.target,
+ StaticGet(:var targetReference) => targetReference.asMember,

+ _ => null,
+ };
+    Constant? nativeAnnotation;
+
+ if (potentiallyNativeTarget != null) {
+ for (final annotation in potentiallyNativeTarget.annotations) {

if (annotation
case ConstantExpression(constant: final InstanceConstant c)) {
           if (c.classNode == coreTypes.pragmaClass) {
@@ -1079,25 +1082,22 @@
}
}
}
+ }

- if (nativeAnnotation == null) {
- diagnosticReporter.report(messageFfiAddressOfMustBeNative,
- node.arguments.fileOffset, 1, node.location?.file);
- return node;
- }
-
- ensureNativeTypeValid(nativeType, node);
- ensureNativeTypeToDartType(nativeType, arg.type, node);
-

- return StaticInvocation(
- nativePrivateAddressOf,
- Arguments([ConstantExpression(nativeAnnotation)], types: [nativeType]),
- )..fileOffset = arg.fileOffset;
-    } else {

+ if (nativeAnnotation == null) {
       diagnosticReporter.report(messageFfiAddressOfMustBeNative, arg.fileOffset,
1, node.location?.file);
return node;
}
+
+    ensureNativeTypeValid(nativeType, node, allowCompounds: true);

+ ensureNativeTypeToDartType(
+ nativeType, arg.getStaticType(staticTypeContext!), node);
+
+ return StaticInvocation(
+ nativePrivateAddressOf,
+ Arguments([ConstantExpression(nativeAnnotation)], types: [nativeType]),
+ )..fileOffset = arg.fileOffset;
}
}

diff --git a/pkg/vm/testcases/transformations/ffi/native_fields.dart b/pkg/vm/testcases/transformations/ffi/native_fields.dart
new file mode 100644
index 0000000..4e88c0e
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/native_fields.dart
@@ -0,0 +1,34 @@
+import 'dart:ffi';
+
+@Native()
+external Pointer<Char> aString;
+
+@Native<Int32>()
+external int anInt;
+
+@Native<Int>()
+external int anotherInt;
+
+final class Vec2d extends Struct {
+ @Double()
+ external double x;
+ @Double()
+ external double y;
+}
+
+@Native()
+external final Vec2d vector;
+
+void main() {
+ print('first char of string: ${aString.value}');
+ print('global int: {$anInt}');
+
+ aString = nullptr;
+ anInt++;
+
+ final vec = vector;
+ print('(${vec.x}, ${vec.y})');
+
+ print(Native.addressOf<Int>(anotherInt));
+ print(Native.addressOf<Vec2d>(vector));
+}
diff --git a/pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect
new file mode 100644
index 0000000..8ee6cda
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect
@@ -0,0 +1,67 @@
+library #lib;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+
+@#C6
+final class Vec2d extends ffi::Struct {
+ constructor #fromTypedDataBase([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] synthesized core::Object #typedDataBase) → self::Vec2d
+ : super ffi::Struct::_fromTypedDataBase(#typedDataBase)
+ ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1] [@vm.unboxing-info.metadata=()->d] get x() → core::double
+ return [@vm.inferred-type.metadata=dart.core::_Double] ffi::_loadDouble([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.ffi::Pointer] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C8.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:2] [@vm.unboxing-info.metadata=()->d] get y() → core::double
+ return [@vm.inferred-type.metadata=dart.core::_Double] ffi::_loadDouble([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.ffi::Pointer] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C10.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
+}
+@#C16
+static get aString() → ffi::Pointer<ffi::Char>
+ return [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_loadPointer<ffi::Char>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Pointer<ffi::Char>>(#C15), #C7);
+static set aString([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::Char> #externalFieldValue) → void
+ ffi::_storePointer<ffi::Char>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Pointer<ffi::Char>>(#C15), #C7, #externalFieldValue);
+[@vm.unboxing-info.metadata=()->i]@#C19
+static get anInt() → core::int
+ return [@vm.inferred-type.metadata=int] ffi::_loadInt32([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Int32>(#C18), #C7);
+[@vm.unboxing-info.metadata=(i)->b]static set anInt([@vm.inferred-arg-type.metadata=int] synthesized core::int #externalFieldValue) → void
+ ffi::_storeInt32([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Int32>(#C18), #C7, #externalFieldValue);
+@#C22
+static get vector() → self::Vec2d
+ return new self::Vec2d::#fromTypedDataBase(_in::unsafeCast<ffi::Pointer<self::Vec2d>>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<self::Vec2d>(#C21)));
+static method main() → void {
+ core::print("first char of string: ${ffi::_loadAbiSpecificInt<ffi::Char>([@vm.inferred-type.metadata=dart.ffi::Pointer] self::aString, #C7)}");
+ core::print("global int: {${self::anInt}}");
+ self::aString = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::nullptr;
+ self::anInt = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] [@vm.inferred-type.metadata=int] self::anInt.{core::num::+}(1){(core::num) → core::int};
+ final self::Vec2d vec = [@vm.inferred-type.metadata=#lib::Vec2d] self::vector;
+ core::print("(${[@vm.direct-call.metadata=#lib::Vec2d.x] vec.{self::Vec2d::x}{core::double}}, ${[@vm.direct-call.metadata=#lib::Vec2d.y] vec.{self::Vec2d::y}{core::double}})");
+ core::print([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Int>(#C24));
+ core::print([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<self::Vec2d>(#C21));
+}
+constants {
+ #C1 = "vm:ffi:struct-fields"
+ #C2 = TypeLiteralConstant(ffi::Double)
+ #C3 = <core::Type>[#C2, #C2]
+ #C4 = null
+ #C5 = ffi::_FfiStructLayout {fieldTypes:#C3, packing:#C4}
+ #C6 = core::pragma {name:#C1, options:#C5}
+ #C7 = 0
+ #C8 = <core::int*>[#C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7]
+ #C9 = 8
+ #C10 = <core::int*>[#C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9]
+ #C11 = "cfe:ffi:native-marker"
+ #C12 = "aString"

+ #C13 = "#lib"
+ #C14 = false
+  #C15 = ffi::Native<ffi::Pointer<ffi::Char>> {symbol:#C12, assetId:#C13, isLeaf:#C14}
+ #C16 = core::pragma {name:#C11, options:#C15}
+ #C17 = "anInt"
+ #C18 = ffi::Native<ffi::Int32> {symbol:#C17, assetId:#C13, isLeaf:#C14}
+ #C19 = core::pragma {name:#C11, options:#C18}
+ #C20 = "vector"
+ #C21 = ffi::Native<self::Vec2d> {symbol:#C20, assetId:#C13, isLeaf:#C14}
+ #C22 = core::pragma {name:#C11, options:#C21}
+ #C23 = "anotherInt"
+ #C24 = ffi::Native<ffi::Int> {symbol:#C23, assetId:#C13, isLeaf:#C14}
+}
diff --git a/pkg/vm/testcases/transformations/ffi/native_fields.dart.expect b/pkg/vm/testcases/transformations/ffi/native_fields.dart.expect
new file mode 100644
index 0000000..af127d1
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/native_fields.dart.expect
@@ -0,0 +1,91 @@
+library #lib;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+
+import "dart:ffi";
+
+@#C6
+final class Vec2d extends ffi::Struct {
+ synthetic constructor •() → self::Vec2d
+ : super ffi::Struct::•()
+ ;
+ constructor #fromTypedDataBase(synthesized core::Object #typedDataBase) → self::Vec2d
+ : super ffi::Struct::_fromTypedDataBase(#typedDataBase)
+ ;
+ @#C7
+ get x() → core::double
+ return ffi::_loadDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
+ @#C7
+ set x(synthesized core::double #externalFieldValue) → void
+ return ffi::_storeDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
+ @#C7
+ get y() → core::double
+ return ffi::_loadDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C11.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
+ @#C7
+ set y(synthesized core::double #externalFieldValue) → void
+ return ffi::_storeDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C11.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
+ @#C13
+ static get #sizeOf() → core::int*
+ return #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
+}
+@#C21
+static get aString() → ffi::Pointer<ffi::Char>
+ return ffi::_loadPointer<ffi::Char>(ffi::Native::_addressOf<ffi::Pointer<ffi::Char>>(#C20), #C8);
+static set aString(synthesized ffi::Pointer<ffi::Char> #externalFieldValue) → void
+ ffi::_storePointer<ffi::Char>(ffi::Native::_addressOf<ffi::Pointer<ffi::Char>>(#C20), #C8, #externalFieldValue);
+@#C24
+static get anInt() → core::int
+ return ffi::_loadInt32(ffi::Native::_addressOf<ffi::Int32>(#C23), #C8);
+static set anInt(synthesized core::int #externalFieldValue) → void
+ ffi::_storeInt32(ffi::Native::_addressOf<ffi::Int32>(#C23), #C8, #externalFieldValue);
+@#C27
+static get anotherInt() → core::int
+ return ffi::_loadAbiSpecificInt<ffi::Int>(ffi::Native::_addressOf<ffi::Int>(#C26), #C8);
+static set anotherInt(synthesized core::int #externalFieldValue) → void
+ ffi::_storeAbiSpecificInt<ffi::Int>(ffi::Native::_addressOf<ffi::Int>(#C26), #C8, #externalFieldValue);
+@#C30
+static get vector() → self::Vec2d
+ return new self::Vec2d::#fromTypedDataBase(ffi::Native::_addressOf<self::Vec2d>(#C29)!);
+static method main() → void {
+ core::print("first char of string: ${ffi::_loadAbiSpecificInt<ffi::Char>(self::aString, #C8)}");
+ core::print("global int: {${self::anInt}}");
+ self::aString = ffi::nullptr;
+ self::anInt = self::anInt.{core::num::+}(1){(core::num) → core::int};
+ final self::Vec2d vec = self::vector;
+ core::print("(${vec.{self::Vec2d::x}{core::double}}, ${vec.{self::Vec2d::y}{core::double}})");
+ core::print(ffi::Native::_addressOf<ffi::Int>(#C26));
+ core::print(ffi::Native::_addressOf<self::Vec2d>(#C29));
+}
+constants {
+ #C1 = "vm:ffi:struct-fields"
+ #C2 = TypeLiteralConstant(ffi::Double)
+ #C3 = <core::Type>[#C2, #C2]
+ #C4 = null
+ #C5 = ffi::_FfiStructLayout {fieldTypes:#C3, packing:#C4}
+ #C6 = core::pragma {name:#C1, options:#C5}
+ #C7 = ffi::Double {}
+ #C8 = 0
+ #C9 = <core::int*>[#C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8]
+ #C10 = 8
+ #C11 = <core::int*>[#C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10]
+ #C12 = "vm:prefer-inline"
+ #C13 = core::pragma {name:#C12, options:#C4}
+ #C14 = 16
+ #C15 = <core::int*>[#C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14]
+ #C16 = "cfe:ffi:native-marker"
+ #C17 = "aString"
+ #C18 = "#lib"
+ #C19 = false
+ #C20 = ffi::Native<ffi::Pointer<ffi::Char>> {symbol:#C17, assetId:#C18, isLeaf:#C19}
+ #C21 = core::pragma {name:#C16, options:#C20}
+ #C22 = "anInt"
+ #C23 = ffi::Native<ffi::Int32> {symbol:#C22, assetId:#C18, isLeaf:#C19}
+ #C24 = core::pragma {name:#C16, options:#C23}
+ #C25 = "anotherInt"
+ #C26 = ffi::Native<ffi::Int> {symbol:#C25, assetId:#C18, isLeaf:#C19}
+ #C27 = core::pragma {name:#C16, options:#C26}
+ #C28 = "vector"
+ #C29 = ffi::Native<self::Vec2d> {symbol:#C28, assetId:#C18, isLeaf:#C19}
+ #C30 = core::pragma {name:#C16, options:#C29}
+}
diff --git a/runtime/bin/ffi_test/ffi_test_functions.cc b/runtime/bin/ffi_test/ffi_test_functions.cc
index 7bc707a..0050636 100644

--- a/runtime/bin/ffi_test/ffi_test_functions.cc
+++ b/runtime/bin/ffi_test/ffi_test_functions.cc
@@ -25,9 +25,11 @@

#if defined(_WIN32)
#define DART_EXPORT extern "C" __declspec(dllexport)
+#define DART_EXPORT_FIELD DART_EXPORT
#else
#define DART_EXPORT \
extern "C" __attribute__((visibility("default"))) __attribute((used))
+#define DART_EXPORT_FIELD __attribute__((visibility("default")))
#endif

namespace dart {
@@ -44,20 +46,30 @@

// Tests for Dart -> native calls.
//
// Note: If this interface is changed please also update
-// sdk/runtime/tools/dartfuzz/ffiapi.dart
+// sdk/runtime/tools/dartfuzz/dartfuzz_ffi_api.dart

-int32_t globalVar;
+struct Coord {
+ double x;
+ double y;
+ Coord* next;
+};
+
+extern "C" {
+DART_EXPORT_FIELD int32_t globalInt;
+DART_EXPORT_FIELD Coord globalStruct;
+DART_EXPORT_FIELD const char* globalString = "Hello Dart!";

+}

DART_EXPORT void InduceACrash() {
*reinterpret_cast<int*>(InduceACrash) = 123;
}

 DART_EXPORT void SetGlobalVar(int32_t v) {
- globalVar = v;
+ globalInt = v;
}

DART_EXPORT int32_t GetGlobalVar() {
- return globalVar;
+ return globalInt;
}

// Sums two ints and adds 42.
@@ -542,12 +554,6 @@
return retval;
}

-struct Coord {
- double x;
- double y;
- Coord* next;
-};
-
// Transposes Coordinate by (10, 10) and returns next Coordinate.
// Used for testing struct pointer parameter, struct pointer return value,
// struct field access, and struct pointer field dereference.
@@ -575,6 +581,10 @@
return retval;
}

+DART_EXPORT Coord CoordinateCopy(Coord* coord) {
+ return *coord;
+}
+
typedef Coord* (*CoordUnOp)(Coord* coord);

// Takes a Coordinate Function(Coordinate) and applies it three times to a
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index f365ab8..bae90b4 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -5056,10 +5056,16 @@

String::ZoneHandle(Z, String::RawCast(native.GetField(asset_id_field)));
const auto& type_args = TypeArguments::Handle(Z, native.GetTypeArguments());
ASSERT(type_args.Length() == 1);
- const auto& native_type =
- FunctionType::Cast(AbstractType::ZoneHandle(Z, type_args.TypeAt(0)));
- const intptr_t arg_n =
- native_type.NumParameters() - native_type.num_implicit_parameters();
+ const auto& native_type = AbstractType::ZoneHandle(Z, type_args.TypeAt(0));
+ intptr_t arg_n;
+ if (native_type.IsFunctionType()) {
+ const auto& native_function_type = FunctionType::Cast(native_type);
+ arg_n = native_function_type.NumParameters() -
+ native_function_type.num_implicit_parameters();
+ } else {
+ // We're looking up the address of a native field.
+ arg_n = 0;
+ }
   const auto& ffi_resolver =
Function::ZoneHandle(Z, IG->object_store()->ffi_resolver_function());

diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart
index 7707cfa..d0202f7 100644
--- a/sdk/lib/ffi/ffi.dart
+++ b/sdk/lib/ffi/ffi.dart
@@ -1117,32 +1117,49 @@
external static Pointer<Void> get initializeApiDLData;
}

-/// Annotation specifying how to bind an external function to native code.
+/// Annotation specifying how to bind an external function or global field to
+/// a native declaration.
///
-/// The annotation applies only to `external` function declarations.
+/// The annotation applies only to `external` function and field declarations.
///
/// A [Native]-annotated `external` function is implemented by native code.
/// The implementation is found in the native library denoted by [assetId].
+/// Similarly, a [Native]-annotated `external` field is implemented by reading
+/// from or writing to memory not managed by the Dart runtime.
///
/// The compiler and/or runtime provides a binding from [assetId] to native
/// library, which depends on the target platform.
/// The compiler/runtime can then resolve/lookup symbols (identifiers)
-/// against the native library, to find a native function,
-/// and bind an `external` Dart function declaration to that native function.
+/// against the native library, to find a native function or a native variable,
+/// and bind an `external` Dart function or field declaration to that native
+/// declaration.
///
-/// Use this annotation on `external` functions to specify that they
+/// Use this annotation on `external` functions and fields to specify that they
/// are resolved against an asset, and to, optionally, provide overrides
/// of the default symbol and asset IDs.
///
-/// The type argument [T] to the [Native] annotation must be a function type
-/// representing the native function's parameter and return types.
-/// The parameter and return types must be subtypes of [NativeType].
+/// When this annotation is used on a function, the type argument [T] to the
+/// [Native] annotation must be a function type representing the native
+/// function's parameter and return types. The parameter and return types must
+/// be subtypes of [NativeType].
+///
+/// When an external field is annotated with [Native], the type argument [T]
+/// must be a compatible native type. An [int] field could be annotated with
+/// [Int32], for example. Some Dart types, namely [Pointer]s, [Compound]
+/// subtypes and [Array]s, only have one possible native type - themselves. For
+/// these types, no type argument is necessary on [Native].
///
/// Example:
///
/// ```dart template:top
/// @Native<Int64 Function(Int64, Int64)>()

/// external int sum(int a, int b);
+///
+/// @Native<Int64>()
+/// external int aGlobalInt;
+///
+/// @Native()
+/// external Pointer<Char> aGlobalString;
/// ```
///

/// Calling such function will try to resolve the [symbol] in (in that order)
@@ -1236,6 +1253,8 @@
/// in a group is trying to perform a GC and a second isolate is blocked in a
/// leaf call, then the first isolate will have to pause and wait until this
/// leaf call returns.
+ ///
+ /// This field has no meaning for native fields.
final bool isLeaf;

const Native({
@@ -1246,20 +1265,44 @@


/// The native address of [native].
///
- /// [native] must be a reference to a method annotated with `@Native` and [T]
- /// must be a [NativeFunction] compatible to the signature of [native].
+  /// [native] must be a reference to a method or field annotated with
+ /// [Native]. [T] must be a [NativeType] compatible to the signature of
+ /// [native], so that the returned `Pointer<T>` correctly represents the
+ /// referenced element. For methods, this means that [T] must be a
+ /// [NativeFunction] matching the signature from the `@Native` annotation.
+ /// For fields, [T] must match the type in the annotation (or that of the
+ /// field if there is no explicit type given on the annotation).
///
- /// Example:
+ /// For example, consider a native C library exposing a function and a global
+ /// variable:
+ ///
+ /// ```C
+ /// #include <stdint.h>
+ ///
+ /// const char* myString;
+ /// int64_t sum(int64_t a, int64_t b) { return a + b; }
+ /// ```
+ ///
+ /// With a [Native] binding to both elements in Dart, [addressOf] allows
+ /// taking the address of them:
+ ///
/// ```dart
/// import 'dart:ffi';
///
+ /// @Native()
+ /// external Pointer<Char> myString;
+ ///
/// typedef NativeAdd = Int64 Function(Int64, Int64);
///
/// @Native<NativeAdd>()

/// external int sum(int a, int b);
///
/// void main() {
- /// final address = Native.addressOf<NativeFunction<NativeAdd>>(sum);
+  ///   final addressSum = Native.addressOf<NativeFunction<NativeAdd>>(sum);
+ /// // This is a Pointer<Pointer<Char>> pointing to the memory location
+ /// // where the loader has placed the `myString` global itself. To get the
+ /// // string value, we would just read the `myString` field.
+ /// final addressMyString = Native.addressOf<Pointer<Char>>(myString);

/// }
/// ```
@Since('3.3')
diff --git a/tests/ffi/native_assets/asset_absolute_test.dart b/tests/ffi/native_assets/asset_absolute_test.dart
index 6cd3595..e00a477 100644
--- a/tests/ffi/native_assets/asset_absolute_test.dart
+++ b/tests/ffi/native_assets/asset_absolute_test.dart
@@ -16,6 +16,7 @@
// SharedObjects=ffi_test_functions

import 'dart:async';
+import 'dart:convert';
import 'dart:ffi';
import 'dart:io';

@@ -65,6 +66,7 @@


Future<void> runTests() async {
testFfiTestfunctionsDll();
+ testFfiTestFieldsDll();
testNonExistingFunction();
}

@@ -80,3 +82,68 @@

.asFunction<int Function(int, int)>();
Expect.equals(2 + 3 + 42, viaAddressOf(2, 3));
}
+
+@Native<Int32>()
+external int globalInt;
+
+@Native<Int32>(symbol: 'globalInt')
+external int get globalIntProcedure;
+
+@Native<Int32>(symbol: 'globalInt')
+external set globalIntProcedure(int value);

+
+@Native<Void Function(Int32)>()
+external void SetGlobalVar(int value);
+
+@Native<Int32 Function()>()
+external int GetGlobalVar();
+
+@Native()
+external final Pointer<Char> globalString;
+
+final class Coord extends Struct {
+ @Double()
+ external double x;
+
+ @Double()
+ external double y;
+
+ external Pointer<Coord> next;
+
+ @Native<Coord Function(Pointer<Coord>)>(symbol: 'CoordinateCopy')
+ external static Coord copy(Pointer<Coord> ptr);
+}
+
+@Native()
+external Coord globalStruct;

+
+void testFfiTestFieldsDll() {
+ SetGlobalVar(42);
+  Expect.equals(globalInt, 42);
+ Expect.equals(globalIntProcedure, 42);
+ globalInt = 13;
+ Expect.equals(GetGlobalVar(), 13);
+ globalIntProcedure = 26;
+ Expect.equals(GetGlobalVar(), 26);
+
+ var readString = utf8.decode(globalString.cast<Uint8>().asTypedList(11));
+ Expect.equals(readString, 'Hello Dart!');
+
+ globalStruct
+ ..x = 1
+ ..y = 2
+ ..next = nullptr;
+ final copy = Coord.copy(Native.addressOf(globalStruct));
+ Expect.equals(copy.x, 1.0);
+ Expect.equals(copy.y, 2.0);
+ Expect.equals(copy.next, nullptr);
+
+ copy.x *= 2;
+ copy.y *= 2;
+ copy.next = Pointer.fromAddress(0xdeadbeef);
+ globalStruct = copy;
+
+ Expect.equals(globalStruct.x, 2.0);
+ Expect.equals(globalStruct.y, 4.0);
+ Expect.equals(globalStruct.next.address, 0xdeadbeef);

+}
diff --git a/tests/ffi/native_assets/asset_library_annotation_test.dart b/tests/ffi/native_assets/asset_library_annotation_test.dart
index 84b5759..481c299 100644
--- a/tests/ffi/native_assets/asset_library_annotation_test.dart
+++ b/tests/ffi/native_assets/asset_library_annotation_test.dart
@@ -13,6 +13,7 @@
library asset_test;

import 'dart:async';
+import 'dart:convert';
import 'dart:ffi';
import 'dart:io';

@@ -63,6 +64,7 @@


Future<void> runTests() async {
testFfiTestfunctionsDll();
+ testFfiTestFieldsDll();
}

@Native<Int32 Function(Int32, Int32)>()
@@ -77,3 +79,68 @@

final function = ptr.asFunction<int Function(int, int)>();
Expect.equals(2 + 3 + 42, function(2, 3));
}
+
+@Native<Int32>()
+external int globalInt;
+
+@Native<Int32>(symbol: 'globalInt')
+external int get globalIntProcedure;
+
+@Native<Int32>(symbol: 'globalInt')
+external set globalIntProcedure(int value);

+
+@Native<Void Function(Int32)>()
+external void SetGlobalVar(int value);
+
+@Native<Int32 Function()>()
+external int GetGlobalVar();
+
+@Native()
+external final Pointer<Char> globalString;
+
+final class Coord extends Struct {
+ @Double()
+ external double x;
+
+ @Double()
+ external double y;
+
+ external Pointer<Coord> next;
+
+ @Native<Coord Function(Pointer<Coord>)>(symbol: 'CoordinateCopy')
+ external static Coord copy(Pointer<Coord> ptr);
+}
+
+@Native()
+external Coord globalStruct;

+
+void testFfiTestFieldsDll() {
+ SetGlobalVar(42);
+  Expect.equals(globalInt, 42);
+ Expect.equals(globalIntProcedure, 42);
+ globalInt = 13;
+ Expect.equals(GetGlobalVar(), 13);
+ globalIntProcedure = 26;
+ Expect.equals(GetGlobalVar(), 26);
+
+ var readString = utf8.decode(globalString.cast<Uint8>().asTypedList(11));
+ Expect.equals(readString, 'Hello Dart!');
+
+ globalStruct
+ ..x = 1
+ ..y = 2
+ ..next = nullptr;
+ final copy = Coord.copy(Native.addressOf(globalStruct));
+ Expect.equals(copy.x, 1.0);
+ Expect.equals(copy.y, 2.0);
+ Expect.equals(copy.next, nullptr);
+
+ copy.x *= 2;
+ copy.y *= 2;
+ copy.next = Pointer.fromAddress(0xdeadbeef);
+ globalStruct = copy;
+
+ Expect.equals(globalStruct.x, 2.0);
+ Expect.equals(globalStruct.y, 4.0);
+ Expect.equals(globalStruct.next.address, 0xdeadbeef);

+}
diff --git a/tests/ffi/native_assets/asset_relative_test.dart b/tests/ffi/native_assets/asset_relative_test.dart
index ea0bef2..47ee83c 100644
--- a/tests/ffi/native_assets/asset_relative_test.dart
+++ b/tests/ffi/native_assets/asset_relative_test.dart
@@ -17,6 +17,7 @@
// SharedObjects=ffi_test_functions

import 'dart:async';
+import 'dart:convert';
import 'dart:ffi';
import 'dart:io';

@@ -120,6 +121,7 @@

Future<void> runTests() async {
testFfiTestfunctionsDll();
   testNonExistingFunction();

+ testFfiTestFieldsDll();
}

@Native<Int32 Function(Int32, Int32)>()
@@ -135,3 +137,68 @@


Expect.equals(2 + 3 + 42, viaAddressOf(2, 3));
}
+
+@Native<Int32>()
+external int globalInt;
+
+@Native<Int32>(symbol: 'globalInt')
+external int get globalIntProcedure;
+
+@Native<Int32>(symbol: 'globalInt')
+external set globalIntProcedure(int value);

+
+@Native<Void Function(Int32)>()
+external void SetGlobalVar(int value);
+
+@Native<Int32 Function()>()
+external int GetGlobalVar();
+
+@Native()
+external final Pointer<Char> globalString;
+
+final class Coord extends Struct {
+ @Double()
+ external double x;
+
+ @Double()
+ external double y;
+
+ external Pointer<Coord> next;
+
+ @Native<Coord Function(Pointer<Coord>)>(symbol: 'CoordinateCopy')
+ external static Coord copy(Pointer<Coord> ptr);
+}
+
+@Native()
+external Coord globalStruct;

+
+void testFfiTestFieldsDll() {
+ SetGlobalVar(42);
+  Expect.equals(globalInt, 42);
+ Expect.equals(globalIntProcedure, 42);
+ globalInt = 13;
+ Expect.equals(GetGlobalVar(), 13);
+ globalIntProcedure = 26;
+ Expect.equals(GetGlobalVar(), 26);
+
+ var readString = utf8.decode(globalString.cast<Uint8>().asTypedList(11));
+ Expect.equals(readString, 'Hello Dart!');
+
+ globalStruct
+ ..x = 1
+ ..y = 2
+ ..next = nullptr;
+ final copy = Coord.copy(Native.addressOf(globalStruct));
+ Expect.equals(copy.x, 1.0);
+ Expect.equals(copy.y, 2.0);
+ Expect.equals(copy.next, nullptr);
+
+ copy.x *= 2;
+ copy.y *= 2;
+ copy.next = Pointer.fromAddress(0xdeadbeef);
+ globalStruct = copy;
+
+ Expect.equals(globalStruct.x, 2.0);
+ Expect.equals(globalStruct.y, 4.0);
+ Expect.equals(globalStruct.next.address, 0xdeadbeef);

+}
diff --git a/tests/ffi/vmspecific_static_checks_native_test.dart b/tests/ffi/vmspecific_static_checks_native_test.dart
index 4cc38e6..604e836 100644
--- a/tests/ffi/vmspecific_static_checks_native_test.dart
+++ b/tests/ffi/vmspecific_static_checks_native_test.dart
@@ -41,14 +41,65 @@
// [analyzer] COMPILE_TIME_ERROR.FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS
external int foo(int v);
// ^
-// [cfe] Native functions must not have more than @Native annotation.
+// [cfe] Native functions and fields must not have more than @Native annotation.
+
+@Native()
+external MyStruct myStruct0;
+
+@Native<MyStruct>()
+external MyStruct myStruct1;
+@Native<Pointer<MyStruct>>()
+external MyStruct myStructInvalid;
+// ^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
+// [cfe] Expected type 'MyStruct' to be 'Pointer<MyStruct>', which is the Dart type corresponding to 'Pointer<MyStruct>'.
+
+@Native()
+external Pointer<MyStruct> myStructPtr0;
+
+@Native<Pointer<MyStruct>>()
+external Pointer<MyStruct> myStructPtr1;
+
+@Native<MyStruct>()
+external Pointer<MyStruct> myStructPtrInvalid;
+// ^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
+// [cfe] Expected type 'Pointer<MyStruct>' to be 'MyStruct', which is the Dart type corresponding to 'MyStruct'.
+
+@Native()
+external int invalidNoInferrence;
+// ^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.NATIVE_FIELD_MISSING_TYPE
+// [cfe] The native type of this field could not be inferred and must be specified in the annotation.
+
+@Native<Handle>()
+external Object invalidUnsupportedHandle;
+// ^^^^^^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.NATIVE_FIELD_INVALID_TYPE
+// [cfe] Unsupported type for native fields. Native fields only support pointers or numeric and compound types.
+
+@Native()
+external Array<IntPtr> invalidUnsupportedArray;
+// ^^^^^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.NATIVE_FIELD_INVALID_TYPE
+// [cfe] Unsupported type for native fields. Native fields only support pointers or numeric and compound types.
+
+class MyClass {
+ @Native<Double>()
+ external double invalidInstanceField;
+ // ^^^^^^^^^^^^^^^^^^^^
+ // [analyzer] COMPILE_TIME_ERROR.NATIVE_FIELD_NOT_STATIC
+ // [cfe] Native fields must be static.
+
+ @Native<Double>()
+ external static double validField;
+}

void addressOf() {
Native.addressOf<NativeFunction<Void Function()>>(notNative);
- // ^
- // [cfe] Argument to 'Native.addressOf' must be annotated with @Native.
// ^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_MUST_BE_NATIVE
+ // [cfe] Argument to 'Native.addressOf' must be annotated with @Native.


var boolean = 1 == 2;
Native.addressOf<NativeFunction<Void Function()>>(boolean ? _valid : _valid2);
@@ -79,4 +130,15 @@

// [cfe] Expected type 'void Function()' to be 'void Function(int)', which is the Dart type corresponding to 'NativeFunction<Void Function(Int)>'.

   Native.addressOf<NativeFunction<ComplexNativeFunction>>(validNative);
+
+ Native.addressOf<MyStruct>(myStruct0);
+ Native.addressOf<MyStruct>(myStruct1);
+ Native.addressOf<Pointer<MyStruct>>(myStructPtr0);
+ Native.addressOf<Pointer<MyStruct>>(myStructPtr1);
+
+ Native.addressOf<Int>(myStruct0);
+//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
+// ^
+// [cfe] Expected type 'MyStruct' to be 'int', which is the Dart type corresponding to 'Int'.
}

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange

Lasse Nielsen (Gerrit)

unread,
Dec 12, 2023, 6:12:38 AM12/12/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Johnni Winther, Jens Johansen, Brian Wilkerson, Daco Harkes, Alexander Markov, Paul Berry

Attention is currently required from: Daco Harkes, Simon Binder.

Patch set 10:Code-Review +1

View Change

17 comments:

  • Patchset:

    • Patch Set #10:

      Am I correct in assuming that the argument expression to `Native.addressOf` must be an expression denoting the declaration, not just the value?
      That is, you can do:
      ```dart
      @Native<Uint64 Function()>()
      external int foo();
      ...
      var adr = Native.addressOf<NativeFunction<Uint64 Function()>>(foo);
      ```
      but not
      ```
      const fooAlias = foo;
      var adrAlias = Native.addressOf<NativeFunction<Uint64 Function()>>(fooAlias);
      ```
      ?
  • File sdk/lib/ffi/ffi.dart:

    • Patch Set #10, Line 1121: /// a native declaration.

      Slightly long (more than one line).
      The "specifying how to" *feels* like filler text. Maybe it isn't, but it's in the same category of words as "represents" or "implements", a connection between a concept and an instance of that concept. Or to Yoda: Do, or do not.

      Let's try without:
      ```
      /// Annotation binding an external function or global field to its native implementation.
      ```
      Still a little over 80 characters 😊
      Also, not sure "global" is the word we use. I assume it means a static or top-level/library declaration.
      And "field" is a word we mainly use for *instance* variables.

      ```
      /// Annotation binding an external declaration to its native implementation.
      ///
      /// Can only be applied to `external` declarations of static and top-level
      /// functions and variables.
      ```

    • I don't think we use `declaration` and `definition` in Dart terminology like in C++. […]

      I'd say "declaration". That's how we distinguish the syntax from the semantics.
      A class declaration introduces a (semantic) class, an interface, a type, and a namespace. There is only one *declaration*, starting with the keyword `class`, but it has many meanings.

      Annotations annotate declarations, so that's fine.

    • Patch Set #10, Line 1127: /// Similarly, a [Native]-annotated `external` field is implemented by reading

      Maybe use "native memory" […]

      SGTM. But "field" -> "variable".

    • Patch Set #10, Line 1139: /// of the default symbol and asset IDs.

      This section feels like it's just repeating the text above.
      Could it be removed? Would anything be missing? (If so, that thing should probably be emphasized more.)

      Or could it be moved further up? It feels introductory, and we've already dived into deeper details.

    • Patch Set #10, Line 1141: /// When this annotation is used on a function, the type argument [T] to the

      When -> If ?

      "When" is fine. "If" is fine too. I'd normally write "If".

      I guess "If" follows the more formal "if precondition then requirement" structure, so use "If" for consistency, to make the "then" be explicit.

    • Patch Set #10, Line 1146: /// When an external field is annotated with [Native], the type argument [T]

      When -> If ?

      "If", "variable", "then"

    • Patch Set #10, Line 1147: /// must be a compatible native type. An [int] field could be annotated with

      Maybe start the sentence with "For example," instead of ending with it.

    • Yes. And "could" -> "can".

    • Maybe rephrase to: "If the Dart type is equal to the native type, the type argument on the `@Native` […]

      Yes to the shorter version.
      I'd avoid "equal", and use "the same".
      (Because I know what "The same" means syntactically, but "equality" can be complicated.)

      Maybe go in the other direction: Instead of saying when a type argument can be omitted, say what happens *if* it is omitted, then say why that can only work for
      a limited set of declared Dart types.
      ```
      /// If the type argument to `@Native` is omitted, it defaults to the Dart type
      /// of the annotated declaration, which *must* then be a native type too.
      /// This will never work for function declarations, but can apply to variables
      /// whose type is some of the types of this library,
      /// for example a [Pointer] or [Array].
      ```

    • What would that imply?
      Should the `external` declaration be `final`?

    • Use an external getter here instead of an external field. (Assuming this is a global const string. […]

      Or use `external final Pointer<Char> aGLobalString;`?

      (Which means *exactly the same* as `external Pointer<Char> get aGlobalString;`, so both should work.)

    • Patch Set #10, Line 1165: /// Calling such function will try to resolve the [symbol] in (in that order)

      Should mention reading/writing to the field.

    • (and change "that order" to "the order")

    • Patch Set #10, Line 1258: final bool isLeaf;

      "This field" -> "This value"

      (Annotate properties by describing their value. Generally describe the semantic property that the user interacts with, not the declaration. Not unless it's the declaration that the reader interacts with, like instructions for overriding in subclasses.)

    • Start with capital letter.
      Also feels like its mixing abstraction levels.
      The line above says "the native address of [native]", which means that [native] is something which lives in native memory.
      This line says that [native] must be a *reference* to a Dart function or variable.

      So, maybe:
      ```dart
      /// The native address of the implementation of [native].
      ///
      /// When calling this function, the argument for [native] must be an expression
      /// denoting a variable or function declaration which is annotated with [Native].
      /// For a variable declaration, the type [T] must be the same native type
      /// as the type argument to that `@Native` annotation.
      /// For a function declaration, the type [T] must be `NativeFunction<F>`
      /// where `F` was the type argument to that `@Native` annotation.
      ```

    • Patch Set #10, Line 1272: /// [NativeFunction] matching the signature from the `@Native` annotation.

      Here "matching" feels a little vague. Read: I don't know what it means.
      I think the "For methods, this means" tries to define it, but it uses "matching" again, so it doesn't help.

      I rewrote above as "the same". I don't know if that is correct.

    • Patch Set #10, Line 1287: /// taking the address of them:

      I'd say something like:
      ```
      /// The following code binds each of these to a Dart variable and function declaration,
      /// and extracts the address of the native implementation.
      ```
      (Always a good idea to say what an example shows before showing the code, then people won't start reading too much into irrelevant details, because they already know that they are irrelevant.)

    • Patch Set #10, Line 1302: /// // This is a Pointer<Pointer<Char>> pointing to the memory location

      I'd write the types here, instead of writing "final name = ..." and the describing the type in prose anyway.

      Something like:
      ```dart
      /// Pointer<NativeFunction<NativeAdd>> addressSum =
      /// NativeAddress.addressOf<NativeFunction<NativeAdd>>(sum);
      ///
      /// Pointer<Pointer<Char>> addressMyString =
      /// NativeAddress.addressOf<Pointer<Char>>(myString)
      ```
      Can the type argument be inferred from the context type. That is, can you write:
      ```dart
      /// Pointer<NativeFunction<NativeAdd>> addressSum =
      /// NativeAddress.addressOf(sum);
      ///
      /// Pointer<Pointer<Char>> addressMyString =
      /// NativeAddress.addressOf(myString)
      ```
      like you would for a normal static function?

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 10
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Daco Harkes <dacoh...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Comment-Date: Tue, 12 Dec 2023 11:12:32 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: Yes
Comment-In-Reply-To: Daco Harkes <dacoh...@google.com>

Daco Harkes (Gerrit)

unread,
Dec 12, 2023, 6:59:00 AM12/12/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

Attention is currently required from: Lasse Nielsen, Simon Binder.

View Change

3 comments:

  • Patchset:

    • Patch Set #10:

      Thanks Lasse!

      Please follow Lasse's comments if they disagree with mine, he's great with words! 😊

  • File sdk/lib/ffi/ffi.dart:

    • Slightly long (more than one line). […]

      👍 Lasse makes everything great!

    • Or use `external final Pointer<Char> aGLobalString;`? […]

      😮 Yes!

      (And please add some tests for this syntax as well.)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 10
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Lasse Nielsen <l...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Comment-Date: Tue, 12 Dec 2023 11:58:49 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Daco Harkes <dacoh...@google.com>
Comment-In-Reply-To: Lasse Nielsen <l...@google.com>

Simon Binder (Gerrit)

unread,
Dec 12, 2023, 4:07:35 PM12/12/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Lasse Nielsen, Simon Binder.

Simon Binder uploaded patch set #11 to this change.

View Change

[vm/ffi] Support `@Native` fields

Allow annotating top-level or static fields with `@Native` to create
fields backed by native memory.
By using the `_addressOf` operator implemented in the VM, these fields
can be implemented in the CFE by replacing them with accessors looking
up the pointer and then using existing methods to load and store the
value.

Closes https://github.com/dart-lang/sdk/issues/50551

TEST=tests/ffi/native_assets/asset_*_test.dart
TEST=pkg/analyzer/test/src/diagnostics/ffi_native_test.dart

Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
---
M pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
M pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
M pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
M pkg/analyzer/lib/src/error/error_code_values.g.dart
M pkg/analyzer/lib/src/generated/ffi_verifier.dart
M pkg/analyzer/messages.yaml
M pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
M pkg/front_end/lib/src/api_unstable/vm.dart
M pkg/front_end/messages.yaml
M pkg/vm/lib/transformations/ffi/common.dart
M pkg/vm/lib/transformations/ffi/definitions.dart
M pkg/vm/lib/transformations/ffi/native.dart
M pkg/vm/lib/transformations/ffi/use_sites.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect
A pkg/vm/testcases/transformations/ffi/native_fields.dart.expect
M runtime/bin/ffi_test/ffi_test_functions.cc
M runtime/vm/compiler/frontend/kernel_to_il.cc
M sdk/lib/ffi/ffi.dart
M tests/ffi/native_assets/asset_absolute_test.dart
M tests/ffi/native_assets/asset_library_annotation_test.dart
M tests/ffi/native_assets/asset_relative_test.dart
M tests/ffi/vmspecific_static_checks_native_test.dart
23 files changed, 1,256 insertions(+), 211 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 11

Simon Binder (Gerrit)

unread,
Dec 12, 2023, 4:38:46 PM12/12/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Lasse Nielsen, Simon Binder.

Simon Binder uploaded patch set #12 to this change.

View Change

[vm/ffi] Support `@Native` fields

Allow annotating top-level or static fields with `@Native` to create
fields backed by native memory.
By using the `_addressOf` operator implemented in the VM, these fields
can be implemented in the CFE by replacing them with accessors looking
up the pointer and then using existing methods to load and store the
value.

Closes https://github.com/dart-lang/sdk/issues/50551

TEST=tests/ffi/native_assets/asset_*_test.dart
TEST=pkg/analyzer/test/src/diagnostics/ffi_native_test.dart

Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
---
M CHANGELOG.md

M pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
M pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
M pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
M pkg/analyzer/lib/src/error/error_code_values.g.dart
M pkg/analyzer/lib/src/generated/ffi_verifier.dart
M pkg/analyzer/messages.yaml
M pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
M pkg/front_end/lib/src/api_unstable/vm.dart
M pkg/front_end/messages.yaml
M pkg/vm/lib/transformations/ffi/common.dart
M pkg/vm/lib/transformations/ffi/definitions.dart
M pkg/vm/lib/transformations/ffi/native.dart
M pkg/vm/lib/transformations/ffi/use_sites.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect
A pkg/vm/testcases/transformations/ffi/native_fields.dart.expect
M runtime/bin/ffi_test/ffi_test_functions.cc
M runtime/vm/compiler/frontend/kernel_to_il.cc
M sdk/lib/ffi/ffi.dart
M tests/ffi/native_assets/asset_absolute_test.dart
M tests/ffi/native_assets/asset_library_annotation_test.dart
M tests/ffi/native_assets/asset_relative_test.dart
M tests/ffi/vmspecific_static_checks_native_test.dart
24 files changed, 1,398 insertions(+), 218 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 12

Simon Binder (Gerrit)

unread,
Dec 12, 2023, 6:53:37 PM12/12/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Lasse Nielsen, Simon Binder.

Simon Binder uploaded patch set #13 to this change.

View Change

[vm/ffi] Support `@Native` fields

Allow annotating top-level or static fields with `@Native` to create
fields backed by native memory.
By using the `_addressOf` operator implemented in the VM, these fields
can be implemented in the CFE by replacing them with accessors looking
up the pointer and then using existing methods to load and store the
value.

Closes https://github.com/dart-lang/sdk/issues/50551

TEST=tests/ffi/native_assets/asset_*_test.dart
TEST=pkg/analyzer/test/src/diagnostics/ffi_native_test.dart

Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
---
M CHANGELOG.md
M pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
M pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
M pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
M pkg/analyzer/lib/src/error/error_code_values.g.dart
M pkg/analyzer/lib/src/generated/ffi_verifier.dart
M pkg/analyzer/messages.yaml
M pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
M pkg/front_end/lib/src/api_unstable/vm.dart
M pkg/front_end/messages.yaml
M pkg/front_end/test/spell_checking_list_messages.txt

M pkg/vm/lib/transformations/ffi/common.dart
M pkg/vm/lib/transformations/ffi/definitions.dart
M pkg/vm/lib/transformations/ffi/native.dart
M pkg/vm/lib/transformations/ffi/use_sites.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect
A pkg/vm/testcases/transformations/ffi/native_fields.dart.expect
M runtime/bin/ffi_test/ffi_test_functions.cc
M runtime/vm/compiler/frontend/kernel_to_il.cc
M sdk/lib/ffi/ffi.dart
M tests/ffi/native_assets/asset_absolute_test.dart
M tests/ffi/native_assets/asset_library_annotation_test.dart
M tests/ffi/native_assets/asset_relative_test.dart
M tests/ffi/vmspecific_static_checks_native_test.dart
25 files changed, 1,433 insertions(+), 223 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 13

Simon Binder (Gerrit)

unread,
Dec 13, 2023, 6:08:10 AM12/13/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Lasse Nielsen, Simon Binder.

Simon Binder uploaded patch set #14 to this change.

View Change

M pkg/vm/lib/transformations/ffi/native_type_cfe.dart

M pkg/vm/lib/transformations/ffi/use_sites.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart
A pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect
A pkg/vm/testcases/transformations/ffi/native_fields.dart.expect
M runtime/bin/ffi_test/ffi_test_functions.cc
M runtime/vm/compiler/frontend/kernel_to_il.cc
M sdk/lib/ffi/ffi.dart
M tests/ffi/native_assets/asset_absolute_test.dart
M tests/ffi/native_assets/asset_library_annotation_test.dart
M tests/ffi/native_assets/asset_relative_test.dart
M tests/ffi/vmspecific_static_checks_native_test.dart
26 files changed, 1,913 insertions(+), 500 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 14

Simon Binder (Gerrit)

unread,
Dec 13, 2023, 6:24:01 AM12/13/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Lasse Nielsen, Simon Binder.

Simon Binder uploaded patch set #15 to this change.

View Change

26 files changed, 1,916 insertions(+), 501 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 15

Simon Binder (Gerrit)

unread,
Dec 13, 2023, 6:29:07 AM12/13/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Lasse Nielsen, Simon Binder.

Simon Binder uploaded patch set #16 to this change.

View Change

26 files changed, 1,918 insertions(+), 501 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 16

Simon Binder (Gerrit)

unread,
Dec 13, 2023, 6:35:25 AM12/13/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Daco Harkes, Alexander Markov, Paul Berry

Attention is currently required from: Daco Harkes.

View Change

36 comments:

  • Patchset:

    • Patch Set #10:

      Am I correct in assuming that the argument expression to `Native. […]

      Your second example would be accepted, but that's an implementation artifact (since constants are canonicalized in the CFE, so there's no way to tell `fooAlias` and `foo` apart when lowering the `addressOf` call).
      It wouldn't work for native variables (which can't be const) or when doing the alias with a non-constant variable.

  • File pkg/analyzer/messages.yaml:

    • Could you expand the documentation here with examples? […]

      Done

  • File pkg/vm/lib/transformations/ffi/common.dart:

    • Patch Set #10, Line 689: DartType? canonicalNativeTypeForDartType(DartType dartType) {

      Add a comment that this is the inverse of `ensureNativeTypeToDartType`

    • It's actually the inverse of `convertNativeTypeToDartType` if I'm not missing something. It's already next to that method, but I've now named it `convertDartTypeToNativeType` and added comments to both methods explaining their relation.

    • Maybe use a case on `canonicalNativeTypeForDartType` the two call sites instead? […]

      I've removed `canInferNativeTypeFromDartType`.

  • File pkg/vm/lib/transformations/ffi/native.dart:

    • Here and in other places, avoid 'we'. (Sorry I'm guilty of this myself as well.) […]

      Done, I hope I didn't miss any.

    • I think in other files we use `generate` instead of `create` for methods generating/creating AST nod […]

      Done

    • nit: maybe start method name with `_generate` so that it's clear we're creating an expression here. […]

      Done

    • I think this can be implemented in pkg/vm/lib/transformations/ffi/native_type_cfe. […]

      Done, it also reduces some code duplication for the existing CFE type implementation since things like looking up the base address of branching due to ABI-specific offsets are duplicated between the types.

    • Patch Set #10, Line 841: // We don't currently allow handles and arrays, but we can provide a more

      Nit: 'We' […]

      Done

    • Patch Set #10, Line 909: if (isFunction) {

      nit: if isFunction is inlined, we have type promotion for nativeType in the then branch.

    • Done

    • Done

    • Nit: "There is" is pointing to somewhere, but nothing specific. […]

      Done

  • File runtime/bin/ffi_test/ffi_test_functions.cc:

    • The changes don't affect the fuzzer because they only add new members.

    • If these changes are unrelated to the fuzzer, maybe move them down under a different header in this file?

    • I have added a new header for fields.

  • File sdk/lib/ffi/ffi.dart:

    • 👍 Lasse makes everything great!

      Done

    • I'd say "declaration". That's how we distinguish the syntax from the semantics. […]

      Done

    • SGTM. But "field" -> "variable".

      Done

    • Patch Set #10, Line 1133: /// against the native library, to find a native function or a native variable,

      "native variable" -> "native global variable"

    • Done

    • This section feels like it's just repeating the text above. […]

      It mentions that the name of the native symbol can be changed, I have replaced the section with a paragraph explaining just that.

    • "When" is fine. "If" is fine too. I'd normally write "If". […]

      Done

    • "If", "variable", "then"

      Done

    • Yes. And "could" -> "can".

      Done

    • Yes to the shorter version. […]

      Done

    • What would that imply? […]

      I think saying "const global variables in C++" may be ambiguous. A `const char*` field deserves a setter, a `char* const` doesn't. If we mention this at all, we should perhaps also mention that Dart's notion of `final` doesn't match const-correctness in C++. For instance, `final Struct myStruct;` allows `myStruct.foo = 3;`, which is not intended for const struct fields in C++. I'm struggling to put this into concise words, at the moment I'm using

      ```
      /// For native global variables that aren't meant to be re-assigned, a final
      /// variable in Dart or a getter can be used to prevent assignments to the
      /// field itself.
      ```

    • I have made the field in the example `final`.

    • (And please add some tests for this syntax as well.)

    • I'm using a `final Pointer<Char> globalString` in `test/ffi/native_assets/` to test this.

    • (and change "that order" to "the order")

      Done

    • "This field" -> "This value" […]

      Done

    • Start with capital letter. […]

      Done

    • Here "matching" feels a little vague. Read: I don't know what it means. […]

      It must be the same type, so the updated documentation is correct.

    • Patch Set #10, Line 1276: /// For example, consider a native C library exposing a function and a global

      For example, for a native C library ...

    • Done

    • I'd say something like: […]

      Done

    • Maybe split into two separate examples, one for field one for function. […]

      Done

    • I'd write the types here, instead of writing "final name = ... […]

      Inferring the type argument works for `addressOf` too, I have changed the examples to use explicit type annotations on the variables only.

  • File tests/ffi/native_assets/asset_absolute_test.dart:

    • This is a bit of a weird API. Usually native code passes a type only by pointer or only by value. […]

      My intention was to have something proving we're actually writing to the correct location - I have added a function reading the global coordinate variable in C instead.

  • File tests/ffi/native_assets/asset_relative_test.dart:

    • Acknowledged

  • File tests/ffi/vmspecific_static_checks_native_test.dart:

    • Done

    • Patch Set #10, Line 79: // [cfe] Unsupported type for native fields. Native fields only support pointers or numeric and compound types.

      or and […]

      Done

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 16
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Daco Harkes <dacoh...@google.com>
Gerrit-Comment-Date: Wed, 13 Dec 2023 11:35:15 +0000

Daco Harkes (Gerrit)

unread,
Dec 13, 2023, 6:40:08 AM12/13/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

Attention is currently required from: Lasse Nielsen, Simon Binder.

View Change

2 comments:

  • File sdk/lib/ffi/ffi.dart:

    • Patch Set #10, Line 1120: /// Annotation specifying how to bind an external function or global field to

      @lrn@google. […]

      Done

    • I think saying "const global variables in C++" may be ambiguous. […]

      Working from your suggestion, how about:

      ```
      /// For native global variables that cannot be re-assigned, a final


    • /// variable in Dart or a getter can be used to prevent assignments to the

    • /// native field.
      ```

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 16
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Lasse Nielsen <l...@google.com>
Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
Gerrit-Comment-Date: Wed, 13 Dec 2023 11:40:00 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Daco Harkes <dacoh...@google.com>
Comment-In-Reply-To: Lasse Nielsen <l...@google.com>
Comment-In-Reply-To: Simon Binder <o...@simonbinder.eu>

Simon Binder (Gerrit)

unread,
Dec 13, 2023, 8:22:42 AM12/13/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

Attention is currently required from: Lasse Nielsen, Simon Binder.

Simon Binder uploaded patch set #17 to this change.

View Change

26 files changed, 1,917 insertions(+), 501 deletions(-)

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newpatchset
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 17

Simon Binder (Gerrit)

unread,
Dec 13, 2023, 8:55:53 AM12/13/23
to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Daco Harkes, Alexander Markov, Paul Berry

Attention is currently required from: Daco Harkes, Lasse Nielsen.

View Change

1 comment:

To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: comment
Gerrit-Project: sdk
Gerrit-Branch: main
Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
Gerrit-Change-Number: 338020
Gerrit-PatchSet: 17
Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
Gerrit-CC: Alexander Markov <alexm...@google.com>
Gerrit-CC: Jens Johansen <je...@google.com>
Gerrit-CC: Johnni Winther <johnni...@google.com>
Gerrit-CC: Paul Berry <paul...@google.com>
Gerrit-Attention: Daco Harkes <dacoh...@google.com>
Gerrit-Attention: Lasse Nielsen <l...@google.com>
Gerrit-Comment-Date: Wed, 13 Dec 2023 13:55:46 +0000

Chloe Stefantsova (Gerrit)

unread,
Dec 13, 2023, 9:32:10 AM12/13/23
to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Daco Harkes, Alexander Markov, Paul Berry

Attention is currently required from: Daco Harkes, Lasse Nielsen, Simon Binder.

Patch set 17:Code-Review +1

View Change

    To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

    Gerrit-MessageType: comment
    Gerrit-Project: sdk
    Gerrit-Branch: main
    Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
    Gerrit-Change-Number: 338020
    Gerrit-PatchSet: 17
    Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
    Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
    Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
    Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
    Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
    Gerrit-CC: Alexander Markov <alexm...@google.com>
    Gerrit-CC: Jens Johansen <je...@google.com>
    Gerrit-CC: Johnni Winther <johnni...@google.com>
    Gerrit-CC: Paul Berry <paul...@google.com>
    Gerrit-Attention: Daco Harkes <dacoh...@google.com>
    Gerrit-Attention: Lasse Nielsen <l...@google.com>
    Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
    Gerrit-Comment-Date: Wed, 13 Dec 2023 14:32:01 +0000
    Gerrit-HasComments: No
    Gerrit-Has-Labels: Yes

    CBuild (Gerrit)

    unread,
    Dec 13, 2023, 10:14:38 AM12/13/23
    to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Commit Queue, Daco Harkes, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

    Attention is currently required from: Daco Harkes, Lasse Nielsen, Simon Binder.

    go/dart-cbuild result: FAILURE (REGRESSIONS DETECTED)

    Details: https://goto.google.com/dart-cbuild/find/eaafa9e7d2a52e8c43d117e30849d43bc65f57fe
    Bugs: go/dart-cbuild-bug/eaafa9e7d2a52e8c43d117e30849d43bc65f57fe

    View Change

      To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

      Gerrit-MessageType: comment
      Gerrit-Project: sdk
      Gerrit-Branch: main
      Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
      Gerrit-Change-Number: 338020
      Gerrit-PatchSet: 17
      Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
      Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
      Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
      Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
      Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
      Gerrit-CC: Alexander Markov <alexm...@google.com>
      Gerrit-CC: Jens Johansen <je...@google.com>
      Gerrit-CC: Johnni Winther <johnni...@google.com>
      Gerrit-CC: Paul Berry <paul...@google.com>
      Gerrit-Attention: Daco Harkes <dacoh...@google.com>
      Gerrit-Attention: Lasse Nielsen <l...@google.com>
      Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
      Gerrit-Comment-Date: Wed, 13 Dec 2023 15:14:28 +0000
      Gerrit-HasComments: No
      Gerrit-Has-Labels: No

      Simon Binder (Gerrit)

      unread,
      Dec 13, 2023, 10:19:05 AM12/13/23
      to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

      Attention is currently required from: Daco Harkes, Lasse Nielsen, Simon Binder.

      Simon Binder uploaded patch set #18 to this change.

      To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

      Gerrit-MessageType: newpatchset
      Gerrit-Project: sdk
      Gerrit-Branch: main
      Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
      Gerrit-Change-Number: 338020
      Gerrit-PatchSet: 18

      Daco Harkes (Gerrit)

      unread,
      Dec 13, 2023, 1:52:56 PM12/13/23
      to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

      Attention is currently required from: Lasse Nielsen, Simon Binder.

      Patch set 18:Code-Review +1

      View Change

      15 comments:

      • Patchset:

        • Patch Set #18:

          Amazing work Simon!

          LGTM once this round of comments is addressed!

      • File pkg/analyzer/lib/src/generated/ffi_verifier.dart:

      • File pkg/analyzer/messages.yaml:

        • Patch Set #18, Line 18782: pointer fields though, as they have an identical representation in

          I think this is only true for global variables. For structs, fixed-size arrays are inline, while pointers are only 4 or 8 bytes and contain only the address to where the array is.

           It is possible to represent array fields as pointer fields though

          Would we want to encourage users to do exactly that? Or do we want to add support for fixed-size arrays? I've filed https://github.com/dart-lang/sdk/issues/54337 to track this.

        • Patch Set #18, Line 18851: @Native<Int>()

          uppercase `Int` can be slightly confusing for users. Maybe we should update the example to use `uint8_t` and `Uint8`?

        • Patch Set #18, Line 18864: The analyzer produces this diagnostic when a field in a class has been

          "instance field" (as opposed to "static field" which is also a field)

        • Patch Set #18, Line 18866: Native fields always refer to global variables in C, C++ or other native

          Nit: Drop "always" (it doesn't add anything to this sentence).

        • Patch Set #18, Line 18922: ```

          Thanks for the very detailed documentation for our users Simon!

      • File pkg/vm/lib/transformations/ffi/native_type_cfe.dart:

        • Patch Set #18, Line 123: Expression generateLoad({

          Maybe add some documentation here.

        • Patch Set #18, Line 208: bool unaligned = false}) {

          Uber nit: `bool unaligned = false, }` so that the formatter formats parameters with 2 indent here and other places.

        • Patch Set #18, Line 290: Expression generateLoad(

          Sweet, I love the reuse of the code here. Thanks for the refactoring!

        • Patch Set #18, Line 589: class ReferencedStructTypeCfe extends NativeTypeCfe {

          What about referenced union?

        • Patch Set #18, Line 620: Expression generateStore(Expression value,

          Hm, this and `generateLoad` duplicate logic.

          Maybe it's cleaner to add a `bool calculateLayout` to `StructNativeTypeCfe` and `UnionNativeTypeCfe`, and throw the `UnsupportedError` if the layout was not calculated.

      • File pkg/vm/testcases/transformations/ffi/native_fields.dart.expect:

        • Patch Set #18, Line 34: return ffi::_fromAddress<ffi::Char>(ffi::_loadAbiSpecificInt<ffi::IntPtr>(ffi::Native::_addressOf<ffi::Pointer<ffi::Char>>(#C20), #C8));

          It looks like your handcrafted implementation was slightly more concise.
          Can the implementation in native_type_cfe.dart be updated?

          (Feel free to ignore, I think the backend compiler should compile it into the same machine code anyway.)

      • File sdk/lib/ffi/ffi.dart:

        • Patch Set #18, Line 1145: /// If an external variable is annotated with [Native], then the type argument

          Uber nit: The sentence is not symmetric with the previous paragram.

          Maybe rephrase this to: If this annotation is used on an external variable, then ...

        • Patch Set #18, Line 1152: /// [Array].

          Remove `or [Array]` because we don't support it yet.

      To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

      Gerrit-MessageType: comment
      Gerrit-Project: sdk
      Gerrit-Branch: main
      Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
      Gerrit-Change-Number: 338020
      Gerrit-PatchSet: 18
      Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
      Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
      Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
      Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
      Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
      Gerrit-CC: Alexander Markov <alexm...@google.com>
      Gerrit-CC: Jens Johansen <je...@google.com>
      Gerrit-CC: Johnni Winther <johnni...@google.com>
      Gerrit-CC: Paul Berry <paul...@google.com>
      Gerrit-Attention: Lasse Nielsen <l...@google.com>
      Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
      Gerrit-Comment-Date: Wed, 13 Dec 2023 18:52:45 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: Yes

      Simon Binder (Gerrit)

      unread,
      Dec 13, 2023, 5:18:42 PM12/13/23
      to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

      Attention is currently required from: Daco Harkes, Lasse Nielsen, Simon Binder.

      Simon Binder uploaded patch set #19 to this change.

      View Change

      26 files changed, 2,040 insertions(+), 521 deletions(-)

      To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

      Gerrit-MessageType: newpatchset
      Gerrit-Project: sdk
      Gerrit-Branch: main
      Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
      Gerrit-Change-Number: 338020
      Gerrit-PatchSet: 19
      Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
      Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
      Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
      Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
      Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
      Gerrit-CC: Alexander Markov <alexm...@google.com>
      Gerrit-CC: Jens Johansen <je...@google.com>
      Gerrit-CC: Johnni Winther <johnni...@google.com>
      Gerrit-CC: Paul Berry <paul...@google.com>
      Gerrit-Attention: Daco Harkes <dacoh...@google.com>

      Simon Binder (Gerrit)

      unread,
      Dec 13, 2023, 5:24:06 PM12/13/23
      to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Daco Harkes, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

      Attention is currently required from: Daco Harkes, Lasse Nielsen.

      View Change

      14 comments:

      • File pkg/analyzer/lib/src/generated/ffi_verifier.dart:

        • Done

      • File pkg/analyzer/messages.yaml:

        • For structs, fixed-size arrays are inline, while pointers are only 4 or 8 bytes and contain only the address to where the array is.

          Good catch, I have updated the documentation to be more explicit about that. It's still valid to replace arrays with pointers on `@Native` fields though.

        • Would we want to encourage users to do exactly that? Or do we want to add support for fixed-size arrays?

        • I think we should support them, and now that we can use the existing `ArrayNativeTypeCfe` in the other transformers, it's only a matter of reading the length annotation. Given the size of this CL I'm inclined to open a follow-up for that, but if you think the feature is incomplete without array support I can also do it in this one.

        • uppercase `Int` can be slightly confusing for users. […]

          Done

        • Patch Set #18, Line 18864: The analyzer produces this diagnostic when a field in a class has been

          "instance field" (as opposed to "static field" which is also a field)

        • Done

        • Patch Set #18, Line 18866: Native fields always refer to global variables in C, C++ or other native

          Nit: Drop "always" (it doesn't add anything to this sentence).

        • Done

        • File pkg/vm/lib/transformations/ffi/native_type_cfe.dart:

          • Done

          • Uber nit: `bool unaligned = false, }` so that the formatter formats parameters with 2 indent here an […]

            Done

          • Sweet, I love the reuse of the code here. […]

            Acknowledged

          • I have renamed it to `ReferencedCompoundSubtypeCfe` since that's what the class really is. I have also added a transformation test.

          • Hm, this and `generateLoad` duplicate logic. […]

            I have factored the common logic out into a mixin, which doesn't dilute the existing CFE type implementations.

        • File pkg/vm/testcases/transformations/ffi/native_fields.dart.expect:

          • Patch Set #18, Line 34: return ffi::_fromAddress<ffi::Char>(ffi::_loadAbiSpecificInt<ffi::IntPtr>(ffi::Native::_addressOf<ffi::Pointer<ffi::Char>>(#C20), #C8));

          • It looks like your handcrafted implementation was slightly more concise. […]

            These all seem to be handled by the same switch branch in `FlowGraphBuilder::BuildGraphOfRecognizedMethod`, but it was easy enough to change and it doesn't hurt to be concise :)

        • File sdk/lib/ffi/ffi.dart:

          • Uber nit: The sentence is not symmetric with the previous paragram. […]

            Done

          • Done

        To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

        Gerrit-MessageType: comment
        Gerrit-Project: sdk
        Gerrit-Branch: main
        Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
        Gerrit-Change-Number: 338020
        Gerrit-PatchSet: 19
        Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
        Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
        Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
        Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
        Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
        Gerrit-CC: Alexander Markov <alexm...@google.com>
        Gerrit-CC: Jens Johansen <je...@google.com>
        Gerrit-CC: Johnni Winther <johnni...@google.com>
        Gerrit-CC: Paul Berry <paul...@google.com>
        Gerrit-Attention: Daco Harkes <dacoh...@google.com>
        Gerrit-Attention: Lasse Nielsen <l...@google.com>
        Gerrit-Comment-Date: Wed, 13 Dec 2023 22:23:56 +0000

        CBuild (Gerrit)

        unread,
        Dec 13, 2023, 5:53:47 PM12/13/23
        to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Daco Harkes, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

        Attention is currently required from: Daco Harkes, Lasse Nielsen.

        go/dart-cbuild result: FAILURE (REGRESSIONS DETECTED)

        Details: https://goto.google.com/dart-cbuild/find/9f66fe82801f0bc54ca1bfaa480a53df64650c16
        Bugs: go/dart-cbuild-bug/9f66fe82801f0bc54ca1bfaa480a53df64650c16

        View Change

          To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

          Gerrit-MessageType: comment
          Gerrit-Project: sdk
          Gerrit-Branch: main
          Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
          Gerrit-Change-Number: 338020
          Gerrit-PatchSet: 19
          Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
          Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
          Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
          Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
          Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
          Gerrit-CC: Alexander Markov <alexm...@google.com>
          Gerrit-CC: Jens Johansen <je...@google.com>
          Gerrit-CC: Johnni Winther <johnni...@google.com>
          Gerrit-CC: Paul Berry <paul...@google.com>
          Gerrit-Attention: Daco Harkes <dacoh...@google.com>
          Gerrit-Attention: Lasse Nielsen <l...@google.com>
          Gerrit-Comment-Date: Wed, 13 Dec 2023 22:53:38 +0000
          Gerrit-HasComments: No
          Gerrit-Has-Labels: No

          Daco Harkes (Gerrit)

          unread,
          Dec 14, 2023, 5:51:33 AM12/14/23
          to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

          Attention is currently required from: Lasse Nielsen, Simon Binder.

          Patch set 19:Code-Review +1

          View Change

          1 comment:

            • I think we should support them, and now that we can use the existing `ArrayNativeTypeCfe` in the other transformers, it's only a matter of reading the length annotation. Given the size of this CL I'm inclined to open a follow-up for that, but if you think the feature is incomplete without array support I can also do it in this one.

            • LGTM to doing it in a follow up CL.

          To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

          Gerrit-MessageType: comment
          Gerrit-Project: sdk
          Gerrit-Branch: main
          Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
          Gerrit-Change-Number: 338020
          Gerrit-PatchSet: 19
          Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
          Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
          Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
          Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
          Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
          Gerrit-CC: Alexander Markov <alexm...@google.com>
          Gerrit-CC: Jens Johansen <je...@google.com>
          Gerrit-CC: Johnni Winther <johnni...@google.com>
          Gerrit-CC: Paul Berry <paul...@google.com>
          Gerrit-Attention: Lasse Nielsen <l...@google.com>
          Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
          Gerrit-Comment-Date: Thu, 14 Dec 2023 10:51:23 +0000
          Gerrit-HasComments: Yes
          Gerrit-Has-Labels: Yes

          CBuild (Gerrit)

          unread,
          Dec 14, 2023, 6:22:48 AM12/14/23
          to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Daco Harkes, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

          Attention is currently required from: Daco Harkes, Lasse Nielsen, Simon Binder.

          go/dart-cbuild result: FAILURE (REGRESSIONS DETECTED)

          Details: https://goto.google.com/dart-cbuild/find/8958ad47b0eb7e6fcd252c5f51f864444ef5e86d
          Bugs: go/dart-cbuild-bug/8958ad47b0eb7e6fcd252c5f51f864444ef5e86d

          View Change

            To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

            Gerrit-MessageType: comment
            Gerrit-Project: sdk
            Gerrit-Branch: main
            Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
            Gerrit-Change-Number: 338020
            Gerrit-PatchSet: 19
            Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
            Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
            Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
            Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
            Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
            Gerrit-CC: Alexander Markov <alexm...@google.com>
            Gerrit-CC: Jens Johansen <je...@google.com>
            Gerrit-CC: Johnni Winther <johnni...@google.com>
            Gerrit-CC: Paul Berry <paul...@google.com>
            Gerrit-Attention: Daco Harkes <dacoh...@google.com>
            Gerrit-Attention: Lasse Nielsen <l...@google.com>
            Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
            Gerrit-Comment-Date: Thu, 14 Dec 2023 11:22:34 +0000
            Gerrit-HasComments: No
            Gerrit-Has-Labels: No

            Daco Harkes (Gerrit)

            unread,
            Dec 14, 2023, 6:24:15 AM12/14/23
            to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

            Attention is currently required from: Daco Harkes, Lasse Nielsen, Simon Binder.

            Patch set 19:Commit-Queue +1

            View Change

            1 comment:

            • File pkg/vm/lib/transformations/ffi/native.dart:

              • Patch Set #19, Line 632: assetName ?? StringConstant(currentLibrary.importUri.toString()),

                This is not fully correct, it's ignoring the `@DefaultAsset` annotation.

                ```
                @DefaultAsset('someAssetId')
                library;
                ```

                Caught by "go/dart-cbuild result: FAILURE (REGRESSIONS DETECTED)".

                I'm a bit surprised none of the existing tests in the SDK caught this.
                tests/ffi/native_assets/asset_library_annotation_test.dart should have caught this.

                Please add a test in pkg/vm/testcases/transformations/ffi/ with `@DefaultAsset('someAssetId')` so that we can see that the CFE transformer picks it up.

            To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

            Gerrit-MessageType: comment
            Gerrit-Project: sdk
            Gerrit-Branch: main
            Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
            Gerrit-Change-Number: 338020
            Gerrit-PatchSet: 19
            Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
            Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
            Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
            Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
            Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
            Gerrit-CC: Alexander Markov <alexm...@google.com>
            Gerrit-CC: Jens Johansen <je...@google.com>
            Gerrit-CC: Johnni Winther <johnni...@google.com>
            Gerrit-CC: Paul Berry <paul...@google.com>
            Gerrit-Attention: Daco Harkes <dacoh...@google.com>
            Gerrit-Attention: Lasse Nielsen <l...@google.com>
            Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
            Gerrit-Comment-Date: Thu, 14 Dec 2023 11:24:01 +0000
            Gerrit-HasComments: Yes
            Gerrit-Has-Labels: Yes

            Simon Binder (Gerrit)

            unread,
            Dec 14, 2023, 3:05:46 PM12/14/23
            to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org

            Attention is currently required from: Daco Harkes, Lasse Nielsen, Simon Binder.

            Simon Binder uploaded patch set #20 to this change.

            View Change

            [vm/ffi] Support `@Native` fields

            Allow annotating top-level or static fields with `@Native` to create
            fields backed by native memory.
            By using the `_addressOf` operator implemented in the VM, these fields
            can be implemented in the CFE by replacing them with accessors looking
            up the pointer and then using existing methods to load and store the
            value.

            Closes https://github.com/dart-lang/sdk/issues/50551

            TEST=tests/ffi/native_assets/asset_*_test.dart
            TEST=pkg/analyzer/test/src/diagnostics/ffi_native_test.dart

            Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
            ---
            M CHANGELOG.md
            M pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
            M pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
            M pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
            M pkg/analyzer/lib/src/error/error_code_values.g.dart
            M pkg/analyzer/lib/src/generated/ffi_verifier.dart
            M pkg/analyzer/messages.yaml
            M pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
            M pkg/front_end/lib/src/api_unstable/vm.dart
            M pkg/front_end/messages.yaml
            M pkg/front_end/test/spell_checking_list_messages.txt
            M pkg/front_end/testcases/general/ffi_sample.dart.strong.transformed.expect
            M pkg/front_end/testcases/general/ffi_sample.dart.weak.transformed.expect
            M pkg/front_end/testcases/incremental/ffi_01.yaml.world.1.expect
            M pkg/front_end/testcases/incremental/ffi_01.yaml.world.2.expect
            M pkg/front_end/testcases/incremental/ffi_02.yaml.world.1.expect
            M pkg/front_end/testcases/incremental/issue_46666.yaml.world.1.expect
            M pkg/front_end/testcases/incremental/issue_46666.yaml.world.2.expect
            M pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.1.expect
            M pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.2.expect
            M pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.3.expect
            M pkg/front_end/testcases/incremental/regress_46004.yaml.world.1.expect
            M pkg/front_end/testcases/incremental/regress_46004.yaml.world.2.expect
            M pkg/front_end/testcases/nnbd/ffi_sample.dart.strong.transformed.expect
            M pkg/front_end/testcases/nnbd/ffi_sample.dart.weak.transformed.expect

            M pkg/vm/lib/transformations/ffi/common.dart
            M pkg/vm/lib/transformations/ffi/definitions.dart
            M pkg/vm/lib/transformations/ffi/native.dart
            M pkg/vm/lib/transformations/ffi/native_type_cfe.dart
            M pkg/vm/lib/transformations/ffi/use_sites.dart
            A pkg/vm/testcases/transformations/ffi/native_asset_id.dart
            A pkg/vm/testcases/transformations/ffi/native_asset_id.dart.aot.expect
            A pkg/vm/testcases/transformations/ffi/native_asset_id.dart.expect

            A pkg/vm/testcases/transformations/ffi/native_fields.dart
            A pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect
            A pkg/vm/testcases/transformations/ffi/native_fields.dart.expect
            M runtime/bin/ffi_test/ffi_test_functions.cc
            M runtime/vm/compiler/frontend/kernel_to_il.cc
            M sdk/lib/ffi/ffi.dart
            M tests/ffi/native_assets/asset_absolute_test.dart
            M tests/ffi/native_assets/asset_library_annotation_test.dart
            M tests/ffi/native_assets/asset_relative_test.dart
            M tests/ffi/vmspecific_static_checks_native_test.dart
            43 files changed, 2,203 insertions(+), 581 deletions(-)

            To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

            Gerrit-MessageType: newpatchset
            Gerrit-Project: sdk
            Gerrit-Branch: main
            Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
            Gerrit-Change-Number: 338020
            Gerrit-PatchSet: 20

            Simon Binder (Gerrit)

            unread,
            Dec 14, 2023, 3:06:54 PM12/14/23
            to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Daco Harkes, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

            Attention is currently required from: Daco Harkes, Lasse Nielsen.

            View Change

            1 comment:

            • File pkg/vm/lib/transformations/ffi/native.dart:

              • tests/ffi/native_assets/asset_library_annotation_test.dart should have caught this.

                I don't fully understand the test infrastructure here, but could this be due to the `// SharedObjects=` line making the symbols available to the fallback resolver looking them up in the process?

                There were some inconsistencies in how this method was called - `_resolveAssetName` is supposed to apply the default asset but the result hasn't been used for fields. And the marker annotation for `addressOf` used its own logic to construct the annotation and had the same problem.

                I've changed this method to apply the default for the library and removed that handling from other places.

              • Please add a test in pkg/vm/testcases/transformations/ffi/ with `@DefaultAsset('someAssetId')`

              • Done.

            To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

            Gerrit-MessageType: comment
            Gerrit-Project: sdk
            Gerrit-Branch: main
            Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
            Gerrit-Change-Number: 338020
            Gerrit-PatchSet: 20
            Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
            Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
            Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
            Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
            Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
            Gerrit-CC: Alexander Markov <alexm...@google.com>
            Gerrit-CC: Jens Johansen <je...@google.com>
            Gerrit-CC: Johnni Winther <johnni...@google.com>
            Gerrit-CC: Paul Berry <paul...@google.com>
            Gerrit-Attention: Daco Harkes <dacoh...@google.com>
            Gerrit-Attention: Lasse Nielsen <l...@google.com>
            Gerrit-Comment-Date: Thu, 14 Dec 2023 20:06:37 +0000

            Daco Harkes (Gerrit)

            unread,
            Dec 15, 2023, 4:56:13 AM12/15/23
            to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

            Attention is currently required from: Lasse Nielsen, Simon Binder.

            Patch set 20:Code-Review +1

            View Change

            1 comment:

            To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

            Gerrit-MessageType: comment
            Gerrit-Project: sdk
            Gerrit-Branch: main
            Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
            Gerrit-Change-Number: 338020
            Gerrit-PatchSet: 20
            Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
            Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
            Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
            Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
            Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
            Gerrit-CC: Alexander Markov <alexm...@google.com>
            Gerrit-CC: Jens Johansen <je...@google.com>
            Gerrit-CC: Johnni Winther <johnni...@google.com>
            Gerrit-CC: Paul Berry <paul...@google.com>
            Gerrit-Attention: Lasse Nielsen <l...@google.com>
            Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
            Gerrit-Comment-Date: Fri, 15 Dec 2023 09:55:57 +0000
            Gerrit-HasComments: Yes
            Gerrit-Has-Labels: Yes

            CBuild (Gerrit)

            unread,
            Dec 15, 2023, 5:44:27 AM12/15/23
            to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Daco Harkes, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

            Attention is currently required from: Daco Harkes, Lasse Nielsen, Simon Binder.

              To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

              Gerrit-MessageType: comment
              Gerrit-Project: sdk
              Gerrit-Branch: main
              Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
              Gerrit-Change-Number: 338020
              Gerrit-PatchSet: 20
              Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
              Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
              Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
              Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
              Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
              Gerrit-CC: Alexander Markov <alexm...@google.com>
              Gerrit-CC: Jens Johansen <je...@google.com>
              Gerrit-CC: Johnni Winther <johnni...@google.com>
              Gerrit-CC: Paul Berry <paul...@google.com>
              Gerrit-Attention: Daco Harkes <dacoh...@google.com>
              Gerrit-Attention: Lasse Nielsen <l...@google.com>
              Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
              Gerrit-Comment-Date: Fri, 15 Dec 2023 10:44:19 +0000
              Gerrit-HasComments: No
              Gerrit-Has-Labels: No

              Simon Binder (Gerrit)

              unread,
              Dec 15, 2023, 6:02:02 AM12/15/23
              to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Daco Harkes, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

              Attention is currently required from: Daco Harkes, Lasse Nielsen.

              View Change

              2 comments:

              • Patchset:

                • Patch Set #22:

                  Is there something I can do about the failing CFE tests? They seem to be failing on `main` too.

              • Commit Message:

                • Done

              To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

              Gerrit-MessageType: comment
              Gerrit-Project: sdk
              Gerrit-Branch: main
              Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
              Gerrit-Change-Number: 338020
              Gerrit-PatchSet: 22
              Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
              Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
              Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
              Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
              Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
              Gerrit-CC: Alexander Markov <alexm...@google.com>
              Gerrit-CC: Jens Johansen <je...@google.com>
              Gerrit-CC: Johnni Winther <johnni...@google.com>
              Gerrit-CC: Paul Berry <paul...@google.com>
              Gerrit-Attention: Daco Harkes <dacoh...@google.com>
              Gerrit-Attention: Lasse Nielsen <l...@google.com>
              Gerrit-Comment-Date: Fri, 15 Dec 2023 11:01:45 +0000

              Daco Harkes (Gerrit)

              unread,
              Dec 15, 2023, 6:15:21 AM12/15/23
              to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

              Attention is currently required from: Lasse Nielsen, Simon Binder.

              Patch set 22:Code-Review +1

              View Change

              2 comments:

              • Patchset:

                • Patch Set #22:

                  Is there something I can do about the failing CFE tests? They seem to be failing on `main` too.

                • When working on new language features this happens from time to time. I either just approve, or submit manually.

                  Eventually a gardener on rotation will approve the current failures on language features that are under development.

              • Commit Message:

                • Patch Set #22, Line 22:

                  Try removing this new line so that the above line is part of the footer. (It's not being picked up yet, no green check mark.)

              To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

              Gerrit-MessageType: comment
              Gerrit-Project: sdk
              Gerrit-Branch: main
              Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
              Gerrit-Change-Number: 338020
              Gerrit-PatchSet: 22
              Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
              Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
              Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
              Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
              Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
              Gerrit-CC: Alexander Markov <alexm...@google.com>
              Gerrit-CC: Jens Johansen <je...@google.com>
              Gerrit-CC: Johnni Winther <johnni...@google.com>
              Gerrit-CC: Paul Berry <paul...@google.com>
              Gerrit-Attention: Lasse Nielsen <l...@google.com>
              Gerrit-Attention: Simon Binder <o...@simonbinder.eu>
              Gerrit-Comment-Date: Fri, 15 Dec 2023 11:15:09 +0000
              Gerrit-HasComments: Yes
              Gerrit-Has-Labels: Yes
              Comment-In-Reply-To: Simon Binder <o...@simonbinder.eu>

              Simon Binder (Gerrit)

              unread,
              Dec 15, 2023, 6:17:36 AM12/15/23
              to dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Daco Harkes, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

              Attention is currently required from: Lasse Nielsen.

              View Change

              1 comment:

              • Commit Message:

                • Try removing this new line so that the above line is part of the footer. […]

                  Done

              To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

              Gerrit-MessageType: comment
              Gerrit-Project: sdk
              Gerrit-Branch: main
              Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
              Gerrit-Change-Number: 338020
              Gerrit-PatchSet: 23
              Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
              Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
              Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
              Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
              Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
              Gerrit-CC: Alexander Markov <alexm...@google.com>
              Gerrit-CC: Jens Johansen <je...@google.com>
              Gerrit-CC: Johnni Winther <johnni...@google.com>
              Gerrit-CC: Paul Berry <paul...@google.com>
              Gerrit-Attention: Lasse Nielsen <l...@google.com>
              Gerrit-Comment-Date: Fri, 15 Dec 2023 11:17:26 +0000

              Daco Harkes (Gerrit)

              unread,
              Dec 15, 2023, 6:20:31 AM12/15/23
              to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

              Daco Harkes submitted this change.

              View Change



              22 is the latest approved patch-set.
              No files were changed between the latest approved patch-set and the submitted one.

              Approvals: Brian Wilkerson: Looks good to me, approved Chloe Stefantsova: Looks good to me, approved Lasse Nielsen: Looks good to me, approved Daco Harkes: Looks good to me, approved
              [vm/ffi] Support `@Native` fields

              Allow annotating top-level or static fields with `@Native` to create
              fields backed by native memory.
              By using the `_addressOf` operator implemented in the VM, these fields
              can be implemented in the CFE by replacing them with accessors looking
              up the pointer and then using existing methods to load and store the
              value.

              Closes https://github.com/dart-lang/sdk/issues/50551

              TEST=tests/ffi/native_assets/asset_*_test.dart
              TEST=pkg/analyzer/test/src/diagnostics/ffi_native_test.dart

              CoreLibraryReviewExempt: VM & dart2wasm only feature
              Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
              Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/338020
              Reviewed-by: Daco Harkes <dacoh...@google.com>
              Reviewed-by: Brian Wilkerson <brianwi...@google.com>
              Reviewed-by: Lasse Nielsen <l...@google.com>
              Reviewed-by: Chloe Stefantsova <cstefa...@google.com>
              
              
              diff --git a/CHANGELOG.md b/CHANGELOG.md
              index bf9ce48..8016a2b 100644
              --- a/CHANGELOG.md
              +++ b/CHANGELOG.md
              @@ -51,6 +51,12 @@
              - `String.fromCharCodes` now allow `start` and `end` to be after the end of
              the `Iterable` argument, just like `skip` and `take` does on an `Iterable`.

              +#### `dart:ffi`
              +
              +- In addition to functions, `@Native` can now be used on fields.
              +- Allow taking the address of native functions and fields via
              + `Native.addressOf`.
              +
              #### `dart:nativewrappers`

              - **Breaking Change** [#51896][]: The NativeWrapperClasses are marked `base` so
              diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
              index 80acad0..50d8bd8 100644
              --- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
              +++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
              @@ -5275,7 +5275,38 @@
              "FfiNativeDuplicateAnnotations",
              analyzerCodes: <String>["FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS"],
              problemMessage:
              - r"""Native functions must not have more than @Native annotation.""");
              + r"""Native functions and fields must not have more than @Native annotation.""");
              +
              +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
              +const Code<Null> codeFfiNativeFieldMissingType =
              + messageFfiNativeFieldMissingType;
              +
              +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
              +const MessageCode messageFfiNativeFieldMissingType = const MessageCode(
              + "FfiNativeFieldMissingType",
              + analyzerCodes: <String>["NATIVE_FIELD_MISSING_TYPE"],
              + problemMessage:
              + r"""The native type of this field could not be inferred and must be specified in the annotation.""");
              +
              +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
              +const Code<Null> codeFfiNativeFieldMustBeStatic =
              + messageFfiNativeFieldMustBeStatic;
              +
              +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
              +const MessageCode messageFfiNativeFieldMustBeStatic = const MessageCode(
              + "FfiNativeFieldMustBeStatic",
              + analyzerCodes: <String>["NATIVE_FIELD_NOT_STATIC"],
              + problemMessage: r"""Native fields must be static.""");
              +
              +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
              +const Code<Null> codeFfiNativeFieldType = messageFfiNativeFieldType;
              +
              +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
              +const MessageCode messageFfiNativeFieldType = const MessageCode(
              + "FfiNativeFieldType",
              + analyzerCodes: <String>["NATIVE_FIELD_INVALID_TYPE"],
              + problemMessage:
              + r"""Unsupported type for native fields. Native fields only support pointers, compounds and numeric types.""");

              // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
              const Code<Null> codeFfiNativeMustBeExternal = messageFfiNativeMustBeExternal;
              @@ -5283,7 +5314,8 @@
              // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
              const MessageCode messageFfiNativeMustBeExternal = const MessageCode(
              "FfiNativeMustBeExternal",
              - problemMessage: r"""Native functions must be marked external.""");
              + problemMessage:
              + r"""Native functions and fields must be marked external.""");

              // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
              const Code<Null> codeFfiNativeOnlyNativeFieldWrapperClassCanBePointer =
              diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
              index b79a6f2..186c743 100644
              --- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
              +++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
              @@ -1798,6 +1798,12 @@
              status: noFix
              FfiCode.MUST_RETURN_VOID:
              status: noFix
              +FfiCode.NATIVE_FIELD_INVALID_TYPE:
              + status: needsEvaluation
              +FfiCode.NATIVE_FIELD_MISSING_TYPE:
              + status: needsEvaluation
              +FfiCode.NATIVE_FIELD_NOT_STATIC:
              + status: needsEvaluation
              FfiCode.NON_CONSTANT_TYPE_ARGUMENT:
              status: noFix
              FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER:
              diff --git a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
              index 7d5a881..e57c340 100644
              --- a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
              +++ b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
              @@ -87,7 +87,8 @@
              static const FfiCode ARGUMENT_MUST_BE_NATIVE = FfiCode(
              'ARGUMENT_MUST_BE_NATIVE',
              "Argument to 'Native.addressOf' must be annotated with @Native",
              - correctionMessage: "Try passing a static function annotated with '@Native'",
              + correctionMessage:
              + "Try passing a static function or field annotated with '@Native'",
              );

              /// No parameters.
              @@ -163,7 +164,7 @@
              /// No parameters
              static const FfiCode FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS = FfiCode(
              'FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS',
              - "Native functions must have exactly one `@Native` annotation.",
              + "Native functions and fields must have exactly one `@Native` annotation.",
              correctionMessage: "Try removing the extra annotation.",
              );

              @@ -369,6 +370,37 @@
              );

              /// Parameters:
              + /// 0: The invalid type.
              + static const FfiCode NATIVE_FIELD_INVALID_TYPE = FfiCode(
              + 'NATIVE_FIELD_INVALID_TYPE',
              + "'{0}' is an unsupported type for native fields. Native fields only "
              + "support pointers or numeric and compound types.",
              + correctionMessage:
              + "Try changing the type in the `@Native` annotation to a numeric FFI "
              + "type, a pointer, or a compound class.",
              + hasPublishedDocs: true,
              + );
              +
              + /// No parameters
              + static const FfiCode NATIVE_FIELD_MISSING_TYPE = FfiCode(
              + 'NATIVE_FIELD_MISSING_TYPE',
              + "The native type of this field could not be inferred and must be specified "
              + "in the annotation.",
              + correctionMessage:
              + "Try adding a type parameter extending `NativeType` to the `@Native` "
              + "annotation.",
              + hasPublishedDocs: true,
              + );
              +
              + /// No parameters
              + static const FfiCode NATIVE_FIELD_NOT_STATIC = FfiCode(
              + 'NATIVE_FIELD_NOT_STATIC',
              + "Native fields must be static.",
              + correctionMessage: "Try adding the modifier 'static' to this field.",
              + hasPublishedDocs: true,
              + );
              +
              + /// Parameters:
              /// 0: the name of the function, method, or constructor having type arguments
              static const FfiCode NON_CONSTANT_TYPE_ARGUMENT = FfiCode(
              'NON_CONSTANT_TYPE_ARGUMENT',
              diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart
              index 9bf851a..efb0bae 100644
              --- a/pkg/analyzer/lib/src/error/error_code_values.g.dart
              +++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart
              @@ -611,6 +611,9 @@
              FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
              FfiCode.MUST_BE_A_SUBTYPE,
              FfiCode.MUST_RETURN_VOID,
              + FfiCode.NATIVE_FIELD_INVALID_TYPE,
              + FfiCode.NATIVE_FIELD_MISSING_TYPE,
              + FfiCode.NATIVE_FIELD_NOT_STATIC,
              FfiCode.NON_CONSTANT_TYPE_ARGUMENT,
              FfiCode.NON_LEAF_CALL_MUST_NOT_TAKE_TYPED_DATA,
              FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER,
              diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
              index 69eb85e..e4c75a1 100644
              --- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
              +++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
              @@ -181,6 +181,19 @@
              if (inCompound) {
              _validateFieldsInCompound(node);
              }
              +
              + for (var declared in node.fields.variables) {
              + var declaredElement = declared.declaredElement;
              + if (declaredElement != null) {
              + _checkFfiNative(
              + errorNode: declared,
              + declarationElement: declaredElement,
              + formalParameterList: null,
              + isExternal: node.externalKeyword != null,
              + );
              + }
              + }
              +
              super.visitFieldDeclaration(node);
              }

              @@ -188,9 +201,9 @@
              void visitFunctionDeclaration(FunctionDeclaration node) {
              _checkFfiNative(
              errorNode: node,
              - annotations: node.declaredElement!.metadata,
              declarationElement: node.declaredElement!,
              formalParameterList: node.functionExpression.parameters,
              + isExternal: node.externalKeyword != null,
              );
              super.visitFunctionDeclaration(node);
              }
              @@ -264,10 +277,11 @@
              @override
              void visitMethodDeclaration(MethodDeclaration node) {
              _checkFfiNative(
              - errorNode: node,
              - annotations: node.declaredElement!.metadata,
              - declarationElement: node.declaredElement!,
              - formalParameterList: node.parameters);
              + errorNode: node,
              + declarationElement: node.declaredElement!,
              + formalParameterList: node.parameters,
              + isExternal: node.externalKeyword != null,
              + );
              super.visitMethodDeclaration(node);
              }

              @@ -336,17 +350,41 @@
              super.visitPropertyAccess(node);
              }

              + @override
              + void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
              + for (var declared in node.variables.variables) {
              + var declaredElement = declared.declaredElement;
              + if (declaredElement != null) {
              + _checkFfiNative(
              + errorNode: declared,
              + declarationElement: declaredElement,
              + formalParameterList: null,
              + isExternal: node.externalKeyword != null,
              + );
              + }
              + }
              + super.visitTopLevelVariableDeclaration(node);
              + }
              +
              + DartType? _canonicalFfiTypeForDartType(DartType dartType) {
              + if (dartType.isPointer || dartType.isCompoundSubtype || dartType.isArray) {
              + return dartType;
              + } else {
              + return null;
              + }
              + }
              +
              void _checkFfiNative({
              required Declaration errorNode,
              - required List<ElementAnnotation> annotations,
              - required ExecutableElement declarationElement,
              + required Element declarationElement,
              required FormalParameterList? formalParameterList,
              + required bool isExternal,
              }) {
              final formalParameters =
              formalParameterList?.parameters ?? <FormalParameter>[];
              var hadNativeAnnotation = false;

              - for (var annotation in annotations) {
              + for (var annotation in declarationElement.metadata) {
              var annotationValue = annotation.computeConstantValue();
              var annotationType = annotationValue?.type; // Native<T>

              @@ -360,124 +398,200 @@
              var name = (annotation as ElementAnnotationImpl).annotationAst.name;
              _errorReporter.reportErrorForNode(
              FfiCode.FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS, name, []);
              + break;
              }

              hadNativeAnnotation = true;

              - var ffiSignature = annotationType.typeArguments[0]; // The T in @Native<T>
              -
              - if (ffiSignature is! FunctionType) {
              - _errorReporter.reportErrorForNode(
              - FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, errorNode, ['T', 'Native']);
              - return;
              - }
              -
              - // Leaf call FFI Natives can't use Handles.
              - var isLeaf =
              - annotationValue.getField(_isLeafParamName)?.toBoolValue() ?? false;
              - if (isLeaf) {
              - _validateFfiLeafCallUsesNoHandles(ffiSignature, errorNode);
              - }
              -
              - if (!declarationElement.isExternal) {
              + if (!isExternal) {
              _errorReporter.reportErrorForNode(
              FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL, errorNode);
              }

              - var ffiParameterTypes =
              - ffiSignature.normalParameterTypes.flattenVarArgs();
              - var ffiParameters = ffiSignature.parameters;
              + var ffiSignature = annotationType.typeArguments[0]; // The T in @Native<T>

              - if ((declarationElement is MethodElement ||
              - declarationElement is PropertyAccessorElementImpl) &&
              - !declarationElement.isStatic) {
              - // Instance methods must have the receiver as an extra parameter in the
              - // Native annotation.
              - if (formalParameters.length + 1 != ffiParameterTypes.length) {
              - _errorReporter.reportErrorForNode(
              - FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER,
              - errorNode,
              - [formalParameters.length + 1, ffiParameterTypes.length]);
              - return;
              - }
              -
              - // Receiver can only be Pointer if the class extends
              - // NativeFieldWrapperClass1.
              - if (ffiSignature.normalParameterTypes[0].isPointer) {
              - final cls = declarationElement.enclosingElement as InterfaceElement;
              - if (!_extendsNativeFieldWrapperClass1(cls.thisType)) {
              - _errorReporter.reportErrorForNode(
              - FfiCode
              - .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
              - errorNode);
              - }
              - }
              -
              - ffiParameterTypes = ffiParameterTypes.sublist(1);
              - ffiParameters = ffiParameters.sublist(1);
              - } else {
              - // Number of parameters in the Native annotation must match the
              - // annotated declaration.
              - if (formalParameters.length != ffiParameterTypes.length) {
              - _errorReporter.reportErrorForNode(
              - FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS,
              - errorNode,
              - [ffiParameterTypes.length, formalParameters.length]);
              - return;
              - }
              - }
              -
              - // Arguments can only be Pointer if the class extends
              - // Pointer or NativeFieldWrapperClass1.
              - for (var i = 0; i < formalParameters.length; i++) {
              - if (ffiParameterTypes[i].isPointer) {
              - final type = formalParameters[i].declaredElement!.type;
              - if (type is! InterfaceType ||
              - (!type.isPointer &&
              - !_extendsNativeFieldWrapperClass1(type) &&
              - !type.isTypedData)) {
              - _errorReporter.reportErrorForNode(
              - FfiCode
              - .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
              - errorNode);
              - }
              - }
              - }
              -
              - final dartType = declarationElement.type;
              - final nativeType = FunctionTypeImpl(
              - typeFormals: ffiSignature.typeFormals,
              - parameters: ffiParameters,
              - returnType: ffiSignature.returnType,
              - nullabilitySuffix: ffiSignature.nullabilitySuffix,
              - );
              - if (!_isValidFfiNativeFunctionType(nativeType)) {
              - _errorReporter.reportErrorForNode(
              - FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
              + if (ffiSignature is FunctionType) {
              + if (declarationElement is ExecutableElement) {
              + _checkFfiNativeFunction(
              errorNode,
              - [nativeType, 'Native']);
              - return;
              + declarationElement,
              + ffiSignature,
              + annotationValue,
              + formalParameters,
              + );
              + } else {
              + // Field annotated with a function type, that can't work.
              + _errorReporter.reportErrorForNode(
              + FfiCode.NATIVE_FIELD_INVALID_TYPE, errorNode, [ffiSignature]);
              + }
              + } else {
              + if (declarationElement is MethodElement ||
              + declarationElement is FunctionElement) {
              + // Function annotated with something that isn't a function type.
              + _errorReporter.reportErrorForNode(
              + FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
              + errorNode,
              + ['T', 'Native']);
              + } else {
              + _checkFfiNativeField(
              + errorNode, declarationElement, ffiSignature, annotationValue);
              + }
              }
              - if (!_validateCompatibleFunctionTypes(
              - dartType,
              - nativeType,
              - nativeFieldWrappersAsPointer: true,
              - allowStricterReturn: true,
              - )) {
              - _errorReporter.reportErrorForNode(FfiCode.MUST_BE_A_SUBTYPE, errorNode,
              - [nativeType, dartType, 'Native']);
              - return;
              - }
              - _validateFfiTypedDataUnwrapping(
              - dartType,
              - nativeType,
              - errorNode,
              - isLeaf: isLeaf,
              - isCall: true,
              - );
              +
              + if (ffiSignature is FunctionType &&
              + declarationElement is ExecutableElement) {}
              }
              }

              + void _checkFfiNativeField(
              + Declaration errorNode,
              + Element declarationElement,
              + DartType ffiSignature,
              + DartObject annotationValue,
              + ) {
              + DartType type;
              +
              + if (declarationElement is FieldElement) {
              + if (!declarationElement.isStatic) {
              + _errorReporter.reportErrorForNode(
              + FfiCode.NATIVE_FIELD_NOT_STATIC, errorNode);
              + }
              + type = declarationElement.type;
              + } else if (declarationElement is TopLevelVariableElement) {
              + type = declarationElement.type;
              + } else if (declarationElement is PropertyAccessorElement) {
              + type = declarationElement.variable.type;
              + } else {
              + _errorReporter.reportErrorForNode(
              + FfiCode.NATIVE_FIELD_NOT_STATIC, errorNode);
              + return;
              + }
              +
              + if (ffiSignature is DynamicType) {
              + // Attempt to infer the native type from the Dart type.
              + final canonical = _canonicalFfiTypeForDartType(type);
              +
              + if (canonical == null) {
              + _errorReporter.reportErrorForNode(
              + FfiCode.NATIVE_FIELD_MISSING_TYPE, errorNode);
              + return;
              + } else {
              + ffiSignature = canonical;
              + }
              + }
              +
              + if (!_validateCompatibleNativeType(type, ffiSignature)) {
              + _errorReporter.reportErrorForNode(
              + FfiCode.MUST_BE_A_SUBTYPE, errorNode, [type, ffiSignature, 'Native']);
              + } else if (ffiSignature.isArray ||
              + ffiSignature.isHandle ||
              + ffiSignature.isNativeFunction) {
              + _errorReporter.reportErrorForNode(
              + FfiCode.NATIVE_FIELD_INVALID_TYPE, errorNode, [ffiSignature]);
              + }
              + }
              +
              + void _checkFfiNativeFunction(
              + Declaration errorNode,
              + ExecutableElement declarationElement,
              + FunctionType ffiSignature,
              + DartObject annotationValue,
              + List<FormalParameter> formalParameters,
              + ) {
              + // Leaf call FFI Natives can't use Handles.
              + var isLeaf =
              + annotationValue.getField(_isLeafParamName)?.toBoolValue() ?? false;
              + if (isLeaf) {
              + _validateFfiLeafCallUsesNoHandles(ffiSignature, errorNode);
              + }
              +
              + var ffiParameterTypes = ffiSignature.normalParameterTypes.flattenVarArgs();
              + var ffiParameters = ffiSignature.parameters;
              +
              + if ((declarationElement is MethodElement ||
              + declarationElement is PropertyAccessorElementImpl) &&
              + !declarationElement.isStatic) {
              + // Instance methods must have the receiver as an extra parameter in the
              + // Native annotation.
              + if (formalParameters.length + 1 != ffiParameterTypes.length) {
              + _errorReporter.reportErrorForNode(
              + FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER,
              + errorNode,
              + [formalParameters.length + 1, ffiParameterTypes.length]);
              + return;
              + }
              +
              + // Receiver can only be Pointer if the class extends
              + // NativeFieldWrapperClass1.
              + if (ffiSignature.normalParameterTypes[0].isPointer) {
              + final cls = declarationElement.enclosingElement as InterfaceElement;
              + if (!_extendsNativeFieldWrapperClass1(cls.thisType)) {
              + _errorReporter.reportErrorForNode(
              + FfiCode
              + .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
              + errorNode);
              + }
              + }
              +
              + ffiParameterTypes = ffiParameterTypes.sublist(1);
              + ffiParameters = ffiParameters.sublist(1);
              + } else {
              + // Number of parameters in the Native annotation must match the
              + // annotated declaration.
              + if (formalParameters.length != ffiParameterTypes.length) {
              + _errorReporter.reportErrorForNode(
              + FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS,
              + errorNode,
              + [ffiParameterTypes.length, formalParameters.length]);
              + return;
              + }
              + }
              +
              + // Arguments can only be Pointer if the class extends
              + // Pointer or NativeFieldWrapperClass1.
              + for (var i = 0; i < formalParameters.length; i++) {
              + if (ffiParameterTypes[i].isPointer) {
              + final type = formalParameters[i].declaredElement!.type;
              + if (type is! InterfaceType ||
              + (!type.isPointer &&
              + !_extendsNativeFieldWrapperClass1(type) &&
              + !type.isTypedData)) {
              + _errorReporter.reportErrorForNode(
              + FfiCode
              + .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
              + errorNode);
              + }
              + }
              + }
              +
              + final dartType = declarationElement.type;
              + final nativeType = FunctionTypeImpl(
              + typeFormals: ffiSignature.typeFormals,
              + parameters: ffiParameters,
              + returnType: ffiSignature.returnType,
              + nullabilitySuffix: ffiSignature.nullabilitySuffix,
              + );
              + if (!_isValidFfiNativeFunctionType(nativeType)) {
              + _errorReporter.reportErrorForNode(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
              + errorNode, [nativeType, 'Native']);
              + return;
              + }
              + if (!_validateCompatibleFunctionTypes(dartType, nativeType,
              + nativeFieldWrappersAsPointer: true, allowStricterReturn: true)) {
              + _errorReporter.reportErrorForNode(FfiCode.MUST_BE_A_SUBTYPE, errorNode,
              + [nativeType, dartType, 'Native']);
              + return;
              + }
              +
              + _validateFfiTypedDataUnwrapping(
              + dartType,
              + nativeType,
              + errorNode,
              + isLeaf: isLeaf,
              + isCall: true,
              + );
              + }
              +
              bool _extendsNativeFieldWrapperClass1(InterfaceType? type) {
              while (type != null) {
              if (type.getDisplayString(withNullability: false) ==
              @@ -1312,7 +1426,7 @@
              var validTarget = false;

              var referencedElement = switch (argument) {
              - Identifier() => argument.staticElement,
              + Identifier() => argument.staticElement?.nonSynthetic,
              _ => null,
              };

              @@ -1323,25 +1437,50 @@

              if (annotationType is InterfaceType &&
              annotationType.element.isNative) {
              - var functionType = annotationType.typeArguments[0];
              + var nativeType = annotationType.typeArguments[0];

              - // When referencing a function, the target type must be a
              - // `NativeFunction<T>` so that `T` matches the type from the
              - // annotation.
              - if (!targetType.isNativeFunction) {
              - _errorReporter.reportErrorForNode(
              - FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
              - node,
              - [targetType, _nativeAddressOf],
              - );
              + if (nativeType is FunctionType) {
              + // When referencing a function, the target type must be a
              + // `NativeFunction<T>` so that `T` matches the type from the
              + // annotation.
              + if (!targetType.isNativeFunction) {
              + _errorReporter.reportErrorForNode(
              + FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
              + node,
              + [targetType, _nativeAddressOf],
              + );
              + } else {
              + var targetFunctionType =
              + (targetType as InterfaceType).typeArguments[0];
              + if (!typeSystem.isAssignableTo(nativeType, targetFunctionType)) {
              + _errorReporter.reportErrorForNode(
              + FfiCode.MUST_BE_A_SUBTYPE,
              + node,
              + [nativeType, targetFunctionType, _nativeAddressOf],
              + );
              + }
              + }
              } else {
              - var targetFunctionType =
              - (targetType as InterfaceType).typeArguments[0];
              - if (!typeSystem.isAssignableTo(functionType, targetFunctionType)) {
              + // A native field is being referenced, this doesn't require a
              + // NativeFunction wrapper. However, we can't read the native type
              + // from the annotation directly because it might be inferred if none
              + // was given.
              + if (nativeType is DynamicType) {
              + final staticType = argument.staticType;
              + if (staticType != null) {
              + final canonical = _canonicalFfiTypeForDartType(staticType);
              +
              + if (canonical != null) {
              + nativeType = canonical;
              + }
              + }
              + }
              +
              + if (!typeSystem.isAssignableTo(nativeType, targetType)) {
              _errorReporter.reportErrorForNode(
              FfiCode.MUST_BE_A_SUBTYPE,
              node,
              - [functionType, targetFunctionType, _nativeAddressOf],
              + [nativeType, targetType, _nativeAddressOf],
              );
              }
              }
              diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
              index cf39e55..7b15eea 100644
              --- a/pkg/analyzer/messages.yaml
              +++ b/pkg/analyzer/messages.yaml
              @@ -18762,8 +18762,164 @@
              ```
              ARGUMENT_MUST_BE_NATIVE:
              problemMessage: "Argument to 'Native.addressOf' must be annotated with @Native"
              - correctionMessage: "Try passing a static function annotated with '@Native'"
              + correctionMessage: "Try passing a static function or field annotated with '@Native'"
              comment: No parameters
              + NATIVE_FIELD_INVALID_TYPE:
              + problemMessage: "'{0}' is an unsupported type for native fields. Native fields only support pointers or numeric and compound types."
              + correctionMessage: "Try changing the type in the `@Native` annotation to a numeric FFI type, a pointer, or a compound class."
              + comment: |-
              + Parameters:
              + 0: The invalid type.
              + hasPublishedDocs: true
              + documentation: |-
              + #### Description
              +
              + The analyzer produces this diagnostic when an `@Native`-annotated field
              + has a type not supported for native fields.
              +
              + Array fields are unsupported because there currently is no size
              + annotation for native fields. It is possible to represent global array
              + variables as pointers though, as they have an identical representation in
              + memory.
              +
              + Handles are unsupported because there is no way to transparently load and
              + store Dart object into pointers.
              +
              + For more information about FFI, see [C interop using dart:ffi][ffi].
              +
              + #### Example
              +
              + The following code produces this diagnostic because the field `f` uses an
              + unsupported type, `Array`:
              +
              + ```dart
              + import 'dart:ffi';
              +
              + @Native()
              + Array<Int> f;
              + ```
              +
              + #### Common fixes
              +
              + For array fields, use a pointer instead:
              +
              + ```dart
              + import 'dart:ffi';
              +
              + @Native()
              + Pointer<Int> f;
              + ```
              + NATIVE_FIELD_MISSING_TYPE:
              + problemMessage: "The native type of this field could not be inferred and must be specified in the annotation."
              + correctionMessage: "Try adding a type parameter extending `NativeType` to the `@Native` annotation."
              + comment: "No parameters"
              + hasPublishedDocs: true
              + documentation: |-
              + #### Description
              +
              + The analyzer produces this diagnostic when an `@Native`-annotated field
              + requires a type hint on the annotation to infer the native type.
              +
              + Dart types like `int` and `double` have multiple possible native
              + representations. Since the native type needs to be known at compile time
              + to generate the correct load and stores when accessing the field, an
              + explicit type must be given.
              +
              + #### Example
              +
              + The following code produces this diagnostic because the field `f` has
              + the type `int` (for which multiple native representations exist), but no
              + explicit type parameter on the `Native` annotation:
              +
              + ```dart
              + import 'dart:ffi';
              +
              + @Native()
              + int f;
              + ```
              +
              + #### Common fixes
              +
              + To fix this diagnostic, find out the correct native representation from
              + the native declaration of the field. Then, add the corresponding type to
              + the annotation. For instance, if `f` was declared as an `uint8_t` in C,
              + the Dart field should be declared as:
              +
              + ```dart
              + import 'dart:ffi';
              +
              + @Native<Uint8>()
              + int f;
              + ```
              +
              + For more information about FFI, see [C interop using dart:ffi][ffi].
              + NATIVE_FIELD_NOT_STATIC:
              + problemMessage: "Native fields must be static."
              + correctionMessage: "Try adding the modifier 'static' to this field."
              + comment: "No parameters"
              + hasPublishedDocs: true
              + documentation: |-
              + #### Description
              +
              + The analyzer produces this diagnostic when an instance field in a class
              + has been annotated with `@Native`.
              + Native fields refer to global variables in C, C++ or other native
              + languages, whereas instance fields in Dart are specific to an instance of
              + that class. Hence, native fields must be static.
              +
              + For more information about FFI, see [C interop using dart:ffi][ffi].
              +
              + #### Example
              +
              + The following code produces this diagnostic because the field `f` in the
              + class `C` is `@Native`, but not `static`:
              +
              + ```dart
              + import 'dart:ffi';
              +
              + class C {
              + @Native<Int>()
              + external int f;
              + }
              + ```
              +
              + #### Common fixes
              +
              + Either make the field static:
              +
              + ```dart
              + import 'dart:ffi';
              +
              + class C {
              + @Native<Int>()
              + external static int f;
              + }
              + ```
              +
              + Or move it out of a class, in which case no explicit `static` modifier is
              + required:
              +
              + ```dart
              + import 'dart:ffi';
              +
              + class C {
              + }
              +
              + @Native<Int>()
              + external int f;
              + ```
              +
              + If you meant to annotate an instance field that should be part of a
              + struct, omit the `@Native` annotation:
              +
              + ```dart
              + import 'dart:ffi';
              +
              + class C extends Struct {
              + @Int()
              + external int f;
              + }
              + ```
              COMPOUND_IMPLEMENTS_FINALIZABLE:
              problemMessage: "The class '{0}' can't implement Finalizable."
              correctionMessage: "Try removing the implements clause from '{0}'."
              @@ -19019,7 +19175,7 @@
              correctionMessage: "Try removing the extra annotation."
              comment: No parameters
              FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS:
              - problemMessage: "Native functions must have exactly one `@Native` annotation."
              + problemMessage: "Native functions and fields must have exactly one `@Native` annotation."
              correctionMessage: "Try removing the extra annotation."
              comment: No parameters
              FIELD_INITIALIZER_IN_STRUCT:
              diff --git a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
              index 28ba4d8..526ff35 100644
              --- a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
              +++ b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
              @@ -13,6 +13,7 @@
              defineReflectiveTests(AddressOfTest);
              defineReflectiveTests(DefaultAssetTest);
              defineReflectiveTests(FfiNativeTest);
              + defineReflectiveTests(NativeFieldTest);
              defineReflectiveTests(NativeTest);
              });
              }
              @@ -29,6 +30,19 @@
              ]);
              }

              + test_invalid_MismatchedInferredType() async {
              + await assertErrorsInCode(r'''
              +import 'dart:ffi';
              +
              +@Native()
              +external Pointer<IntPtr> global;
              +
              +void main() => print(Native.addressOf<Pointer<Double>>(global));
              +''', [
              + error(FfiCode.MUST_BE_A_SUBTYPE, 85, 41),
              + ]);
              + }
              +
              test_invalid_MismatchingType() async {
              await assertErrorsInCode(r'''
              import 'dart:ffi';
              @@ -93,8 +107,12 @@
              @Native<Void Function()>()
              external void foo();

              +@Native()
              +external Pointer<IntPtr> global;
              +
              void main() {
              print(Native.addressOf<NativeFunction<Void Function()>>(foo));
              + print(Native.addressOf<Pointer<IntPtr>>(global));
              }
              ''');
              }
              @@ -324,7 +342,142 @@
              }

              @reflectiveTest
              +class NativeFieldTest extends PubPackageResolutionTest {
              + test_Accessors() async {
              + await assertNoErrorsInCode(r'''
              +import 'dart:ffi';
              +
              +@Native<IntPtr>()
              +external int get foo;
              +
              +@Native<IntPtr>()
              +external set foo(int value);
              +''');
              + }
              +
              + test_Infer() async {
              + await assertNoErrorsInCode(r'''
              +import 'dart:ffi';
              +
              +final class MyStruct extends Struct {
              + external Pointer<MyStruct> next;
              +}
              +
              +@Native()
              +external MyStruct first;
              +
              +@Native()
              +external Pointer<MyStruct> last;
              +''');
              + }
              +
              + test_InvalidFunctionType() async {
              + await assertErrorsInCode(r'''
              +import 'dart:ffi';
              +@Native<IntPtr Function(IntPtr)>()
              +external int field;
              +''', [
              + error(FfiCode.NATIVE_FIELD_INVALID_TYPE, 67, 5),
              + ]);
              + }
              +
              + test_InvalidInstanceMember() async {
              + await assertErrorsInCode(r'''
              +import 'dart:ffi';
              +
              +class Foo {
              + @Native<IntPtr>()
              + external int field;
              +}
              +''', [
              + error(FfiCode.NATIVE_FIELD_NOT_STATIC, 67, 5),
              + ]);
              + }
              +
              + test_InvalidNotExternal() async {
              + await assertErrorsInCode(r'''
              +import 'dart:ffi';
              +
              +@Native<IntPtr>()
              +int field;
              +''', [
              + error(CompileTimeErrorCode.NOT_INITIALIZED_NON_NULLABLE_VARIABLE, 42, 5),
              + error(FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL, 42, 5),
              + ]);
              + }
              +
              + test_MismatchingType() async {
              + await assertErrorsInCode(r'''
              +import 'dart:ffi';
              +
              +@Native<Double>()
              +external int field;
              +''', [
              + error(FfiCode.MUST_BE_A_SUBTYPE, 51, 5),
              + ]);
              + }
              +
              + test_MissingType() async {
              + await assertErrorsInCode(r'''
              +import 'dart:ffi';
              +
              +@Native()
              +external int invalid;
              +
              +@Native()
              +external Pointer<IntPtr> valid;
              +''', [
              + error(FfiCode.NATIVE_FIELD_MISSING_TYPE, 43, 7),
              + ]);
              + }
              +
              + test_Unsupported_Array() async {
              + await assertErrorsInCode(r'''
              +import 'dart:ffi';
              +
              +@Native()
              +external Array<IntPtr> field;
              +''', [
              + error(FfiCode.NATIVE_FIELD_INVALID_TYPE, 53, 5),
              + ]);
              + }
              +
              + test_Unsupported_Function() async {
              + await assertErrorsInCode(r'''
              +import 'dart:ffi';
              +
              +@Native<NativeFunction<Void Function()>>()
              +external void Function() field;
              +''', [
              + error(FfiCode.MUST_BE_A_SUBTYPE, 88, 5),
              + ]);
              + }
              +
              + test_Unsupported_Handle() async {
              + await assertErrorsInCode(r'''
              +import 'dart:ffi';
              +
              +@Native<Handle>()
              +external Object field;
              +''', [
              + error(FfiCode.NATIVE_FIELD_INVALID_TYPE, 54, 5),
              + ]);
              + }
              +}
              +
              +@reflectiveTest
              class NativeTest extends PubPackageResolutionTest {
              + test_annotation_InvalidFieldType() async {
              + await assertErrorsInCode(r'''
              +import 'dart:ffi';
              +
              +@Native<IntPtr>()
              +external int foo();
              +''', [
              + error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 20, 37),
              + ]);
              + }
              +
              test_annotation_MissingType() async {
              await assertErrorsInCode(r'''
              import 'dart:ffi';
              diff --git a/pkg/front_end/lib/src/api_unstable/vm.dart b/pkg/front_end/lib/src/api_unstable/vm.dart
              index c537937..91c0d86 100644
              --- a/pkg/front_end/lib/src/api_unstable/vm.dart
              +++ b/pkg/front_end/lib/src/api_unstable/vm.dart
              @@ -70,6 +70,9 @@
              messageFfiLeafCallMustNotReturnHandle,
              messageFfiLeafCallMustNotTakeHandle,
              messageFfiNativeDuplicateAnnotations,
              + messageFfiNativeFieldMissingType,
              + messageFfiNativeFieldMustBeStatic,
              + messageFfiNativeFieldType,
              messageFfiNativeMustBeExternal,
              messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
              messageFfiNonLeafCallMustNotTakeTypedData,
              diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
              index d008481..4162c3d 100644
              --- a/pkg/front_end/messages.yaml
              +++ b/pkg/front_end/messages.yaml
              @@ -5239,15 +5239,33 @@

              FfiNativeMustBeExternal:
              # Used by dart:ffi
              - problemMessage: "Native functions must be marked external."
              + problemMessage: "Native functions and fields must be marked external."
              external: test/ffi_test.dart

              FfiNativeDuplicateAnnotations:
              # Used by dart:ffi
              - problemMessage: "Native functions must not have more than @Native annotation."
              + problemMessage: "Native functions and fields must not have more than @Native annotation."
              external: test/ffi_test.dart
              analyzerCode: FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS

              +FfiNativeFieldMustBeStatic:
              + # Used by dart:ffi
              + problemMessage: "Native fields must be static."
              + analyzerCode: NATIVE_FIELD_NOT_STATIC
              + external: test/ffi_test.dart
              +
              +FfiNativeFieldMissingType:
              + # Used by dart:ffi
              + problemMessage: "The native type of this field could not be inferred and must be specified in the annotation."
              + analyzerCode: NATIVE_FIELD_MISSING_TYPE
              + external: test/ffi_test.dart
              +
              +FfiNativeFieldType:
              + # Used by dart:ffi
              + problemMessage: "Unsupported type for native fields. Native fields only support pointers, compounds and numeric types."
              + analyzerCode: NATIVE_FIELD_INVALID_TYPE
              + external: test/ffi_test.dart
              +
              FfiAddressOfMustBeNative:
              # Used by dart:ffi
              problemMessage: "Argument to 'Native.addressOf' must be annotated with @Native."
              diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt
              index 516baa4..436df49 100644
              --- a/pkg/front_end/test/spell_checking_list_messages.txt
              +++ b/pkg/front_end/test/spell_checking_list_messages.txt
              @@ -29,6 +29,7 @@
              collide
              compilercontext.runincontext
              compilesdk
              +compounds
              conformance
              constructor(s)
              core
              diff --git a/pkg/front_end/testcases/general/ffi_sample.dart.strong.transformed.expect b/pkg/front_end/testcases/general/ffi_sample.dart.strong.transformed.expect
              index 990e1dc..7940a64 100644
              --- a/pkg/front_end/testcases/general/ffi_sample.dart.strong.transformed.expect
              +++ b/pkg/front_end/testcases/general/ffi_sample.dart.strong.transformed.expect
              @@ -47,9 +47,9 @@
              set y(synthesized core::double* #v) → void
              return ffi::_storeDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C12.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #v);
              get next() → ffi::Pointer<self::Coordinate*>*
              - return ffi::_fromAddress<self::Coordinate*>(ffi::_loadAbiSpecificInt<ffi::IntPtr>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}));
              + return ffi::_loadPointer<self::Coordinate*>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
              set next(synthesized ffi::Pointer<self::Coordinate*>* #v) → void
              - return ffi::_storeAbiSpecificInt<ffi::IntPtr>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #v.{ffi::Pointer::address}{core::int});
              + return ffi::_storePointer<self::Coordinate*>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #v);
              @#C16
              static get /*isNonNullableByDefault*/ #sizeOf() → core::int*
              return #C19.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
              diff --git a/pkg/front_end/testcases/general/ffi_sample.dart.weak.transformed.expect b/pkg/front_end/testcases/general/ffi_sample.dart.weak.transformed.expect
              index c0da0c1..cd1ee8e 100644
              --- a/pkg/front_end/testcases/general/ffi_sample.dart.weak.transformed.expect
              +++ b/pkg/front_end/testcases/general/ffi_sample.dart.weak.transformed.expect
              @@ -40,9 +40,9 @@
              set y(synthesized core::double* #v) → void
              return ffi::_storeDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C12.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #v);
              get next() → ffi::Pointer<self::Coordinate*>*
              - return ffi::_fromAddress<self::Coordinate*>(ffi::_loadAbiSpecificInt<ffi::IntPtr>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}));
              + return ffi::_loadPointer<self::Coordinate*>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
              set next(synthesized ffi::Pointer<self::Coordinate*>* #v) → void
              - return ffi::_storeAbiSpecificInt<ffi::IntPtr>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #v.{ffi::Pointer::address}{core::int});
              + return ffi::_storePointer<self::Coordinate*>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #v);
              @#C16
              static get /*isNonNullableByDefault*/ #sizeOf() → core::int*
              return #C19.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
              diff --git a/pkg/front_end/testcases/incremental/ffi_01.yaml.world.1.expect b/pkg/front_end/testcases/incremental/ffi_01.yaml.world.1.expect
              index 288c762..c4a7b52 100644
              --- a/pkg/front_end/testcases/incremental/ffi_01.yaml.world.1.expect
              +++ b/pkg/front_end/testcases/incremental/ffi_01.yaml.world.1.expect
              @@ -21,9 +21,9 @@
              set y(synthesized dart.core::double #externalFieldValue) → void
              return dart.ffi::_storeDouble(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get next() → dart.ffi::Pointer<lib::Coordinate>
              - return dart.ffi::_fromAddress<lib::Coordinate>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set next(synthesized dart.ffi::Pointer<lib::Coordinate> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              static factory allocate(dart.core::double x, dart.core::double y, dart.ffi::Pointer<lib::Coordinate>? next) → lib::Coordinate {
              throw "";
              }
              diff --git a/pkg/front_end/testcases/incremental/ffi_01.yaml.world.2.expect b/pkg/front_end/testcases/incremental/ffi_01.yaml.world.2.expect
              index d2ca547..6174d54 100644
              --- a/pkg/front_end/testcases/incremental/ffi_01.yaml.world.2.expect
              +++ b/pkg/front_end/testcases/incremental/ffi_01.yaml.world.2.expect
              @@ -21,9 +21,9 @@
              set y(synthesized dart.core::double #externalFieldValue) → void
              return dart.ffi::_storeDouble(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get next() → dart.ffi::Pointer<lib::Coordinate>
              - return dart.ffi::_fromAddress<lib::Coordinate>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set next(synthesized dart.ffi::Pointer<lib::Coordinate> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              static factory allocate(dart.core::double x, dart.core::double y, dart.ffi::Pointer<lib::Coordinate>? next) → lib::Coordinate {
              throw "";
              }
              diff --git a/pkg/front_end/testcases/incremental/ffi_02.yaml.world.1.expect b/pkg/front_end/testcases/incremental/ffi_02.yaml.world.1.expect
              index a69a0c0..c7839a5 100644
              --- a/pkg/front_end/testcases/incremental/ffi_02.yaml.world.1.expect
              +++ b/pkg/front_end/testcases/incremental/ffi_02.yaml.world.1.expect
              @@ -21,9 +21,9 @@
              set y(synthesized dart.core::double #externalFieldValue) → void
              return dart.ffi::_storeDouble(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get next() → dart.ffi::Pointer<lib::Coordinate>
              - return dart.ffi::_fromAddress<lib::Coordinate>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set next(synthesized dart.ffi::Pointer<lib::Coordinate> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              static factory allocate(dart.core::double x, dart.core::double y, dart.ffi::Pointer<lib::Coordinate>? next) → lib::Coordinate {
              throw "";
              }
              diff --git a/pkg/front_end/testcases/incremental/issue_46666.yaml.world.1.expect b/pkg/front_end/testcases/incremental/issue_46666.yaml.world.1.expect
              index 2df0acb..e4c9c11 100644
              --- a/pkg/front_end/testcases/incremental/issue_46666.yaml.world.1.expect
              +++ b/pkg/front_end/testcases/incremental/issue_46666.yaml.world.1.expect
              @@ -12,17 +12,17 @@
              : super dart.ffi::Struct::_fromTypedDataBase(#typedDataBase)
              ;
              get a1() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set a1(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get a2() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set a2(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get a3() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set a3(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get blah() → a::NestedStruct
              return new a::NestedStruct::#fromTypedDataBase( block {
              synthesized dart.core::Object #typedDataBase = this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object};
              @@ -43,17 +43,17 @@
              : super dart.ffi::Struct::_fromTypedDataBase(#typedDataBase)
              ;
              get n1() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set n1(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get n2() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set n2(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get n3() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set n3(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              @#C19
              static get #sizeOf() → dart.core::int*
              return #C17.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*};
              diff --git a/pkg/front_end/testcases/incremental/issue_46666.yaml.world.2.expect b/pkg/front_end/testcases/incremental/issue_46666.yaml.world.2.expect
              index 2df0acb..e4c9c11 100644
              --- a/pkg/front_end/testcases/incremental/issue_46666.yaml.world.2.expect
              +++ b/pkg/front_end/testcases/incremental/issue_46666.yaml.world.2.expect
              @@ -12,17 +12,17 @@
              : super dart.ffi::Struct::_fromTypedDataBase(#typedDataBase)
              ;
              get a1() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set a1(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get a2() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set a2(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get a3() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set a3(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get blah() → a::NestedStruct
              return new a::NestedStruct::#fromTypedDataBase( block {
              synthesized dart.core::Object #typedDataBase = this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object};
              @@ -43,17 +43,17 @@
              : super dart.ffi::Struct::_fromTypedDataBase(#typedDataBase)
              ;
              get n1() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set n1(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C9.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get n2() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set n2(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get n3() → dart.ffi::Pointer<dart.ffi::Void>
              - return dart.ffi::_fromAddress<dart.ffi::Void>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set n3(synthesized dart.ffi::Pointer<dart.ffi::Void> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::Void>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              @#C19
              static get #sizeOf() → dart.core::int*
              return #C17.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*};
              diff --git a/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.1.expect b/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.1.expect
              index 288c762..c4a7b52 100644
              --- a/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.1.expect
              +++ b/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.1.expect
              @@ -21,9 +21,9 @@
              set y(synthesized dart.core::double #externalFieldValue) → void
              return dart.ffi::_storeDouble(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get next() → dart.ffi::Pointer<lib::Coordinate>
              - return dart.ffi::_fromAddress<lib::Coordinate>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set next(synthesized dart.ffi::Pointer<lib::Coordinate> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              static factory allocate(dart.core::double x, dart.core::double y, dart.ffi::Pointer<lib::Coordinate>? next) → lib::Coordinate {
              throw "";
              }
              diff --git a/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.2.expect b/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.2.expect
              index 1d6b172..d8b6786 100644
              --- a/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.2.expect
              +++ b/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.2.expect
              @@ -21,9 +21,9 @@
              set y(synthesized dart.core::double #externalFieldValue) → void
              return dart.ffi::_storeDouble(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get next() → dart.ffi::Pointer<lib::Coordinate>
              - return dart.ffi::_fromAddress<lib::Coordinate>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set next(synthesized dart.ffi::Pointer<lib::Coordinate> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              static factory allocate(dart.core::double x, dart.core::double y, dart.ffi::Pointer<lib::Coordinate>? next) → lib::Coordinate {
              throw "";
              }
              diff --git a/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.3.expect b/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.3.expect
              index e1b30db..fdfac41 100644
              --- a/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.3.expect
              +++ b/pkg/front_end/testcases/incremental/no_outline_change_35.yaml.world.3.expect
              @@ -21,9 +21,9 @@
              set y(synthesized dart.core::double #externalFieldValue) → void
              return dart.ffi::_storeDouble(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C12.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get next() → dart.ffi::Pointer<lib::Coordinate>
              - return dart.ffi::_fromAddress<lib::Coordinate>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set next(synthesized dart.ffi::Pointer<lib::Coordinate> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<lib::Coordinate>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C14.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              static factory allocate(dart.core::double x, dart.core::double y, dart.ffi::Pointer<lib::Coordinate>? next) → lib::Coordinate {
              dart.core::print("hello");
              throw "";
              diff --git a/pkg/front_end/testcases/incremental/regress_46004.yaml.world.1.expect b/pkg/front_end/testcases/incremental/regress_46004.yaml.world.1.expect
              index 70c5a9e..2a44008 100644
              --- a/pkg/front_end/testcases/incremental/regress_46004.yaml.world.1.expect
              +++ b/pkg/front_end/testcases/incremental/regress_46004.yaml.world.1.expect
              @@ -12,9 +12,9 @@
              : super dart.ffi::Struct::_fromTypedDataBase(#typedDataBase)
              ;
              get lpVtbl() → dart.ffi::Pointer<dart.ffi::IntPtr>
              - return dart.ffi::_fromAddress<dart.ffi::IntPtr>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C8.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C8.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set lpVtbl(synthesized dart.ffi::Pointer<dart.ffi::IntPtr> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C8.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C8.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get vtable() → dart.ffi::Pointer<dart.ffi::IntPtr>
              return dart.ffi::Pointer::fromAddress<dart.ffi::IntPtr>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{lib::COMObject::lpVtbl}{dart.ffi::Pointer<dart.ffi::IntPtr>}, #C7));
              @#C10
              diff --git a/pkg/front_end/testcases/incremental/regress_46004.yaml.world.2.expect b/pkg/front_end/testcases/incremental/regress_46004.yaml.world.2.expect
              index 70c5a9e..2a44008 100644
              --- a/pkg/front_end/testcases/incremental/regress_46004.yaml.world.2.expect
              +++ b/pkg/front_end/testcases/incremental/regress_46004.yaml.world.2.expect
              @@ -12,9 +12,9 @@
              : super dart.ffi::Struct::_fromTypedDataBase(#typedDataBase)
              ;
              get lpVtbl() → dart.ffi::Pointer<dart.ffi::IntPtr>
              - return dart.ffi::_fromAddress<dart.ffi::IntPtr>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C8.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}));
              + return dart.ffi::_loadPointer<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C8.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*});
              set lpVtbl(synthesized dart.ffi::Pointer<dart.ffi::IntPtr> #externalFieldValue) → void
              - return dart.ffi::_storeAbiSpecificInt<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C8.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue.{dart.ffi::Pointer::address}{dart.core::int});
              + return dart.ffi::_storePointer<dart.ffi::IntPtr>(this.{dart.ffi::_Compound::_typedDataBase}{dart.core::Object}, #C8.{dart.core::List::[]}(dart.ffi::_abi()){(dart.core::int) → dart.core::int*}, #externalFieldValue);
              get vtable() → dart.ffi::Pointer<dart.ffi::IntPtr>
              return dart.ffi::Pointer::fromAddress<dart.ffi::IntPtr>(dart.ffi::_loadAbiSpecificInt<dart.ffi::IntPtr>(this.{lib::COMObject::lpVtbl}{dart.ffi::Pointer<dart.ffi::IntPtr>}, #C7));
              @#C10
              diff --git a/pkg/front_end/testcases/nnbd/ffi_sample.dart.strong.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_sample.dart.strong.transformed.expect
              index b16646d..a3bbc05 100644
              --- a/pkg/front_end/testcases/nnbd/ffi_sample.dart.strong.transformed.expect
              +++ b/pkg/front_end/testcases/nnbd/ffi_sample.dart.strong.transformed.expect
              @@ -24,9 +24,9 @@
              set y(synthesized core::double #externalFieldValue) → void
              return ffi::_storeDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C12.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
              get next() → ffi::Pointer<self::Coordinate>
              - return ffi::_fromAddress<self::Coordinate>(ffi::_loadAbiSpecificInt<ffi::IntPtr>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}));
              + return ffi::_loadPointer<self::Coordinate>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
              set next(synthesized ffi::Pointer<self::Coordinate> #externalFieldValue) → void
              - return ffi::_storeAbiSpecificInt<ffi::IntPtr>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue.{ffi::Pointer::address}{core::int});
              + return ffi::_storePointer<self::Coordinate>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
              static factory allocate(ffi::Allocator allocator, core::double x, core::double y, ffi::Pointer<self::Coordinate> next) → self::Coordinate {
              return let final self::Coordinate #t1 = new self::Coordinate::#fromTypedDataBase(allocator.{ffi::Allocator::allocate}<self::Coordinate>(self::Coordinate::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer<self::Coordinate>}!) in block {
              #t1.{self::Coordinate::x} = x;
              diff --git a/pkg/front_end/testcases/nnbd/ffi_sample.dart.weak.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_sample.dart.weak.transformed.expect
              index b16646d..a3bbc05 100644
              --- a/pkg/front_end/testcases/nnbd/ffi_sample.dart.weak.transformed.expect
              +++ b/pkg/front_end/testcases/nnbd/ffi_sample.dart.weak.transformed.expect
              @@ -24,9 +24,9 @@
              set y(synthesized core::double #externalFieldValue) → void
              return ffi::_storeDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C12.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
              get next() → ffi::Pointer<self::Coordinate>
              - return ffi::_fromAddress<self::Coordinate>(ffi::_loadAbiSpecificInt<ffi::IntPtr>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}));
              + return ffi::_loadPointer<self::Coordinate>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
              set next(synthesized ffi::Pointer<self::Coordinate> #externalFieldValue) → void
              - return ffi::_storeAbiSpecificInt<ffi::IntPtr>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue.{ffi::Pointer::address}{core::int});
              + return ffi::_storePointer<self::Coordinate>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
              static factory allocate(ffi::Allocator allocator, core::double x, core::double y, ffi::Pointer<self::Coordinate> next) → self::Coordinate {
              return let final self::Coordinate #t1 = new self::Coordinate::#fromTypedDataBase(allocator.{ffi::Allocator::allocate}<self::Coordinate>(self::Coordinate::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer<self::Coordinate>}!) in block {
              #t1.{self::Coordinate::x} = x;
              diff --git a/pkg/vm/lib/transformations/ffi/common.dart b/pkg/vm/lib/transformations/ffi/common.dart
              index 436fb58..a41d7a0 100644
              --- a/pkg/vm/lib/transformations/ffi/common.dart
              +++ b/pkg/vm/lib/transformations/ffi/common.dart
              @@ -642,6 +642,10 @@
              /// If [dartType] is provided, it can be used to guide what Dart type is
              /// expected. Currently, this is only used if [allowTypedData] is provided,
              /// because `Pointer` is a one to many mapping in this case.
              + ///
              + /// Some Dart types only have one possible native type (and vice-versa). For
              + /// those types, [convertNativeTypeToDartType] is the inverse of
              + /// [convertDartTypeToNativeType].
              DartType? convertNativeTypeToDartType(
              DartType nativeType, {
              DartType? dartType,
              @@ -761,6 +765,25 @@
              return FunctionType(argumentTypes, returnType, Nullability.legacy);
              }

              + /// Finds a native type for the given [dartType] if there is only one possible
              + /// native type.
              + ///
              + /// This is impossible for some types (like [int] which needs a specific ffi
              + /// type to denote the width in C). This method returns `null` for those
              + /// types.
              + ///
              + /// For types where this returns a non-null value, this is the inverse of
              + /// [convertNativeTypeToDartType].
              + DartType? convertDartTypeToNativeType(DartType dartType) {
              + if (isPointerType(dartType) ||
              + isCompoundSubtype(dartType) ||
              + isArrayType(dartType)) {
              + return dartType;
              + } else {
              + return null;
              + }
              + }
              +
              /// Removes the VarArgs from a DartType list.
              ///
              /// ```
              @@ -933,6 +956,14 @@
              /// ```
              Expression typedDataBaseOffset(Expression typedDataBase, Expression offset,
              Expression length, DartType dartType, int fileOffset) {
              + // Avoid generating the branch on the kind of typed data and the offset
              + // calculation if the end result is a no-op. This offset-generating method
              + // is used to load compound subtypes, which in many cases are not using any
              + // offset from their base.
              + if (offset case ConstantExpression(constant: IntConstant(value: 0))) {
              + return typedDataBase;
              + }
              +
              final typedDataBaseVar = VariableDeclaration("#typedDataBase",
              initializer: typedDataBase,
              type: coreTypes.objectNonNullableRawType,
              @@ -1129,6 +1160,27 @@
              return null;
              }

              + Expression? inlineSizeOf(InterfaceType nativeType) {
              + final Class nativeClass = nativeType.classNode;
              + final NativeType? nt = getType(nativeClass);
              + if (nt == null) {
              + // User-defined compounds.
              + final Procedure sizeOfGetter = nativeClass.procedures
              + .firstWhere((function) => function.name == Name('#sizeOf'));
              + return StaticGet(sizeOfGetter);
              + }
              + final int size = nativeTypeSizes[nt]!;
              + if (size == WORD_SIZE) {
              + return runtimeBranchOnLayout(wordSize);
              + }
              + if (size != UNKNOWN) {
              + return ConstantExpression(IntConstant(size),
              + InterfaceType(intClass, currentLibrary.nonNullable));
              + }
              + // Size unknown.
              + return null;
              + }
              +
              /// Generates an expression performing an Abi specific integer load or store.
              ///
              /// If [value] is provided, it is a store, otherwise a load.
              @@ -1400,12 +1452,14 @@
              bool allowHandle = false,
              bool allowVoid = false,
              bool allowTypedData = false,
              + bool allowArray = false,
              }) {
              final DartType correspondingDartType = convertNativeTypeToDartType(
              nativeType,
              dartType: dartType,
              allowCompounds: true,
              allowHandle: allowHandle,
              + allowInlineArray: allowArray,
              allowVoid: allowVoid,
              allowTypedData: allowTypedData,
              )!;
              diff --git a/pkg/vm/lib/transformations/ffi/definitions.dart b/pkg/vm/lib/transformations/ffi/definitions.dart
              index 3627f53..0a3d241 100644
              --- a/pkg/vm/lib/transformations/ffi/definitions.dart
              +++ b/pkg/vm/lib/transformations/ffi/definitions.dart
              @@ -429,9 +429,9 @@
              f.fileUri);
              // This class is invalid, but continue reporting other errors on it.
              success = false;
              - } else if (isPointerType(type) ||
              - isCompoundSubtype(type) ||
              - isArrayType(type)) {
              + } else if (convertDartTypeToNativeType(type) != null) {
              + // If the native type is obvious from the Dart type alone, don't allow
              + // a native type annotation.
              if (nativeTypeAnnos.isNotEmpty) {
              diagnosticReporter.report(
              templateFfiFieldNoAnnotation.withArguments(f.name.text),
              diff --git a/pkg/vm/lib/transformations/ffi/native.dart b/pkg/vm/lib/transformations/ffi/native.dart
              index 5b3aa09..06b7e85 100644
              --- a/pkg/vm/lib/transformations/ffi/native.dart
              +++ b/pkg/vm/lib/transformations/ffi/native.dart
              @@ -6,12 +6,16 @@
              show
              messageFfiDefaultAssetDuplicate,
              messageFfiNativeDuplicateAnnotations,
              + messageFfiNativeFieldMissingType,
              + messageFfiNativeFieldMustBeStatic,
              + messageFfiNativeFieldType,
              messageFfiNativeMustBeExternal,
              messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
              templateCantHaveNamedParameters,
              templateCantHaveOptionalParameters,
              templateFfiNativeUnexpectedNumberOfParameters,
              - templateFfiNativeUnexpectedNumberOfParametersWithReceiver;
              + templateFfiNativeUnexpectedNumberOfParametersWithReceiver,
              + templateFfiTypeInvalid;

              import 'package:kernel/ast.dart';
              import 'package:kernel/core_types.dart';
              @@ -21,7 +25,8 @@
              import 'package:kernel/target/targets.dart' show DiagnosticReporter;
              import 'package:kernel/type_environment.dart';

              -import 'common.dart' show FfiStaticTypeError, FfiTransformer;
              +import 'common.dart' show FfiStaticTypeError, FfiTransformer, NativeType;
              +import 'native_type_cfe.dart';

              /// Transform @Native annotated functions into FFI native function pointer
              /// functions.
              @@ -163,6 +168,26 @@
              return false;
              }

              + StringConstant _resolveNativeSymbolName(
              + Member member, InstanceConstant native) {
              + final nativeFunctionConst =
              + native.fieldValues[nativeSymbolField.fieldReference];
              + return nativeFunctionConst is StringConstant
              + ? nativeFunctionConst
              + : StringConstant(member.name.text);
              + }
              +
              + StringConstant? _assetNameFromAnnotation(InstanceConstant native) {
              + final assetConstant = native.fieldValues[nativeAssetField.fieldReference];
              + return assetConstant is StringConstant ? assetConstant : null;
              + }
              +
              + bool _isLeaf(InstanceConstant native) {
              + return (native.fieldValues[nativeIsLeafField.fieldReference]
              + as BoolConstant)
              + .value;
              + }
              +
              // Replaces parameters with Pointer if:
              // 1) they extend NativeFieldWrapperClass1, and
              // 2) the corresponding FFI parameter is Pointer.
              @@ -444,7 +469,7 @@
              Procedure _transformProcedure(
              Procedure node,
              StringConstant nativeFunctionName,
              - StringConstant? assetName,
              + StringConstant? overriddenAssetName,
              bool isLeaf,
              int annotationOffset,
              FunctionType dartFunctionType,
              @@ -462,15 +487,11 @@
              return node;
              }

              - final resolvedNative = InstanceConstant(
              - nativeClass.reference,
              - [ffiFunctionType],
              - {
              - nativeSymbolField.fieldReference: nativeFunctionName,
              - nativeAssetField.fieldReference:
              - assetName ?? StringConstant(currentLibrary.importUri.toString()),
              - nativeIsLeafField.fieldReference: BoolConstant(isLeaf),
              - },
              + final resolvedNative = _generateResolvedNativeConstant(
              + nativeType: ffiFunctionType,
              + nativeName: nativeFunctionName,
              + isLeaf: isLeaf,
              + overriddenAssetName: overriddenAssetName,
              );
              final pragmaConstant = ConstantExpression(
              InstanceConstant(pragmaClass.reference, [], {
              @@ -584,6 +605,29 @@
              return node;
              }

              + /// Creates a `Native` constant with all fields resolved.
              + ///
              + /// Re-using the constant from the original `@Native` annotation doesn't work
              + /// because the asset field may be inferred from the library.
              + InstanceConstant _generateResolvedNativeConstant({
              + required DartType nativeType,
              + required StringConstant nativeName,
              + required bool isLeaf,
              + StringConstant? overriddenAssetName,
              + }) {
              + return InstanceConstant(
              + nativeClass.reference,
              + [nativeType],
              + {
              + nativeSymbolField.fieldReference: nativeName,
              + nativeAssetField.fieldReference: overriddenAssetName ??
              + currentAsset ??
              + StringConstant(currentLibrary.importUri.toString()),
              + nativeIsLeafField.fieldReference: BoolConstant(isLeaf),
              + },
              + );
              + }
              +
              // Transform Native instance methods.
              //
              // Example:
              @@ -665,7 +709,7 @@
              Procedure node,
              FunctionType ffiFunctionType,
              StringConstant nativeFunctionName,
              - StringConstant? assetName,
              + StringConstant? overriddenAssetName,
              bool isLeaf,
              int annotationOffset) {
              final dartFunctionType =
              @@ -679,7 +723,7 @@
              return _transformProcedure(
              node,
              nativeFunctionName,
              - assetName,
              + overriddenAssetName,
              isLeaf,
              annotationOffset,
              dartFunctionType,
              @@ -689,6 +733,75 @@
              );
              }

              + Expression _generateAddressOfField(
              + Member node, InterfaceType ffiType, InstanceConstant native) {
              + return StaticInvocation(
              + nativePrivateAddressOf,
              + Arguments([ConstantExpression(native)], types: [ffiType]),
              + )..fileOffset = node.fileOffset;
              + }
              +
              + DartType _validateOrInferNativeFieldType(
              + Member node, DartType ffiType, DartType dartType) {
              + if (ffiType is DynamicType) {
              + // If no type argument is given on the @Native annotation, try to infer
              + // it.
              + final inferred = convertDartTypeToNativeType(dartType);
              + if (inferred != null) {
              + ffiType = inferred;
              + } else {
              + diagnosticReporter.report(messageFfiNativeFieldMissingType,
              + node.fileOffset, 1, node.location?.file);
              + throw FfiStaticTypeError();
              + }
              + }
              +
              + ensureNativeTypeValid(
              + ffiType,
              + node,
              + allowCompounds: true,
              + // Handles and arrays are not currently supported, but checking them
              + // separately and allowing them here yields a more specific error message.
              + allowHandle: true,
              + allowInlineArray: true,
              + );
              + ensureNativeTypeToDartType(ffiType, dartType, node,
              + allowHandle: true, allowArray: true);
              + // Only allow compound, pointer and numeric types.
              + if (isCompoundSubtype(ffiType) || isAbiSpecificIntegerSubtype(ffiType)) {
              + return ffiType;
              + }
              + final type = switch (ffiType) {
              + InterfaceType(:var classNode) => getType(classNode),
              + _ => null,
              + };
              + if (type == null ||
              + type == NativeType.kNativeFunction ||
              + type == NativeType.kHandle ||
              + isArrayType(ffiType)) {
              + diagnosticReporter.report(
              + messageFfiNativeFieldType, node.fileOffset, 1, node.location?.file);
              + throw FfiStaticTypeError();
              + }
              +
              + return ffiType;
              + }
              +
              + @override
              + TreeNode visitField(Field node) {
              + final nativeAnnotation = tryGetNativeAnnotationOrWarnOnDuplicates(node);
              + if (nativeAnnotation == null) {
              + return node;
              + }
              + // @Native annotations on fields are valid if the field is external. But
              + // external fields are represented as a getter/setter pair in Kernel, so
              + // only visit fields to verify that no native annotation is present.
              + assert(!node.isExternal);
              + diagnosticReporter.report(messageFfiNativeMustBeExternal, node.fileOffset,
              + 1, node.location?.file);
              + return node;
              + }
              +
              @override
              visitProcedure(Procedure node) {
              // Only transform functions that are external and have Native annotation:
              @@ -708,37 +821,102 @@
              node.annotations.remove(ffiNativeAnnotation);

              final ffiConstant = ffiNativeAnnotation.constant as InstanceConstant;
              - final nativeType = ffiConstant.typeArguments[0];
              - try {
              - final nativeFunctionType = InterfaceType(
              - nativeFunctionClass, Nullability.nonNullable, [nativeType]);
              - ensureNativeTypeValid(nativeFunctionType, ffiNativeAnnotation,
              - allowCompounds: true, allowHandle: true);
              - } on FfiStaticTypeError {
              - // We've already reported an error.
              - return node;
              - }
              - final ffiFunctionType = ffiConstant.typeArguments[0] as FunctionType;
              - final nativeFunctionConst =
              - ffiConstant.fieldValues[nativeSymbolField.fieldReference];
              - final nativeFunctionName = nativeFunctionConst is StringConstant
              - ? nativeFunctionConst
              - : StringConstant(node.name.text);
              - final assetConstant =
              - ffiConstant.fieldValues[nativeAssetField.fieldReference];
              - final assetName =
              - assetConstant is StringConstant ? assetConstant : currentAsset;
              - final isLeaf = (ffiConstant.fieldValues[nativeIsLeafField.fieldReference]
              - as BoolConstant)
              - .value;
              + var nativeType = ffiConstant.typeArguments[0];

              - if (!node.isStatic) {
              - return _transformInstanceMethod(node, ffiFunctionType, nativeFunctionName,
              - assetName, isLeaf, ffiNativeAnnotation.fileOffset);
              + final nativeName = _resolveNativeSymbolName(node, ffiConstant);
              + final overriddenAssetName = _assetNameFromAnnotation(ffiConstant);
              + final isLeaf = _isLeaf(ffiConstant);
              +
              + if (nativeType is FunctionType) {
              + try {
              + final nativeFunctionType = InterfaceType(
              + nativeFunctionClass, Nullability.nonNullable, [nativeType]);
              + ensureNativeTypeValid(nativeFunctionType, ffiNativeAnnotation,
              + allowCompounds: true, allowHandle: true);
              + } on FfiStaticTypeError {
              + // We've already reported an error.
              + return node;
              + }
              + final ffiFunctionType = ffiConstant.typeArguments[0] as FunctionType;
              +
              + if (!node.isStatic) {
              + return _transformInstanceMethod(node, ffiFunctionType, nativeName,
              + overriddenAssetName, isLeaf, ffiNativeAnnotation.fileOffset);
              + }
              +
              + return _transformStaticFunction(node, ffiFunctionType, nativeName,
              + overriddenAssetName, isLeaf, ffiNativeAnnotation.fileOffset);
              + } else if (node.kind == ProcedureKind.Getter ||
              + node.kind == ProcedureKind.Setter) {
              + if (!node.isStatic) {
              + diagnosticReporter.report(messageFfiNativeFieldMustBeStatic,
              + node.fileOffset, 1, node.location?.file);
              + }
              +
              + DartType dartType;
              + try {
              + dartType = node.kind == ProcedureKind.Getter
              + ? node.function.returnType
              + : node.function.positionalParameters[0].type;
              + nativeType =
              + _validateOrInferNativeFieldType(node, nativeType, dartType);
              + } on FfiStaticTypeError {
              + return node;
              + }
              + final resolved = _generateResolvedNativeConstant(
              + nativeType: nativeType,
              + nativeName: nativeName,
              + isLeaf: isLeaf,
              + overriddenAssetName: overriddenAssetName,
              + );
              + node.isExternal = false;
              +
              + final nativeTypeCfe = NativeTypeCfe.withoutLayout(this, nativeType);
              + final zeroOffset = ConstantExpression(IntConstant(0));
              +
              + if (node.kind == ProcedureKind.Getter) {
              + node.function.body = ReturnStatement(nativeTypeCfe.generateLoad(
              + dartType: dartType,
              + fileOffset: node.fileOffset,
              + typedDataBase: _generateAddressOfField(
              + node, nativeType as InterfaceType, resolved),
              + transformer: this,
              + offsetInBytes: zeroOffset,
              + ));
              + node.annotations.add(ConstantExpression(
              + InstanceConstant(pragmaClass.reference, [], {
              + pragmaName.fieldReference: StringConstant(nativeMarker),
              + pragmaOptions.fieldReference: resolved,
              + }),
              + InterfaceType(
              + pragmaClass,
              + Nullability.nonNullable,
              + [],
              + ),
              + ));
              + } else {
              + node.function.body = ExpressionStatement(nativeTypeCfe.generateStore(
              + VariableGet(node.function.positionalParameters[0]),
              + dartType: dartType,
              + fileOffset: node.fileOffset,
              + typedDataBase: _generateAddressOfField(
              + node, nativeType as InterfaceType, resolved),
              + transformer: this,
              + offsetInBytes: zeroOffset,
              + ));
              + }
              + } else {
              + // This function is not annotated with a native function type, which is
              + // invalid.
              + diagnosticReporter.report(
              + templateFfiTypeInvalid.withArguments(
              + nativeType, currentLibrary.isNonNullableByDefault),
              + node.fileOffset,
              + 1,
              + node.location?.file);
              }

              - return _transformStaticFunction(node, ffiFunctionType, nativeFunctionName,
              - assetName, isLeaf, ffiNativeAnnotation.fileOffset);
              + return node;
              }

              /// Checks whether the FFI function type is valid and reports any errors.
              diff --git a/pkg/vm/lib/transformations/ffi/native_type_cfe.dart b/pkg/vm/lib/transformations/ffi/native_type_cfe.dart
              index a566b81..b75132f 100644
              --- a/pkg/vm/lib/transformations/ffi/native_type_cfe.dart
              +++ b/pkg/vm/lib/transformations/ffi/native_type_cfe.dart
              @@ -13,7 +13,21 @@
              ///
              /// This algebraic data structure does not stand on its own but refers
              /// intimately to AST nodes such as [Class].
              -abstract class NativeTypeCfe {
              +sealed class NativeTypeCfe {
              + NativeTypeCfe._();
              +
              + /// Constructs a [NativeTypeCfe] for transformers that can refer to types
              + /// without having to know their internal layout or size.
              + factory NativeTypeCfe.withoutLayout(
              + FfiTransformer transformer, DartType dartType) {
              + if (transformer.isCompoundSubtype(dartType)) {
              + return ReferencedCompoundSubtypeCfe(
              + (dartType as InterfaceType).classNode);
              + } else {
              + return NativeTypeCfe(transformer, dartType);
              + }
              + }
              +
              factory NativeTypeCfe(FfiTransformer transformer, DartType dartType,
              {List<int>? arrayDimensions,
              Map<Class, NativeTypeCfe> compoundCache = const {},
              @@ -107,11 +121,72 @@
              /// See runtime/vm/compiler/ffi/native_type.cc:NativeType::FromAbstractType.
              Constant generateConstant(FfiTransformer transformer);

              + /// Generates an expression evaluating to an instance of [dartType], which is
              + /// assumed to be a Dart type compatible to this native type, by loading this
              + /// instance from memory.
              + ///
              + /// [typedDataBase] is an expression evaluating to a `Pointer` or `TypedData`,
              + /// the type will be loaded from that buffer, starting at [offsetInBytes].
              + ///
              + /// For example, loading a `Pointer` from memory (via [PointerNativeTypeCfe])
              + /// would build an expression like `ffi._loadPointer<T>
              + /// (#typedDataBase, #offsetInBytes)`, where `Pointer<T> == dartType`.
              + ///
              + /// For struct fields, [generateGetterStatement] fills in values for
              + /// [offsetInBytes] and [unaligned] based on the ABI of the struct. It also
              + /// wraps the expression in a return statement.
              + Expression generateLoad({
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + });
              +
              + /// Generates an expression storing [value], which must evaluate to a
              + /// [dartType] compatible with this native type, in native memory.
              + ///
              + /// [typedDataBase] is an expression evaluating to a `Pointer` or `TypedData`,
              + /// the [value] will be stored in that buffer from [offsetInBytes].
              + ///
              + /// For example, storing a `Pointer` (via [PointerNativeTypeCfe]) would
              + /// generate a call to `ffi._storePointer`.
              + ///
              + /// For struct fields, [generateSetterStatement] fills in values for
              + /// [offsetInBytes] and [unaligned] based on the ABI of the struct. It also
              + /// wraps the expression in a return statement.
              + Expression generateStore(
              + Expression value, {
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + });
              +
              /// Generates the return statement for a compound field getter with this type.
              ///
              /// Takes [transformer] to be able to lookup classes and methods.
              - ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
              - Map<Abi, int?> offsets, bool unalignedAccess, FfiTransformer transformer);
              + ReturnStatement generateGetterStatement(
              + DartType dartType,
              + int fileOffset,
              + Map<Abi, int?> offsets,
              + bool unalignedAccess,
              + FfiTransformer transformer) {
              + return ReturnStatement(
              + generateLoad(
              + dartType: dartType,
              + fileOffset: fileOffset,
              + typedDataBase: transformer.getCompoundTypedDataBaseField(
              + ThisExpression(), fileOffset),
              + transformer: transformer,
              + unaligned: unalignedAccess,
              + offsetInBytes: transformer.runtimeBranchOnLayout(offsets),
              + ),
              + );
              + }

              /// Generates the return statement for a compound field setter with this type.
              ///
              @@ -122,13 +197,24 @@
              Map<Abi, int?> offsets,
              bool unalignedAccess,
              VariableDeclaration argument,
              - FfiTransformer transformer);
              + FfiTransformer transformer) {
              + return ReturnStatement(generateStore(
              + VariableGet(argument)..fileOffset = fileOffset,
              + dartType: dartType,
              + fileOffset: fileOffset,
              + typedDataBase: transformer.getCompoundTypedDataBaseField(
              + ThisExpression(), fileOffset),
              + transformer: transformer,
              + offsetInBytes: transformer.runtimeBranchOnLayout(offsets),
              + unaligned: unalignedAccess,
              + ));
              + }
              }

              -class InvalidNativeTypeCfe implements NativeTypeCfe {
              +final class InvalidNativeTypeCfe extends NativeTypeCfe {
              final String reason;

              - InvalidNativeTypeCfe(this.reason);
              + InvalidNativeTypeCfe(this.reason) : super._();

              @override
              Map<Abi, int?> get alignment => throw reason;
              @@ -140,23 +226,29 @@
              Constant generateConstant(FfiTransformer transformer) => throw reason;

              @override
              - ReturnStatement generateGetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - FfiTransformer transformer) =>
              - throw reason;
              + Expression generateLoad({
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + }) {
              + throw reason;
              + }

              @override
              - ReturnStatement generateSetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - VariableDeclaration argument,
              - FfiTransformer transformer) =>
              - throw reason;
              + Expression generateStore(
              + Expression value, {
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + }) {
              + throw reason;
              + }

              @override
              Map<Abi, int?> get size => throw reason;
              @@ -165,12 +257,12 @@
              int? getSizeFor(Abi abi) => throw reason;
              }

              -class PrimitiveNativeTypeCfe implements NativeTypeCfe {
              +class PrimitiveNativeTypeCfe extends NativeTypeCfe {
              final NativeType nativeType;

              final Class clazz;

              - PrimitiveNativeTypeCfe(this.nativeType, this.clazz);
              + PrimitiveNativeTypeCfe(this.nativeType, this.clazz) : super._();

              @override
              Map<Abi, int?> get size {
              @@ -219,56 +311,59 @@
              return false;
              }

              - /// Sample output for `int get x =>`:
              + /// Sample output for [nativeType] being [NativeType.kInt8]:
              ///
              /// ```
              - /// _loadInt8(_typedDataBase, offset);
              + /// _loadInt8(#typedDataBase, #offsetInBytes)
              /// ```
              @override
              - ReturnStatement generateGetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - FfiTransformer transformer) =>
              - ReturnStatement(StaticInvocation(
              - (unalignedAccess && isFloat
              - ? transformer.loadUnalignedMethods
              - : transformer.loadMethods)[nativeType]!,
              - Arguments([
              - transformer.getCompoundTypedDataBaseField(
              - ThisExpression(), fileOffset),
              - transformer.runtimeBranchOnLayout(offsets)
              - ]))
              - ..fileOffset = fileOffset);
              + Expression generateLoad({
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + }) {
              + return StaticInvocation(
              + (unaligned && isFloat
              + ? transformer.loadUnalignedMethods
              + : transformer.loadMethods)[nativeType]!,
              + Arguments([typedDataBase, offsetInBytes]))
              + ..fileOffset = fileOffset;
              + }

              - /// Sample output for `set x(int #v) =>`:
              + /// Sample output for [nativeType] being [NativeType.kInt8]:
              ///
              /// ```
              - /// _storeInt8(_typedDataBase, offset, #v);
              + /// _storeInt8(#typedDataBase, #offsetInBytes, #value)
              /// ```
              @override
              - ReturnStatement generateSetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - VariableDeclaration argument,
              - FfiTransformer transformer) =>
              - ReturnStatement(StaticInvocation(
              - (unalignedAccess && isFloat
              - ? transformer.storeUnalignedMethods
              - : transformer.storeMethods)[nativeType]!,
              - Arguments([
              - transformer.getCompoundTypedDataBaseField(
              - ThisExpression(), fileOffset),
              - transformer.runtimeBranchOnLayout(offsets),
              - VariableGet(argument)
              - ]))
              - ..fileOffset = fileOffset);
              + Expression generateStore(
              + Expression value, {
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + }) {
              + return StaticInvocation(
              + (unaligned && isFloat
              + ? transformer.storeUnalignedMethods
              + : transformer.storeMethods)[nativeType]!,
              + Arguments([
              + typedDataBase,
              + offsetInBytes,
              + value,
              + ]))
              + ..fileOffset = fileOffset;
              + }
              }

              -class PointerNativeTypeCfe implements NativeTypeCfe {
              +class PointerNativeTypeCfe extends NativeTypeCfe {
              + PointerNativeTypeCfe() : super._();
              +
              @override
              Map<Abi, int?> get size => wordSize;

              @@ -288,66 +383,61 @@
              transformer.pointerClass.superclass!, Nullability.nonNullable)
              ]));

              - /// Sample output for `Pointer<Int8> get x =>`:
              + /// Sample output:
              ///
              /// ```
              - /// _fromAddress<Int8>(_loadAbiSpecificInt<IntPtr>(_typedDataBase, offset));
              + /// _loadPointer<#dartType>(#typedDataBase, #offsetInBytes);
              /// ```
              @override
              - ReturnStatement generateGetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - FfiTransformer transformer) =>
              - ReturnStatement(StaticInvocation(
              - transformer.fromAddressInternal,
              - Arguments([
              - transformer.abiSpecificLoadOrStoreExpression(
              - transformer.intptrNativeTypeCfe,
              - typedDataBase: transformer.getCompoundTypedDataBaseField(
              - ThisExpression(), fileOffset),
              - offsetInBytes: transformer.runtimeBranchOnLayout(offsets),
              - fileOffset: fileOffset,
              - ),
              - ], types: [
              - (dartType as InterfaceType).typeArguments.single
              - ]))
              - ..fileOffset = fileOffset);
              + Expression generateLoad({
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + }) {
              + return StaticInvocation(
              + transformer.loadMethods[NativeType.kPointer]!,
              + Arguments([
              + typedDataBase,
              + offsetInBytes,
              + ], types: [
              + (dartType as InterfaceType).typeArguments.single
              + ]),
              + )..fileOffset = fileOffset;
              + }

              - /// Sample output for `set x(Pointer<Int8> #v) =>`:
              + /// Sample output:
              ///
              /// ```
              - /// _storeAbiSpecificInt<IntPtr>(
              - /// _typedDataBase,
              - /// offset,
              - /// (#v as Pointer<Int8>).address,
              + /// _storePointer<#dartType>(
              + /// #typedDataBase,
              + /// #offsetInBytes,
              + /// (#value as Pointer<#dartType>),
              /// );
              /// ```
              @override
              - ReturnStatement generateSetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - VariableDeclaration argument,
              - FfiTransformer transformer) =>
              - ReturnStatement(
              - transformer.abiSpecificLoadOrStoreExpression(
              - transformer.intptrNativeTypeCfe,
              - typedDataBase: transformer.getCompoundTypedDataBaseField(
              - ThisExpression(), fileOffset),
              - offsetInBytes: transformer.runtimeBranchOnLayout(offsets),
              - value: InstanceGet(
              - InstanceAccessKind.Instance,
              - VariableGet(argument),
              - transformer.addressGetter.name,
              - interfaceTarget: transformer.addressGetter,
              - resultType: transformer.addressGetter.getterType,
              - )..fileOffset = fileOffset,
              - fileOffset: fileOffset,
              - ),
              - );
              + Expression generateStore(
              + Expression value, {
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + }) {
              + return StaticInvocation(
              + transformer.storeMethods[NativeType.kPointer]!,
              + Arguments([
              + typedDataBase,
              + offsetInBytes,
              + value,
              + ], types: [
              + (dartType as InterfaceType).typeArguments.single
              + ]),
              + )..fileOffset = fileOffset;
              + }
              }

              /// The layout of a `Struct` or `Union` in one [Abi].
              @@ -366,14 +456,94 @@
              CompoundLayout(this.size, this.alignment, this.offsets);
              }

              -abstract class CompoundNativeTypeCfe implements NativeTypeCfe {
              +abstract mixin class _CompoundLoadAndStoreMixin implements NativeTypeCfe {
              + Class get clazz;
              + bool get knowsLayout;
              +
              + /// Generates an expression evaluating to the size of this compound subtype in
              + /// bytes.
              + ///
              + /// If we know the size, we can construct a constant or a runtime lookup based
              + /// on the ABI. Otherwise, we'll look it up from the `#size` field generated
              + /// by the definitions transformer.
              + Expression _generateSize(FfiTransformer transformer) {
              + if (knowsLayout) {
              + return transformer.runtimeBranchOnLayout(size);
              + } else {
              + return transformer.inlineSizeOf(
              + clazz.getThisType(transformer.coreTypes, Nullability.nonNullable))!;
              + }
              + }
              +
              + /// Sample output for `MyStruct`:
              + ///
              + /// ```
              + /// MyStruct.#fromTypedDataBase(
              + /// typedDataBaseOffset(#typedDataBase, #offsetInBytes, size)
              + /// );
              + /// ```
              + @override
              + Expression generateLoad({
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + }) {
              + final constructor = clazz.constructors
              + .firstWhere((c) => c.name == Name("#fromTypedDataBase"));
              +
              + return ConstructorInvocation(
              + constructor,
              + Arguments([
              + transformer.typedDataBaseOffset(typedDataBase, offsetInBytes,
              + _generateSize(transformer), dartType, fileOffset)
              + ]))
              + ..fileOffset = fileOffset;
              + }
              +
              + /// Sample output for `set x(MyStruct #v) =>`:
              + ///
              + /// ```
              + /// _memCopy(#typedDataBase, #offsetInBytes, #v._typedDataBase, 0, size);
              + /// ```
              + @override
              + Expression generateStore(
              + Expression value, {
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + }) {
              + return StaticInvocation(
              + transformer.memCopy,
              + Arguments([
              + typedDataBase,
              + offsetInBytes,
              + transformer.getCompoundTypedDataBaseField(value, fileOffset),
              + ConstantExpression(IntConstant(0)),
              + _generateSize(transformer),
              + ]))
              + ..fileOffset = fileOffset;
              + }
              +}
              +
              +abstract class CompoundNativeTypeCfe extends NativeTypeCfe
              + with _CompoundLoadAndStoreMixin {
              + @override
              final Class clazz;

              final List<NativeTypeCfe> members;

              final Map<Abi, CompoundLayout> layout;

              - CompoundNativeTypeCfe._(this.clazz, this.members, this.layout);
              + @override
              + bool get knowsLayout => true;
              +
              + CompoundNativeTypeCfe._(this.clazz, this.members, this.layout) : super._();

              @override
              Map<Abi, int?> get size =>
              @@ -392,63 +562,6 @@
              @override
              Constant generateConstant(FfiTransformer transformer) =>
              TypeLiteralConstant(InterfaceType(clazz, Nullability.nonNullable));
              -
              - /// Sample output for `MyStruct get x =>`:
              - ///
              - /// ```
              - /// MyStruct.#fromTypedDataBase(
              - /// typedDataBaseOffset(_typedDataBase, offset, size, dartType)
              - /// );
              - /// ```
              - @override
              - ReturnStatement generateGetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - FfiTransformer transformer) {
              - final constructor = clazz.constructors
              - .firstWhere((c) => c.name == Name("#fromTypedDataBase"));
              -
              - return ReturnStatement(ConstructorInvocation(
              - constructor,
              - Arguments([
              - transformer.typedDataBaseOffset(
              - transformer.getCompoundTypedDataBaseField(
              - ThisExpression(), fileOffset),
              - transformer.runtimeBranchOnLayout(offsets),
              - transformer.runtimeBranchOnLayout(size),
              - dartType,
              - fileOffset)
              - ]))
              - ..fileOffset = fileOffset);
              - }
              -
              - /// Sample output for `set x(MyStruct #v) =>`:
              - ///
              - /// ```
              - /// _memCopy(_typedDataBase, offset, #v._typedDataBase, 0, size);
              - /// ```
              - @override
              - ReturnStatement generateSetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - VariableDeclaration argument,
              - FfiTransformer transformer) =>
              - ReturnStatement(StaticInvocation(
              - transformer.memCopy,
              - Arguments([
              - transformer.getCompoundTypedDataBaseField(
              - ThisExpression(), fileOffset),
              - transformer.runtimeBranchOnLayout(offsets),
              - transformer.getCompoundTypedDataBaseField(
              - VariableGet(argument), fileOffset),
              - ConstantExpression(IntConstant(0)),
              - transformer.runtimeBranchOnLayout(size),
              - ]))
              - ..fileOffset = fileOffset);
              }

              class StructNativeTypeCfe extends CompoundNativeTypeCfe {
              @@ -520,11 +633,51 @@
              }
              }

              -class ArrayNativeTypeCfe implements NativeTypeCfe {
              +/// A compound type only being referenced (instead of being fully resolved like
              +/// in [CompoundNativeTypeCfe]).
              +///
              +/// This type can't report the underlying size, alignment or inner fields of
              +/// the struct or union.
              +///
              +/// Since the definitions transformer generates static size fields on compounds,
              +/// other transformers not needing access to individual fields can use this type
              +/// to generate loads and stores to compounds when only having their class.
              +class ReferencedCompoundSubtypeCfe extends NativeTypeCfe
              + with _CompoundLoadAndStoreMixin {
              + @override
              + final Class clazz;
              +
              + ReferencedCompoundSubtypeCfe(this.clazz) : super._();
              +
              + Never _informationUnavailable() {
              + throw UnsupportedError('Reference to struct');
              + }
              +
              + @override
              + bool get knowsLayout => false;
              +
              + @override
              + Map<Abi, int?> get alignment => _informationUnavailable();
              +
              + @override
              + Constant generateConstant(FfiTransformer transformer) =>
              + _informationUnavailable();
              +
              + @override
              + int? getAlignmentFor(Abi abi) => _informationUnavailable();
              +
              + @override
              + int? getSizeFor(Abi abi) => _informationUnavailable();
              +
              + @override
              + Map<Abi, int?> get size => _informationUnavailable();
              +}
              +
              +class ArrayNativeTypeCfe extends NativeTypeCfe {
              final NativeTypeCfe elementType;
              final int length;

              - ArrayNativeTypeCfe(this.elementType, this.length);
              + ArrayNativeTypeCfe(this.elementType, this.length) : super._();

              factory ArrayNativeTypeCfe.multi(
              NativeTypeCfe elementType, List<int> dimensions) {
              @@ -580,29 +733,31 @@
              IntConstant(dimensionsFlattened)
              });

              - /// Sample output for `Array<Int8> get x =>`:
              + /// Sample output for `Array<Int8>`:
              ///
              /// ```
              /// Array<Int8>._(
              - /// typedDataBaseOffset(_typedDataBase, offset, size, typeArgument)
              + /// typedDataBaseOffset(#typedDataBase, #offsetInBytes, size, typeArgument)
              /// );
              /// ```
              @override
              - ReturnStatement generateGetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - FfiTransformer transformer) {
              + Expression generateLoad({
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + }) {
              InterfaceType typeArgument =
              (dartType as InterfaceType).typeArguments.single as InterfaceType;
              - return ReturnStatement(ConstructorInvocation(
              +
              + return ConstructorInvocation(
              transformer.arrayConstructor,
              Arguments([
              transformer.typedDataBaseOffset(
              - transformer.getCompoundTypedDataBaseField(
              - ThisExpression(), fileOffset),
              - transformer.runtimeBranchOnLayout(offsets),
              + typedDataBase,
              + offsetInBytes,
              transformer.runtimeBranchOnLayout(size),
              typeArgument,
              fileOffset),
              @@ -611,42 +766,43 @@
              ], types: [
              typeArgument
              ]))
              - ..fileOffset = fileOffset);
              + ..fileOffset = fileOffset;
              }

              /// Sample output for `set x(Array #v) =>`:
              ///
              /// ```
              - /// _memCopy(_typedDataBase, offset, #v._typedDataBase, 0, size);
              + /// _memCopy(#typedDataBase, #offsetInBytes, #v._typedDataBase, 0, size);
              /// ```
              @override
              - ReturnStatement generateSetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - VariableDeclaration argument,
              - FfiTransformer transformer) =>
              - ReturnStatement(StaticInvocation(
              - transformer.memCopy,
              - Arguments([
              - transformer.getCompoundTypedDataBaseField(
              - ThisExpression(), fileOffset),
              - transformer.runtimeBranchOnLayout(offsets),
              - transformer.getArrayTypedDataBaseField(
              - VariableGet(argument), fileOffset),
              - ConstantExpression(IntConstant(0)),
              - transformer.runtimeBranchOnLayout(size),
              - ]))
              - ..fileOffset = fileOffset);
              + Expression generateStore(
              + Expression value, {
              + required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false,
              + }) {
              + return StaticInvocation(
              + transformer.memCopy,
              + Arguments([
              + typedDataBase,
              + offsetInBytes,
              + transformer.getArrayTypedDataBaseField(value, fileOffset),
              + ConstantExpression(IntConstant(0)),
              + transformer.runtimeBranchOnLayout(size),
              + ]))
              + ..fileOffset = fileOffset;
              + }
              }

              -class AbiSpecificNativeTypeCfe implements NativeTypeCfe {
              +class AbiSpecificNativeTypeCfe extends NativeTypeCfe {
              final Map<Abi, NativeTypeCfe> abiSpecificTypes;

              final Class clazz;

              - AbiSpecificNativeTypeCfe(this.abiSpecificTypes, this.clazz);
              + AbiSpecificNativeTypeCfe(this.abiSpecificTypes, this.clazz) : super._();

              @override
              Map<Abi, int?> get size => abiSpecificTypes.map(
              @@ -667,42 +823,35 @@
              TypeLiteralConstant(InterfaceType(clazz, Nullability.nonNullable));

              @override
              - ReturnStatement generateGetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - FfiTransformer transformer,
              - ) {
              - return ReturnStatement(
              - transformer.abiSpecificLoadOrStoreExpression(
              - this,
              - typedDataBase: transformer.getCompoundTypedDataBaseField(
              - ThisExpression(), fileOffset),
              - offsetInBytes: transformer.runtimeBranchOnLayout(offsets),
              - fileOffset: fileOffset,
              - ),
              + Expression generateLoad(
              + {required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false}) {
              + return transformer.abiSpecificLoadOrStoreExpression(
              + this,
              + typedDataBase: typedDataBase,
              + offsetInBytes: offsetInBytes,
              + fileOffset: fileOffset,
              );
              }

              @override
              - ReturnStatement generateSetterStatement(
              - DartType dartType,
              - int fileOffset,
              - Map<Abi, int?> offsets,
              - bool unalignedAccess,
              - VariableDeclaration argument,
              - FfiTransformer transformer,
              - ) {
              - return ReturnStatement(
              - transformer.abiSpecificLoadOrStoreExpression(
              - this,
              - typedDataBase: transformer.getCompoundTypedDataBaseField(
              - ThisExpression(), fileOffset),
              - offsetInBytes: transformer.runtimeBranchOnLayout(offsets),
              - value: VariableGet(argument),
              - fileOffset: fileOffset,
              - ),
              + Expression generateStore(Expression value,
              + {required DartType dartType,
              + required int fileOffset,
              + required Expression typedDataBase,
              + required FfiTransformer transformer,
              + required Expression offsetInBytes,
              + bool unaligned = false}) {
              + return transformer.abiSpecificLoadOrStoreExpression(
              + this,
              + typedDataBase: typedDataBase,
              + offsetInBytes: offsetInBytes,
              + value: value,
              + fileOffset: fileOffset,
              );
              }
              }
              diff --git a/pkg/vm/lib/transformations/ffi/use_sites.dart b/pkg/vm/lib/transformations/ffi/use_sites.dart
              index 72731d5..0d15a5a 100644
              --- a/pkg/vm/lib/transformations/ffi/use_sites.dart
              +++ b/pkg/vm/lib/transformations/ffi/use_sites.dart
              @@ -29,18 +29,10 @@
              show FunctionTypeInstantiator, Substitution;
              import 'package:kernel/type_environment.dart';

              -import 'abi.dart' show wordSize;
              import 'definitions.dart' as definitions;
              import 'native_type_cfe.dart';
              import 'native.dart' as native;
              -import 'common.dart'
              - show
              - FfiStaticTypeError,
              - FfiTransformer,
              - NativeType,
              - nativeTypeSizes,
              - UNKNOWN,
              - WORD_SIZE;
              +import 'common.dart' show FfiStaticTypeError, FfiTransformer, NativeType;
              import 'finalizable.dart';

              /// Checks and replaces calls to dart:ffi compound fields and methods.
              @@ -268,7 +260,8 @@

              ensureNativeTypeValid(nativeType, node, allowCompounds: true);

              - Expression? inlineSizeOf = _inlineSizeOf(nativeType as InterfaceType);
              + Expression? inlineSizeOf =
              + this.inlineSizeOf(nativeType as InterfaceType);
              if (inlineSizeOf != null) {
              // Generates `receiver.offsetBy(inlineSizeOfExpression)`.
              return InstanceInvocation(InstanceAccessKind.Instance, pointer,
              @@ -304,7 +297,7 @@
              allowCompounds: true, allowVoid: true);

              if (nativeType is InterfaceType) {
              - Expression? inlineSizeOf = _inlineSizeOf(nativeType);
              + Expression? inlineSizeOf = this.inlineSizeOf(nativeType);
              if (inlineSizeOf != null) {
              return inlineSizeOf;
              }
              @@ -418,7 +411,7 @@

              // Inline the body to get rid of a generic invocation of sizeOf.
              // TODO(http://dartbug.com/39964): Add `alignmentOf<T>()` call.
              - Expression? sizeInBytes = _inlineSizeOf(nativeType as InterfaceType);
              + Expression? sizeInBytes = inlineSizeOf(nativeType as InterfaceType);
              if (sizeInBytes != null) {
              if (node.arguments.positional.length == 2) {
              sizeInBytes = multiply(node.arguments.positional[1], sizeInBytes);
              @@ -452,27 +445,6 @@
              .distinct()
              .fold(nestedExpression, invokeCompoundConstructor);

              - Expression? _inlineSizeOf(InterfaceType nativeType) {
              - final Class nativeClass = nativeType.classNode;
              - final NativeType? nt = getType(nativeClass);
              - if (nt == null) {
              - // User-defined compounds.
              - final Procedure sizeOfGetter = nativeClass.procedures
              - .firstWhere((function) => function.name == Name('#sizeOf'));
              - return StaticGet(sizeOfGetter);
              - }
              - final int size = nativeTypeSizes[nt]!;
              - if (size == WORD_SIZE) {
              - return runtimeBranchOnLayout(wordSize);
              - }
              - if (size != UNKNOWN) {
              - return ConstantExpression(IntConstant(size),
              - InterfaceType(intClass, currentLibrary.nonNullable));
              - }
              - // Size unknown.
              - return null;
              - }
              -
              // We need to replace calls to 'DynamicLibrary.lookupFunction' with explicit
              // Kernel, because we cannot have a generic call to 'asFunction' in its body.
              //
              @@ -825,8 +797,8 @@
              Expression _replaceGetRef(StaticInvocation node) {
              final dartType = node.arguments.types[0];
              final clazz = (dartType as InterfaceType).classNode;
              - final constructor = clazz.constructors
              - .firstWhere((c) => c.name == Name("#fromTypedDataBase"));
              + final referencedStruct = ReferencedCompoundSubtypeCfe(clazz);
              +
              Expression pointer = NullCheck(node.arguments.positional[0]);
              if (node.arguments.positional.length == 2) {
              pointer = InstanceInvocation(
              @@ -834,45 +806,50 @@
              pointer,
              offsetByMethod.name,
              Arguments([
              - multiply(node.arguments.positional[1], _inlineSizeOf(dartType)!)
              + multiply(node.arguments.positional[1], inlineSizeOf(dartType)!)
              ]),
              interfaceTarget: offsetByMethod,
              functionType:
              Substitution.fromPairs(pointerClass.typeParameters, [dartType])
              .substituteType(offsetByMethod.getterType) as FunctionType);
              }
              - return ConstructorInvocation(constructor, Arguments([pointer]));
              +
              + return referencedStruct.generateLoad(
              + dartType: dartType,
              + transformer: this,
              + typedDataBase: pointer,
              + offsetInBytes: ConstantExpression(IntConstant(0)),
              + fileOffset: node.fileOffset,
              + );
              }

              /// Replaces a `.ref=` or `[]=` on a compound pointer extension with a mem
              /// copy call.
              Expression _replaceSetRef(StaticInvocation node) {
              final target = node.arguments.positional[0]; // Receiver of extension
              + final referencedStruct = ReferencedCompoundSubtypeCfe(
              + (node.arguments.types[0] as InterfaceType).classNode);

              - final Expression source, targetOffset;
              + final Expression sourceStruct, targetOffset;

              if (node.arguments.positional.length == 3) {
              // []= call, args are (receiver, index, source)
              - source = getCompoundTypedDataBaseField(
              - node.arguments.positional[2], node.fileOffset);
              + sourceStruct = node.arguments.positional[2];
              targetOffset = multiply(node.arguments.positional[1],
              - _inlineSizeOf(node.arguments.types[0] as InterfaceType)!);
              + inlineSizeOf(node.arguments.types[0] as InterfaceType)!);
              } else {
              // .ref= call, args are (receiver, source)
              - source = getCompoundTypedDataBaseField(
              - node.arguments.positional[1], node.fileOffset);
              + sourceStruct = node.arguments.positional[1];
              targetOffset = ConstantExpression(IntConstant(0));
              }

              - return StaticInvocation(
              - memCopy,
              - Arguments([
              - target,
              - targetOffset,
              - source,
              - ConstantExpression(IntConstant(0)),
              - _inlineSizeOf(node.arguments.types[0] as InterfaceType)!,
              - ]),
              + return referencedStruct.generateStore(
              + sourceStruct,
              + dartType: node.arguments.types[0],
              + offsetInBytes: targetOffset,
              + typedDataBase: target,
              + transformer: this,
              + fileOffset: node.fileOffset,
              );
              }

              @@ -884,8 +861,8 @@

              final typedDataBasePrime = typedDataBaseOffset(
              getArrayTypedDataBaseField(NullCheck(node.arguments.positional[0])),
              - multiply(node.arguments.positional[1], _inlineSizeOf(dartType)!),
              - _inlineSizeOf(dartType)!,
              + multiply(node.arguments.positional[1], inlineSizeOf(dartType)!),
              + inlineSizeOf(dartType)!,
              dartType,
              node.fileOffset);

              @@ -905,7 +882,7 @@
              /// Array #array = this!;
              /// int #index = index!;
              /// #array._checkIndex(#index);
              - /// int #singleElementSize = _inlineSizeOf<innermost(T)>();
              + /// int #singleElementSize = inlineSizeOf<innermost(T)>();
              /// int #elementSize = #array.nestedDimensionsFlattened * #singleElementSize;
              /// int #offset = #elementSize * #index;
              ///
              @@ -927,7 +904,7 @@
              /// Array #array = this!;
              /// int #index = index!;
              /// #array._checkIndex(#index);
              - /// int #singleElementSize = _inlineSizeOf<innermost(T)>();
              + /// int #singleElementSize = inlineSizeOf<innermost(T)>();
              /// int #elementSize = #array.nestedDimensionsFlattened * #singleElementSize;
              /// int #offset = #elementSize * #index;
              ///
              @@ -950,7 +927,7 @@
              isSynthesized: true)
              ..fileOffset = node.fileOffset;
              final singleElementSizeVar = VariableDeclaration("#singleElementSize",
              - initializer: _inlineSizeOf(elementType as InterfaceType),
              + initializer: inlineSizeOf(elementType as InterfaceType),
              type: coreTypes.intNonNullableRawType,
              isSynthesized: true)
              ..fileOffset = node.fileOffset;
              @@ -1121,13 +1098,16 @@
              final arg = node.arguments.positional.single;
              final nativeType = node.arguments.types.single;

              - // `x` must be a method annotated with `@Native`, so referencing it makes
              - // it a tear-off.
              - if (arg case ConstantExpression(constant: StaticTearOffConstant method)) {
              - // The method must have the `vm:ffi:native` pragma added by the native
              - // transformer.
              - Constant? nativeAnnotation;
              - for (final annotation in method.target.annotations) {
              + final potentiallyNativeTarget = switch (arg) {
              + ConstantExpression(constant: StaticTearOffConstant method) =>
              + method.target,
              + StaticGet(:var targetReference) => targetReference.asMember,
              + _ => null,
              + };
              + Constant? nativeAnnotation;
              +
              + if (potentiallyNativeTarget != null) {
              + for (final annotation in potentiallyNativeTarget.annotations) {
              if (annotation
              case ConstantExpression(constant: final InstanceConstant c)) {
              if (c.classNode == coreTypes.pragmaClass) {
              @@ -1141,25 +1121,22 @@
              }
              }
              }
              + }

              - if (nativeAnnotation == null) {
              - diagnosticReporter.report(messageFfiAddressOfMustBeNative,
              - node.arguments.fileOffset, 1, node.location?.file);
              - return node;
              - }
              -
              - ensureNativeTypeValid(nativeType, node);
              - ensureNativeTypeToDartType(nativeType, arg.type, node);
              -
              - return StaticInvocation(
              - nativePrivateAddressOf,
              - Arguments([ConstantExpression(nativeAnnotation)], types: [nativeType]),
              - )..fileOffset = arg.fileOffset;
              - } else {
              + if (nativeAnnotation == null) {
              diagnosticReporter.report(messageFfiAddressOfMustBeNative, arg.fileOffset,
              1, node.location?.file);
              return node;
              }
              +
              + ensureNativeTypeValid(nativeType, node, allowCompounds: true);
              + ensureNativeTypeToDartType(
              + nativeType, arg.getStaticType(staticTypeContext!), node);
              +
              + return StaticInvocation(
              + nativePrivateAddressOf,
              + Arguments([ConstantExpression(nativeAnnotation)], types: [nativeType]),
              + )..fileOffset = arg.fileOffset;
              }
              }

              diff --git a/pkg/vm/testcases/transformations/ffi/native_asset_id.dart b/pkg/vm/testcases/transformations/ffi/native_asset_id.dart
              new file mode 100644
              index 0000000..5604b6c
              --- /dev/null
              +++ b/pkg/vm/testcases/transformations/ffi/native_asset_id.dart
              @@ -0,0 +1,22 @@
              +@DefaultAsset('someAssetId')
              +library;
              +
              +import 'dart:ffi';
              +
              +@Native<Pointer<Void> Function()>()
              +external Pointer<Void> malloc();
              +
              +@Native<Pointer<Void> Function()>(assetId: 'anotherAsset')
              +external Pointer<Void> mallocInAsset();
              +
              +@Native()
              +external final Pointer<Void> ptr;
              +
              +@Native(assetId: 'anotherAsset')
              +external final Pointer<Void> ptrInAsset;
              +
              +void main() {
              + print(malloc());
              + print(mallocInAsset());
              + print(ptr);
              +}
              diff --git a/pkg/vm/testcases/transformations/ffi/native_asset_id.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/native_asset_id.dart.aot.expect
              new file mode 100644
              index 0000000..9c8a2fd
              --- /dev/null
              +++ b/pkg/vm/testcases/transformations/ffi/native_asset_id.dart.aot.expect
              @@ -0,0 +1,40 @@
              +library #lib;
              +import self as self;
              +import "dart:core" as core;
              +import "dart:ffi" as ffi;
              +
              +import "dart:ffi";
              +
              +@#C6
              +@#C8
              +external static method malloc() → ffi::Pointer<ffi::Void>;
              +@#C12
              +@#C13
              +external static method mallocInAsset() → ffi::Pointer<ffi::Void>;
              +@#C16
              +static get ptr() → ffi::Pointer<ffi::Void>
              + return [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_loadPointer<ffi::Void>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Pointer<ffi::Void>>(#C15), #C17);
              +static method main() → void {
              + core::print([@vm.inferred-type.metadata=dart.ffi::Pointer] self::malloc());
              + core::print([@vm.inferred-type.metadata=dart.ffi::Pointer] self::mallocInAsset());
              + core::print([@vm.inferred-type.metadata=dart.ffi::Pointer] self::ptr);
              +}
              +constants {
              + #C1 = "cfe:ffi:native-marker"
              + #C2 = "malloc"
              + #C3 = "someAssetId"
              + #C4 = false
              + #C5 = ffi::Native<() → ffi::Pointer<ffi::Void>> {symbol:#C2, assetId:#C3, isLeaf:#C4}
              + #C6 = core::pragma {name:#C1, options:#C5}
              + #C7 = "vm:ffi:native"
              + #C8 = core::pragma {name:#C7, options:#C5}
              + #C9 = "mallocInAsset"
              + #C10 = "anotherAsset"
              + #C11 = ffi::Native<() → ffi::Pointer<ffi::Void>> {symbol:#C9, assetId:#C10, isLeaf:#C4}
              + #C12 = core::pragma {name:#C1, options:#C11}
              + #C13 = core::pragma {name:#C7, options:#C11}
              + #C14 = "ptr"
              + #C15 = ffi::Native<ffi::Pointer<ffi::Void>> {symbol:#C14, assetId:#C3, isLeaf:#C4}
              + #C16 = core::pragma {name:#C1, options:#C15}
              + #C17 = 0
              +}
              diff --git a/pkg/vm/testcases/transformations/ffi/native_asset_id.dart.expect b/pkg/vm/testcases/transformations/ffi/native_asset_id.dart.expect
              new file mode 100644
              index 0000000..38dd2e6
              --- /dev/null
              +++ b/pkg/vm/testcases/transformations/ffi/native_asset_id.dart.expect
              @@ -0,0 +1,48 @@
              +@#C2
              +library #lib;
              +import self as self;
              +import "dart:ffi" as ffi;
              +import "dart:core" as core;
              +
              +import "dart:ffi";
              +
              +@#C7
              +@#C9
              +external static method malloc() → ffi::Pointer<ffi::Void>;
              +@#C13
              +@#C14
              +external static method mallocInAsset() → ffi::Pointer<ffi::Void>;
              +@#C17
              +static get ptr() → ffi::Pointer<ffi::Void>
              + return ffi::_loadPointer<ffi::Void>(ffi::Native::_addressOf<ffi::Pointer<ffi::Void>>(#C16), #C18);
              +@#C21
              +static get ptrInAsset() → ffi::Pointer<ffi::Void>
              + return ffi::_loadPointer<ffi::Void>(ffi::Native::_addressOf<ffi::Pointer<ffi::Void>>(#C20), #C18);
              +static method main() → void {
              + core::print(self::malloc());
              + core::print(self::mallocInAsset());
              + core::print(self::ptr);
              +}
              +constants {
              + #C1 = "someAssetId"
              + #C2 = ffi::DefaultAsset {id:#C1}
              + #C3 = "cfe:ffi:native-marker"
              + #C4 = "malloc"
              + #C5 = false
              + #C6 = ffi::Native<() → ffi::Pointer<ffi::Void>> {symbol:#C4, assetId:#C1, isLeaf:#C5}
              + #C7 = core::pragma {name:#C3, options:#C6}
              + #C8 = "vm:ffi:native"
              + #C9 = core::pragma {name:#C8, options:#C6}
              + #C10 = "mallocInAsset"
              + #C11 = "anotherAsset"
              + #C12 = ffi::Native<() → ffi::Pointer<ffi::Void>> {symbol:#C10, assetId:#C11, isLeaf:#C5}
              + #C13 = core::pragma {name:#C3, options:#C12}
              + #C14 = core::pragma {name:#C8, options:#C12}
              + #C15 = "ptr"
              + #C16 = ffi::Native<ffi::Pointer<ffi::Void>> {symbol:#C15, assetId:#C1, isLeaf:#C5}
              + #C17 = core::pragma {name:#C3, options:#C16}
              + #C18 = 0
              + #C19 = "ptrInAsset"
              + #C20 = ffi::Native<ffi::Pointer<ffi::Void>> {symbol:#C19, assetId:#C11, isLeaf:#C5}
              + #C21 = core::pragma {name:#C3, options:#C20}
              +}
              diff --git a/pkg/vm/testcases/transformations/ffi/native_fields.dart b/pkg/vm/testcases/transformations/ffi/native_fields.dart
              new file mode 100644
              index 0000000..bf9039c
              --- /dev/null
              +++ b/pkg/vm/testcases/transformations/ffi/native_fields.dart
              @@ -0,0 +1,45 @@
              +import 'dart:ffi';
              +
              +@Native()
              +external Pointer<Char> aString;
              +
              +@Native<Int32>()
              +external int anInt;
              +
              +@Native<Int>()
              +external int anotherInt;
              +
              +final class Vec2d extends Struct {
              + @Double()
              + external double x;
              + @Double()
              + external double y;
              +}
              +
              +final class MyUnion extends Union {
              + external Vec2d vector;
              + external Pointer<Vec2d> indirectVector;
              +}
              +
              +@Native()
              +external final Vec2d vector;
              +
              +@Native()
              +external MyUnion union;
              +
              +void main() {
              + print('first char of string: ${aString.value}');
              + print('global int: {$anInt}');
              +
              + aString = nullptr;
              + anInt++;
              +
              + final vec = vector;
              + print('(${vec.x}, ${vec.y})');
              +
              + union.indirectVector = Native.addressOf(vector);
              +
              + print(Native.addressOf<Int>(anotherInt));
              + print(Native.addressOf<Vec2d>(vector));
              + print(Native.addressOf<MyUnion>(union));
              +}
              diff --git a/pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect
              new file mode 100644
              index 0000000..51a1e5e
              --- /dev/null
              +++ b/pkg/vm/testcases/transformations/ffi/native_fields.dart.aot.expect
              @@ -0,0 +1,87 @@
              +library #lib;
              +import self as self;
              +import "dart:core" as core;
              +import "dart:ffi" as ffi;
              +
              +import "dart:ffi";
              +
              +@#C6
              +final class Vec2d extends ffi::Struct {
              + constructor #fromTypedDataBase([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] synthesized core::Object #typedDataBase) → self::Vec2d
              + : super ffi::Struct::_fromTypedDataBase(#typedDataBase)
              + ;
              +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1] [@vm.unboxing-info.metadata=()->d] get x() → core::double
              + return [@vm.inferred-type.metadata=dart.core::_Double] ffi::_loadDouble([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.ffi::Pointer] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C8.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
              +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:2] [@vm.unboxing-info.metadata=()->d] get y() → core::double
              + return [@vm.inferred-type.metadata=dart.core::_Double] ffi::_loadDouble([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.ffi::Pointer] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C10.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
              +}
              +@#C15
              +final class MyUnion extends ffi::Union {
              + constructor #fromTypedDataBase([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] synthesized core::Object #typedDataBase) → self::MyUnion
              + : super ffi::Union::_fromTypedDataBase(#typedDataBase)
              + ;
              +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3] set indirectVector([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<self::Vec2d> #externalFieldValue) → void
              + return ffi::_storePointer<self::Vec2d>([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.ffi::Pointer] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C8.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
              +}
              +@#C21
              +static get aString() → ffi::Pointer<ffi::Char>
              + return [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_loadPointer<ffi::Char>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Pointer<ffi::Char>>(#C20), #C7);
              +static set aString([@vm.inferred-arg-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::Char> #externalFieldValue) → void
              + ffi::_storePointer<ffi::Char>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Pointer<ffi::Char>>(#C20), #C7, #externalFieldValue);
              +[@vm.unboxing-info.metadata=()->i]@#C24
              +static get anInt() → core::int
              + return [@vm.inferred-type.metadata=int] ffi::_loadInt32([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Int32>(#C23), #C7);
              +[@vm.unboxing-info.metadata=(i)->b]static set anInt([@vm.inferred-arg-type.metadata=int] synthesized core::int #externalFieldValue) → void
              + ffi::_storeInt32([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Int32>(#C23), #C7, #externalFieldValue);
              +@#C27
              +static get vector() → self::Vec2d
              + return new self::Vec2d::#fromTypedDataBase([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<self::Vec2d>(#C26));
              +@#C30
              +static get union() → self::MyUnion
              + return new self::MyUnion::#fromTypedDataBase([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<self::MyUnion>(#C29));
              +static method main() → void {
              + core::print("first char of string: ${ffi::_loadAbiSpecificInt<ffi::Char>([@vm.inferred-type.metadata=dart.ffi::Pointer] self::aString, #C7)}");
              + core::print("global int: {${self::anInt}}");
              + self::aString = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::nullptr;
              + self::anInt = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] [@vm.inferred-type.metadata=int] self::anInt.{core::num::+}(1){(core::num) → core::int};
              + final self::Vec2d vec = [@vm.inferred-type.metadata=#lib::Vec2d] self::vector;
              + core::print("(${[@vm.direct-call.metadata=#lib::Vec2d.x] vec.{self::Vec2d::x}{core::double}}, ${[@vm.direct-call.metadata=#lib::Vec2d.y] vec.{self::Vec2d::y}{core::double}})");
              + [@vm.direct-call.metadata=#lib::MyUnion.indirectVector] [@vm.inferred-type.metadata=!? (skip check)] [@vm.inferred-type.metadata=#lib::MyUnion] self::union.{self::MyUnion::indirectVector} = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<self::Vec2d>(#C26);
              + core::print([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<ffi::Int>(#C32));
              + core::print([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<self::Vec2d>(#C26));
              + core::print([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Native::_addressOf<self::MyUnion>(#C29));
              +}
              +constants {
              + #C1 = "vm:ffi:struct-fields"
              + #C2 = TypeLiteralConstant(ffi::Double)
              + #C3 = <core::Type>[#C2, #C2]
              + #C4 = null
              + #C5 = ffi::_FfiStructLayout {fieldTypes:#C3, packing:#C4}
              + #C6 = core::pragma {name:#C1, options:#C5}
              + #C7 = 0
              + #C8 = <core::int*>[#C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7, #C7]
              + #C9 = 8
              + #C10 = <core::int*>[#C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9, #C9]
              + #C11 = TypeLiteralConstant(self::Vec2d)
              + #C12 = TypeLiteralConstant(ffi::Pointer<ffi::NativeType>)
              + #C13 = <core::Type>[#C11, #C12]
              + #C14 = ffi::_FfiStructLayout {fieldTypes:#C13, packing:#C4}
              + #C15 = core::pragma {name:#C1, options:#C14}
              + #C16 = "cfe:ffi:native-marker"
              + #C17 = "aString"
              + #C18 = "#lib"
              + #C19 = false
              + #C20 = ffi::Native<ffi::Pointer<ffi::Char>> {symbol:#C17, assetId:#C18, isLeaf:#C19}
              + #C21 = core::pragma {name:#C16, options:#C20}
              + #C22 = "anInt"
              + #C23 = ffi::Native<ffi::Int32> {symbol:#C22, assetId:#C18, isLeaf:#C19}
              + #C24 = core::pragma {name:#C16, options:#C23}
              + #C25 = "vector"
              + #C26 = ffi::Native<self::Vec2d> {symbol:#C25, assetId:#C18, isLeaf:#C19}
              + #C27 = core::pragma {name:#C16, options:#C26}
              + #C28 = "union"
              + #C29 = ffi::Native<self::MyUnion> {symbol:#C28, assetId:#C18, isLeaf:#C19}
              + #C30 = core::pragma {name:#C16, options:#C29}
              + #C31 = "anotherInt"
              + #C32 = ffi::Native<ffi::Int> {symbol:#C31, assetId:#C18, isLeaf:#C19}
              +}
              diff --git a/pkg/vm/testcases/transformations/ffi/native_fields.dart.expect b/pkg/vm/testcases/transformations/ffi/native_fields.dart.expect
              new file mode 100644
              index 0000000..0a2373f
              --- /dev/null
              +++ b/pkg/vm/testcases/transformations/ffi/native_fields.dart.expect
              @@ -0,0 +1,131 @@
              +library #lib;
              +import self as self;
              +import "dart:core" as core;
              +import "dart:ffi" as ffi;
              +import "dart:typed_data" as typ;
              +import "dart:_internal" as _in;
              +
              +import "dart:ffi";
              +
              +@#C6
              +final class Vec2d extends ffi::Struct {
              + synthetic constructor •() → self::Vec2d
              + : super ffi::Struct::•()
              + ;
              + constructor #fromTypedDataBase(synthesized core::Object #typedDataBase) → self::Vec2d
              + : super ffi::Struct::_fromTypedDataBase(#typedDataBase)
              + ;
              + @#C7
              + get x() → core::double
              + return ffi::_loadDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
              + @#C7
              + set x(synthesized core::double #externalFieldValue) → void
              + return ffi::_storeDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
              + @#C7
              + get y() → core::double
              + return ffi::_loadDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C11.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
              + @#C7
              + set y(synthesized core::double #externalFieldValue) → void
              + return ffi::_storeDouble(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C11.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
              + @#C13
              + static get #sizeOf() → core::int*
              + return #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
              +}
              +@#C20
              +final class MyUnion extends ffi::Union {
              + synthetic constructor •() → self::MyUnion
              + : super ffi::Union::•()
              + ;
              + constructor #fromTypedDataBase(synthesized core::Object #typedDataBase) → self::MyUnion
              + : super ffi::Union::_fromTypedDataBase(#typedDataBase)
              + ;
              + get vector() → self::Vec2d
              + return new self::Vec2d::#fromTypedDataBase( block {
              + synthesized core::Object #typedDataBase = this.{ffi::_Compound::_typedDataBase}{core::Object};
              + synthesized core::int #offset = #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
              + } =>#typedDataBase is{ForLegacy} ffi::Pointer<ffi::NativeType> ?{core::Object} ffi::_fromAddress<self::Vec2d>(#typedDataBase.{ffi::Pointer::address}{core::int}.{core::num::+}(#offset){(core::num) → core::num}) : let synthesized typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in #typedData.{typ::TypedData::buffer}{typ::ByteBuffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}{core::int}.{core::num::+}(#offset){(core::num) → core::num}, #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}){([core::int, core::int?]) → typ::Uint8List});
              + set vector(synthesized self::Vec2d #externalFieldValue) → void
              + return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #C8, #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
              + get indirectVector() → ffi::Pointer<self::Vec2d>
              + return ffi::_loadPointer<self::Vec2d>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
              + set indirectVector(synthesized ffi::Pointer<self::Vec2d> #externalFieldValue) → void
              + return ffi::_storePointer<self::Vec2d>(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
              + @#C13
              + static get #sizeOf() → core::int*
              + return #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
              +}
              +@#C26
              +static get aString() → ffi::Pointer<ffi::Char>
              + return ffi::_loadPointer<ffi::Char>(ffi::Native::_addressOf<ffi::Pointer<ffi::Char>>(#C25), #C8);
              +static set aString(synthesized ffi::Pointer<ffi::Char> #externalFieldValue) → void
              + ffi::_storePointer<ffi::Char>(ffi::Native::_addressOf<ffi::Pointer<ffi::Char>>(#C25), #C8, #externalFieldValue);
              +@#C29
              +static get anInt() → core::int
              + return ffi::_loadInt32(ffi::Native::_addressOf<ffi::Int32>(#C28), #C8);
              +static set anInt(synthesized core::int #externalFieldValue) → void
              + ffi::_storeInt32(ffi::Native::_addressOf<ffi::Int32>(#C28), #C8, #externalFieldValue);
              +@#C32
              +static get anotherInt() → core::int
              + return ffi::_loadAbiSpecificInt<ffi::Int>(ffi::Native::_addressOf<ffi::Int>(#C31), #C8);
              +static set anotherInt(synthesized core::int #externalFieldValue) → void
              + ffi::_storeAbiSpecificInt<ffi::Int>(ffi::Native::_addressOf<ffi::Int>(#C31), #C8, #externalFieldValue);
              +@#C35
              +static get vector() → self::Vec2d
              + return new self::Vec2d::#fromTypedDataBase(ffi::Native::_addressOf<self::Vec2d>(#C34));
              +@#C38
              +static get union() → self::MyUnion
              + return new self::MyUnion::#fromTypedDataBase(ffi::Native::_addressOf<self::MyUnion>(#C37));
              +static set union(synthesized self::MyUnion #externalFieldValue) → void
              + ffi::_memCopy(ffi::Native::_addressOf<self::MyUnion>(#C37), #C8, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #C8, self::MyUnion::#sizeOf);
              +static method main() → void {
              + core::print("first char of string: ${ffi::_loadAbiSpecificInt<ffi::Char>(self::aString, #C8)}");
              + core::print("global int: {${self::anInt}}");
              + self::aString = ffi::nullptr;
              + self::anInt = self::anInt.{core::num::+}(1){(core::num) → core::int};
              + final self::Vec2d vec = self::vector;
              + core::print("(${vec.{self::Vec2d::x}{core::double}}, ${vec.{self::Vec2d::y}{core::double}})");
              + self::union.{self::MyUnion::indirectVector} = ffi::Native::_addressOf<self::Vec2d>(#C34);
              + core::print(ffi::Native::_addressOf<ffi::Int>(#C31));
              + core::print(ffi::Native::_addressOf<self::Vec2d>(#C34));
              + core::print(ffi::Native::_addressOf<self::MyUnion>(#C37));
              +}
              +constants {
              + #C1 = "vm:ffi:struct-fields"
              + #C2 = TypeLiteralConstant(ffi::Double)
              + #C3 = <core::Type>[#C2, #C2]
              + #C4 = null
              + #C5 = ffi::_FfiStructLayout {fieldTypes:#C3, packing:#C4}
              + #C6 = core::pragma {name:#C1, options:#C5}
              + #C7 = ffi::Double {}
              + #C8 = 0
              + #C9 = <core::int*>[#C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8]
              + #C10 = 8
              + #C11 = <core::int*>[#C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10]
              + #C12 = "vm:prefer-inline"
              + #C13 = core::pragma {name:#C12, options:#C4}
              + #C14 = 16
              + #C15 = <core::int*>[#C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14]
              + #C16 = TypeLiteralConstant(self::Vec2d)
              + #C17 = TypeLiteralConstant(ffi::Pointer<ffi::NativeType>)
              + #C18 = <core::Type>[#C16, #C17]
              + #C19 = ffi::_FfiStructLayout {fieldTypes:#C18, packing:#C4}
              + #C20 = core::pragma {name:#C1, options:#C19}
              + #C21 = "cfe:ffi:native-marker"
              + #C22 = "aString"
              + #C23 = "#lib"
              + #C24 = false
              + #C25 = ffi::Native<ffi::Pointer<ffi::Char>> {symbol:#C22, assetId:#C23, isLeaf:#C24}
              + #C26 = core::pragma {name:#C21, options:#C25}
              + #C27 = "anInt"
              + #C28 = ffi::Native<ffi::Int32> {symbol:#C27, assetId:#C23, isLeaf:#C24}
              + #C29 = core::pragma {name:#C21, options:#C28}
              + #C30 = "anotherInt"
              + #C31 = ffi::Native<ffi::Int> {symbol:#C30, assetId:#C23, isLeaf:#C24}
              + #C32 = core::pragma {name:#C21, options:#C31}
              + #C33 = "vector"
              + #C34 = ffi::Native<self::Vec2d> {symbol:#C33, assetId:#C23, isLeaf:#C24}
              + #C35 = core::pragma {name:#C21, options:#C34}
              + #C36 = "union"
              + #C37 = ffi::Native<self::MyUnion> {symbol:#C36, assetId:#C23, isLeaf:#C24}
              + #C38 = core::pragma {name:#C21, options:#C37}
              +}
              diff --git a/runtime/bin/ffi_test/ffi_test_functions.cc b/runtime/bin/ffi_test/ffi_test_functions.cc
              index 7bc707a..11373b2 100644
              --- a/runtime/bin/ffi_test/ffi_test_functions.cc
              +++ b/runtime/bin/ffi_test/ffi_test_functions.cc
              @@ -25,9 +25,11 @@

              #if defined(_WIN32)
              #define DART_EXPORT extern "C" __declspec(dllexport)
              +#define DART_EXPORT_FIELD DART_EXPORT
              #else
              #define DART_EXPORT \
              extern "C" __attribute__((visibility("default"))) __attribute((used))
              +#define DART_EXPORT_FIELD __attribute__((visibility("default")))
              #endif

              namespace dart {
              @@ -41,23 +43,39 @@
              #define CHECK_EQ(X, Y) CHECK((X) == (Y))

              ////////////////////////////////////////////////////////////////////////////////
              +// Tests for Dart -> native fields.
              +struct Coord {
              + double x;
              + double y;
              + Coord* next;
              +};
              +
              +extern "C" {
              +DART_EXPORT_FIELD int32_t globalInt;
              +DART_EXPORT_FIELD Coord globalStruct;
              +DART_EXPORT_FIELD const char* globalString = "Hello Dart!";
              +}
              +
              +////////////////////////////////////////////////////////////////////////////////
              // Tests for Dart -> native calls.
              //
              // Note: If this interface is changed please also update
              -// sdk/runtime/tools/dartfuzz/ffiapi.dart
              -
              -int32_t globalVar;
              +// sdk/runtime/tools/dartfuzz/dartfuzz_ffi_api.dart

              DART_EXPORT void InduceACrash() {
              *reinterpret_cast<int*>(InduceACrash) = 123;
              }

              DART_EXPORT void SetGlobalVar(int32_t v) {
              - globalVar = v;
              + globalInt = v;
              }

              DART_EXPORT int32_t GetGlobalVar() {
              - return globalVar;
              + return globalInt;
              +}
              +
              +DART_EXPORT Coord GetGlobalStruct() {
              + return globalStruct;
              }

              // Sums two ints and adds 42.
              @@ -542,12 +560,6 @@
              return retval;
              }

              -struct Coord {
              - double x;
              - double y;
              - Coord* next;
              -};
              -
              // Transposes Coordinate by (10, 10) and returns next Coordinate.
              // Used for testing struct pointer parameter, struct pointer return value,
              // struct field access, and struct pointer field dereference.
              diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
              index 29a43a0..903781e 100644
              --- a/runtime/vm/compiler/frontend/kernel_to_il.cc
              +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
              @@ -5062,10 +5062,16 @@
              String::ZoneHandle(Z, String::RawCast(native.GetField(asset_id_field)));
              const auto& type_args = TypeArguments::Handle(Z, native.GetTypeArguments());
              ASSERT(type_args.Length() == 1);
              - const auto& native_type =
              - FunctionType::Cast(AbstractType::ZoneHandle(Z, type_args.TypeAt(0)));
              - const intptr_t arg_n =
              - native_type.NumParameters() - native_type.num_implicit_parameters();
              + const auto& native_type = AbstractType::ZoneHandle(Z, type_args.TypeAt(0));
              + intptr_t arg_n;
              + if (native_type.IsFunctionType()) {
              + const auto& native_function_type = FunctionType::Cast(native_type);
              + arg_n = native_function_type.NumParameters() -
              + native_function_type.num_implicit_parameters();
              + } else {
              + // We're looking up the address of a native field.
              + arg_n = 0;
              + }
              const auto& ffi_resolver =
              Function::ZoneHandle(Z, IG->object_store()->ffi_resolver_function());

              diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart
              index 7d5b9c2..577e329 100644
              --- a/sdk/lib/ffi/ffi.dart
              +++ b/sdk/lib/ffi/ffi.dart
              @@ -1495,42 +1495,63 @@
              external static Pointer<Void> get initializeApiDLData;
              }

              -/// Annotation specifying how to bind an external function to native code.
              +/// Annotation binding an external declaration to its native implementation.
              ///
              -/// The annotation applies only to `external` function declarations.
              +/// Can only be applied to `external` declarations of static and top-level
              +/// functions and variables.
              ///
              /// A [Native]-annotated `external` function is implemented by native code.
              /// The implementation is found in the native library denoted by [assetId].
              +/// Similarly, a [Native]-annotated `external` variable is implemented by
              +/// reading from or writing to native memory.
              ///
              /// The compiler and/or runtime provides a binding from [assetId] to native
              /// library, which depends on the target platform.
              /// The compiler/runtime can then resolve/lookup symbols (identifiers)
              -/// against the native library, to find a native function,
              -/// and bind an `external` Dart function declaration to that native function.
              +/// against the native library, to find a native function or a native global
              +/// variable, and bind an `external` Dart function or variable declaration to
              +/// that native declaration.
              +/// By default, the runtime expects a native symbol with the same name as the
              +/// annotated function or variable in Dart. This can be overridden with the
              +/// [symbol] parameter on the annotation.
              ///
              -/// Use this annotation on `external` functions to specify that they
              -/// are resolved against an asset, and to, optionally, provide overrides
              -/// of the default symbol and asset IDs.
              +/// If this annotation is used on a function, then the type argument [T] to the
              +/// [Native] annotation must be a function type representing the native
              +/// function's parameter and return types. The parameter and return types must
              +/// be subtypes of [NativeType].
              ///
              -/// The type argument [T] to the [Native] annotation must be a function type
              -/// representing the native function's parameter and return types.
              -/// The parameter and return types must be subtypes of [NativeType].
              +/// If this annotation is used on an external variable, then the type argument
              +/// [T] must be a compatible native type. For example, an [int] field can be
              +/// annotated with [Int32].
              +/// If the type argument to `@Native` is omitted, it defaults to the Dart type
              +/// of the annotated declaration, which *must* then be a native type too.
              +/// This will never work for function declarations, but can apply to variables
              +/// whose type is some of the types of this library, such as [Pointer].
              +/// For native global variables that cannot be re-assigned, a final variable in
              +/// Dart or a getter can be used to prevent assignments to the native field.
              ///
              /// Example:
              ///
              /// ```dart template:top
              /// @Native<Int64 Function(Int64, Int64)>()
              /// external int sum(int a, int b);
              +///
              +/// @Native<Int64>()
              +/// external int aGlobalInt;
              +///
              +/// @Native()
              +/// external final Pointer<Char> aGlobalString;
              /// ```
              ///
              -/// Calling such function will try to resolve the [symbol] in (in that order)
              +/// Calling a `@Native` function, as well as reading or writing to a `@Native`
              +/// variable, will try to resolve the [symbol] in (in the order):
              /// 1. the provided or default [assetId],
              /// 2. the native resolver set with `Dart_SetFfiNativeResolver` in
              /// `dart_api.h`, and
              /// 3. the current process.
              ///
              /// At least one of those three *must* provide a binding for the symbol,
              -/// otherwise the method call fails.
              +/// otherwise the method call or the variable access fails.
              ///
              /// NOTE: This is an experimental feature and may change in the future.
              @Since('2.19')
              @@ -1614,6 +1635,8 @@
              /// in a group is trying to perform a GC and a second isolate is blocked in a
              /// leaf call, then the first isolate will have to pause and wait until this
              /// leaf call returns.
              + ///
              + /// This value has no meaning for native fields.
              final bool isLeaf;

              const Native({
              @@ -1622,12 +1645,26 @@
              this.symbol,
              });

              - /// The native address of [native].
              + /// The native address of the implementation of [native].
              ///
              - /// [native] must be a reference to a method annotated with `@Native` and [T]
              - /// must be a [NativeFunction] compatible to the signature of [native].
              + /// When calling this function, the argument for [native] must be an
              + /// expression denoting a variable or function declaration which is annotated
              + /// with [Native].
              + /// For a variable declaration, the type [T] must be the same native type
              + /// as the type argument to that `@Native` annotation.
              + /// For a function declaration, the type [T] must be `NativeFunction<F>`
              + /// where `F` was the type argument to that `@Native` annotation.
              ///
              - /// Example:
              + /// For example, for a native C library exposing a function:
              + ///
              + /// ```C
              + /// #include <stdint.h>
              + /// int64_t sum(int64_t a, int64_t b) { return a + b; }
              + /// ```
              + ///
              + /// The following code binds `sum` to a Dart function declaration, and
              + /// extracts the address of the native `sum` implementation:
              + ///
              /// ```dart
              /// import 'dart:ffi';
              ///
              @@ -1637,7 +1674,30 @@
              /// external int sum(int a, int b);
              ///
              /// void main() {
              - /// final address = Native.addressOf<NativeFunction<NativeAdd>>(sum);
              + /// Pointer<NativeFunction<NativeAdd>> addressSum = Native.addressOf(sum);
              + /// }
              + /// ```
              + ///
              + /// Similarly, for a native C library exposing a global variable:
              + ///
              + /// ```C
              + /// const char* myString;
              + /// ```
              + ///
              + /// The following code binds `myString` to a top-level variable in Dart, and
              + /// extracts the address of the underlying native field:
              + ///
              + /// ```dart
              + /// import 'dart:ffi';
              + ///
              + /// @Native()
              + /// external Pointer<Char> myString;
              + ///
              + /// void main() {
              + /// // This pointer points to the memory location where the loader has
              + /// // placed the `myString` global itself. To get the string value, read
              + /// // the myString field directly.
              + /// Pointer<Pointer<Char>> addressMyString = Native.addressOf(myString);
              /// }
              /// ```
              @Since('3.3')
              diff --git a/tests/ffi/native_assets/asset_absolute_test.dart b/tests/ffi/native_assets/asset_absolute_test.dart
              index 6cd3595..7b44c53 100644
              --- a/tests/ffi/native_assets/asset_absolute_test.dart
              +++ b/tests/ffi/native_assets/asset_absolute_test.dart
              @@ -16,6 +16,7 @@
              // SharedObjects=ffi_test_functions

              import 'dart:async';
              +import 'dart:convert';
              import 'dart:ffi';
              import 'dart:io';

              @@ -65,6 +66,7 @@

              Future<void> runTests() async {
              testFfiTestfunctionsDll();
              + testFfiTestFieldsDll();
              testNonExistingFunction();
              }

              @@ -80,3 +82,68 @@
              .asFunction<int Function(int, int)>();
              Expect.equals(2 + 3 + 42, viaAddressOf(2, 3));
              }
              +
              +@Native<Int32>()
              +external int globalInt;
              +
              +@Native<Int32>(symbol: 'globalInt')
              +external int get globalIntProcedure;
              +
              +@Native<Int32>(symbol: 'globalInt')
              +external set globalIntProcedure(int value);
              +
              +@Native<Void Function(Int32)>()
              +external void SetGlobalVar(int value);
              +
              +@Native<Int32 Function()>()
              +external int GetGlobalVar();
              +
              +@Native()
              +external final Pointer<Char> globalString;
              +
              +final class Coord extends Struct {
              + @Double()
              + external double x;
              +
              + @Double()
              + external double y;
              +
              + external Pointer<Coord> next;
              +}
              +
              +@Native()
              +external Coord globalStruct;
              +
              +@Native<Coord Function()>()
              +external Coord GetGlobalStruct();
              +
              +void testFfiTestFieldsDll() {
              + SetGlobalVar(42);
              + Expect.equals(globalInt, 42);
              + Expect.equals(globalIntProcedure, 42);
              + globalInt = 13;
              + Expect.equals(GetGlobalVar(), 13);
              + globalIntProcedure = 26;
              + Expect.equals(GetGlobalVar(), 26);
              +
              + var readString = utf8.decode(globalString.cast<Uint8>().asTypedList(11));
              + Expect.equals(readString, 'Hello Dart!');
              +
              + globalStruct
              + ..x = 1
              + ..y = 2
              + ..next = nullptr;
              + final viaFunction = GetGlobalStruct();
              + Expect.equals(viaFunction.x, 1.0);
              + Expect.equals(viaFunction.y, 2.0);
              + Expect.equals(viaFunction.next, nullptr);
              +
              + viaFunction.x *= 2;
              + viaFunction.y *= 2;
              + viaFunction.next = Pointer.fromAddress(0xdeadbeef);
              + globalStruct = viaFunction;
              +
              + Expect.equals(globalStruct.x, 2.0);
              + Expect.equals(globalStruct.y, 4.0);
              + Expect.equals(globalStruct.next.address, 0xdeadbeef);
              +}
              diff --git a/tests/ffi/native_assets/asset_library_annotation_test.dart b/tests/ffi/native_assets/asset_library_annotation_test.dart
              index 84b5759..9e60b5d 100644
              --- a/tests/ffi/native_assets/asset_library_annotation_test.dart
              +++ b/tests/ffi/native_assets/asset_library_annotation_test.dart
              @@ -13,6 +13,7 @@
              library asset_test;

              import 'dart:async';
              +import 'dart:convert';
              import 'dart:ffi';
              import 'dart:io';

              @@ -63,6 +64,7 @@

              Future<void> runTests() async {
              testFfiTestfunctionsDll();
              + testFfiTestFieldsDll();
              }

              @Native<Int32 Function(Int32, Int32)>()
              @@ -77,3 +79,68 @@
              final function = ptr.asFunction<int Function(int, int)>();
              Expect.equals(2 + 3 + 42, function(2, 3));
              }
              +
              +@Native<Int32>()
              +external int globalInt;
              +
              +@Native<Int32>(symbol: 'globalInt')
              +external int get globalIntProcedure;
              +
              +@Native<Int32>(symbol: 'globalInt')
              +external set globalIntProcedure(int value);
              +
              +@Native<Void Function(Int32)>()
              +external void SetGlobalVar(int value);
              +
              +@Native<Int32 Function()>()
              +external int GetGlobalVar();
              +
              +@Native()
              +external final Pointer<Char> globalString;
              +
              +final class Coord extends Struct {
              + @Double()
              + external double x;
              +
              + @Double()
              + external double y;
              +
              + external Pointer<Coord> next;
              +}
              +
              +@Native()
              +external Coord globalStruct;
              +
              +@Native<Coord Function()>()
              +external Coord GetGlobalStruct();
              +
              +void testFfiTestFieldsDll() {
              + SetGlobalVar(42);
              + Expect.equals(globalInt, 42);
              + Expect.equals(globalIntProcedure, 42);
              + globalInt = 13;
              + Expect.equals(GetGlobalVar(), 13);
              + globalIntProcedure = 26;
              + Expect.equals(GetGlobalVar(), 26);
              +
              + var readString = utf8.decode(globalString.cast<Uint8>().asTypedList(11));
              + Expect.equals(readString, 'Hello Dart!');
              +
              + globalStruct
              + ..x = 1
              + ..y = 2
              + ..next = nullptr;
              + final viaFunction = GetGlobalStruct();
              + Expect.equals(viaFunction.x, 1.0);
              + Expect.equals(viaFunction.y, 2.0);
              + Expect.equals(viaFunction.next, nullptr);
              +
              + viaFunction.x *= 2;
              + viaFunction.y *= 2;
              + viaFunction.next = Pointer.fromAddress(0xdeadbeef);
              + globalStruct = viaFunction;
              +
              + Expect.equals(globalStruct.x, 2.0);
              + Expect.equals(globalStruct.y, 4.0);
              + Expect.equals(globalStruct.next.address, 0xdeadbeef);
              +}
              diff --git a/tests/ffi/native_assets/asset_relative_test.dart b/tests/ffi/native_assets/asset_relative_test.dart
              index ea0bef2..a5e3d7d 100644
              --- a/tests/ffi/native_assets/asset_relative_test.dart
              +++ b/tests/ffi/native_assets/asset_relative_test.dart
              @@ -17,6 +17,7 @@
              // SharedObjects=ffi_test_functions

              import 'dart:async';
              +import 'dart:convert';
              import 'dart:ffi';
              import 'dart:io';

              @@ -120,6 +121,7 @@
              Future<void> runTests() async {
              testFfiTestfunctionsDll();
              testNonExistingFunction();
              + testFfiTestFieldsDll();
              }

              @Native<Int32 Function(Int32, Int32)>()
              @@ -135,3 +137,68 @@

              Expect.equals(2 + 3 + 42, viaAddressOf(2, 3));
              }
              +
              +@Native<Int32>()
              +external int globalInt;
              +
              +@Native<Int32>(symbol: 'globalInt')
              +external int get globalIntProcedure;
              +
              +@Native<Int32>(symbol: 'globalInt')
              +external set globalIntProcedure(int value);
              +
              +@Native<Void Function(Int32)>()
              +external void SetGlobalVar(int value);
              +
              +@Native<Int32 Function()>()
              +external int GetGlobalVar();
              +
              +@Native()
              +external final Pointer<Char> globalString;
              +
              +final class Coord extends Struct {
              + @Double()
              + external double x;
              +
              + @Double()
              + external double y;
              +
              + external Pointer<Coord> next;
              +}
              +
              +@Native()
              +external Coord globalStruct;
              +
              +@Native<Coord Function()>()
              +external Coord GetGlobalStruct();
              +
              +void testFfiTestFieldsDll() {
              + SetGlobalVar(42);
              + Expect.equals(globalInt, 42);
              + Expect.equals(globalIntProcedure, 42);
              + globalInt = 13;
              + Expect.equals(GetGlobalVar(), 13);
              + globalIntProcedure = 26;
              + Expect.equals(GetGlobalVar(), 26);
              +
              + var readString = utf8.decode(globalString.cast<Uint8>().asTypedList(11));
              + Expect.equals(readString, 'Hello Dart!');
              +
              + globalStruct
              + ..x = 1
              + ..y = 2
              + ..next = nullptr;
              + final viaFunction = GetGlobalStruct();
              + Expect.equals(viaFunction.x, 1.0);
              + Expect.equals(viaFunction.y, 2.0);
              + Expect.equals(viaFunction.next, nullptr);
              +
              + viaFunction.x *= 2;
              + viaFunction.y *= 2;
              + viaFunction.next = Pointer.fromAddress(0xdeadbeef);
              + globalStruct = viaFunction;
              +
              + Expect.equals(globalStruct.x, 2.0);
              + Expect.equals(globalStruct.y, 4.0);
              + Expect.equals(globalStruct.next.address, 0xdeadbeef);
              +}
              diff --git a/tests/ffi/vmspecific_static_checks_native_test.dart b/tests/ffi/vmspecific_static_checks_native_test.dart
              index 4cc38e6..da4fab0 100644
              --- a/tests/ffi/vmspecific_static_checks_native_test.dart
              +++ b/tests/ffi/vmspecific_static_checks_native_test.dart
              @@ -41,14 +41,66 @@
              // [analyzer] COMPILE_TIME_ERROR.FFI_NATIVE_INVALID_MULTIPLE_ANNOTATIONS
              external int foo(int v);
              // ^
              -// [cfe] Native functions must not have more than @Native annotation.
              +// [cfe] Native functions and fields must not have more than @Native annotation.
              +
              +@Native()
              +external final MyStruct myStruct0;
              +
              +@Native<MyStruct>()
              +external MyStruct myStruct1;
              +
              +@Native<Pointer<MyStruct>>()
              +external MyStruct myStructInvalid;
              +// ^^^^^^^^^^^^^^^
              +// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
              +// [cfe] Expected type 'MyStruct' to be 'Pointer<MyStruct>', which is the Dart type corresponding to 'Pointer<MyStruct>'.
              +
              +@Native()
              +external Pointer<MyStruct> myStructPtr0;
              +
              +@Native<Pointer<MyStruct>>()
              +external final Pointer<MyStruct> myStructPtr1;
              +
              +@Native<MyStruct>()
              +external Pointer<MyStruct> myStructPtrInvalid;
              +// ^^^^^^^^^^^^^^^^^^
              +// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
              +// [cfe] Expected type 'Pointer<MyStruct>' to be 'MyStruct', which is the Dart type corresponding to 'MyStruct'.
              +
              +@Native()
              +external int invalidNoInferrence;
              +// ^^^^^^^^^^^^^^^^^^^
              +// [analyzer] COMPILE_TIME_ERROR.NATIVE_FIELD_MISSING_TYPE
              +// [cfe] The native type of this field could not be inferred and must be specified in the annotation.
              +
              +@Native<Handle>()
              +external Object invalidUnsupportedHandle;
              +// ^^^^^^^^^^^^^^^^^^^^^^^^
              +// [analyzer] COMPILE_TIME_ERROR.NATIVE_FIELD_INVALID_TYPE
              +// [cfe] Unsupported type for native fields. Native fields only support pointers, compounds and numeric types.
              +
              +@Native()
              +external Array<IntPtr> invalidUnsupportedArray;
              +// ^^^^^^^^^^^^^^^^^^^^^^^
              +// [analyzer] COMPILE_TIME_ERROR.NATIVE_FIELD_INVALID_TYPE
              +// [cfe] Unsupported type for native fields. Native fields only support pointers, compounds and numeric types.
              +
              +class MyClass {
              + @Native<Double>()
              + external double invalidInstanceField;
              + // ^^^^^^^^^^^^^^^^^^^^
              + // [analyzer] COMPILE_TIME_ERROR.NATIVE_FIELD_NOT_STATIC
              + // [cfe] Native fields must be static.
              +
              + @Native<Double>()
              + external static double validField;
              +}

              void addressOf() {
              Native.addressOf<NativeFunction<Void Function()>>(notNative);
              - // ^
              - // [cfe] Argument to 'Native.addressOf' must be annotated with @Native.
              // ^^^^^^^^^
              // [analyzer] COMPILE_TIME_ERROR.ARGUMENT_MUST_BE_NATIVE
              + // [cfe] Argument to 'Native.addressOf' must be annotated with @Native.

              var boolean = 1 == 2;
              Native.addressOf<NativeFunction<Void Function()>>(boolean ? _valid : _valid2);
              @@ -79,4 +131,15 @@
              // [cfe] Expected type 'void Function()' to be 'void Function(int)', which is the Dart type corresponding to 'NativeFunction<Void Function(Int)>'.

              Native.addressOf<NativeFunction<ComplexNativeFunction>>(validNative);
              +
              + Native.addressOf<MyStruct>(myStruct0);
              + Native.addressOf<MyStruct>(myStruct1);
              + Native.addressOf<Pointer<MyStruct>>(myStructPtr0);
              + Native.addressOf<Pointer<MyStruct>>(myStructPtr1);
              +
              + Native.addressOf<Int>(myStruct0);
              +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
              +// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
              +// ^
              +// [cfe] Expected type 'MyStruct' to be 'int', which is the Dart type corresponding to 'Int'.
              }

              To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

              Gerrit-MessageType: merged
              Gerrit-Project: sdk
              Gerrit-Branch: main
              Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
              Gerrit-Change-Number: 338020
              Gerrit-PatchSet: 24

              CBuild (Gerrit)

              unread,
              Dec 15, 2023, 6:48:11 AM12/15/23
              to Daco Harkes, Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

                To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

                Gerrit-MessageType: comment
                Gerrit-Project: sdk
                Gerrit-Branch: main
                Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
                Gerrit-Change-Number: 338020
                Gerrit-PatchSet: 24
                Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
                Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
                Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
                Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
                Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
                Gerrit-CC: Alexander Markov <alexm...@google.com>
                Gerrit-CC: Jens Johansen <je...@google.com>
                Gerrit-CC: Johnni Winther <johnni...@google.com>
                Gerrit-CC: Paul Berry <paul...@google.com>
                Gerrit-Comment-Date: Fri, 15 Dec 2023 11:48:06 +0000
                Gerrit-HasComments: No
                Gerrit-Has-Labels: No

                Brian Quinlan (Gerrit)

                unread,
                Dec 15, 2023, 1:22:11 PM12/15/23
                to Daco Harkes, Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

                Brian Quinlan has created a revert of this change.

                View Change

                To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

                Gerrit-MessageType: revert

                Daco Harkes (Gerrit)

                unread,
                Dec 18, 2023, 2:25:03 PM12/18/23
                to Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Marya Belanger, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

                View Change

                1 comment:

                • Patchset:

                  • Patch Set #24:

                    Hey Marya,

                    PTAL at pkg/analyzer/messages.yaml for the doc changes.

                    Kind regards & happy holidays,

                    Daco

                To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

                Gerrit-MessageType: comment
                Gerrit-Project: sdk
                Gerrit-Branch: main
                Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
                Gerrit-Change-Number: 338020
                Gerrit-PatchSet: 24
                Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
                Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
                Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
                Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
                Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
                Gerrit-Reviewer: Marya Belanger <mbel...@google.com>
                Gerrit-CC: Alexander Markov <alexm...@google.com>
                Gerrit-CC: Jens Johansen <je...@google.com>
                Gerrit-CC: Johnni Winther <johnni...@google.com>
                Gerrit-CC: Paul Berry <paul...@google.com>
                Gerrit-Comment-Date: Mon, 18 Dec 2023 19:24:58 +0000
                Gerrit-HasComments: Yes
                Gerrit-Has-Labels: No

                Daco Harkes (Gerrit)

                unread,
                Dec 18, 2023, 2:25:04 PM12/18/23
                to Marya Belanger, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, Simon Binder, Chloe Stefantsova, Lasse Nielsen, Brian Wilkerson

                Daco Harkes would like Marya Belanger to review this change authored by Simon Binder.

                View Change

                To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

                Gerrit-MessageType: newchange

                Marya Belanger (Gerrit)

                unread,
                Dec 18, 2023, 3:52:21 PM12/18/23
                to Daco Harkes, Simon Binder, dart-analys...@google.com, dart-fe-te...@google.com, dart-uxr...@google.com, dart-vm-compil...@google.com, rev...@dartlang.org, vm-...@dartlang.org, CBuild, Commit Queue, Chloe Stefantsova, Lasse Nielsen, Johnni Winther, Jens Johansen, Brian Wilkerson, Alexander Markov, Paul Berry

                View Change

                1 comment:

                To view, visit change 338020. To unsubscribe, or for help writing mail filters, visit settings.

                Gerrit-MessageType: comment
                Gerrit-Project: sdk
                Gerrit-Branch: main
                Gerrit-Change-Id: I61dccc88076723d6a6ba02d7fd848b18e4caf780
                Gerrit-Change-Number: 338020
                Gerrit-PatchSet: 24
                Gerrit-Owner: Simon Binder <o...@simonbinder.eu>
                Gerrit-Reviewer: Brian Wilkerson <brianwi...@google.com>
                Gerrit-Reviewer: Chloe Stefantsova <cstefa...@google.com>
                Gerrit-Reviewer: Daco Harkes <dacoh...@google.com>
                Gerrit-Reviewer: Lasse Nielsen <l...@google.com>
                Gerrit-Reviewer: Marya Belanger <mbel...@google.com>
                Gerrit-CC: Alexander Markov <alexm...@google.com>
                Gerrit-CC: Jens Johansen <je...@google.com>
                Gerrit-CC: Johnni Winther <johnni...@google.com>
                Gerrit-CC: Paul Berry <paul...@google.com>
                Gerrit-Comment-Date: Mon, 18 Dec 2023 20:52:17 +0000
                Gerrit-HasComments: Yes
                Gerrit-Has-Labels: No
                Comment-In-Reply-To: Daco Harkes <dacoh...@google.com>
                Reply all
                Reply to author
                Forward
                0 new messages