diff --git a/BUILD.gn b/BUILD.gn index 3dd681d9d41..c2ddf40491e 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -248,6 +248,7 @@ if (is_fuchsia) { "tests/ffi/has_symbol_test.dart", "tests/ffi/inline_array_multi_dimensional_test.dart", "tests/ffi/inline_array_test.dart", + "tests/ffi/inline_array_variable_length_test.dart", "tests/ffi/invoke_callback_after_suspension_test.dart", "tests/ffi/isolate_local_function_callbacks_test.dart", "tests/ffi/msan_test.dart", 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 8fa70f505ae..0b7f11e1c73 100644 --- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart +++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart @@ -6654,6 +6654,19 @@ Message _withArgumentsFfiStructGeneric(String string, String name) { ); } +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeFfiVariableLengthArrayNotLast = + messageFfiVariableLengthArrayNotLast; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode messageFfiVariableLengthArrayNotLast = const MessageCode( + "FfiVariableLengthArrayNotLast", + problemMessage: + r"""Variable length 'Array's must only occur as the last field of Structs.""", + correctionMessage: + r"""Try adjusting the arguments in the 'Array' annotation.""", +); + // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const Template templateFieldAlreadyInitializedAtDeclaration = 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 95142b4d4ec..addf1347f02 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 @@ -1816,6 +1816,8 @@ FfiCode.PACKED_ANNOTATION_ALIGNMENT: status: noFix FfiCode.SIZE_ANNOTATION_DIMENSIONS: status: noFix +FfiCode.VARIABLE_LENGTH_ARRAY_NOT_LAST: + status: noFix FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS: status: hasFix FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS: 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 6baf6c36b2c..d1ba5d2d385 100644 --- a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart +++ b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart @@ -496,6 +496,13 @@ class FfiCode extends AnalyzerErrorCode { uniqueName: 'SUBTYPE_OF_STRUCT_CLASS_IN_WITH', ); + /// No parameters. + static const FfiCode VARIABLE_LENGTH_ARRAY_NOT_LAST = FfiCode( + 'VARIABLE_LENGTH_ARRAY_NOT_LAST', + "Variable length 'Array's must only occur as the last field of Structs.", + correctionMessage: "Try adjusting the arguments in the 'Array' annotation.", + ); + /// Initialize a newly created error code to have the given [name]. const FfiCode( String name, 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 50e992ff1ae..e0ad8abea8d 100644 --- a/pkg/analyzer/lib/src/error/error_code_values.g.dart +++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart @@ -644,6 +644,7 @@ const List errorCodeValues = [ FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS, FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS, FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_WITH, + FfiCode.VARIABLE_LENGTH_ARRAY_NOT_LAST, HintCode.DEPRECATED_COLON_FOR_DEFAULT_VALUE, HintCode.DEPRECATED_MEMBER_USE, HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE, diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart index 9a5f69753c4..a736149fc32 100644 --- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart +++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart @@ -499,8 +499,14 @@ class FfiVerifier extends RecursiveAstVisitor { arguments: ['T', 'Native'], ); } else { - _checkFfiNativeField(errorNode, declarationElement, metadata, - ffiSignature, annotationValue); + _checkFfiNativeField( + errorNode, + declarationElement, + metadata, + ffiSignature, + annotationValue, + false, + ); } } @@ -515,6 +521,7 @@ class FfiVerifier extends RecursiveAstVisitor { NodeList metadata, DartType ffiSignature, DartObject annotationValue, + bool allowVariableLength, ) { DartType type; @@ -574,7 +581,11 @@ class FfiVerifier extends RecursiveAstVisitor { } else if (ffiSignature.isArray) { // Array fields need an `@Array` size annotation. _validateSizeOfAnnotation( - errorToken, metadata, ffiSignature.arrayDimensions); + errorToken, + metadata, + ffiSignature.arrayDimensions, + allowVariableLength, + ); } else if (ffiSignature.isHandle || ffiSignature.isNativeFunction) { _errorReporter.atToken( errorToken, @@ -1466,7 +1477,19 @@ class FfiVerifier extends RecursiveAstVisitor { ); } var arrayDimensions = declaredType.arrayDimensions; - _validateSizeOfAnnotation(fieldType, annotations, arrayDimensions); + var fieldElement = node.fields.variables.first.declaredElement; + var lastElement = (fieldElement?.enclosingElement as ClassElement?) + ?.fields + .reversed + .where((field) => !field.isStatic) + .firstOrNull; + var isLastField = fieldElement == lastElement; + _validateSizeOfAnnotation( + fieldType, + annotations, + arrayDimensions, + isLastField, + ); } else if (declaredType.isCompoundSubtype) { var clazz = (declaredType as InterfaceType).element; if (clazz.isEmptyStruct) { @@ -1903,8 +1926,12 @@ class FfiVerifier extends RecursiveAstVisitor { /// Validate that the [annotations] include exactly one size annotation. If /// an error is produced that cannot be associated with an annotation, /// associate it with the [errorEntity]. - void _validateSizeOfAnnotation(SyntacticEntity errorEntity, - NodeList annotations, int arrayDimensions) { + void _validateSizeOfAnnotation( + SyntacticEntity errorEntity, + NodeList annotations, + int arrayDimensions, + bool allowVariableLength, + ) { var ffiSizeAnnotations = annotations.where((annotation) => annotation.isArray).toList(); @@ -1928,7 +1955,8 @@ class FfiVerifier extends RecursiveAstVisitor { // Check number of dimensions. var annotation = ffiSizeAnnotations.first; - var dimensions = annotation.elementAnnotation?.arraySizeDimensions ?? []; + var (dimensions, variableLength) = + annotation.elementAnnotation?.arraySizeDimensions ?? ([], false); var annotationDimensions = dimensions.length; if (annotationDimensions != arrayDimensions) { _errorReporter.atNode( @@ -1937,7 +1965,16 @@ class FfiVerifier extends RecursiveAstVisitor { ); } - // Check dimensions are positive + if (variableLength) { + if (!allowVariableLength) { + _errorReporter.atNode( + annotation, + FfiCode.VARIABLE_LENGTH_ARRAY_NOT_LAST, + ); + } + } + + // Check dimensions are positive. List? getArgumentNodes() { var arguments = annotation.arguments?.arguments; if (arguments != null && arguments.length == 1) { @@ -1950,6 +1987,9 @@ class FfiVerifier extends RecursiveAstVisitor { } for (int i = 0; i < dimensions.length; i++) { + if (i == 0 && variableLength) { + continue; // First dimension is variable. + } if (dimensions[i] <= 0) { AstNode errorNode = annotation; var argumentNodes = getArgumentNodes(); @@ -2032,10 +2072,13 @@ extension on Annotation { } extension on ElementAnnotation { - List get arraySizeDimensions { + (List, bool) get arraySizeDimensions { assert(isArray); var value = computeConstantValue(); + var variableLength = + value?.getField('variableLength')?.toBoolValue() ?? false; + // Element of `@Array.multi([1, 2, 3])`. var listField = value?.getField('dimensions'); if (listField != null) { @@ -2045,7 +2088,7 @@ extension on ElementAnnotation { .whereType() .toList(); if (listValues != null) { - return listValues; + return ([if (variableLength) 0, ...listValues], variableLength); } } @@ -2064,7 +2107,7 @@ extension on ElementAnnotation { result.add(dimensionValue); } } - return result; + return (result, variableLength); } bool get isArray { diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart index 107ccd02317..dbb62e5e0cd 100644 --- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart +++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart @@ -895,6 +895,13 @@ final class Array extends _Compound { int dimension5]) = _ArraySize; const factory Array.multi(List dimensions) = _ArraySize.multi; + + @Since('3.6') + const factory Array.variable() = _ArraySize.variable; + + @Since('3.6') + const factory Array.variableMulti(List dimensions) = + _ArraySize.variableMulti; } final class _ArraySize implements Array { @@ -906,16 +913,44 @@ final class _ArraySize implements Array { final List? dimensions; - const _ArraySize(this.dimension1, - [this.dimension2, this.dimension3, this.dimension4, this.dimension5]) - : dimensions = null; + final bool variableLength; + + const _ArraySize( + this.dimension1, [ + this.dimension2, + this.dimension3, + this.dimension4, + this.dimension5, + ]) : dimensions = null, + variableLength = false; const _ArraySize.multi(this.dimensions) : dimension1 = null, dimension2 = null, dimension3 = null, dimension4 = null, - dimension5 = null; + dimension5 = null, + variableLength = false; + + static const variableLengthLength = 0; + + const _ArraySize.variable([ + this.dimension2, + this.dimension3, + this.dimension4, + this.dimension5, + ]) : dimension1 = variableLengthLength, + dimensions = null, + variableLength = true; + + const _ArraySize.variableMulti(List nestedDimensions) + : dimensions = nestedDimensions, + dimension1 = null, + dimension2 = null, + dimension3 = null, + dimension4 = null, + dimension5 = null, + variableLength = true; } extension StructPointer on Pointer { diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml index c7bb24f031f..8a145efef71 100644 --- a/pkg/analyzer/messages.yaml +++ b/pkg/analyzer/messages.yaml @@ -20543,6 +20543,17 @@ FfiCode: external Array a0; } ``` + + If this is a variable length inline array, change the annotation to `Array.variable()`: + + ```dart + import 'dart:ffi'; + + final class MyStruct extends Struct { + @Array.variable() + external Array a0; + } + ``` NON_SIZED_TYPE_ARGUMENT: problemMessage: "The type '{1}' isn't a valid type argument for '{0}'. The type argument must be a native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct', 'Union', or 'AbiSpecificInteger'." correctionMessage: "Try using a native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct', 'Union', or 'AbiSpecificInteger'." @@ -20666,6 +20677,65 @@ FfiCode: external Pointer notEmpty; } ``` + VARIABLE_LENGTH_ARRAY_NOT_LAST: + problemMessage: "Variable length 'Array's must only occur as the last field of Structs." + correctionMessage: "Try adjusting the arguments in the 'Array' annotation." + comment: No parameters. + documentation: |- + #### Description + + The analyzer produces this diagnostic when a variable length inline `Array` + is not the last member of a `Struct`. + + For more information about FFI, see [C interop using dart:ffi][ffi]. + + #### Example + + The following code produces this diagnostic because the field `a0` has a + type with three nested arrays, but only two dimensions are given in the + `Array` annotation: + + ```dart + import 'dart:ffi'; + + final class C extends Struct { + [!@Array.variable()!] + external Array a0; + + @Uint8() + external int a1; + } + ``` + + #### Common fixes + + Move the variable length inline `Array` to be the last field in the struct. + + ```dart + import 'dart:ffi'; + + final class C extends Struct { + @Uint8() + external int a1; + + @Array.variable() + external Array a0; + } + ``` + + If the inline array has a fixed size, annotate it with the size: + + ```dart + import 'dart:ffi'; + + final class C extends Struct { + @Array(10) + external Array a0; + + @Uint8() + external int a1; + } + ``` SIZE_ANNOTATION_DIMENSIONS: problemMessage: "'Array's must have an 'Array' annotation that matches the dimensions." correctionMessage: "Try adjusting the arguments in the 'Array' annotation." diff --git a/pkg/analyzer/tool/diagnostics/diagnostics.md b/pkg/analyzer/tool/diagnostics/diagnostics.md index 49e03480a5e..5309448ad88 100644 --- a/pkg/analyzer/tool/diagnostics/diagnostics.md +++ b/pkg/analyzer/tool/diagnostics/diagnostics.md @@ -15726,6 +15726,17 @@ final class MyStruct extends Struct { } ``` +If this is a variable length inline array, change the annotation to `Array.variable()`: + +```dart +import 'dart:ffi'; + +final class MyStruct extends Struct { + @Array.variable() + external Array a0; +} +``` + ### non_sized_type_argument _The type '{1}' isn't a valid type argument for '{0}'. The type argument must be @@ -23674,6 +23685,65 @@ enum E { } ``` +### variable_length_array_not_last + +_Variable length 'Array's must only occur as the last field of Structs._ + +#### Description + +The analyzer produces this diagnostic when a variable length inline `Array` +is not the last member of a `Struct`. + +For more information about FFI, see [C interop using dart:ffi][ffi]. + +#### Example + +The following code produces this diagnostic because the field `a0` has a +type with three nested arrays, but only two dimensions are given in the +`Array` annotation: + +```dart +import 'dart:ffi'; + +final class C extends Struct { + [!@Array.variable()!] + external Array a0; + + @Uint8() + external int a1; +} +``` + +#### Common fixes + +Move the variable length inline `Array` to be the last field in the struct. + +```dart +import 'dart:ffi'; + +final class C extends Struct { + @Uint8() + external int a1; + + @Array.variable() + external Array a0; +} +``` + +If the inline array has a fixed size, annotate it with the size: + +```dart +import 'dart:ffi'; + +final class C extends Struct { + @Array(10) + external Array a0; + + @Uint8() + external int a1; +} +``` + ### variable_pattern_keyword_in_declaration_context _Variable patterns in declaration context can't specify 'var' or 'final' diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status index b70bd3a41fe..60fbb79148d 100644 --- a/pkg/front_end/messages.status +++ b/pkg/front_end/messages.status @@ -422,6 +422,7 @@ FfiStructAnnotation/analyzerCode: Fail FfiStructGeneric/analyzerCode: Fail FfiTypeInvalid/analyzerCode: Fail FfiTypeMismatch/analyzerCode: Fail +FfiVariableLengthArrayNotLast/analyzerCode: Fail FieldInitializedOutsideDeclaringClass/part_wrapped_script1: Fail FieldInitializedOutsideDeclaringClass/script1: Fail FieldInitializerOutsideConstructor/part_wrapped_script1: Fail diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index 104e90315f2..38ba85872d5 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -5014,6 +5014,12 @@ FfiSizeAnnotationDimensions: problemMessage: "Field '#name' must have an 'Array' annotation that matches the dimensions." external: test/ffi_test.dart +FfiVariableLengthArrayNotLast: + # Used by dart:ffi + problemMessage: "Variable length 'Array's must only occur as the last field of Structs." + correctionMessage: "Try adjusting the arguments in the 'Array' annotation." + external: test/ffi_test.dart + FfiStructGeneric: # Used by dart:ffi problemMessage: "#string '#name' should not be generic." diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt index 087fda3ec95..3f9b7fd1952 100644 --- a/pkg/front_end/test/spell_checking_list_messages.txt +++ b/pkg/front_end/test/spell_checking_list_messages.txt @@ -17,6 +17,7 @@ annotate api apis argument(s) +array's assigning augment augmentation diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect index 60d05fe8618..7d8879871dc 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect @@ -9,9 +9,9 @@ final class StructInlineArray extends ffi::Struct { synthetic constructor •() → self::StructInlineArray : super ffi::Struct::•() ; - @#C3 + @#C4 external get a0() → ffi::Array; - @#C3 + @#C4 external set a0(synthesized ffi::Array #externalFieldValue) → void; } static method main() → dynamic {} @@ -19,7 +19,8 @@ static method main() → dynamic {} constants { #C1 = 8 #C2 = null - #C3 = ffi::_ArraySize {dimension1:#C1, dimension2:#C2, dimension3:#C2, dimension4:#C2, dimension5:#C2, dimensions:#C2} + #C3 = false + #C4 = ffi::_ArraySize {dimension1:#C1, dimension2:#C2, dimension3:#C2, dimension4:#C2, dimension5:#C2, dimensions:#C2, variableLength:#C3} } diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.modular.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.modular.expect index 60d05fe8618..7d8879871dc 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.modular.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.modular.expect @@ -9,9 +9,9 @@ final class StructInlineArray extends ffi::Struct { synthetic constructor •() → self::StructInlineArray : super ffi::Struct::•() ; - @#C3 + @#C4 external get a0() → ffi::Array; - @#C3 + @#C4 external set a0(synthesized ffi::Array #externalFieldValue) → void; } static method main() → dynamic {} @@ -19,7 +19,8 @@ static method main() → dynamic {} constants { #C1 = 8 #C2 = null - #C3 = ffi::_ArraySize {dimension1:#C1, dimension2:#C2, dimension3:#C2, dimension4:#C2, dimension5:#C2, dimensions:#C2} + #C3 = false + #C4 = ffi::_ArraySize {dimension1:#C1, dimension2:#C2, dimension3:#C2, dimension4:#C2, dimension5:#C2, dimensions:#C2, variableLength:#C3} } diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.outline.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.outline.expect index 84b4a73fea3..5eb6facdfbe 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.outline.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.outline.expect @@ -18,6 +18,6 @@ static method main() → dynamic Extra constant evaluation status: -Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array.dart:10:4 -> InstanceConstant(const _ArraySize{_ArraySize.dimension1: 8, _ArraySize.dimension2: null, _ArraySize.dimension3: null, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null}) -Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array.dart:10:4 -> InstanceConstant(const _ArraySize{_ArraySize.dimension1: 8, _ArraySize.dimension2: null, _ArraySize.dimension3: null, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null}) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array.dart:10:4 -> InstanceConstant(const _ArraySize{_ArraySize.dimension1: 8, _ArraySize.dimension2: null, _ArraySize.dimension3: null, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null, _ArraySize.variableLength: false}) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array.dart:10:4 -> InstanceConstant(const _ArraySize{_ArraySize.dimension1: 8, _ArraySize.dimension2: null, _ArraySize.dimension3: null, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null, _ArraySize.variableLength: false}) Extra constant evaluation: evaluated: 2, effectively constant: 2 diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect index 1416f43b326..777bbb35202 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect @@ -18,18 +18,18 @@ final class StructInlineArray extends ffi::Struct { constructor #fromTypedData(synthesized typ::TypedData #typedData, synthesized core::int #offset, synthesized core::int #sizeInBytes) → self::StructInlineArray : super ffi::Struct::_fromTypedData(#typedData, #offset, #sizeInBytes) ; - @#C9 + @#C10 get a0() → ffi::Array - return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::StructInlineArray::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C3, #C10); - @#C9 + return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::StructInlineArray::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C3, #C11); + @#C10 set a0(synthesized ffi::Array #externalFieldValue) → void - return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::StructInlineArray::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C11.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); - @#C13 + return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::StructInlineArray::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C12.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); + @#C14 static get a0#offsetOf() → core::int - return #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; - @#C13 + return #C16.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + @#C14 static get #sizeOf() → core::int - return #C11.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + return #C12.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; } static method main() → dynamic {} @@ -42,13 +42,14 @@ constants { #C6 = null #C7 = ffi::_FfiStructLayout {fieldTypes:#C5, packing:#C6} #C8 = core::pragma {name:#C1, options:#C7} - #C9 = ffi::_ArraySize {dimension1:#C3, dimension2:#C6, dimension3:#C6, dimension4:#C6, dimension5:#C6, dimensions:#C6} - #C10 = [] - #C11 = [#C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3] - #C12 = "vm:prefer-inline" - #C13 = core::pragma {name:#C12, options:#C6} - #C14 = 0 - #C15 = [#C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14] + #C9 = false + #C10 = ffi::_ArraySize {dimension1:#C3, dimension2:#C6, dimension3:#C6, dimension4:#C6, dimension5:#C6, dimensions:#C6, variableLength:#C9} + #C11 = [] + #C12 = [#C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3] + #C13 = "vm:prefer-inline" + #C14 = core::pragma {name:#C13, options:#C6} + #C15 = 0 + #C16 = [#C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15] } diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect index 3b7e97c5332..a0bcd8775c5 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect @@ -10,25 +10,26 @@ final class StructInlineArrayMultiDimensional extends ffi::Struct { synthetic constructor •() → self::StructInlineArrayMultiDimensional : super ffi::Struct::•() ; - @#C3 + @#C4 external get a0() → ffi::Array>>; - @#C3 + @#C4 external set a0(synthesized ffi::Array>> #externalFieldValue) → void; } static method main() → dynamic { - final ffi::Pointer pointer = ffi::AllocatorAlloc|call(#C4); + final ffi::Pointer pointer = ffi::AllocatorAlloc|call(#C5); final self::StructInlineArrayMultiDimensional struct = ffi::StructPointer|get#ref(pointer); final ffi::Array>> array = struct.{self::StructInlineArrayMultiDimensional::a0}{ffi::Array>>}; final ffi::Array> subArray = ffi::ArrayArray|[]>(array, 0); ffi::ArrayArray|[]=>(array, 1, subArray); - #C4.{all::CallocAllocator::free}(pointer){(ffi::Pointer) → void}; + #C5.{all::CallocAllocator::free}(pointer){(ffi::Pointer) → void}; } constants { #C1 = 2 #C2 = null - #C3 = ffi::_ArraySize {dimension1:#C1, dimension2:#C1, dimension3:#C1, dimension4:#C2, dimension5:#C2, dimensions:#C2} - #C4 = all::CallocAllocator {} + #C3 = false + #C4 = ffi::_ArraySize {dimension1:#C1, dimension2:#C1, dimension3:#C1, dimension4:#C2, dimension5:#C2, dimensions:#C2, variableLength:#C3} + #C5 = all::CallocAllocator {} } diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.modular.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.modular.expect index 3b7e97c5332..a0bcd8775c5 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.modular.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.modular.expect @@ -10,25 +10,26 @@ final class StructInlineArrayMultiDimensional extends ffi::Struct { synthetic constructor •() → self::StructInlineArrayMultiDimensional : super ffi::Struct::•() ; - @#C3 + @#C4 external get a0() → ffi::Array>>; - @#C3 + @#C4 external set a0(synthesized ffi::Array>> #externalFieldValue) → void; } static method main() → dynamic { - final ffi::Pointer pointer = ffi::AllocatorAlloc|call(#C4); + final ffi::Pointer pointer = ffi::AllocatorAlloc|call(#C5); final self::StructInlineArrayMultiDimensional struct = ffi::StructPointer|get#ref(pointer); final ffi::Array>> array = struct.{self::StructInlineArrayMultiDimensional::a0}{ffi::Array>>}; final ffi::Array> subArray = ffi::ArrayArray|[]>(array, 0); ffi::ArrayArray|[]=>(array, 1, subArray); - #C4.{all::CallocAllocator::free}(pointer){(ffi::Pointer) → void}; + #C5.{all::CallocAllocator::free}(pointer){(ffi::Pointer) → void}; } constants { #C1 = 2 #C2 = null - #C3 = ffi::_ArraySize {dimension1:#C1, dimension2:#C1, dimension3:#C1, dimension4:#C2, dimension5:#C2, dimensions:#C2} - #C4 = all::CallocAllocator {} + #C3 = false + #C4 = ffi::_ArraySize {dimension1:#C1, dimension2:#C1, dimension3:#C1, dimension4:#C2, dimension5:#C2, dimensions:#C2, variableLength:#C3} + #C5 = all::CallocAllocator {} } diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.outline.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.outline.expect index 01b89d09a37..e0cfe940469 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.outline.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.outline.expect @@ -18,6 +18,6 @@ static method main() → dynamic Extra constant evaluation status: -Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:10:4 -> InstanceConstant(const _ArraySize{_ArraySize.dimension1: 2, _ArraySize.dimension2: 2, _ArraySize.dimension3: 2, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null}) -Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:10:4 -> InstanceConstant(const _ArraySize{_ArraySize.dimension1: 2, _ArraySize.dimension2: 2, _ArraySize.dimension3: 2, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null}) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:10:4 -> InstanceConstant(const _ArraySize{_ArraySize.dimension1: 2, _ArraySize.dimension2: 2, _ArraySize.dimension3: 2, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null, _ArraySize.variableLength: false}) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:10:4 -> InstanceConstant(const _ArraySize{_ArraySize.dimension1: 2, _ArraySize.dimension2: 2, _ArraySize.dimension3: 2, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null, _ArraySize.variableLength: false}) Extra constant evaluation: evaluated: 2, effectively constant: 2 diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect index 5700286ff20..175b061221e 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect @@ -19,28 +19,28 @@ final class StructInlineArrayMultiDimensional extends ffi::Struct { constructor #fromTypedData(synthesized typ::TypedData #typedData, synthesized core::int #offset, synthesized core::int #sizeInBytes) → self::StructInlineArrayMultiDimensional : super ffi::Struct::_fromTypedData(#typedData, #offset, #sizeInBytes) ; - @#C10 + @#C11 get a0() → ffi::Array>> - return new ffi::Array::_>>(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::StructInlineArrayMultiDimensional::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C9, #C11); - @#C10 + return new ffi::Array::_>>(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::StructInlineArrayMultiDimensional::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C9, #C12); + @#C11 set a0(synthesized ffi::Array>> #externalFieldValue) → void - return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::StructInlineArrayMultiDimensional::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C12.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); - @#C14 + return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::StructInlineArrayMultiDimensional::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C13.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); + @#C15 static get a0#offsetOf() → core::int - return #C16.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; - @#C14 + return #C17.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + @#C15 static get #sizeOf() → core::int - return #C12.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + return #C13.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; } static method main() → dynamic { - final ffi::Pointer pointer = #C17.{ffi::Allocator::allocate}(self::StructInlineArrayMultiDimensional::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; - final self::StructInlineArrayMultiDimensional struct = new self::StructInlineArrayMultiDimensional::#fromTypedDataBase(pointer!, #C15); + final ffi::Pointer pointer = #C18.{ffi::Allocator::allocate}(self::StructInlineArrayMultiDimensional::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; + final self::StructInlineArrayMultiDimensional struct = new self::StructInlineArrayMultiDimensional::#fromTypedDataBase(pointer!, #C16); final ffi::Array>> array = struct.{self::StructInlineArrayMultiDimensional::a0}{ffi::Array>>}; final ffi::Array> subArray = block { synthesized ffi::Array #array = array!; synthesized core::int #index = 0!; #array.{ffi::Array::_checkIndex}(#index){(core::int) → void}; - synthesized core::int #singleElementSize = #C18; + synthesized core::int #singleElementSize = #C19; synthesized core::int #elementSize = #singleElementSize.{core::num::*}(#array.{ffi::Array::_nestedDimensionsFlattened}{core::int}){(core::num) → core::num}; synthesized core::int #offset = #elementSize.{core::num::*}(#index){(core::num) → core::num}; } =>new ffi::Array::_>(#array.{ffi::_Compound::_typedDataBase}{core::Object}, #array.{ffi::_Compound::_offsetInBytes}{core::int}.{core::num::+}(#offset){(core::num) → core::num}, #array.{ffi::Array::_nestedDimensionsFirst}{core::int}, #array.{ffi::Array::_nestedDimensionsRest}{core::List}); @@ -48,12 +48,12 @@ static method main() → dynamic { synthesized ffi::Array #array = array!; synthesized core::int #index = 1!; #array.{ffi::Array::_checkIndex}(#index){(core::int) → void}; - synthesized core::int #singleElementSize = #C18; + synthesized core::int #singleElementSize = #C19; synthesized core::int #elementSize = #singleElementSize.{core::num::*}(#array.{ffi::Array::_nestedDimensionsFlattened}{core::int}){(core::num) → core::num}; synthesized core::int #offset = #elementSize.{core::num::*}(#index){(core::num) → core::num}; synthesized ffi::Array #value = subArray!; } =>ffi::_memCopy(#array.{ffi::_Compound::_typedDataBase}{core::Object}, #array.{ffi::_Compound::_offsetInBytes}{core::int}.{core::num::+}(#offset){(core::num) → core::num}, #value.{ffi::_Compound::_typedDataBase}{core::Object}, #value.{ffi::_Compound::_offsetInBytes}{core::int}, #elementSize); - #C17.{all::CallocAllocator::free}(pointer){(ffi::Pointer) → void}; + #C18.{all::CallocAllocator::free}(pointer){(ffi::Pointer) → void}; } constants { @@ -66,15 +66,16 @@ constants { #C7 = ffi::_FfiStructLayout {fieldTypes:#C5, packing:#C6} #C8 = core::pragma {name:#C1, options:#C7} #C9 = 2 - #C10 = ffi::_ArraySize {dimension1:#C9, dimension2:#C9, dimension3:#C9, dimension4:#C6, dimension5:#C6, dimensions:#C6} - #C11 = [#C9, #C9] - #C12 = [#C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3] - #C13 = "vm:prefer-inline" - #C14 = core::pragma {name:#C13, options:#C6} - #C15 = 0 - #C16 = [#C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15] - #C17 = all::CallocAllocator {} - #C18 = 1 + #C10 = false + #C11 = ffi::_ArraySize {dimension1:#C9, dimension2:#C9, dimension3:#C9, dimension4:#C6, dimension5:#C6, dimensions:#C6, variableLength:#C10} + #C12 = [#C9, #C9] + #C13 = [#C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3] + #C14 = "vm:prefer-inline" + #C15 = core::pragma {name:#C14, options:#C6} + #C16 = 0 + #C17 = [#C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16] + #C18 = all::CallocAllocator {} + #C19 = 1 } Extra constant evaluation status: diff --git a/pkg/vm/lib/modular/transformations/ffi/common.dart b/pkg/vm/lib/modular/transformations/ffi/common.dart index 3ae1f31a6d4..f3b3544d903 100644 --- a/pkg/vm/lib/modular/transformations/ffi/common.dart +++ b/pkg/vm/lib/modular/transformations/ffi/common.dart @@ -13,6 +13,7 @@ import 'package:front_end/src/codes/cfe_codes.dart' show messageFfiLeafCallMustNotReturnHandle, messageFfiLeafCallMustNotTakeHandle, + messageFfiVariableLengthArrayNotLast, messageNonPositiveArrayDimensions, templateFfiSizeAnnotation, templateFfiSizeAnnotationDimensions, @@ -223,6 +224,7 @@ class FfiTransformer extends Transformer { final Field arraySizeDimension4Field; final Field arraySizeDimension5Field; final Field arraySizeDimensionsField; + final Field arraySizeVariableLengthField; final Class pointerClass; final Class compoundClass; final Class structClass; @@ -413,6 +415,8 @@ class FfiTransformer extends Transformer { index.getField('dart:ffi', '_ArraySize', 'dimension5'), arraySizeDimensionsField = index.getField('dart:ffi', '_ArraySize', 'dimensions'), + arraySizeVariableLengthField = + index.getField('dart:ffi', '_ArraySize', 'variableLength'), pointerClass = index.getClass('dart:ffi', 'Pointer'), compoundClass = index.getClass('dart:ffi', '_Compound'), structClass = index.getClass('dart:ffi', 'Struct'), @@ -985,9 +989,14 @@ class FfiTransformer extends Transformer { /// matching its [type]. /// /// Throws an [FfiStaticTypeError] otherwise. - List ensureArraySizeAnnotation(Member node, DartType type) { + List ensureArraySizeAnnotation( + Member node, + DartType type, + bool allowVariableLength, + ) { final sizeAnnotations = getArraySizeAnnotations(node); List dimensions; + bool variableLength; var success = true; if (sizeAnnotations.length == 1) { @@ -996,7 +1005,8 @@ class FfiTransformer extends Transformer { assert(singleElementType is InvalidType); throw FfiStaticTypeError(); } else { - dimensions = sizeAnnotations.single; + dimensions = sizeAnnotations.single.$1; + variableLength = sizeAnnotations.single.$2; if (arrayDimensions(type) != dimensions.length) { diagnosticReporter.report( templateFfiSizeAnnotationDimensions.withArguments(node.name.text), @@ -1004,6 +1014,17 @@ class FfiTransformer extends Transformer { node.name.text.length, node.fileUri); } + if (variableLength) { + if (!allowVariableLength) { + diagnosticReporter.report( + messageFfiVariableLengthArrayNotLast, + node.fileOffset, + node.name.text.length, + node.fileUri, + ); + } + return dimensions; // Variable length single dimension. + } for (var dimension in dimensions) { if (dimension <= 0) { diagnosticReporter.report(messageNonPositiveArrayDimensions, @@ -1028,7 +1049,7 @@ class FfiTransformer extends Transformer { return dimensions; } - Iterable> getArraySizeAnnotations(Member node) { + Iterable<(List, bool)> getArraySizeAnnotations(Member node) { return node.annotations .whereType() .map((e) => e.constant) @@ -1038,16 +1059,18 @@ class FfiTransformer extends Transformer { } /// Reads the dimensions from a constant instance of `_ArraySize`. - List _arraySize(InstanceConstant constant) { + (List, bool) _arraySize(InstanceConstant constant) { + final variableLength = + (constant.fieldValues[arraySizeVariableLengthField.fieldReference] + as BoolConstant) + .value; final dimensions = constant.fieldValues[arraySizeDimensionsField.fieldReference]; if (dimensions != null) { if (dimensions is ListConstant) { - final result = dimensions.entries - .whereType() - .map((e) => e.value) - .toList(); - return result; + final result = + dimensions.entries.whereType().map((e) => e.value); + return ([if (variableLength) 0, ...result], variableLength); } } final dimensionFields = [ @@ -1062,7 +1085,7 @@ class FfiTransformer extends Transformer { .whereType() .map((c) => c.value) .toList(); - return result; + return (result, variableLength); } /// Returns the number of dimensions of `Array`. diff --git a/pkg/vm/lib/modular/transformations/ffi/definitions.dart b/pkg/vm/lib/modular/transformations/ffi/definitions.dart index b21f056f4f3..bd8edcbae9d 100644 --- a/pkg/vm/lib/modular/transformations/ffi/definitions.dart +++ b/pkg/vm/lib/modular/transformations/ffi/definitions.dart @@ -442,6 +442,7 @@ class _FfiDefinitionTransformer extends FfiTransformer { bool success = true; final membersWithAnnotations = _compoundFieldMembers(node, includeSetters: false); + final lastField = membersWithAnnotations.lastOrNull; for (final Member f in membersWithAnnotations) { if (f is Field) { if (f.initializer is! NullLiteral) { @@ -485,7 +486,8 @@ class _FfiDefinitionTransformer extends FfiTransformer { if (isArrayType(type)) { try { ensureNativeTypeValid(type, f, allowInlineArray: true); - ensureArraySizeAnnotation(f, type); + final isLastField = f == lastField; + ensureArraySizeAnnotation(f, type, isLastField); } on FfiStaticTypeError { // It's OK to swallow the exception because the diagnostics issued will // cause compilation to fail. By continuing, we can report more @@ -674,7 +676,7 @@ class _FfiDefinitionTransformer extends FfiTransformer { if (isArrayType(dartType)) { final sizeAnnotations = getArraySizeAnnotations(m).toList(); if (sizeAnnotations.length == 1) { - final arrayDimensions = sizeAnnotations.single; + final arrayDimensions = sizeAnnotations.single.$1; if (this.arrayDimensions(dartType) == arrayDimensions.length) { final elementType = arraySingleElementType(dartType); if (elementType is! InterfaceType) { diff --git a/pkg/vm/lib/modular/transformations/ffi/native.dart b/pkg/vm/lib/modular/transformations/ffi/native.dart index 9cb3c37d825..3143afe1f7e 100644 --- a/pkg/vm/lib/modular/transformations/ffi/native.dart +++ b/pkg/vm/lib/modular/transformations/ffi/native.dart @@ -764,7 +764,7 @@ class FfiNativeTransformer extends FfiTransformer { // Array types must have an @Array annotation denoting its size. if (isArrayType(ffiType)) { - final dimensions = ensureArraySizeAnnotation(node, ffiType); + final dimensions = ensureArraySizeAnnotation(node, ffiType, false); return ( ffiType, NativeTypeCfe.withoutLayout(this, dartType, arrayDimensions: dimensions) diff --git a/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart.expect b/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart.expect index 9b56c76abae..9e954238e92 100644 --- a/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart.expect +++ b/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart.expect @@ -60,31 +60,31 @@ final class WCharArrayStruct extends ffi::Struct { constructor #fromTypedData(synthesized typ::TypedData #typedData, synthesized core::int #offset, synthesized core::int #sizeInBytes) → self::WCharArrayStruct : super ffi::Struct::_fromTypedData(#typedData, #offset, #sizeInBytes) ; - @#C83 + @#C84 get a0() → ffi::Array - return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::WCharArrayStruct::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C78, #C84); - @#C83 + return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::WCharArrayStruct::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C78, #C85); + @#C84 set a0(synthesized ffi::Array #externalFieldValue) → void - return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::WCharArrayStruct::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C87.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); + return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::WCharArrayStruct::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C88.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); @#C67 static get a0#offsetOf() → core::int return #C75.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; @#C67 static get #sizeOf() → core::int - return #C87.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + return #C88.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; } class _DummyAllocator extends core::Object implements ffi::Allocator /*hasConstConstructor*/ { const constructor •() → self::_DummyAllocator : super core::Object::•() ; - @#C88 + @#C89 method allocate(core::int byteCount, {core::int? alignment = #C66}) → ffi::Pointer { return ffi::Pointer::fromAddress(0); } - @#C88 + @#C89 method free(ffi::Pointer pointer) → void {} } -static const field self::_DummyAllocator noAlloc = #C89; +static const field self::_DummyAllocator noAlloc = #C90; static method main() → void { self::testSizeOf(); self::testStoreLoad(); @@ -97,29 +97,29 @@ static method testSizeOf() → void { core::print(size); } static method testStoreLoad() → void { - final ffi::Pointer p = #C89.{ffi::Allocator::allocate}(self::WChar::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; + final ffi::Pointer p = #C90.{ffi::Allocator::allocate}(self::WChar::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; ffi::_storeAbiSpecificInt(p, #C1, 10); core::print(ffi::_loadAbiSpecificInt(p, #C1)); - #C89.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; + #C90.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; } static method testStoreLoadIndexed() → void { - final ffi::Pointer p = #C89.{ffi::Allocator::allocate}(2.{core::num::*}(self::WChar::#sizeOf){(core::num) → core::num}){(core::int, {alignment: core::int?}) → ffi::Pointer}; + final ffi::Pointer p = #C90.{ffi::Allocator::allocate}(2.{core::num::*}(self::WChar::#sizeOf){(core::num) → core::num}){(core::int, {alignment: core::int?}) → ffi::Pointer}; ffi::_storeAbiSpecificIntAtIndex(p, #C1, 0, 10); ffi::_storeAbiSpecificIntAtIndex(p, #C1, 1, 3); core::print(ffi::_loadAbiSpecificIntAtIndex(p, #C1, 0)); core::print(ffi::_loadAbiSpecificIntAtIndex(p, #C1, 1)); - #C89.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; + #C90.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; } static method testStruct() → void { - final ffi::Pointer p = #C89.{ffi::Allocator::allocate}(self::WCharStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; + final ffi::Pointer p = #C90.{ffi::Allocator::allocate}(self::WCharStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; new self::WCharStruct::#fromTypedDataBase(p!, #C1).{self::WCharStruct::a0} = 1; core::print(new self::WCharStruct::#fromTypedDataBase(p!, #C1).{self::WCharStruct::a0}{core::int}); new self::WCharStruct::#fromTypedDataBase(p!, #C1).{self::WCharStruct::a0} = 2; core::print(new self::WCharStruct::#fromTypedDataBase(p!, #C1).{self::WCharStruct::a0}{core::int}); - #C89.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; + #C90.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; } static method testInlineArray() → void { - final ffi::Pointer p = #C89.{ffi::Allocator::allocate}(self::WCharArrayStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; + final ffi::Pointer p = #C90.{ffi::Allocator::allocate}(self::WCharArrayStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; final ffi::Array array = new self::WCharArrayStruct::#fromTypedDataBase(p!, #C1).{self::WCharArrayStruct::a0}{ffi::Array}; for (core::int i = 0; i.{core::num::<}(100){(core::num) → core::bool}; i = i.{core::num::+}(1){(core::num) → core::int}) { block { @@ -135,7 +135,7 @@ static method testInlineArray() → void { #array.{ffi::Array::_checkIndex}(#index){(core::int) → void}; } =>ffi::_loadAbiSpecificIntAtIndex(#array.{ffi::_Compound::_typedDataBase}{core::Object}, #array.{ffi::_Compound::_offsetInBytes}{core::int}, #index)); } - #C89.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; + #C90.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; } constants { #C1 = 0 @@ -220,11 +220,12 @@ constants { #C80 = [#C79] #C81 = ffi::_FfiStructLayout {fieldTypes:#C80, packing:#C66} #C82 = core::pragma {name:#C69, options:#C81} - #C83 = ffi::_ArraySize {dimension1:#C78, dimension2:#C66, dimension3:#C66, dimension4:#C66, dimension5:#C66, dimensions:#C66} - #C84 = [] - #C85 = 400 - #C86 = 200 - #C87 = [#C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C85, #C86, #C86, #C86] - #C88 = core::_Override {} - #C89 = self::_DummyAllocator {} + #C83 = false + #C84 = ffi::_ArraySize {dimension1:#C78, dimension2:#C66, dimension3:#C66, dimension4:#C66, dimension5:#C66, dimensions:#C66, variableLength:#C83} + #C85 = [] + #C86 = 400 + #C87 = 200 + #C88 = [#C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C86, #C87, #C87, #C87] + #C89 = core::_Override {} + #C90 = self::_DummyAllocator {} } diff --git a/pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart.expect b/pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart.expect index f08aed43e4c..71844b5963c 100644 --- a/pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart.expect +++ b/pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart.expect @@ -60,31 +60,31 @@ final class IncompleteArrayStruct extends ffi::Struct { constructor #fromTypedData(synthesized typ::TypedData #typedData, synthesized core::int #offset, synthesized core::int #sizeInBytes) → self::IncompleteArrayStruct : super ffi::Struct::_fromTypedData(#typedData, #offset, #sizeInBytes) ; - @#C46 + @#C47 get a0() → ffi::Array - return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::IncompleteArrayStruct::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C41, #C47); - @#C46 + return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::IncompleteArrayStruct::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C41, #C48); + @#C47 set a0(synthesized ffi::Array #externalFieldValue) → void - return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::IncompleteArrayStruct::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, ffi::_checkAbiSpecificIntegerMapping(#C49.{core::List::[]}(ffi::_abi()){(core::int) → core::int?})); + return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::IncompleteArrayStruct::a0#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, ffi::_checkAbiSpecificIntegerMapping(#C50.{core::List::[]}(ffi::_abi()){(core::int) → core::int?})); @#C29 static get a0#offsetOf() → core::int return #C38.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; @#C29 static get #sizeOf() → core::int - return ffi::_checkAbiSpecificIntegerMapping(#C49.{core::List::[]}(ffi::_abi()){(core::int) → core::int?}); + return ffi::_checkAbiSpecificIntegerMapping(#C50.{core::List::[]}(ffi::_abi()){(core::int) → core::int?}); } class _DummyAllocator extends core::Object implements ffi::Allocator /*hasConstConstructor*/ { const constructor •() → self::_DummyAllocator : super core::Object::•() ; - @#C50 + @#C51 method allocate(core::int byteCount, {core::int? alignment = #C23}) → ffi::Pointer { return ffi::Pointer::fromAddress(0); } - @#C50 + @#C51 method free(ffi::Pointer pointer) → void {} } -static const field self::_DummyAllocator noAlloc = #C51; +static const field self::_DummyAllocator noAlloc = #C52; static method main() → void { self::testSizeOf(); self::testStoreLoad(); @@ -97,29 +97,29 @@ static method testSizeOf() → void { core::print(size); } static method testStoreLoad() → void { - final ffi::Pointer p = #C51.{ffi::Allocator::allocate}(self::Incomplete::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; + final ffi::Pointer p = #C52.{ffi::Allocator::allocate}(self::Incomplete::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; ffi::_storeAbiSpecificInt(p, #C4, 10); core::print(ffi::_loadAbiSpecificInt(p, #C4)); - #C51.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; + #C52.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; } static method testStoreLoadIndexed() → void { - final ffi::Pointer p = #C51.{ffi::Allocator::allocate}(2.{core::num::*}(self::Incomplete::#sizeOf){(core::num) → core::num}){(core::int, {alignment: core::int?}) → ffi::Pointer}; + final ffi::Pointer p = #C52.{ffi::Allocator::allocate}(2.{core::num::*}(self::Incomplete::#sizeOf){(core::num) → core::num}){(core::int, {alignment: core::int?}) → ffi::Pointer}; ffi::_storeAbiSpecificIntAtIndex(p, #C4, 0, 10); ffi::_storeAbiSpecificIntAtIndex(p, #C4, 1, 3); core::print(ffi::_loadAbiSpecificIntAtIndex(p, #C4, 0)); core::print(ffi::_loadAbiSpecificIntAtIndex(p, #C4, 1)); - #C51.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; + #C52.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; } static method testStruct() → void { - final ffi::Pointer p = #C51.{ffi::Allocator::allocate}(self::IncompleteStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; + final ffi::Pointer p = #C52.{ffi::Allocator::allocate}(self::IncompleteStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; new self::IncompleteStruct::#fromTypedDataBase(p!, #C4).{self::IncompleteStruct::a0} = 1; core::print(new self::IncompleteStruct::#fromTypedDataBase(p!, #C4).{self::IncompleteStruct::a0}{core::int}); new self::IncompleteStruct::#fromTypedDataBase(p!, #C4).{self::IncompleteStruct::a0} = 2; core::print(new self::IncompleteStruct::#fromTypedDataBase(p!, #C4).{self::IncompleteStruct::a0}{core::int}); - #C51.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; + #C52.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; } static method testInlineArray() → void { - final ffi::Pointer p = #C51.{ffi::Allocator::allocate}(self::IncompleteArrayStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; + final ffi::Pointer p = #C52.{ffi::Allocator::allocate}(self::IncompleteArrayStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; final ffi::Array array = new self::IncompleteArrayStruct::#fromTypedDataBase(p!, #C4).{self::IncompleteArrayStruct::a0}{ffi::Array}; for (core::int i = 0; i.{core::num::<}(100){(core::num) → core::bool}; i = i.{core::num::+}(1){(core::num) → core::int}) { block { @@ -135,7 +135,7 @@ static method testInlineArray() → void { #array.{ffi::Array::_checkIndex}(#index){(core::int) → void}; } =>ffi::_loadAbiSpecificIntAtIndex(#array.{ffi::_Compound::_typedDataBase}{core::Object}, #array.{ffi::_Compound::_offsetInBytes}{core::int}, #index)); } - #C51.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; + #C52.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; } constants { #C1 = 3 @@ -183,10 +183,11 @@ constants { #C43 = [#C42] #C44 = ffi::_FfiStructLayout {fieldTypes:#C43, packing:#C23} #C45 = core::pragma {name:#C32, options:#C44} - #C46 = ffi::_ArraySize {dimension1:#C41, dimension2:#C23, dimension3:#C23, dimension4:#C23, dimension5:#C23, dimensions:#C23} - #C47 = [] - #C48 = 400 - #C49 = [#C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C48, #C48, #C48, #C48, #C23, #C23, #C23, #C23, #C23, #C23, #C23] - #C50 = core::_Override {} - #C51 = self::_DummyAllocator {} + #C46 = false + #C47 = ffi::_ArraySize {dimension1:#C41, dimension2:#C23, dimension3:#C23, dimension4:#C23, dimension5:#C23, dimensions:#C23, variableLength:#C46} + #C48 = [] + #C49 = 400 + #C50 = [#C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C49, #C49, #C49, #C49, #C23, #C23, #C23, #C23, #C23, #C23, #C23] + #C51 = core::_Override {} + #C52 = self::_DummyAllocator {} } diff --git a/pkg/vm/testcases/transformations/ffi/address_of_struct.dart.expect b/pkg/vm/testcases/transformations/ffi/address_of_struct.dart.expect index afd96215d85..0edadd2fa78 100644 --- a/pkg/vm/testcases/transformations/ffi/address_of_struct.dart.expect +++ b/pkg/vm/testcases/transformations/ffi/address_of_struct.dart.expect @@ -17,20 +17,20 @@ final class MyStruct extends ffi::Struct { constructor #fromTypedData(synthesized typ::TypedData #typedData, synthesized core::int #offset, synthesized core::int #sizeInBytes) → self::MyStruct : super ffi::Struct::_fromTypedData(#typedData, #offset, #sizeInBytes) ; - @#C9 + @#C10 get a() → ffi::Array - return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::a#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C3, #C10); - @#C9 + return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::a#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C3, #C11); + @#C10 set a(synthesized ffi::Array #externalFieldValue) → void - return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::a#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C11.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); - @#C13 + return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::a#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C12.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); + @#C14 static get a#offsetOf() → core::int - return #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; - @#C13 + return #C16.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + @#C14 static get #sizeOf() → core::int - return #C11.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + return #C12.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; } -@#C18 +@#C19 final class MyUnion extends ffi::Union { synthetic constructor •() → self::MyUnion : super ffi::Union::•() @@ -41,43 +41,43 @@ final class MyUnion extends ffi::Union { constructor #fromTypedData(synthesized typ::TypedData #typedData, synthesized core::int #offset, synthesized core::int #sizeInBytes) → self::MyUnion : super ffi::Union::_fromTypedData(#typedData, #offset, #sizeInBytes) ; - @#C19 + @#C20 get a() → core::int return ffi::_loadInt8(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyUnion::a#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}); - @#C19 + @#C20 set a(synthesized core::int #externalFieldValue) → void return ffi::_storeInt8(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyUnion::a#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue); - @#C13 + @#C14 static get a#offsetOf() → core::int - return #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; - @#C13 + return #C16.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + @#C14 static get #sizeOf() → core::int - return #C21.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + return #C22.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; } static method main() → void { - final self::MyStruct myStruct = new self::MyStruct::#fromTypedDataBase(typ::Uint8List::•(self::MyStruct::#sizeOf), #C14); + final self::MyStruct myStruct = new self::MyStruct::#fromTypedDataBase(typ::Uint8List::•(self::MyStruct::#sizeOf), #C15); self::myNative#C(myStruct); - final self::MyUnion myUnion = new self::MyUnion::#fromTypedDataBase(typ::Uint8List::•(self::MyUnion::#sizeOf), #C14); + final self::MyUnion myUnion = new self::MyUnion::#fromTypedDataBase(typ::Uint8List::•(self::MyUnion::#sizeOf), #C15); self::myNative2#C(myUnion); self::myNative3#C(myStruct.{self::MyStruct::a}{ffi::Array}); } -@#C27 -@#C29 +@#C28 +@#C30 external static method myNative(ffi::Pointer pointer) → void; -@#C32 @#C33 +@#C34 external static method myNative2(ffi::Pointer pointer) → void; -@#C36 @#C37 +@#C38 external static method myNative3(ffi::Pointer pointer) → void; -@#C27 -@#C29 +@#C28 +@#C30 external static method myNative#C(ffi::_Compound pointer) → void; -@#C32 @#C33 +@#C34 external static method myNative2#C(ffi::_Compound pointer) → void; -@#C36 @#C37 +@#C38 external static method myNative3#C(ffi::_Compound pointer) → void; constants { #C1 = "vm:ffi:struct-fields" @@ -88,33 +88,34 @@ constants { #C6 = null #C7 = ffi::_FfiStructLayout {fieldTypes:#C5, packing:#C6} #C8 = core::pragma {name:#C1, options:#C7} - #C9 = ffi::_ArraySize {dimension1:#C3, dimension2:#C6, dimension3:#C6, dimension4:#C6, dimension5:#C6, dimensions:#C6} - #C10 = [] - #C11 = [#C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3] - #C12 = "vm:prefer-inline" - #C13 = core::pragma {name:#C12, options:#C6} - #C14 = 0 - #C15 = [#C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14] - #C16 = [#C2] - #C17 = ffi::_FfiStructLayout {fieldTypes:#C16, packing:#C6} - #C18 = core::pragma {name:#C1, options:#C17} - #C19 = ffi::Int8 {} - #C20 = 1 - #C21 = [#C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20] - #C22 = "cfe:ffi:native-marker" - #C23 = "myNative" - #C24 = "#lib" - #C25 = true - #C26 = ffi::Native<(ffi::Pointer) → ffi::Void> {symbol:#C23, assetId:#C24, isLeaf:#C25} - #C27 = core::pragma {name:#C22, options:#C26} - #C28 = "vm:ffi:native" - #C29 = core::pragma {name:#C28, options:#C26} - #C30 = "myNative2" - #C31 = ffi::Native<(ffi::Pointer) → ffi::Void> {symbol:#C30, assetId:#C24, isLeaf:#C25} - #C32 = core::pragma {name:#C22, options:#C31} - #C33 = core::pragma {name:#C28, options:#C31} - #C34 = "myNative3" - #C35 = ffi::Native<(ffi::Pointer) → ffi::Void> {symbol:#C34, assetId:#C24, isLeaf:#C25} - #C36 = core::pragma {name:#C22, options:#C35} - #C37 = core::pragma {name:#C28, options:#C35} + #C9 = false + #C10 = ffi::_ArraySize {dimension1:#C3, dimension2:#C6, dimension3:#C6, dimension4:#C6, dimension5:#C6, dimensions:#C6, variableLength:#C9} + #C11 = [] + #C12 = [#C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3] + #C13 = "vm:prefer-inline" + #C14 = core::pragma {name:#C13, options:#C6} + #C15 = 0 + #C16 = [#C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15, #C15] + #C17 = [#C2] + #C18 = ffi::_FfiStructLayout {fieldTypes:#C17, packing:#C6} + #C19 = core::pragma {name:#C1, options:#C18} + #C20 = ffi::Int8 {} + #C21 = 1 + #C22 = [#C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21] + #C23 = "cfe:ffi:native-marker" + #C24 = "myNative" + #C25 = "#lib" + #C26 = true + #C27 = ffi::Native<(ffi::Pointer) → ffi::Void> {symbol:#C24, assetId:#C25, isLeaf:#C26} + #C28 = core::pragma {name:#C23, options:#C27} + #C29 = "vm:ffi:native" + #C30 = core::pragma {name:#C29, options:#C27} + #C31 = "myNative2" + #C32 = ffi::Native<(ffi::Pointer) → ffi::Void> {symbol:#C31, assetId:#C25, isLeaf:#C26} + #C33 = core::pragma {name:#C23, options:#C32} + #C34 = core::pragma {name:#C29, options:#C32} + #C35 = "myNative3" + #C36 = ffi::Native<(ffi::Pointer) → ffi::Void> {symbol:#C35, assetId:#C25, isLeaf:#C26} + #C37 = core::pragma {name:#C23, options:#C36} + #C38 = core::pragma {name:#C29, options:#C36} } diff --git a/pkg/vm/testcases/transformations/ffi/address_of_struct_element.dart.expect b/pkg/vm/testcases/transformations/ffi/address_of_struct_element.dart.expect index b62434c5cca..a00e4cdfe25 100644 --- a/pkg/vm/testcases/transformations/ffi/address_of_struct_element.dart.expect +++ b/pkg/vm/testcases/transformations/ffi/address_of_struct_element.dart.expect @@ -29,35 +29,35 @@ final class MyStruct extends ffi::Struct { @#C11 set b(synthesized core::int #externalFieldValue) → void return ffi::_storeInt8(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::b#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue); - @#C12 + @#C13 get array() → ffi::Array - return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::array#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C3, #C13); - @#C12 + return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::array#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C3, #C14); + @#C13 set array(synthesized ffi::Array #externalFieldValue) → void - return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::array#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C14.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); - @#C12 + return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::array#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); + @#C13 get array2() → ffi::Array - return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::array2#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C3, #C13); - @#C12 + return new ffi::Array::_(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::array2#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #C3, #C14); + @#C13 set array2(synthesized ffi::Array #externalFieldValue) → void - return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::array2#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C17.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); - @#C19 + return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyStruct::array2#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #externalFieldValue.{ffi::_Compound::_offsetInBytes}{core::int}, #C18.{core::List::[]}(ffi::_abi()){(core::int) → core::int}); + @#C20 static get a#offsetOf() → core::int - return #C21.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; - @#C19 + return #C22.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + @#C20 static get b#offsetOf() → core::int - return #C23.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; - @#C19 + return #C24.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + @#C20 static get array#offsetOf() → core::int - return #C25.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; - @#C19 + return #C26.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + @#C20 static get array2#offsetOf() → core::int - return #C28.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; - @#C19 + return #C29.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + @#C20 static get #sizeOf() → core::int - return #C31.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + return #C32.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; } -@#C34 +@#C35 final class MyUnion extends ffi::Union { synthetic constructor •() → self::MyUnion : super ffi::Union::•() @@ -80,41 +80,41 @@ final class MyUnion extends ffi::Union { @#C11 set b(synthesized core::int #externalFieldValue) → void return ffi::_storeInt8(this.{ffi::_Compound::_typedDataBase}{core::Object}, self::MyUnion::b#offsetOf.{core::num::+}(this.{ffi::_Compound::_offsetInBytes}{core::int}){(core::num) → core::num}, #externalFieldValue); - @#C19 + @#C20 static get a#offsetOf() → core::int - return #C21.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; - @#C19 + return #C22.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + @#C20 static get b#offsetOf() → core::int - return #C21.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; - @#C19 + return #C22.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + @#C20 static get #sizeOf() → core::int - return #C23.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; + return #C24.{core::List::[]}(ffi::_abi()){(core::int) → core::int}; } static method main() → void { - final self::MyStruct myStruct = new self::MyStruct::#fromTypedDataBase(typ::Uint8List::•(self::MyStruct::#sizeOf), #C20); + final self::MyStruct myStruct = new self::MyStruct::#fromTypedDataBase(typ::Uint8List::•(self::MyStruct::#sizeOf), #C21); self::myNative#CC( block { synthesized ffi::_Compound pointer#value = myStruct; } =>new ffi::_Compound::_fromTypedDataBase(pointer#value.{ffi::_Compound::_typedDataBase}{core::Object}, pointer#value.{ffi::_Compound::_offsetInBytes}{core::int}.{core::num::+}(self::MyStruct::a#offsetOf){(core::num) → core::num}), block { synthesized ffi::_Compound pointer2#value = myStruct; } =>new ffi::_Compound::_fromTypedDataBase(pointer2#value.{ffi::_Compound::_typedDataBase}{core::Object}, pointer2#value.{ffi::_Compound::_offsetInBytes}{core::int}.{core::num::+}(self::MyStruct::b#offsetOf){(core::num) → core::num})); - final self::MyUnion myUnion = new self::MyUnion::#fromTypedDataBase(typ::Uint8List::•(self::MyUnion::#sizeOf), #C20); + final self::MyUnion myUnion = new self::MyUnion::#fromTypedDataBase(typ::Uint8List::•(self::MyUnion::#sizeOf), #C21); self::myNative#CC(myUnion, myUnion); self::myNative#CC( block { synthesized ffi::_Compound pointer#value = myStruct.{self::MyStruct::array}{ffi::Array}; - } =>new ffi::_Compound::_fromTypedDataBase(pointer#value.{ffi::_Compound::_typedDataBase}{core::Object}, pointer#value.{ffi::_Compound::_offsetInBytes}{core::int}.{core::num::+}(#C22.{core::num::*}(3){(core::num) → core::num}){(core::num) → core::num}), block { + } =>new ffi::_Compound::_fromTypedDataBase(pointer#value.{ffi::_Compound::_typedDataBase}{core::Object}, pointer#value.{ffi::_Compound::_offsetInBytes}{core::int}.{core::num::+}(#C23.{core::num::*}(3){(core::num) → core::num}){(core::num) → core::num}), block { synthesized ffi::_Compound pointer2#value = myStruct.{self::MyStruct::array}{ffi::Array}; - } =>new ffi::_Compound::_fromTypedDataBase(pointer2#value.{ffi::_Compound::_typedDataBase}{core::Object}, pointer2#value.{ffi::_Compound::_offsetInBytes}{core::int}.{core::num::+}(#C22.{core::num::*}(4){(core::num) → core::num}){(core::num) → core::num})); + } =>new ffi::_Compound::_fromTypedDataBase(pointer2#value.{ffi::_Compound::_typedDataBase}{core::Object}, pointer2#value.{ffi::_Compound::_offsetInBytes}{core::int}.{core::num::+}(#C23.{core::num::*}(4){(core::num) → core::num}){(core::num) → core::num})); self::myNative#CC( block { synthesized ffi::_Compound pointer#value = myStruct.{self::MyStruct::array2}{ffi::Array}; } =>new ffi::_Compound::_fromTypedDataBase(pointer#value.{ffi::_Compound::_typedDataBase}{core::Object}, pointer#value.{ffi::_Compound::_offsetInBytes}{core::int}.{core::num::+}(ffi::UnsignedLong::#sizeOf.{core::num::*}(3){(core::num) → core::num}){(core::num) → core::num}), block { synthesized ffi::_Compound pointer2#value = myStruct.{self::MyStruct::array2}{ffi::Array}; } =>new ffi::_Compound::_fromTypedDataBase(pointer2#value.{ffi::_Compound::_typedDataBase}{core::Object}, pointer2#value.{ffi::_Compound::_offsetInBytes}{core::int}.{core::num::+}(ffi::UnsignedLong::#sizeOf.{core::num::*}(4){(core::num) → core::num}){(core::num) → core::num})); } -@#C40 -@#C42 +@#C41 +@#C43 external static method myNative(ffi::Pointer pointer, ffi::Pointer pointer2) → void; -@#C40 -@#C42 +@#C41 +@#C43 external static method myNative#CC(ffi::_Compound pointer, ffi::_Compound pointer2) → void; constants { #C1 = "vm:ffi:struct-fields" @@ -128,35 +128,36 @@ constants { #C9 = ffi::_FfiStructLayout {fieldTypes:#C7, packing:#C8} #C10 = core::pragma {name:#C1, options:#C9} #C11 = ffi::Int8 {} - #C12 = ffi::_ArraySize {dimension1:#C3, dimension2:#C8, dimension3:#C8, dimension4:#C8, dimension5:#C8, dimensions:#C8} - #C13 = [] - #C14 = [#C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3] - #C15 = 40 - #C16 = 80 - #C17 = [#C15, #C16, #C15, #C16, #C16, #C16, #C16, #C16, #C15, #C16, #C16, #C15, #C16, #C15, #C16, #C15, #C16, #C16, #C16, #C15, #C15, #C15] - #C18 = "vm:prefer-inline" - #C19 = core::pragma {name:#C18, options:#C8} - #C20 = 0 - #C21 = [#C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20, #C20] - #C22 = 1 - #C23 = [#C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22, #C22] - #C24 = 2 - #C25 = [#C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24, #C24] - #C26 = 12 - #C27 = 16 - #C28 = [#C26, #C27, #C26, #C27, #C27, #C27, #C27, #C27, #C26, #C27, #C27, #C26, #C27, #C26, #C27, #C26, #C27, #C27, #C27, #C26, #C26, #C26] - #C29 = 52 - #C30 = 96 - #C31 = [#C29, #C30, #C29, #C30, #C30, #C30, #C30, #C30, #C29, #C30, #C30, #C29, #C30, #C29, #C30, #C29, #C30, #C30, #C30, #C29, #C29, #C29] - #C32 = [#C2, #C2] - #C33 = ffi::_FfiStructLayout {fieldTypes:#C32, packing:#C8} - #C34 = core::pragma {name:#C1, options:#C33} - #C35 = "cfe:ffi:native-marker" - #C36 = "myNative" - #C37 = "#lib" - #C38 = true - #C39 = ffi::Native<(ffi::Pointer, ffi::Pointer) → ffi::Void> {symbol:#C36, assetId:#C37, isLeaf:#C38} - #C40 = core::pragma {name:#C35, options:#C39} - #C41 = "vm:ffi:native" - #C42 = core::pragma {name:#C41, options:#C39} + #C12 = false + #C13 = ffi::_ArraySize {dimension1:#C3, dimension2:#C8, dimension3:#C8, dimension4:#C8, dimension5:#C8, dimensions:#C8, variableLength:#C12} + #C14 = [] + #C15 = [#C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3, #C3] + #C16 = 40 + #C17 = 80 + #C18 = [#C16, #C17, #C16, #C17, #C17, #C17, #C17, #C17, #C16, #C17, #C17, #C16, #C17, #C16, #C17, #C16, #C17, #C17, #C17, #C16, #C16, #C16] + #C19 = "vm:prefer-inline" + #C20 = core::pragma {name:#C19, options:#C8} + #C21 = 0 + #C22 = [#C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21] + #C23 = 1 + #C24 = [#C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23] + #C25 = 2 + #C26 = [#C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25, #C25] + #C27 = 12 + #C28 = 16 + #C29 = [#C27, #C28, #C27, #C28, #C28, #C28, #C28, #C28, #C27, #C28, #C28, #C27, #C28, #C27, #C28, #C27, #C28, #C28, #C28, #C27, #C27, #C27] + #C30 = 52 + #C31 = 96 + #C32 = [#C30, #C31, #C30, #C31, #C31, #C31, #C31, #C31, #C30, #C31, #C31, #C30, #C31, #C30, #C31, #C30, #C31, #C31, #C31, #C30, #C30, #C30] + #C33 = [#C2, #C2] + #C34 = ffi::_FfiStructLayout {fieldTypes:#C33, packing:#C8} + #C35 = core::pragma {name:#C1, options:#C34} + #C36 = "cfe:ffi:native-marker" + #C37 = "myNative" + #C38 = "#lib" + #C39 = true + #C40 = ffi::Native<(ffi::Pointer, ffi::Pointer) → ffi::Void> {symbol:#C37, assetId:#C38, isLeaf:#C39} + #C41 = core::pragma {name:#C36, options:#C40} + #C42 = "vm:ffi:native" + #C43 = core::pragma {name:#C42, options:#C40} } diff --git a/pkg/vm/testcases/transformations/ffi/native_fields.dart.expect b/pkg/vm/testcases/transformations/ffi/native_fields.dart.expect index 91c4568a228..01e14ae046d 100644 --- a/pkg/vm/testcases/transformations/ffi/native_fields.dart.expect +++ b/pkg/vm/testcases/transformations/ffi/native_fields.dart.expect @@ -184,7 +184,7 @@ constants { #C39 = 1 #C40 = 2 #C41 = 3 - #C42 = ffi::_ArraySize {dimension1:#C39, dimension2:#C40, dimension3:#C41, dimension4:#C4, dimension5:#C4, dimensions:#C4} + #C42 = ffi::_ArraySize {dimension1:#C39, dimension2:#C40, dimension3:#C41, dimension4:#C4, dimension5:#C4, dimensions:#C4, variableLength:#C24} #C43 = "manyNumbers" #C44 = ffi::Native>>> {symbol:#C43, assetId:#C23, isLeaf:#C24} #C45 = core::pragma {name:#C21, options:#C44} diff --git a/runtime/bin/ffi_test/ffi_test_functions_generated.cc b/runtime/bin/ffi_test/ffi_test_functions_generated.cc index 9b0d8910268..1fdf90f1891 100644 --- a/runtime/bin/ffi_test/ffi_test_functions_generated.cc +++ b/runtime/bin/ffi_test/ffi_test_functions_generated.cc @@ -578,6 +578,26 @@ struct StructInlineArrayInt { wchar_t a0[10]; }; +struct StructInlineArrayVariable { + uint32_t a0; + uint8_t a1[]; +}; + +struct StructInlineArrayVariableNested { + uint32_t a0; + uint8_t a1[][2][2]; +}; + +struct StructInlineArrayVariableNestedDeep { + uint32_t a0; + uint8_t a1[][2][2][2][2][2][2]; +}; + +struct StructInlineArrayVariableAlign { + uint8_t a0; + uint32_t a1[]; +}; + // Used for testing structs and unions by value. // Smallest struct with data. // 10 struct arguments will exhaust available registers. @@ -4894,6 +4914,73 @@ PassPointerStruct12BytesHomogeneousInt32(Struct12BytesHomogeneousInt32* a0) { return result; } +// Used for testing structs and unions by value. +// Variable length array +DART_EXPORT int64_t +PassPointerStructInlineArrayVariable(StructInlineArrayVariable* a0) { + std::cout << "PassPointerStructInlineArrayVariable" + << "((" << a0->a0 << ", [" << static_cast(a0->a1[0]) << ", " + << static_cast(a0->a1[1]) << ", " + << static_cast(a0->a1[2]) << ", " + << static_cast(a0->a1[3]) << ", " + << static_cast(a0->a1[4]) << ", " + << static_cast(a0->a1[5]) << ", " + << static_cast(a0->a1[6]) << ", " + << static_cast(a0->a1[7]) << ", " + << static_cast(a0->a1[8]) << ", " + << static_cast(a0->a1[9]) << "]))" + << "\n"; + + int64_t result = 0; + + result += a0->a0; + result += a0->a1[0]; + result += a0->a1[1]; + result += a0->a1[2]; + result += a0->a1[3]; + result += a0->a1[4]; + result += a0->a1[5]; + result += a0->a1[6]; + result += a0->a1[7]; + result += a0->a1[8]; + result += a0->a1[9]; + + std::cout << "result = " << result << "\n"; + + return result; +} + +// Used for testing structs and unions by value. +// Variable length array with variable length element having more alignment than +// the rest of the struct. +DART_EXPORT int64_t +PassPointerStructInlineArrayVariableAlign(StructInlineArrayVariableAlign* a0) { + std::cout << "PassPointerStructInlineArrayVariableAlign" + << "((" << static_cast(a0->a0) << ", [" << a0->a1[0] << ", " + << a0->a1[1] << ", " << a0->a1[2] << ", " << a0->a1[3] << ", " + << a0->a1[4] << ", " << a0->a1[5] << ", " << a0->a1[6] << ", " + << a0->a1[7] << ", " << a0->a1[8] << ", " << a0->a1[9] << "]))" + << "\n"; + + int64_t result = 0; + + result += a0->a0; + result += a0->a1[0]; + result += a0->a1[1]; + result += a0->a1[2]; + result += a0->a1[3]; + result += a0->a1[4]; + result += a0->a1[5]; + result += a0->a1[6]; + result += a0->a1[7]; + result += a0->a1[8]; + result += a0->a1[9]; + + std::cout << "result = " << result << "\n"; + + return result; +} + // Used for testing structs and unions by value. // Smallest struct with data. DART_EXPORT Struct1ByteInt ReturnStruct1ByteInt(int8_t a0) { @@ -12630,8 +12717,9 @@ DART_EXPORT intptr_t TestPassInt64x7Struct12BytesHomogeneousInt32( DART_EXPORT intptr_t TestPassPointerStruct12BytesHomogeneousInt32( // NOLINTNEXTLINE(whitespace/parens) int64_t (*f)(Struct12BytesHomogeneousInt32* a0)) { - Struct12BytesHomogeneousInt32 a0_value = {}; - Struct12BytesHomogeneousInt32* a0 = &a0_value; + Struct12BytesHomogeneousInt32* a0 = + static_cast( + calloc(1, sizeof(Struct12BytesHomogeneousInt32))); a0->a0 = -1; a0->a1 = 2; @@ -12661,6 +12749,120 @@ DART_EXPORT intptr_t TestPassPointerStruct12BytesHomogeneousInt32( CHECK_EQ(0, result); + free(a0); + + return 0; +} + +// Used for testing structs and unions by value. +// Variable length array +DART_EXPORT intptr_t TestPassPointerStructInlineArrayVariable( + // NOLINTNEXTLINE(whitespace/parens) + int64_t (*f)(StructInlineArrayVariable* a0)) { + StructInlineArrayVariable* a0 = static_cast( + calloc(1, sizeof(StructInlineArrayVariable) + 10 * sizeof(uint8_t))); + + a0->a0 = 1; + a0->a1[0] = 2; + a0->a1[1] = 3; + a0->a1[2] = 4; + a0->a1[3] = 5; + a0->a1[4] = 6; + a0->a1[5] = 7; + a0->a1[6] = 8; + a0->a1[7] = 9; + a0->a1[8] = 10; + a0->a1[9] = 11; + + std::cout << "Calling TestPassPointerStructInlineArrayVariable(" + << "((" << a0->a0 << ", [" << static_cast(a0->a1[0]) << ", " + << static_cast(a0->a1[1]) << ", " + << static_cast(a0->a1[2]) << ", " + << static_cast(a0->a1[3]) << ", " + << static_cast(a0->a1[4]) << ", " + << static_cast(a0->a1[5]) << ", " + << static_cast(a0->a1[6]) << ", " + << static_cast(a0->a1[7]) << ", " + << static_cast(a0->a1[8]) << ", " + << static_cast(a0->a1[9]) << "]))" + << ")\n"; + + int64_t result = f(a0); + + std::cout << "result = " << result << "\n"; + + CHECK_EQ(66, result); + + // Pass argument that will make the Dart callback throw. + a0->a0 = 42; + + result = f(a0); + + CHECK_EQ(0, result); + + // Pass argument that will make the Dart callback return null. + a0->a0 = 84; + + result = f(a0); + + CHECK_EQ(0, result); + + free(a0); + + return 0; +} + +// Used for testing structs and unions by value. +// Variable length array with variable length element having more alignment than +// the rest of the struct. +DART_EXPORT intptr_t TestPassPointerStructInlineArrayVariableAlign( + // NOLINTNEXTLINE(whitespace/parens) + int64_t (*f)(StructInlineArrayVariableAlign* a0)) { + StructInlineArrayVariableAlign* a0 = + static_cast(calloc( + 1, sizeof(StructInlineArrayVariableAlign) + 10 * sizeof(uint32_t))); + + a0->a0 = 1; + a0->a1[0] = 2; + a0->a1[1] = 3; + a0->a1[2] = 4; + a0->a1[3] = 5; + a0->a1[4] = 6; + a0->a1[5] = 7; + a0->a1[6] = 8; + a0->a1[7] = 9; + a0->a1[8] = 10; + a0->a1[9] = 11; + + std::cout << "Calling TestPassPointerStructInlineArrayVariableAlign(" + << "((" << static_cast(a0->a0) << ", [" << a0->a1[0] << ", " + << a0->a1[1] << ", " << a0->a1[2] << ", " << a0->a1[3] << ", " + << a0->a1[4] << ", " << a0->a1[5] << ", " << a0->a1[6] << ", " + << a0->a1[7] << ", " << a0->a1[8] << ", " << a0->a1[9] << "]))" + << ")\n"; + + int64_t result = f(a0); + + std::cout << "result = " << result << "\n"; + + CHECK_EQ(66, result); + + // Pass argument that will make the Dart callback throw. + a0->a0 = 42; + + result = f(a0); + + CHECK_EQ(0, result); + + // Pass argument that will make the Dart callback return null. + a0->a0 = 84; + + result = f(a0); + + CHECK_EQ(0, result); + + free(a0); + return 0; } @@ -21287,8 +21489,9 @@ DART_EXPORT void TestAsyncPassInt64x7Struct12BytesHomogeneousInt32( DART_EXPORT void TestAsyncPassPointerStruct12BytesHomogeneousInt32( // NOLINTNEXTLINE(whitespace/parens) void (*f)(Struct12BytesHomogeneousInt32* a0)) { - Struct12BytesHomogeneousInt32 a0_value = {}; - Struct12BytesHomogeneousInt32* a0 = &a0_value; + Struct12BytesHomogeneousInt32* a0 = + static_cast( + calloc(1, sizeof(Struct12BytesHomogeneousInt32))); a0->a0 = -1; a0->a1 = 2; @@ -21299,6 +21502,80 @@ DART_EXPORT void TestAsyncPassPointerStruct12BytesHomogeneousInt32( << ")\n"; f(a0); + + free(a0); +} + +// Used for testing structs and unions by value. +// Variable length array +DART_EXPORT void TestAsyncPassPointerStructInlineArrayVariable( + // NOLINTNEXTLINE(whitespace/parens) + void (*f)(StructInlineArrayVariable* a0)) { + StructInlineArrayVariable* a0 = static_cast( + calloc(1, sizeof(StructInlineArrayVariable) + 10 * sizeof(uint8_t))); + + a0->a0 = 1; + a0->a1[0] = 2; + a0->a1[1] = 3; + a0->a1[2] = 4; + a0->a1[3] = 5; + a0->a1[4] = 6; + a0->a1[5] = 7; + a0->a1[6] = 8; + a0->a1[7] = 9; + a0->a1[8] = 10; + a0->a1[9] = 11; + + std::cout << "Calling TestAsyncPassPointerStructInlineArrayVariable(" + << "((" << a0->a0 << ", [" << static_cast(a0->a1[0]) << ", " + << static_cast(a0->a1[1]) << ", " + << static_cast(a0->a1[2]) << ", " + << static_cast(a0->a1[3]) << ", " + << static_cast(a0->a1[4]) << ", " + << static_cast(a0->a1[5]) << ", " + << static_cast(a0->a1[6]) << ", " + << static_cast(a0->a1[7]) << ", " + << static_cast(a0->a1[8]) << ", " + << static_cast(a0->a1[9]) << "]))" + << ")\n"; + + f(a0); + + free(a0); +} + +// Used for testing structs and unions by value. +// Variable length array with variable length element having more alignment than +// the rest of the struct. +DART_EXPORT void TestAsyncPassPointerStructInlineArrayVariableAlign( + // NOLINTNEXTLINE(whitespace/parens) + void (*f)(StructInlineArrayVariableAlign* a0)) { + StructInlineArrayVariableAlign* a0 = + static_cast(calloc( + 1, sizeof(StructInlineArrayVariableAlign) + 10 * sizeof(uint32_t))); + + a0->a0 = 1; + a0->a1[0] = 2; + a0->a1[1] = 3; + a0->a1[2] = 4; + a0->a1[3] = 5; + a0->a1[4] = 6; + a0->a1[5] = 7; + a0->a1[6] = 8; + a0->a1[7] = 9; + a0->a1[8] = 10; + a0->a1[9] = 11; + + std::cout << "Calling TestAsyncPassPointerStructInlineArrayVariableAlign(" + << "((" << static_cast(a0->a0) << ", [" << a0->a1[0] << ", " + << a0->a1[1] << ", " << a0->a1[2] << ", " << a0->a1[3] << ", " + << a0->a1[4] << ", " << a0->a1[5] << ", " << a0->a1[6] << ", " + << a0->a1[7] << ", " << a0->a1[8] << ", " << a0->a1[9] << "]))" + << ")\n"; + + f(a0); + + free(a0); } // Used for testing structs and unions by value. diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart index 1cd21a05bf7..e5f4b2e8b21 100644 --- a/sdk/lib/_internal/vm/lib/ffi_patch.dart +++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart @@ -344,7 +344,12 @@ final class Array extends _Compound { List get _nestedDimensionsRest => _nestedDimensionsRestCache ??= _nestedDimensions.sublist(1); + static const _variableLengthLength = 0; + void _checkIndex(int index) { + if (_size == _variableLengthLength) { + return; + } if (index < 0 || index >= _size) { throw RangeError.range(index, 0, _size - 1); } diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart index dbffdfc4085..3208b3b8e3a 100644 --- a/sdk/lib/ffi/ffi.dart +++ b/sdk/lib/ffi/ffi.dart @@ -88,7 +88,7 @@ final class Pointer implements SizedNativeType { /// A fixed-sized array of [T]s. @Since('2.13') final class Array extends _Compound { - /// Const constructor to specify [Array] dimensions in [Struct]s. + /// Annotation to specify [Array] dimensions in [Struct]s. /// /// ```dart /// final class MyStruct extends Struct { @@ -107,7 +107,7 @@ final class Array extends _Compound { int dimension4, int dimension5]) = _ArraySize; - /// Const constructor to specify [Array] dimensions in [Struct]s. + /// Annotation to specify [Array] dimensions in [Struct]s. /// /// ```dart /// final class MyStruct extends Struct { @@ -121,6 +121,97 @@ final class Array extends _Compound { /// /// Do not invoke in normal code. const factory Array.multi(List dimensions) = _ArraySize.multi; + + /// Annotation to specify a variable length [Array] in [Struct]s. + /// + /// Can only be used on the last field of a struct. The last field of the + /// struct is _not_ taken into account in [sizeOf]. Using an + /// [AllocatorAlloc.call] will _not_ allocate any backing storage for the + /// variable length array. Instead use [Allocator.allocate] and calculate the + /// required number of bytes manually. + /// + /// ```dart + /// import 'dart:ffi'; + /// import 'package:ffi/ffi.dart'; + /// + /// final class MyStruct extends Struct { + /// @Size() + /// external int length; + /// + /// @Array.variable() + /// external Array inlineArray; + /// + /// static Pointer allocate(Allocator allocator, int length) { + /// final lengthInBytes = sizeOf() + sizeOf() * length; + /// final result = allocator.allocate(lengthInBytes); + /// result.ref.length = length; + /// return result; + /// } + /// } + /// + /// void main() { + /// final myStruct = MyStruct.allocate(calloc, 10); + /// } + /// ``` + /// + /// The variable lenght is always the outermost dimension of the array. + /// + /// ```dart + /// import 'dart:ffi'; + /// import 'package:ffi/ffi.dart'; + /// + /// final class MyStruct extends Struct { + /// @Size() + /// external int length; + /// + /// @Array.variable(10, 10) + /// external Array>> inlineArray; + /// + /// static Pointer allocate(Allocator allocator, int length) { + /// final lengthInBytes = sizeOf() + sizeOf() * length * 100; + /// final result = allocator.allocate(lengthInBytes); + /// result.ref.length = length; + /// return result; + /// } + /// } + /// ``` + /// + /// Accessing variable length inline arrays of structs passed by value in FFI + /// calls and callbacks is undefined behavior. Accessing variable length + /// inline arrays in structs passed by value is undefined behavior in C. + /// + /// For more information about variable length inline arrays in C, please + /// refer to: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html. + /// + /// Do not invoke in normal code. + @Since('3.6') + const factory Array.variable([ + int dimension2, + int dimension3, + int dimension4, + int dimension5, + ]) = _ArraySize.variable; + + /// Annotation to a variable length [Array] in [Struct]s. + /// + /// ```dart + /// final class MyStruct extends Struct { + /// @Array.variableMulti([2, 2]) + /// external Array>> threeDimensionalInlineArray; + /// } + /// + /// final class MyStruct2 extends Struct { + /// @Array.variableMulti([2, 2, 2, 2, 2, 2, 2]) + /// external Array>>>>>>> eightDimensionalInlineArray; + /// } + /// ``` + /// + /// The variable lenght is always the outermost dimension of the array. + /// + /// Do not invoke in normal code. + @Since('3.6') + const factory Array.variableMulti(List dimensions) = + _ArraySize.variableMulti; } final class _ArraySize implements Array { @@ -132,16 +223,59 @@ final class _ArraySize implements Array { final List? dimensions; - const _ArraySize(this.dimension1, - [this.dimension2, this.dimension3, this.dimension4, this.dimension5]) - : dimensions = null; + // When `true`, [dimension1] is [variableLengthLength], or [dimensions] + // should be prepended with [variableLengthLength]. + final bool variableLength; + + const _ArraySize( + this.dimension1, [ + this.dimension2, + this.dimension3, + this.dimension4, + this.dimension5, + ]) : dimensions = null, + variableLength = false; const _ArraySize.multi(this.dimensions) : dimension1 = null, dimension2 = null, dimension3 = null, dimension4 = null, - dimension5 = null; + dimension5 = null, + variableLength = false; + + // Inline arrays in C of length 0 are undefined. + // + // GNU uses 0 to signal variable length arrays. + // https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html + // + // Some Windows APIs use an inline array length of 1 for variable length + // inline arrays. + // https://devblogs.microsoft.com/oldnewthing/20040826-00/?p=38043 + // However, this is perfectly valid C code. + // + // We follow the GNU standard here. This follows the behavior of structs + // with variable length arrays when malloc'ed and passed by value (the + // variable length array is ignored). + static const variableLengthLength = 0; + + const _ArraySize.variable([ + this.dimension2, + this.dimension3, + this.dimension4, + this.dimension5, + ]) : dimension1 = variableLengthLength, + dimensions = null, + variableLength = true; + + const _ArraySize.variableMulti(List nestedDimensions) + : dimensions = nestedDimensions, // Should be `[0, ...nestedDimensions]`. + dimension1 = null, + dimension2 = null, + dimension3 = null, + dimension4 = null, + dimension5 = null, + variableLength = true; } /// Extension on [Pointer] specialized for the type argument [NativeFunction]. diff --git a/tests/ffi/function_callbacks_structs_by_value_generated_test.dart b/tests/ffi/function_callbacks_structs_by_value_generated_test.dart index 147910ebb54..92d99822183 100644 --- a/tests/ffi/function_callbacks_structs_by_value_generated_test.dart +++ b/tests/ffi/function_callbacks_structs_by_value_generated_test.dart @@ -382,6 +382,16 @@ final testCases = [ Pointer.fromFunction( passPointerStruct12BytesHomogeneousInt32, 0), noChecks), + CallbackTest.withCheck( + "PassPointerStructInlineArrayVariable", + Pointer.fromFunction( + passPointerStructInlineArrayVariable, 0), + noChecks), + CallbackTest.withCheck( + "PassPointerStructInlineArrayVariableAlign", + Pointer.fromFunction( + passPointerStructInlineArrayVariableAlign, 0), + noChecks), CallbackTest.withCheck( "ReturnStruct1ByteInt", Pointer.fromFunction(returnStruct1ByteInt), @@ -7970,6 +7980,109 @@ int passPointerStruct12BytesHomogeneousInt32( return result; } +typedef PassPointerStructInlineArrayVariableType = Int64 Function( + Pointer); + +// Global variables to be able to test inputs after callback returned. +Pointer passPointerStructInlineArrayVariable_a0 = + nullptr; + +// Result variable also global, so we can delete it after the callback. +int passPointerStructInlineArrayVariableResult = 0; + +int passPointerStructInlineArrayVariableCalculateResult() { + int result = 0; + + result += passPointerStructInlineArrayVariable_a0.ref.a0; + result += passPointerStructInlineArrayVariable_a0.ref.a1[0]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[1]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[2]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[3]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[4]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[5]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[6]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[7]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[8]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[9]; + + passPointerStructInlineArrayVariableResult = result; + + return result; +} + +/// Variable length array +int passPointerStructInlineArrayVariable( + Pointer a0) { + print("passPointerStructInlineArrayVariable(${a0})"); + + // Possibly throw. + if (a0.ref.a0 == 42 || a0.ref.a0 == 84) { + print("throwing!"); + throw Exception( + "PassPointerStructInlineArrayVariable throwing on purpose!"); + } + + passPointerStructInlineArrayVariable_a0 = a0; + + final result = passPointerStructInlineArrayVariableCalculateResult(); + + print("result = $result"); + + return result; +} + +typedef PassPointerStructInlineArrayVariableAlignType = Int64 Function( + Pointer); + +// Global variables to be able to test inputs after callback returned. +Pointer + passPointerStructInlineArrayVariableAlign_a0 = nullptr; + +// Result variable also global, so we can delete it after the callback. +int passPointerStructInlineArrayVariableAlignResult = 0; + +int passPointerStructInlineArrayVariableAlignCalculateResult() { + int result = 0; + + result += passPointerStructInlineArrayVariableAlign_a0.ref.a0; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[0]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[1]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[2]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[3]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[4]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[5]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[6]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[7]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[8]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[9]; + + passPointerStructInlineArrayVariableAlignResult = result; + + return result; +} + +/// Variable length array with variable length element having more alignment than +/// the rest of the struct. +int passPointerStructInlineArrayVariableAlign( + Pointer a0) { + print("passPointerStructInlineArrayVariableAlign(${a0})"); + + // Possibly throw. + if (a0.ref.a0 == 42 || a0.ref.a0 == 84) { + print("throwing!"); + throw Exception( + "PassPointerStructInlineArrayVariableAlign throwing on purpose!"); + } + + passPointerStructInlineArrayVariableAlign_a0 = a0; + + final result = passPointerStructInlineArrayVariableAlignCalculateResult(); + + print("result = $result"); + + return result; +} + typedef ReturnStruct1ByteIntType = Struct1ByteInt Function(Int8); // Global variables to be able to test inputs after callback returned. diff --git a/tests/ffi/function_callbacks_structs_by_value_native_callable_generated_test.dart b/tests/ffi/function_callbacks_structs_by_value_native_callable_generated_test.dart index 47aa721fc8a..467ae75757a 100644 --- a/tests/ffi/function_callbacks_structs_by_value_native_callable_generated_test.dart +++ b/tests/ffi/function_callbacks_structs_by_value_native_callable_generated_test.dart @@ -457,6 +457,19 @@ final testCases = [ passPointerStruct12BytesHomogeneousInt32, exceptionalReturn: 0), noChecks), + CallbackTest.withCheck( + "PassPointerStructInlineArrayVariable", + NativeCallable.isolateLocal( + passPointerStructInlineArrayVariable, + exceptionalReturn: 0), + noChecks), + CallbackTest.withCheck( + "PassPointerStructInlineArrayVariableAlign", + NativeCallable< + PassPointerStructInlineArrayVariableAlignType>.isolateLocal( + passPointerStructInlineArrayVariableAlign, + exceptionalReturn: 0), + noChecks), CallbackTest.withCheck( "ReturnStruct1ByteInt", NativeCallable.isolateLocal( @@ -8055,6 +8068,109 @@ int passPointerStruct12BytesHomogeneousInt32( return result; } +typedef PassPointerStructInlineArrayVariableType = Int64 Function( + Pointer); + +// Global variables to be able to test inputs after callback returned. +Pointer passPointerStructInlineArrayVariable_a0 = + nullptr; + +// Result variable also global, so we can delete it after the callback. +int passPointerStructInlineArrayVariableResult = 0; + +int passPointerStructInlineArrayVariableCalculateResult() { + int result = 0; + + result += passPointerStructInlineArrayVariable_a0.ref.a0; + result += passPointerStructInlineArrayVariable_a0.ref.a1[0]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[1]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[2]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[3]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[4]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[5]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[6]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[7]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[8]; + result += passPointerStructInlineArrayVariable_a0.ref.a1[9]; + + passPointerStructInlineArrayVariableResult = result; + + return result; +} + +/// Variable length array +int passPointerStructInlineArrayVariable( + Pointer a0) { + print("passPointerStructInlineArrayVariable(${a0})"); + + // Possibly throw. + if (a0.ref.a0 == 42 || a0.ref.a0 == 84) { + print("throwing!"); + throw Exception( + "PassPointerStructInlineArrayVariable throwing on purpose!"); + } + + passPointerStructInlineArrayVariable_a0 = a0; + + final result = passPointerStructInlineArrayVariableCalculateResult(); + + print("result = $result"); + + return result; +} + +typedef PassPointerStructInlineArrayVariableAlignType = Int64 Function( + Pointer); + +// Global variables to be able to test inputs after callback returned. +Pointer + passPointerStructInlineArrayVariableAlign_a0 = nullptr; + +// Result variable also global, so we can delete it after the callback. +int passPointerStructInlineArrayVariableAlignResult = 0; + +int passPointerStructInlineArrayVariableAlignCalculateResult() { + int result = 0; + + result += passPointerStructInlineArrayVariableAlign_a0.ref.a0; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[0]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[1]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[2]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[3]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[4]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[5]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[6]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[7]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[8]; + result += passPointerStructInlineArrayVariableAlign_a0.ref.a1[9]; + + passPointerStructInlineArrayVariableAlignResult = result; + + return result; +} + +/// Variable length array with variable length element having more alignment than +/// the rest of the struct. +int passPointerStructInlineArrayVariableAlign( + Pointer a0) { + print("passPointerStructInlineArrayVariableAlign(${a0})"); + + // Possibly throw. + if (a0.ref.a0 == 42 || a0.ref.a0 == 84) { + print("throwing!"); + throw Exception( + "PassPointerStructInlineArrayVariableAlign throwing on purpose!"); + } + + passPointerStructInlineArrayVariableAlign_a0 = a0; + + final result = passPointerStructInlineArrayVariableAlignCalculateResult(); + + print("result = $result"); + + return result; +} + typedef ReturnStruct1ByteIntType = Struct1ByteInt Function(Int8); // Global variables to be able to test inputs after callback returned. diff --git a/tests/ffi/function_structs_by_value_generated_args_leaf_test.dart b/tests/ffi/function_structs_by_value_generated_args_leaf_test.dart index 9b7ba35bfe1..4c4e672c479 100644 --- a/tests/ffi/function_structs_by_value_generated_args_leaf_test.dart +++ b/tests/ffi/function_structs_by_value_generated_args_leaf_test.dart @@ -93,6 +93,8 @@ void main() { testPassWCharStructInlineArrayIntUintPtrx2LongUnsignedLeaf(); testPassInt64x7Struct12BytesHomogeneousInt32Leaf(); testPassPointerStruct12BytesHomogeneousInt32Leaf(); + testPassPointerStructInlineArrayVariableLeaf(); + testPassPointerStructInlineArrayVariableAlignLeaf(); } } @@ -5509,7 +5511,8 @@ final passPointerStruct12BytesHomogeneousInt32Leaf = /// Passing a pointer to a struct void testPassPointerStruct12BytesHomogeneousInt32Leaf() { - final a0 = calloc(); + final a0 = calloc.allocate( + sizeOf()); a0.ref.a0 = -1; a0.ref.a1 = 2; @@ -5523,3 +5526,70 @@ void testPassPointerStruct12BytesHomogeneousInt32Leaf() { calloc.free(a0); } + +final passPointerStructInlineArrayVariableLeaf = + ffiTestFunctions.lookupFunction< + Int64 Function(Pointer), + int Function(Pointer)>( + "PassPointerStructInlineArrayVariable", + isLeaf: true); + +/// Variable length array +void testPassPointerStructInlineArrayVariableLeaf() { + final a0 = calloc.allocate( + sizeOf() + 10 * sizeOf()); + + a0.ref.a0 = 1; + a0.ref.a1[0] = 2; + a0.ref.a1[1] = 3; + a0.ref.a1[2] = 4; + a0.ref.a1[3] = 5; + a0.ref.a1[4] = 6; + a0.ref.a1[5] = 7; + a0.ref.a1[6] = 8; + a0.ref.a1[7] = 9; + a0.ref.a1[8] = 10; + a0.ref.a1[9] = 11; + + final result = passPointerStructInlineArrayVariableLeaf(a0); + + print("result = $result"); + + Expect.equals(66, result); + + calloc.free(a0); +} + +final passPointerStructInlineArrayVariableAlignLeaf = + ffiTestFunctions.lookupFunction< + Int64 Function(Pointer), + int Function(Pointer)>( + "PassPointerStructInlineArrayVariableAlign", + isLeaf: true); + +/// Variable length array with variable length element having more alignment than +/// the rest of the struct. +void testPassPointerStructInlineArrayVariableAlignLeaf() { + final a0 = calloc.allocate( + sizeOf() + 10 * sizeOf()); + + a0.ref.a0 = 1; + a0.ref.a1[0] = 2; + a0.ref.a1[1] = 3; + a0.ref.a1[2] = 4; + a0.ref.a1[3] = 5; + a0.ref.a1[4] = 6; + a0.ref.a1[5] = 7; + a0.ref.a1[6] = 8; + a0.ref.a1[7] = 9; + a0.ref.a1[8] = 10; + a0.ref.a1[9] = 11; + + final result = passPointerStructInlineArrayVariableAlignLeaf(a0); + + print("result = $result"); + + Expect.equals(66, result); + + calloc.free(a0); +} diff --git a/tests/ffi/function_structs_by_value_generated_args_native_leaf_test.dart b/tests/ffi/function_structs_by_value_generated_args_native_leaf_test.dart index 012b6a5d11e..b5249820f80 100644 --- a/tests/ffi/function_structs_by_value_generated_args_native_leaf_test.dart +++ b/tests/ffi/function_structs_by_value_generated_args_native_leaf_test.dart @@ -96,6 +96,8 @@ void main() { testPassWCharStructInlineArrayIntUintPtrx2LongUnsignedNativeLeaf(); testPassInt64x7Struct12BytesHomogeneousInt32NativeLeaf(); testPassPointerStruct12BytesHomogeneousInt32NativeLeaf(); + testPassPointerStructInlineArrayVariableNativeLeaf(); + testPassPointerStructInlineArrayVariableAlignNativeLeaf(); } } @@ -5471,7 +5473,8 @@ external int passPointerStruct12BytesHomogeneousInt32NativeLeaf( /// Passing a pointer to a struct void testPassPointerStruct12BytesHomogeneousInt32NativeLeaf() { - final a0 = calloc(); + final a0 = calloc.allocate( + sizeOf()); a0.ref.a0 = -1; a0.ref.a1 = 2; @@ -5485,3 +5488,66 @@ void testPassPointerStruct12BytesHomogeneousInt32NativeLeaf() { calloc.free(a0); } + +@Native)>( + symbol: 'PassPointerStructInlineArrayVariable', isLeaf: true) +external int passPointerStructInlineArrayVariableNativeLeaf( + Pointer a0); + +/// Variable length array +void testPassPointerStructInlineArrayVariableNativeLeaf() { + final a0 = calloc.allocate( + sizeOf() + 10 * sizeOf()); + + a0.ref.a0 = 1; + a0.ref.a1[0] = 2; + a0.ref.a1[1] = 3; + a0.ref.a1[2] = 4; + a0.ref.a1[3] = 5; + a0.ref.a1[4] = 6; + a0.ref.a1[5] = 7; + a0.ref.a1[6] = 8; + a0.ref.a1[7] = 9; + a0.ref.a1[8] = 10; + a0.ref.a1[9] = 11; + + final result = passPointerStructInlineArrayVariableNativeLeaf(a0); + + print("result = $result"); + + Expect.equals(66, result); + + calloc.free(a0); +} + +@Native)>( + symbol: 'PassPointerStructInlineArrayVariableAlign', isLeaf: true) +external int passPointerStructInlineArrayVariableAlignNativeLeaf( + Pointer a0); + +/// Variable length array with variable length element having more alignment than +/// the rest of the struct. +void testPassPointerStructInlineArrayVariableAlignNativeLeaf() { + final a0 = calloc.allocate( + sizeOf() + 10 * sizeOf()); + + a0.ref.a0 = 1; + a0.ref.a1[0] = 2; + a0.ref.a1[1] = 3; + a0.ref.a1[2] = 4; + a0.ref.a1[3] = 5; + a0.ref.a1[4] = 6; + a0.ref.a1[5] = 7; + a0.ref.a1[6] = 8; + a0.ref.a1[7] = 9; + a0.ref.a1[8] = 10; + a0.ref.a1[9] = 11; + + final result = passPointerStructInlineArrayVariableAlignNativeLeaf(a0); + + print("result = $result"); + + Expect.equals(66, result); + + calloc.free(a0); +} diff --git a/tests/ffi/function_structs_by_value_generated_args_native_test.dart b/tests/ffi/function_structs_by_value_generated_args_native_test.dart index 368e0d7b8b4..39c18f21140 100644 --- a/tests/ffi/function_structs_by_value_generated_args_native_test.dart +++ b/tests/ffi/function_structs_by_value_generated_args_native_test.dart @@ -96,6 +96,8 @@ void main() { testPassWCharStructInlineArrayIntUintPtrx2LongUnsignedNative(); testPassInt64x7Struct12BytesHomogeneousInt32Native(); testPassPointerStruct12BytesHomogeneousInt32Native(); + testPassPointerStructInlineArrayVariableNative(); + testPassPointerStructInlineArrayVariableAlignNative(); } } @@ -5481,7 +5483,8 @@ external int passPointerStruct12BytesHomogeneousInt32Native( /// Passing a pointer to a struct void testPassPointerStruct12BytesHomogeneousInt32Native() { - final a0 = calloc(); + final a0 = calloc.allocate( + sizeOf()); a0.ref.a0 = -1; a0.ref.a1 = 2; @@ -5495,3 +5498,66 @@ void testPassPointerStruct12BytesHomogeneousInt32Native() { calloc.free(a0); } + +@Native)>( + symbol: 'PassPointerStructInlineArrayVariable') +external int passPointerStructInlineArrayVariableNative( + Pointer a0); + +/// Variable length array +void testPassPointerStructInlineArrayVariableNative() { + final a0 = calloc.allocate( + sizeOf() + 10 * sizeOf()); + + a0.ref.a0 = 1; + a0.ref.a1[0] = 2; + a0.ref.a1[1] = 3; + a0.ref.a1[2] = 4; + a0.ref.a1[3] = 5; + a0.ref.a1[4] = 6; + a0.ref.a1[5] = 7; + a0.ref.a1[6] = 8; + a0.ref.a1[7] = 9; + a0.ref.a1[8] = 10; + a0.ref.a1[9] = 11; + + final result = passPointerStructInlineArrayVariableNative(a0); + + print("result = $result"); + + Expect.equals(66, result); + + calloc.free(a0); +} + +@Native)>( + symbol: 'PassPointerStructInlineArrayVariableAlign') +external int passPointerStructInlineArrayVariableAlignNative( + Pointer a0); + +/// Variable length array with variable length element having more alignment than +/// the rest of the struct. +void testPassPointerStructInlineArrayVariableAlignNative() { + final a0 = calloc.allocate( + sizeOf() + 10 * sizeOf()); + + a0.ref.a0 = 1; + a0.ref.a1[0] = 2; + a0.ref.a1[1] = 3; + a0.ref.a1[2] = 4; + a0.ref.a1[3] = 5; + a0.ref.a1[4] = 6; + a0.ref.a1[5] = 7; + a0.ref.a1[6] = 8; + a0.ref.a1[7] = 9; + a0.ref.a1[8] = 10; + a0.ref.a1[9] = 11; + + final result = passPointerStructInlineArrayVariableAlignNative(a0); + + print("result = $result"); + + Expect.equals(66, result); + + calloc.free(a0); +} diff --git a/tests/ffi/function_structs_by_value_generated_args_test.dart b/tests/ffi/function_structs_by_value_generated_args_test.dart index 6536956c199..d78618b7cad 100644 --- a/tests/ffi/function_structs_by_value_generated_args_test.dart +++ b/tests/ffi/function_structs_by_value_generated_args_test.dart @@ -93,6 +93,8 @@ void main() { testPassWCharStructInlineArrayIntUintPtrx2LongUnsigned(); testPassInt64x7Struct12BytesHomogeneousInt32(); testPassPointerStruct12BytesHomogeneousInt32(); + testPassPointerStructInlineArrayVariable(); + testPassPointerStructInlineArrayVariableAlign(); } } @@ -5430,7 +5432,8 @@ final passPointerStruct12BytesHomogeneousInt32 = /// Passing a pointer to a struct void testPassPointerStruct12BytesHomogeneousInt32() { - final a0 = calloc(); + final a0 = calloc.allocate( + sizeOf()); a0.ref.a0 = -1; a0.ref.a1 = 2; @@ -5444,3 +5447,67 @@ void testPassPointerStruct12BytesHomogeneousInt32() { calloc.free(a0); } + +final passPointerStructInlineArrayVariable = ffiTestFunctions.lookupFunction< + Int64 Function(Pointer), + int Function(Pointer)>( + "PassPointerStructInlineArrayVariable"); + +/// Variable length array +void testPassPointerStructInlineArrayVariable() { + final a0 = calloc.allocate( + sizeOf() + 10 * sizeOf()); + + a0.ref.a0 = 1; + a0.ref.a1[0] = 2; + a0.ref.a1[1] = 3; + a0.ref.a1[2] = 4; + a0.ref.a1[3] = 5; + a0.ref.a1[4] = 6; + a0.ref.a1[5] = 7; + a0.ref.a1[6] = 8; + a0.ref.a1[7] = 9; + a0.ref.a1[8] = 10; + a0.ref.a1[9] = 11; + + final result = passPointerStructInlineArrayVariable(a0); + + print("result = $result"); + + Expect.equals(66, result); + + calloc.free(a0); +} + +final passPointerStructInlineArrayVariableAlign = + ffiTestFunctions.lookupFunction< + Int64 Function(Pointer), + int Function(Pointer)>( + "PassPointerStructInlineArrayVariableAlign"); + +/// Variable length array with variable length element having more alignment than +/// the rest of the struct. +void testPassPointerStructInlineArrayVariableAlign() { + final a0 = calloc.allocate( + sizeOf() + 10 * sizeOf()); + + a0.ref.a0 = 1; + a0.ref.a1[0] = 2; + a0.ref.a1[1] = 3; + a0.ref.a1[2] = 4; + a0.ref.a1[3] = 5; + a0.ref.a1[4] = 6; + a0.ref.a1[5] = 7; + a0.ref.a1[6] = 8; + a0.ref.a1[7] = 9; + a0.ref.a1[8] = 10; + a0.ref.a1[9] = 11; + + final result = passPointerStructInlineArrayVariableAlign(a0); + + print("result = $result"); + + Expect.equals(66, result); + + calloc.free(a0); +} diff --git a/tests/ffi/function_structs_by_value_generated_compounds.dart b/tests/ffi/function_structs_by_value_generated_compounds.dart index 3ce851f1ee2..b1e18f78aa2 100644 --- a/tests/ffi/function_structs_by_value_generated_compounds.dart +++ b/tests/ffi/function_structs_by_value_generated_compounds.dart @@ -1277,3 +1277,43 @@ final class StructInlineArrayInt extends Struct { String toString() => "(${[for (var i0 = 0; i0 < 10; i0 += 1) a0[i0]]})"; } + +final class StructInlineArrayVariable extends Struct { + @Uint32() + external int a0; + + @Array.variable() + external Array a1; + + String toString() => "(${a0}, ${a1})"; +} + +final class StructInlineArrayVariableNested extends Struct { + @Uint32() + external int a0; + + @Array.variable(2, 2) + external Array>> a1; + + String toString() => "(${a0}, ${a1})"; +} + +final class StructInlineArrayVariableNestedDeep extends Struct { + @Uint32() + external int a0; + + @Array.variableMulti([2, 2, 2, 2, 2, 2]) + external Array>>>>>> a1; + + String toString() => "(${a0}, ${a1})"; +} + +final class StructInlineArrayVariableAlign extends Struct { + @Uint8() + external int a0; + + @Array.variable() + external Array a1; + + String toString() => "(${a0}, ${a1})"; +} diff --git a/tests/ffi/generator/c_types.dart b/tests/ffi/generator/c_types.dart index 9bc255e782f..6e9fed2e7c5 100644 --- a/tests/ffi/generator/c_types.dart +++ b/tests/ffi/generator/c_types.dart @@ -247,7 +247,8 @@ class Member { String postFix = ""; if (type is FixedLengthArrayType) { final dimensions = (type as FixedLengthArrayType).dimensions; - postFix = "[${dimensions.join("][")}]"; + postFix = + "[${dimensions.map((d) => d == 0 ? '' : d.toString()).join("][")}]"; } return "${type.cType} $name$postFix;"; } @@ -479,6 +480,26 @@ class FixedLengthArrayType extends CType { bool get isOnlyBool => elementType.isOnlyBool; } +class VariableLengthArrayType extends FixedLengthArrayType { + VariableLengthArrayType( + CType elementType, + ) : super(elementType, 0); + + factory VariableLengthArrayType.multi( + CType elementType, List fixedDimensions) { + final nestedArray = + FixedLengthArrayType.multi(elementType, fixedDimensions); + return VariableLengthArrayType(nestedArray); + } + + String get dartStructFieldAnnotation { + if (dimensions.length > 5) { + return "@Array.variableMulti([${dimensions.skip(1).join(", ")}])"; + } + return "@Array.variable(${dimensions.skip(1).join(", ")})"; + } +} + class FunctionType extends CType { final List arguments; final int? varArgsIndex; diff --git a/tests/ffi/generator/structs_by_value_tests_configuration.dart b/tests/ffi/generator/structs_by_value_tests_configuration.dart index f217b7e975a..59bc829fb9a 100644 --- a/tests/ffi/generator/structs_by_value_tests_configuration.dart +++ b/tests/ffi/generator/structs_by_value_tests_configuration.dart @@ -457,6 +457,21 @@ Struct stradles last argument register"""), int64, """ Passing a pointer to a struct"""), + FunctionType( + [ + PointerType(structVariableLengthArray), + ], + int64, + """ +Variable length array"""), + FunctionType( + [ + PointerType(structVariableLengthArray4), + ], + int64, + """ +Variable length array with variable length element having more alignment than +the rest of the struct."""), ]; /// Functions that return a struct by value. @@ -711,6 +726,10 @@ final compounds = [ union16bytesFloat, union16bytesFloat2, structArrayWChar, + structVariableLengthArray, + structVariableLengthArray2, + structVariableLengthArray3, + structVariableLengthArray4, ]; /// Function signatures for variadic argument tests. @@ -1061,3 +1080,35 @@ final union16bytesFloat2 = /// This struct contains an AbiSpecificInt type. final structArrayWChar = StructType([FixedLengthArrayType(wchar, 10)]); + +final structVariableLengthArray = StructType.override( + [ + uint32, + VariableLengthArrayType(uint8), + ], + "InlineArrayVariable", +); + +final structVariableLengthArray2 = StructType.override( + [ + uint32, + VariableLengthArrayType.multi(uint8, [2, 2]), + ], + "InlineArrayVariableNested", +); + +final structVariableLengthArray3 = StructType.override( + [ + uint32, + VariableLengthArrayType.multi(uint8, [2, 2, 2, 2, 2, 2]), + ], + "InlineArrayVariableNestedDeep", +); + +final structVariableLengthArray4 = StructType.override( + [ + uint8, + VariableLengthArrayType(uint32), + ], + "InlineArrayVariableAlign", +); diff --git a/tests/ffi/generator/structs_by_value_tests_generator.dart b/tests/ffi/generator/structs_by_value_tests_generator.dart index 077a9e36e43..68b5221272d 100644 --- a/tests/ffi/generator/structs_by_value_tests_generator.dart +++ b/tests/ffi/generator/structs_by_value_tests_generator.dart @@ -76,8 +76,13 @@ extension on CType { return this_.members.take(1).toList().coutExpression("$variableName."); case FixedLengthArrayType: + case VariableLengthArrayType: final this_ = this as FixedLengthArrayType; - final indices = [for (var i = 0; i < this_.length; i += 1) i]; + final int length = switch (this_) { + VariableLengthArrayType _ => _variableLengthLength, + FixedLengthArrayType _ => this_.length, + }; + final indices = [for (var i = 0; i < length; i += 1) i]; String result = '<< "["'; result += indices @@ -144,8 +149,13 @@ extension on CType { .addToResultStatements("$variableName.${member.name}", isDart); case FixedLengthArrayType: + case VariableLengthArrayType: final this_ = this as FixedLengthArrayType; - final indices = [for (var i = 0; i < this_.length; i += 1) i]; + final int length = switch (this_) { + VariableLengthArrayType _ => _variableLengthLength, + FixedLengthArrayType _ => this_.length, + }; + final indices = [for (var i = 0; i < length; i += 1) i]; return indices .map((i) => this_.elementType .addToResultStatements("$variableName[$i]", isDart)) @@ -192,8 +202,13 @@ extension on CType { .assignValueStatements(a, "$variableName.${member.name}", isDart); case FixedLengthArrayType: + case VariableLengthArrayType: final this_ = this as FixedLengthArrayType; - final indices = [for (var i = 0; i < this_.length; i += 1) i]; + final int length = switch (this_) { + VariableLengthArrayType _ => _variableLengthLength, + FixedLengthArrayType _ => this_.length, + }; + final indices = [for (var i = 0; i < length; i += 1) i]; return indices .map((i) => this_.elementType .assignValueStatements(a, "$variableName[$i]", isDart)) @@ -301,8 +316,14 @@ extension on CType { final pointerTo = this_.pointerTo; switch (pointerTo) { case StructType _: + final lastMember = pointerTo.memberTypes.last; + final String extraBytes = switch (lastMember) { + VariableLengthArrayType _ => + '+ $_variableLengthLength * sizeOf<${lastMember.elementType.dartCType}>()', + _ => '', + }; return ''' - final ${variableName} = calloc<${pointerTo.dartType}>(); + final ${variableName} = calloc.allocate<${pointerTo.dartType}>(sizeOf<${pointerTo.dartType}>() $extraBytes); '''; } return "\n"; @@ -408,15 +429,33 @@ extension on CType { final pointerTo = this_.pointerTo; switch (pointerTo) { case StructType _: + final lastMember = pointerTo.memberTypes.last; + final String extraBytes = switch (lastMember) { + VariableLengthArrayType _ => + '+ $_variableLengthLength * sizeof(${lastMember.elementType.cType})', + _ => '', + }; return ''' -${pointerTo.cType} ${variableName}_value = {}; -${cType} ${variableName} = &${variableName}_value; +${cType} ${variableName} = static_cast<${cType}>(calloc(1, sizeof(${pointerTo.cType}) $extraBytes)); '''; } } throw Exception("Not implemented for ${this.runtimeType}"); } + + String cFreeStatements(String variableName) { + switch (this.runtimeType) { + case FundamentalType: + case StructType: + case UnionType: + return ""; + case PointerType: + return 'free(${variableName});'; + } + + throw Exception("Not implemented for ${this.runtimeType}"); + } } extension on List { @@ -425,6 +464,10 @@ extension on List { return map((m) => m.type.cAllocateStatements("$namePrefix${m.name}")) .join(); } + + String cFreeStatements([String namePrefix = ""]) { + return map((m) => m.type.cFreeStatements("$namePrefix${m.name}")).join(); + } } extension on CType { @@ -597,7 +640,8 @@ extension CompositeTypeGenerator on CompositeType { dartFields += "${member.dartStructField()}\n\n"; } String toStringBody = members.map((m) { - if (m.type is FixedLengthArrayType) { + if (m.type is FixedLengthArrayType && + m.type is! VariableLengthArrayType) { int dimensionNumber = 0; String inlineFor = ""; String read = m.name; @@ -993,6 +1037,7 @@ $varArgsUnpack String get cCallbackCode { final a = ArgumentValueAssigner(); final argumentAllocations = arguments.cAllocateStatements(); + final argumentFrees = arguments.cFreeStatements(); final assignValues = arguments.assignValueStatements(a, false); final argumentString = [ @@ -1062,6 +1107,8 @@ $varArgsUnpack $expectsZero + $argumentFrees + return 0; } @@ -1071,6 +1118,7 @@ $varArgsUnpack String get cAsyncCallbackCode { final a = ArgumentValueAssigner(); final argumentAllocations = arguments.cAllocateStatements(); + final argumentFrees = arguments.cFreeStatements(); final assignValues = arguments.assignValueStatements(a, false); final argumentString = [ @@ -1095,6 +1143,8 @@ $varArgsUnpack std::cout << "Calling TestAsync$cName(" ${arguments.coutExpression()} << ")\\n"; f($argumentNames); + + $argumentFrees } """; @@ -1469,3 +1519,6 @@ void main(List arguments) async { writeC(), ]); } + +// This test uses this number of elements for variable length arrays. +const _variableLengthLength = 10; diff --git a/tests/ffi/inline_array_variable_length_test.dart b/tests/ffi/inline_array_variable_length_test.dart new file mode 100644 index 00000000000..646c9dec891 --- /dev/null +++ b/tests/ffi/inline_array_variable_length_test.dart @@ -0,0 +1,94 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +// +// SharedObjects=ffi_test_functions + +import 'dart:ffi'; + +import "package:expect/expect.dart"; +import 'package:ffi/ffi.dart'; + +// Reuse compound definitions. +import 'function_structs_by_value_generated_compounds.dart'; + +void main() { + testInlineArray(); + testInlineArrayNested(); + testInlineArrayNestedDeep(); + testSizeOf(); +} + +void testInlineArray() { + const length = 10; + final lengthInBytes = + sizeOf() + sizeOf() * length; + final pointer = calloc.allocate(lengthInBytes); + pointer.ref.a0 = length; + final struct = pointer.ref; + for (int i = 0; i < length; i++) { + struct.a1[i] = i; + } + var sum = 0; + for (int i = 0; i < length; i++) { + sum += struct.a1[i]; + } + calloc.free(pointer); + Expect.equals(45, sum); +} + +void testInlineArrayNested() { + const length = 10; + final lengthInBytes = sizeOf() + + sizeOf() * length * 2 * 2; + final pointer = + calloc.allocate(lengthInBytes); + pointer.ref.a0 = length; + final struct = pointer.ref; + for (int i = 0; i < length; i++) { + struct.a1[i][0][0] = i; + } + var sum = 0; + for (int i = 0; i < length; i++) { + sum += struct.a1[i][0][0]; + } + calloc.free(pointer); + Expect.equals(45, sum); +} + +void testInlineArrayNestedDeep() { + const length = 10; + final lengthInBytes = sizeOf() + + sizeOf() * length * 2 * 2 * 2 * 2 * 2 * 2; + final pointer = + calloc.allocate(lengthInBytes); + pointer.ref.a0 = length; + final struct = pointer.ref; + for (int i = 0; i < length; i++) { + struct.a1[i][0][0][1][1][0][0] = i; + } + var sum = 0; + for (int i = 0; i < length; i++) { + sum += struct.a1[i][0][0][1][1][0][0]; + } + calloc.free(pointer); + Expect.equals(45, sum); +} + +final class Foo extends Struct { + @Int8() + external int field0; +} + +final class Foo2 extends Struct { + @Int8() + external int field0; + + @Array.variable() + external Array field1; +} + +void testSizeOf() { + Expect.equals(1, sizeOf()); + Expect.equals(4, sizeOf()); +} diff --git a/tests/ffi/native_callables_sync_structs_by_value_generated_test.dart b/tests/ffi/native_callables_sync_structs_by_value_generated_test.dart index 66a13fb190e..b033c4c7556 100644 --- a/tests/ffi/native_callables_sync_structs_by_value_generated_test.dart +++ b/tests/ffi/native_callables_sync_structs_by_value_generated_test.dart @@ -382,6 +382,16 @@ final testCases = [ Pointer.fromFunction( passPointerStruct12BytesHomogeneousInt32), noChecksAsync), + AsyncCallbackTest( + "PassPointerStructInlineArrayVariable", + Pointer.fromFunction( + passPointerStructInlineArrayVariable), + noChecksAsync), + AsyncCallbackTest( + "PassPointerStructInlineArrayVariableAlign", + Pointer.fromFunction( + passPointerStructInlineArrayVariableAlign), + noChecksAsync), AsyncCallbackTest( "ReturnStruct1ByteInt", Pointer.fromFunction(returnStruct1ByteInt), @@ -5167,6 +5177,77 @@ Future passPointerStruct12BytesHomogeneousInt32AfterCallback() async { Expect.approxEquals(-2, result); } +typedef PassPointerStructInlineArrayVariableType = Void Function( + Pointer); + +// Global variable that stores the result. +final PassPointerStructInlineArrayVariableResult = Completer(); + +/// Variable length array +void passPointerStructInlineArrayVariable( + Pointer a0) { + print("passPointerStructInlineArrayVariable(${a0})"); + + double result = 0; + + result += a0.ref.a0; + result += a0.ref.a1[0]; + result += a0.ref.a1[1]; + result += a0.ref.a1[2]; + result += a0.ref.a1[3]; + result += a0.ref.a1[4]; + result += a0.ref.a1[5]; + result += a0.ref.a1[6]; + result += a0.ref.a1[7]; + result += a0.ref.a1[8]; + result += a0.ref.a1[9]; + + print("result = $result"); + PassPointerStructInlineArrayVariableResult.complete(result); +} + +Future passPointerStructInlineArrayVariableAfterCallback() async { + final result = await PassPointerStructInlineArrayVariableResult.future; + print("after callback result = $result"); + Expect.approxEquals(66, result); +} + +typedef PassPointerStructInlineArrayVariableAlignType = Void Function( + Pointer); + +// Global variable that stores the result. +final PassPointerStructInlineArrayVariableAlignResult = Completer(); + +/// Variable length array with variable length element having more alignment than +/// the rest of the struct. +void passPointerStructInlineArrayVariableAlign( + Pointer a0) { + print("passPointerStructInlineArrayVariableAlign(${a0})"); + + double result = 0; + + result += a0.ref.a0; + result += a0.ref.a1[0]; + result += a0.ref.a1[1]; + result += a0.ref.a1[2]; + result += a0.ref.a1[3]; + result += a0.ref.a1[4]; + result += a0.ref.a1[5]; + result += a0.ref.a1[6]; + result += a0.ref.a1[7]; + result += a0.ref.a1[8]; + result += a0.ref.a1[9]; + + print("result = $result"); + PassPointerStructInlineArrayVariableAlignResult.complete(result); +} + +Future passPointerStructInlineArrayVariableAlignAfterCallback() async { + final result = await PassPointerStructInlineArrayVariableAlignResult.future; + print("after callback result = $result"); + Expect.approxEquals(66, result); +} + typedef ReturnStruct1ByteIntType = Void Function(Int8); // Global variable that stores the result. diff --git a/tests/ffi/static_checks/vmspecific_static_checks_array_test.dart b/tests/ffi/static_checks/vmspecific_static_checks_array_test.dart new file mode 100644 index 00000000000..3c9a39b8125 --- /dev/null +++ b/tests/ffi/static_checks/vmspecific_static_checks_array_test.dart @@ -0,0 +1,74 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +// +// Dart test program for testing dart:ffi extra checks +// +// SharedObjects=ffi_test_dynamic_library ffi_test_functions + +import 'dart:ffi'; + +void main() {} + +final class TestStruct1 extends Struct { + /**/ @Array.variable() + // ^^^^^^^^^^^^^^^^^ + // [analyzer] COMPILE_TIME_ERROR.SIZE_ANNOTATION_DIMENSIONS + external Array> a0; + // ^ + // [cfe] Field 'a0' must have an 'Array' annotation that matches the dimensions. +} + +final class TestStruct2 extends Struct { + /**/ @Array.variable() + // ^^^^^^^^^^^^^^^^^ + // [analyzer] COMPILE_TIME_ERROR.VARIABLE_LENGTH_ARRAY_NOT_LAST + external Array a0; + // ^ + // [cfe] Variable length 'Array's must only occur as the last field of Structs. + + @Uint8() + external int a1; +} + +final class TestStruct3 extends Struct { + // This should be a Array.variable() not an `@Array(0)`. + @Array(0) + // ^ + // [analyzer] COMPILE_TIME_ERROR.NON_POSITIVE_ARRAY_DIMENSION + external Array a0; + // ^^ + // [cfe] Array dimensions must be positive numbers. +} + +final class TestStruct4 extends Struct { + /**/ @Array.variable(1, 2) + // ^^^^^^^^^^^^^^^^^^^^^ + // [analyzer] COMPILE_TIME_ERROR.SIZE_ANNOTATION_DIMENSIONS + external Array> a0; + // ^ + // [cfe] Field 'a0' must have an 'Array' annotation that matches the dimensions. +} + +final class TestStruct5 extends Struct { + /**/ @Array.variableMulti([1, 2]) + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // [analyzer] COMPILE_TIME_ERROR.SIZE_ANNOTATION_DIMENSIONS + external Array> a0; + // ^ + // [cfe] Field 'a0' must have an 'Array' annotation that matches the dimensions. +} + +final class TestStruct6 extends Struct { + @Array.variableMulti([1, 2]) + external Array>> a0; +} + +final class TestStruct7 extends Struct { + /**/ @Array.variableMulti([1, 2]) + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // [analyzer] COMPILE_TIME_ERROR.SIZE_ANNOTATION_DIMENSIONS + external Array>>> a0; + // ^ + // [cfe] Field 'a0' must have an 'Array' annotation that matches the dimensions. +}