Skip to content

Commit b6b82dd

Browse files
dcharkescommit-bot@chromium.org
authored andcommitted
[vm/ffi] Support nested structs
This CL adds support for nested structs in FFI calls, callbacks, and memory loads and stores through the Struct classes itself. Nesting empty structs and nesting a structs in themselves (directly or indirectly) is reported as error. This feature is almost fully implemented in the CFE transformation. Because structs depend on the sizes of their nested structs, the structs now need to be processed in topological order. Field access to nested structs branches at runtime on making a derived Pointer if the backing memory of the outer struct was a Pointer or making a TypedDataView if the backing memory of the outer struct was a TypedData. Assigning to a nested struct is a byte for byte copy from the source. The only changes in the VM are contained in the native calling convention calculation which now recursively needs to reason about fundamental types instead of just 1 struct deep. Because of the amount of corner cases in the calling conventions that need to be covered, the tests are generated, rather than hand-written. ABIs tested on CQ: x64 (Linux, MacOS, Windows), ia32 (Linux, Windows), arm (Android softFP, Linux hardFP), arm64 Android. ABIs tested locally through Flutter: arm64 iOS. ABIs not tested: ia32 Android (emulator), x64 iOS (simulator), arm iOS. TEST=runtime/bin/ffi_test/ffi_test_functions_generated.cc TEST=runtime/bin/ffi_test/ffi_test_functions.cc TEST=tests/{ffi,ffi_2}/function_structs_by_value_generated_test.dart TEST=tests/{ffi,ffi_2}/function_callbacks_structs_by_value_generated_tes TEST=tests/{ffi,ffi_2}/function_callbacks_structs_by_value_test.dart TEST=tests/{ffi,ffi_2}/vmspecific_static_checks_test.dart Closes #37271. Contains a temporary workaround for #44454. Change-Id: I5e5d10e09e5c3fc209f5f7e997efe17bd362214d Cq-Include-Trybots: luci.dart.try:dart-sdk-linux-try,dart-sdk-mac-try,dart-sdk-win-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-mac-debug-x64-try,vm-kernel-linux-debug-ia32-try,vm-kernel-linux-debug-x64-try,vm-kernel-nnbd-linux-debug-x64-try,vm-kernel-nnbd-linux-debug-ia32-try,vm-kernel-nnbd-mac-release-x64-try,vm-kernel-nnbd-win-debug-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-debug-simarm_x64-try,vm-kernel-precomp-nnbd-linux-debug-x64-try,vm-kernel-precomp-win-release-x64-try,vm-kernel-reload-linux-debug-x64-try,vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-precomp-ffi-qemu-linux-release-arm-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-msan-linux-release-x64-try,vm-kernel-precomp-msan-linux-release-x64-try,vm-kernel-precomp-android-release-arm_x64-try,analyzer-analysis-server-linux-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/169221 Commit-Queue: Daco Harkes <[email protected]> Reviewed-by: Martin Kustermann <[email protected]> Reviewed-by: Dmitry Stefantsov <[email protected]>
1 parent ba0a7c5 commit b6b82dd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+14746
-5020
lines changed

pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3788,6 +3788,31 @@ Message _withArgumentsFfiFieldAnnotation(String name) {
37883788
arguments: {'name': name});
37893789
}
37903790

3791+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3792+
const Template<Message Function(String name, List<String> _names)>
3793+
templateFfiFieldCyclic =
3794+
const Template<Message Function(String name, List<String> _names)>(
3795+
messageTemplate: r"""Struct '#name' contains itself. Cycle elements:
3796+
#names""", withArguments: _withArgumentsFfiFieldCyclic);
3797+
3798+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3799+
const Code<Message Function(String name, List<String> _names)>
3800+
codeFfiFieldCyclic =
3801+
const Code<Message Function(String name, List<String> _names)>(
3802+
"FfiFieldCyclic",
3803+
);
3804+
3805+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3806+
Message _withArgumentsFfiFieldCyclic(String name, List<String> _names) {
3807+
if (name.isEmpty) throw 'No name provided';
3808+
name = demangleMixinApplicationName(name);
3809+
if (_names.isEmpty) throw 'No names provided';
3810+
String names = itemizeNames(_names);
3811+
return new Message(codeFfiFieldCyclic,
3812+
message: """Struct '${name}' contains itself. Cycle elements:
3813+
${names}""", arguments: {'name': name, 'names': _names});
3814+
}
3815+
37913816
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
37923817
const Template<
37933818
Message Function(String name)> templateFfiFieldInitializer = const Template<

pkg/analyzer/lib/error/error.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ const List<ErrorCode> errorCodeValues = [
450450
CompileTimeErrorCode.YIELD_IN_NON_GENERATOR,
451451
CompileTimeErrorCode.YIELD_OF_INVALID_TYPE,
452452
FfiCode.ANNOTATION_ON_POINTER_FIELD,
453+
FfiCode.EMPTY_STRUCT,
453454
FfiCode.EXTRA_ANNOTATION_ON_STRUCT_FIELD,
454455
FfiCode.FIELD_IN_STRUCT_WITH_INITIALIZER,
455456
FfiCode.FIELD_INITIALIZER_IN_STRUCT,

pkg/analyzer/lib/src/dart/error/ffi_code.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ class FfiCode extends AnalyzerErrorCode {
2222
"any annotations.",
2323
correction: "Try removing the annotation.");
2424

25+
/**
26+
* Parameters:
27+
* 0: the name of the struct class
28+
*/
29+
static const FfiCode EMPTY_STRUCT = FfiCode(
30+
name: 'EMPTY_STRUCT',
31+
message: "Struct '{0}' is empty. Empty structs are undefined behavior.",
32+
correction: "Try adding a field to '{0}' or use a different Struct.");
33+
2534
/**
2635
* No parameters.
2736
*/
@@ -76,8 +85,9 @@ class FfiCode extends AnalyzerErrorCode {
7685
name: 'INVALID_FIELD_TYPE_IN_STRUCT',
7786
message:
7887
"Fields in struct classes can't have the type '{0}'. They can only "
79-
"be declared as 'int', 'double' or 'Pointer'.",
80-
correction: "Try using 'int', 'double' or 'Pointer'.");
88+
"be declared as 'int', 'double', 'Pointer', or subtype of 'Struct'.",
89+
correction:
90+
"Try using 'int', 'double', 'Pointer', or subtype of 'Struct'.");
8191

8292
/**
8393
* No parameters.

pkg/analyzer/lib/src/generated/ffi_verifier.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
174174
structFieldCount++;
175175
} else if (_isPointer(declaredType.element)) {
176176
structFieldCount++;
177+
} else if (_isStructClass(declaredType)) {
178+
structFieldCount++;
177179
}
178180
}
179181
return structFieldCount == 0;
@@ -228,7 +230,11 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
228230
/// Returns `true` iff [nativeType] is a struct type.
229231
bool _isStructClass(DartType nativeType) {
230232
if (nativeType is InterfaceType) {
231-
final superClassElement = nativeType.element.supertype.element;
233+
final superType = nativeType.element.supertype;
234+
if (superType == null) {
235+
return false;
236+
}
237+
final superClassElement = superType.element;
232238
if (superClassElement.library.name == 'dart.ffi') {
233239
return superClassElement.name == 'Struct' &&
234240
nativeType.typeArguments?.isEmpty == true;
@@ -518,6 +524,12 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
518524
_validateAnnotations(fieldType, annotations, _PrimitiveDartType.double);
519525
} else if (_isPointer(declaredType.element)) {
520526
_validateNoAnnotations(annotations);
527+
} else if (_isStructClass(declaredType)) {
528+
final clazz = (declaredType as InterfaceType).element;
529+
if (_isEmptyStruct(clazz)) {
530+
_errorReporter
531+
.reportErrorForNode(FfiCode.EMPTY_STRUCT, node, [clazz.name]);
532+
}
521533
} else {
522534
_errorReporter.reportErrorForNode(FfiCode.INVALID_FIELD_TYPE_IN_STRUCT,
523535
fieldType, [fieldType.toSource()]);

pkg/front_end/lib/src/api_unstable/vm.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export '../fasta/fasta_codes.dart'
5252
templateFfiExpectedNoExceptionalReturn,
5353
templateFfiExtendsOrImplementsSealedClass,
5454
templateFfiFieldAnnotation,
55+
templateFfiFieldCyclic,
5556
templateFfiFieldInitializer,
5657
templateFfiFieldNoAnnotation,
5758
templateFfiNotStatic,

pkg/front_end/messages.status

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ FfiExpectedExceptionalReturn/analyzerCode: Fail
322322
FfiExpectedNoExceptionalReturn/analyzerCode: Fail
323323
FfiExtendsOrImplementsSealedClass/analyzerCode: Fail
324324
FfiFieldAnnotation/analyzerCode: Fail
325+
FfiFieldCyclic/analyzerCode: Fail
325326
FfiFieldInitializer/analyzerCode: Fail
326327
FfiFieldNoAnnotation/analyzerCode: Fail
327328
FfiNotStatic/analyzerCode: Fail

pkg/front_end/messages.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4253,6 +4253,13 @@ FfiFieldNoAnnotation:
42534253
template: "Field '#name' requires no annotation to declare its native type, it is a Pointer which is represented by the same type in Dart and native code."
42544254
external: test/ffi_test.dart
42554255

4256+
FfiFieldCyclic:
4257+
# Used by dart:ffi
4258+
template: |
4259+
Struct '#name' contains itself. Cycle elements:
4260+
#names
4261+
external: test/ffi_test.dart
4262+
42564263
FfiNotStatic:
42574264
# Used by dart:ffi
42584265
template: "#name expects a static function as parameter. dart:ffi only supports calling static Dart functions from native code."

pkg/front_end/testcases/general/ffi_sample.dart.strong.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class Coordinate extends ffi::Struct {
1313
@#C3
1414
static final field core::int* #sizeOf = (#C11).{core::List::[]}(ffi::_abi());
1515
@#C3
16-
constructor #fromPointer(dynamic #pointer) → dynamic
16+
constructor #fromTypedDataBase(dynamic #pointer) → dynamic
1717
: super ffi::Struct::_fromPointer(#pointer)
1818
;
1919
static factory allocate(core::double* x, core::double* y, ffi::Pointer<self::Coordinate*>* next) → self::Coordinate* {

pkg/front_end/testcases/general_nnbd_opt_out/ffi_sample.dart.strong.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Coordinate extends ffi::Struct {
1212
@#C3
1313
static final field core::int* #sizeOf = (#C6).{core::List::[]}(ffi::_abi());
1414
@#C3
15-
constructor #fromPointer(dynamic #pointer) → dynamic
15+
constructor #fromTypedDataBase(dynamic #pointer) → dynamic
1616
: super ffi::Struct::_fromPointer(#pointer)
1717
;
1818
static factory allocate(core::double* x, core::double* y, ffi::Pointer<self::Coordinate*>* next) → self::Coordinate* {

pkg/front_end/testcases/general_nnbd_opt_out/ffi_sample.dart.weak.transformed.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class Coordinate extends ffi::Struct {
1313
@#C3
1414
static final field core::int* #sizeOf = (#C11).{core::List::[]}(ffi::_abi());
1515
@#C3
16-
constructor #fromPointer(dynamic #pointer) → dynamic
16+
constructor #fromTypedDataBase(dynamic #pointer) → dynamic
1717
: super ffi::Struct::_fromPointer(#pointer)
1818
;
1919
static factory allocate(core::double* x, core::double* y, ffi::Pointer<self::Coordinate*>* next) → self::Coordinate* {

pkg/front_end/testcases/incremental_initialize_from_dill/ffi_01.yaml.world.1.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ library from "org-dartlang-test:///lib.dart" as lib {
99
@#C3
1010
static final field dart.core::int* #sizeOf = (#C11).{dart.core::List::[]}(dart.ffi::_abi());
1111
@#C3
12-
constructor #fromPointer(dynamic #pointer) → dynamic
12+
constructor #fromTypedDataBase(dynamic #pointer) → dynamic
1313
: super dart.ffi::Struct::_fromPointer(#pointer)
1414
;
1515
static factory allocate(dart.core::double* x, dart.core::double* y, dart.ffi::Pointer<lib::Coordinate*>* next) → lib::Coordinate* {

pkg/front_end/testcases/incremental_initialize_from_dill/ffi_01.yaml.world.2.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ library from "org-dartlang-test:///lib.dart" as lib {
99
@#C3
1010
static final field dart.core::int* #sizeOf = (#C11).{dart.core::List::[]}(dart.ffi::_abi());
1111
@#C3
12-
constructor #fromPointer(dynamic #pointer) → dynamic
12+
constructor #fromTypedDataBase(dynamic #pointer) → dynamic
1313
: super dart.ffi::Struct::_fromPointer(#pointer)
1414
;
1515
static factory allocate(dart.core::double* x, dart.core::double* y, dart.ffi::Pointer<lib::Coordinate*>* next) → lib::Coordinate* {

pkg/front_end/testcases/incremental_initialize_from_dill/ffi_02.yaml.world.1.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ library from "org-dartlang-test:///lib.dart" as lib {
99
@#C3
1010
static final field dart.core::int* #sizeOf = (#C11).{dart.core::List::[]}(dart.ffi::_abi());
1111
@#C3
12-
constructor #fromPointer(dynamic #pointer) → dynamic
12+
constructor #fromTypedDataBase(dynamic #pointer) → dynamic
1313
: super dart.ffi::Struct::_fromPointer(#pointer)
1414
;
1515
static factory allocate(dart.core::double* x, dart.core::double* y, dart.ffi::Pointer<lib::Coordinate*>* next) → lib::Coordinate* {

pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.1.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ library from "org-dartlang-test:///lib.dart" as lib {
99
@#C3
1010
static final field dart.core::int* #sizeOf = (#C11).{dart.core::List::[]}(dart.ffi::_abi());
1111
@#C3
12-
constructor #fromPointer(dynamic #pointer) → dynamic
12+
constructor #fromTypedDataBase(dynamic #pointer) → dynamic
1313
: super dart.ffi::Struct::_fromPointer(#pointer)
1414
;
1515
static factory allocate(dart.core::double* x, dart.core::double* y, dart.ffi::Pointer<lib::Coordinate*>* next) → lib::Coordinate* {

pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.2.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ library from "org-dartlang-test:///lib.dart" as lib {
99
@#C3
1010
static final field dart.core::int* #sizeOf = (#C11).{dart.core::List::[]}(dart.ffi::_abi());
1111
@#C3
12-
constructor #fromPointer(dynamic #pointer) → dynamic
12+
constructor #fromTypedDataBase(dynamic #pointer) → dynamic
1313
: super dart.ffi::Struct::_fromPointer(#pointer)
1414
;
1515
static factory allocate(dart.core::double* x, dart.core::double* y, dart.ffi::Pointer<lib::Coordinate*>* next) → lib::Coordinate* {

pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.3.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ library from "org-dartlang-test:///lib.dart" as lib {
99
@#C3
1010
static final field dart.core::int* #sizeOf = (#C11).{dart.core::List::[]}(dart.ffi::_abi());
1111
@#C3
12-
constructor #fromPointer(dynamic #pointer) → dynamic
12+
constructor #fromTypedDataBase(dynamic #pointer) → dynamic
1313
: super dart.ffi::Struct::_fromPointer(#pointer)
1414
;
1515
static factory allocate(dart.core::double* x, dart.core::double* y, dart.ffi::Pointer<lib::Coordinate*>* next) → lib::Coordinate* {

pkg/vm/lib/transformations/ffi.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,16 @@ class FfiTransformer extends Transformer {
193193
final Class doubleClass;
194194
final Class listClass;
195195
final Class typeClass;
196+
final Procedure unsafeCastMethod;
197+
final Class typedDataClass;
198+
final Procedure typedDataBufferGetter;
199+
final Procedure typedDataOffsetInBytesGetter;
200+
final Procedure byteBufferAsUint8List;
196201
final Class pragmaClass;
197202
final Field pragmaName;
198203
final Field pragmaOptions;
199204
final Procedure listElementAt;
205+
final Procedure numAddition;
200206

201207
final Library ffiLibrary;
202208
final Class nativeFunctionClass;
@@ -221,6 +227,7 @@ class FfiTransformer extends Transformer {
221227
final Map<NativeType, Procedure> storeMethods;
222228
final Map<NativeType, Procedure> elementAtMethods;
223229
final Procedure loadStructMethod;
230+
final Procedure memCopy;
224231
final Procedure asFunctionTearoff;
225232
final Procedure lookupFunctionTearoff;
226233

@@ -235,10 +242,20 @@ class FfiTransformer extends Transformer {
235242
doubleClass = coreTypes.doubleClass,
236243
listClass = coreTypes.listClass,
237244
typeClass = coreTypes.typeClass,
245+
unsafeCastMethod =
246+
index.getTopLevelMember('dart:_internal', 'unsafeCast'),
247+
typedDataClass = index.getClass('dart:typed_data', 'TypedData'),
248+
typedDataBufferGetter =
249+
index.getMember('dart:typed_data', 'TypedData', 'get:buffer'),
250+
typedDataOffsetInBytesGetter = index.getMember(
251+
'dart:typed_data', 'TypedData', 'get:offsetInBytes'),
252+
byteBufferAsUint8List =
253+
index.getMember('dart:typed_data', 'ByteBuffer', 'asUint8List'),
238254
pragmaClass = coreTypes.pragmaClass,
239255
pragmaName = coreTypes.pragmaName,
240256
pragmaOptions = coreTypes.pragmaOptions,
241257
listElementAt = coreTypes.index.getMember('dart:core', 'List', '[]'),
258+
numAddition = coreTypes.index.getMember('dart:core', 'num', '+'),
242259
ffiLibrary = index.getLibrary('dart:ffi'),
243260
nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'),
244261
pointerClass = index.getClass('dart:ffi', 'Pointer'),
@@ -283,6 +300,7 @@ class FfiTransformer extends Transformer {
283300
return index.getTopLevelMember('dart:ffi', "_elementAt$name");
284301
}),
285302
loadStructMethod = index.getTopLevelMember('dart:ffi', '_loadStruct'),
303+
memCopy = index.getTopLevelMember('dart:ffi', '_memCopy'),
286304
asFunctionTearoff = index.getMember('dart:ffi', 'NativeFunctionPointer',
287305
LibraryIndex.tearoffPrefix + 'asFunction'),
288306
lookupFunctionTearoff = index.getMember(

0 commit comments

Comments
 (0)