diff --git a/benchmarks/FfiMemory/dart/FfiMemory.dart b/benchmarks/FfiMemory/dart/FfiMemory.dart index 26ff8e78480..686d6bd5bf2 100644 --- a/benchmarks/FfiMemory/dart/FfiMemory.dart +++ b/benchmarks/FfiMemory/dart/FfiMemory.dart @@ -15,6 +15,34 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:benchmark_harness/benchmark_harness.dart'; +/// Represents a native unsigned pointer-sized integer in C. +/// +/// [UintPtr] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), + Abi.androidArm64: Uint64(), + Abi.androidIA32: Uint32(), + Abi.androidX64: Uint64(), + Abi.fuchsiaArm64: Uint64(), + Abi.fuchsiaX64: Uint64(), + Abi.iosArm: Uint32(), + Abi.iosArm64: Uint64(), + Abi.iosX64: Uint64(), + Abi.linuxArm: Uint32(), + Abi.linuxArm64: Uint64(), + Abi.linuxIA32: Uint32(), + Abi.linuxX64: Uint64(), + Abi.macosArm64: Uint64(), + Abi.macosX64: Uint64(), + Abi.windowsArm64: Uint64(), + Abi.windowsIA32: Uint32(), + Abi.windowsX64: Uint64(), +}) +class UintPtr extends AbiSpecificInteger { + const UintPtr(); +} + // // Pointer store. // @@ -73,6 +101,12 @@ void doStoreUint64(Pointer pointer, int length) { } } +void doStoreUintPtr(Pointer pointer, int length) { + for (int i = 0; i < length; i++) { + pointer[i] = 1; + } +} + void doStoreFloat(Pointer pointer, int length) { for (int i = 0; i < length; i++) { pointer[i] = 1.0; @@ -174,6 +208,14 @@ int doLoadUint64(Pointer pointer, int length) { return x; } +int doLoadUintPtr(Pointer pointer, int length) { + int x = 0; + for (int i = 0; i < length; i++) { + x += pointer[i]; + } + return x; +} + double doLoadFloat(Pointer pointer, int length) { double x = 0; for (int i = 0; i < length; i++) { @@ -452,6 +494,25 @@ class PointerUint64 extends BenchmarkBase { } } +class PointerUintPtr extends BenchmarkBase { + Pointer pointer = nullptr; + PointerUintPtr() : super('FfiMemory.PointerUintPtr'); + + @override + void setup() => pointer = calloc(N); + @override + void teardown() => calloc.free(pointer); + + @override + void run() { + doStoreUintPtr(pointer, N); + final int x = doLoadUintPtr(pointer, N); + if (x != N) { + throw Exception('$name: Unexpected result: $x'); + } + } +} + class PointerFloat extends BenchmarkBase { Pointer pointer = nullptr; PointerFloat() : super('FfiMemory.PointerFloat'); @@ -555,6 +616,7 @@ void main() { () => PointerInt64(), () => PointerInt64Mint(), () => PointerUint64(), + () => PointerUintPtr(), () => PointerFloat(), () => PointerDouble(), () => PointerPointer(), diff --git a/benchmarks/FfiMemory/dart2/FfiMemory.dart b/benchmarks/FfiMemory/dart2/FfiMemory.dart index 8ed63022336..a170aaa4915 100644 --- a/benchmarks/FfiMemory/dart2/FfiMemory.dart +++ b/benchmarks/FfiMemory/dart2/FfiMemory.dart @@ -17,6 +17,34 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:benchmark_harness/benchmark_harness.dart'; +/// Represents a native unsigned pointer-sized integer in C. +/// +/// [UintPtr] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), + Abi.androidArm64: Uint64(), + Abi.androidIA32: Uint32(), + Abi.androidX64: Uint64(), + Abi.fuchsiaArm64: Uint64(), + Abi.fuchsiaX64: Uint64(), + Abi.iosArm: Uint32(), + Abi.iosArm64: Uint64(), + Abi.iosX64: Uint64(), + Abi.linuxArm: Uint32(), + Abi.linuxArm64: Uint64(), + Abi.linuxIA32: Uint32(), + Abi.linuxX64: Uint64(), + Abi.macosArm64: Uint64(), + Abi.macosX64: Uint64(), + Abi.windowsArm64: Uint64(), + Abi.windowsIA32: Uint32(), + Abi.windowsX64: Uint64(), +}) +class UintPtr extends AbiSpecificInteger { + const UintPtr(); +} + // // Pointer store. // @@ -75,6 +103,12 @@ void doStoreUint64(Pointer pointer, int length) { } } +void doStoreUintPtr(Pointer pointer, int length) { + for (int i = 0; i < length; i++) { + pointer[i] = 1; + } +} + void doStoreFloat(Pointer pointer, int length) { for (int i = 0; i < length; i++) { pointer[i] = 1.0; @@ -176,6 +210,14 @@ int doLoadUint64(Pointer pointer, int length) { return x; } +int doLoadUintPtr(Pointer pointer, int length) { + int x = 0; + for (int i = 0; i < length; i++) { + x += pointer[i]; + } + return x; +} + double doLoadFloat(Pointer pointer, int length) { double x = 0; for (int i = 0; i < length; i++) { @@ -391,7 +433,7 @@ class PointerUint32 extends BenchmarkBase { } class PointerUint32Unaligned extends BenchmarkBase { - Pointer pointer; + Pointer pointer; Pointer unalignedPointer; PointerUint32Unaligned() : super('FfiMemory.PointerUint32Unaligned'); @@ -452,6 +494,25 @@ class PointerUint64 extends BenchmarkBase { } } +class PointerUintPtr extends BenchmarkBase { + Pointer pointer; + PointerUintPtr() : super('FfiMemory.PointerUintPtr'); + + @override + void setup() => pointer = calloc(N); + @override + void teardown() => calloc.free(pointer); + + @override + void run() { + doStoreUintPtr(pointer, N); + final int x = doLoadUintPtr(pointer, N); + if (x != N) { + throw Exception('$name: Unexpected result: $x'); + } + } +} + class PointerFloat extends BenchmarkBase { Pointer pointer; PointerFloat() : super('FfiMemory.PointerFloat'); @@ -555,6 +616,7 @@ void main() { () => PointerInt64(), () => PointerInt64Mint(), () => PointerUint64(), + () => PointerUintPtr(), () => PointerFloat(), () => PointerDouble(), () => PointerPointer(), 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 ac646231656..14682eadc43 100644 --- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart +++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart @@ -3835,6 +3835,26 @@ const MessageCode messageFastaUsageShort = const MessageCode("FastaUsageShort", -o Generate the output into . -h Display this message (add -v for information about all options)."""); +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeFfiAbiSpecificIntegerInvalid = + messageFfiAbiSpecificIntegerInvalid; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode messageFfiAbiSpecificIntegerInvalid = const MessageCode( + "FfiAbiSpecificIntegerInvalid", + problemMessage: + r"""Classes extending 'AbiSpecificInteger' must have exactly one const constructor, no other members, and no type arguments."""); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeFfiAbiSpecificIntegerMappingInvalid = + messageFfiAbiSpecificIntegerMappingInvalid; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode messageFfiAbiSpecificIntegerMappingInvalid = const MessageCode( + "FfiAbiSpecificIntegerMappingInvalid", + problemMessage: + r"""Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a NativeType integer with a fixed size."""); + // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const Template< Message Function( diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart index 80ccf01183a..8a34bc85ec2 100644 --- a/pkg/analyzer/lib/error/error.dart +++ b/pkg/analyzer/lib/error/error.dart @@ -477,6 +477,7 @@ const List errorCodeValues = [ CompileTimeErrorCode.YIELD_IN_NON_GENERATOR, CompileTimeErrorCode.YIELD_EACH_OF_INVALID_TYPE, CompileTimeErrorCode.YIELD_OF_INVALID_TYPE, + FfiCode.ABI_SPECIFIC_INTEGER_INVALID, FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_EXTRA, FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_MISSING, FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED, 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 ee9823d9918..620289ca40b 100644 --- a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart +++ b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart @@ -15,6 +15,18 @@ import "package:analyzer/src/error/analyzer_error_code.dart"; // ignore_for_file: slash_for_doc_comments class FfiCode extends AnalyzerErrorCode { + /** + * No parameters. + */ + static const FfiCode ABI_SPECIFIC_INTEGER_INVALID = FfiCode( + 'ABI_SPECIFIC_INTEGER_INVALID', + "Classes extending 'AbiSpecificInteger' must have exactly one const " + "constructor, no other members, and no type arguments.", + correctionMessage: + "Try removing all type arguments, removing all members, and adding one " + "const constructor.", + ); + /** * No parameters. */ @@ -451,9 +463,10 @@ class FfiCode extends AnalyzerErrorCode { */ static const FfiCode SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS = FfiCode( 'SUBTYPE_OF_STRUCT_CLASS', - "The class '{0}' can't extend '{1}' because '{1}' is a subtype of 'Struct' " - "or 'Union'.", - correctionMessage: "Try extending 'Struct' or 'Union' directly.", + "The class '{0}' can't extend '{1}' because '{1}' is a subtype of " + "'Struct', 'Union', or 'AbiSpecificInteger'.", + correctionMessage: + "Try extending 'Struct', 'Union', or 'AbiSpecificInteger' directly.", uniqueName: 'SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS', ); @@ -465,8 +478,9 @@ class FfiCode extends AnalyzerErrorCode { static const FfiCode SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS = FfiCode( 'SUBTYPE_OF_STRUCT_CLASS', "The class '{0}' can't implement '{1}' because '{1}' is a subtype of " - "'Struct' or 'Union'.", - correctionMessage: "Try extending 'Struct' or 'Union' directly.", + "'Struct', 'Union', or 'AbiSpecificInteger'.", + correctionMessage: + "Try extending 'Struct', 'Union', or 'AbiSpecificInteger' directly.", uniqueName: 'SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS', ); @@ -477,9 +491,10 @@ class FfiCode extends AnalyzerErrorCode { */ static const FfiCode SUBTYPE_OF_STRUCT_CLASS_IN_WITH = FfiCode( 'SUBTYPE_OF_STRUCT_CLASS', - "The class '{0}' can't mix in '{1}' because '{1}' is a subtype of 'Struct' " - "or 'Union'.", - correctionMessage: "Try extending 'Struct' or 'Union' directly.", + "The class '{0}' can't mix in '{1}' because '{1}' is a subtype of " + "'Struct', 'Union', or 'AbiSpecificInteger'.", + correctionMessage: + "Try extending 'Struct', 'Union', or 'AbiSpecificInteger' directly.", uniqueName: 'SUBTYPE_OF_STRUCT_CLASS_IN_WITH', ); diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart index 69b37a52dd2..aadcb78f9fd 100644 --- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart +++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart @@ -92,6 +92,7 @@ class FfiVerifier extends RecursiveAstVisitor { _validatePackedAnnotation(node.metadata); } } else if (className == _abiSpecificIntegerClassName) { + _validateAbiSpecificIntegerAnnotation(node); _validateAbiSpecificIntegerMappingAnnotation( node.name, node.metadata); } else if (className != _allocatorClassName && @@ -102,7 +103,8 @@ class FfiVerifier extends RecursiveAstVisitor { superclass.name, [node.name.name, superclass.name.name]); } - } else if (superclass.isCompoundSubtype) { + } else if (superclass.isCompoundSubtype || + superclass.isAbiSpecificIntegerSubtype) { _errorReporter.reportErrorForNode( FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS, superclass, @@ -120,7 +122,8 @@ class FfiVerifier extends RecursiveAstVisitor { if (typename.ffiClass != null) { _errorReporter.reportErrorForNode(subtypeOfFfiCode, typename, [node.name.name, typename.name.toSource()]); - } else if (typename.isCompoundSubtype) { + } else if (typename.isCompoundSubtype || + typename.isAbiSpecificIntegerSubtype) { _errorReporter.reportErrorForNode(subtypeOfStructCode, typename, [node.name.name, typename.name.toSource()]); } @@ -614,6 +617,16 @@ class FfiVerifier extends RecursiveAstVisitor { return _PrimitiveDartType.none; } + void _validateAbiSpecificIntegerAnnotation(ClassDeclaration node) { + if ((node.typeParameters?.length ?? 0) != 0 || + node.members.length != 1 || + node.members.single is! ConstructorDeclaration || + (node.members.single as ConstructorDeclaration).constKeyword == null) { + _errorReporter.reportErrorForNode( + FfiCode.ABI_SPECIFIC_INTEGER_INVALID, node.name); + } + } + /// Validate that the [annotations] include at most one mapping annotation. void _validateAbiSpecificIntegerMappingAnnotation( AstNode errorNode, NodeList annotations) { @@ -1557,6 +1570,17 @@ extension on DartType { return false; } + bool get isAbiSpecificInteger { + final self = this; + if (self is InterfaceType) { + final element = self.element; + final name = element.name; + return name == FfiVerifier._abiSpecificIntegerClassName && + element.isFfiClass; + } + return false; + } + /// Returns `true` iff this is an Abi-specific integer type, /// i.e. a subtype of `AbiSpecificInteger`. bool get isAbiSpecificIntegerSubtype { @@ -1626,4 +1650,13 @@ extension on NamedType { } return false; } + + /// Return `true` if this represents a subtype of `Struct` or `Union`. + bool get isAbiSpecificIntegerSubtype { + var element = name.staticElement; + if (element is ClassElement) { + return element.allSupertypes.any((e) => e.isAbiSpecificInteger); + } + return false; + } } diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml index 7ad997e935d..675733f537c 100644 --- a/pkg/analyzer/messages.yaml +++ b/pkg/analyzer/messages.yaml @@ -13775,6 +13775,10 @@ CompileTimeErrorCode: } ``` FfiCode: + ABI_SPECIFIC_INTEGER_INVALID: + problemMessage: "Classes extending 'AbiSpecificInteger' must have exactly one const constructor, no other members, and no type arguments." + correctionMessage: Try removing all type arguments, removing all members, and adding one const constructor. + comment: No parameters. ABI_SPECIFIC_INTEGER_MAPPING_EXTRA: problemMessage: "Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a 'NativeType' integer with a fixed size." correctionMessage: Try removing the extra annotation. @@ -13978,24 +13982,24 @@ FfiCode: 1: the name of the class being extended, implemented, or mixed in SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS: sharedName: SUBTYPE_OF_STRUCT_CLASS - problemMessage: "The class '{0}' can't extend '{1}' because '{1}' is a subtype of 'Struct' or 'Union'." - correctionMessage: "Try extending 'Struct' or 'Union' directly." + problemMessage: "The class '{0}' can't extend '{1}' because '{1}' is a subtype of 'Struct', 'Union', or 'AbiSpecificInteger'." + correctionMessage: "Try extending 'Struct', 'Union', or 'AbiSpecificInteger' directly." comment: |- Parameters: 0: the name of the subclass 1: the name of the class being extended, implemented, or mixed in SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS: sharedName: SUBTYPE_OF_STRUCT_CLASS - problemMessage: "The class '{0}' can't implement '{1}' because '{1}' is a subtype of 'Struct' or 'Union'." - correctionMessage: "Try extending 'Struct' or 'Union' directly." + problemMessage: "The class '{0}' can't implement '{1}' because '{1}' is a subtype of 'Struct', 'Union', or 'AbiSpecificInteger'." + correctionMessage: "Try extending 'Struct', 'Union', or 'AbiSpecificInteger' directly." comment: |- Parameters: 0: the name of the subclass 1: the name of the class being extended, implemented, or mixed in SUBTYPE_OF_STRUCT_CLASS_IN_WITH: sharedName: SUBTYPE_OF_STRUCT_CLASS - problemMessage: "The class '{0}' can't mix in '{1}' because '{1}' is a subtype of 'Struct' or 'Union'." - correctionMessage: "Try extending 'Struct' or 'Union' directly." + problemMessage: "The class '{0}' can't mix in '{1}' because '{1}' is a subtype of 'Struct', 'Union', or 'AbiSpecificInteger'." + correctionMessage: "Try extending 'Struct', 'Union', or 'AbiSpecificInteger' directly." comment: |- Parameters: 0: the name of the subclass diff --git a/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart b/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart index 969c1be5eba..0d24687a173 100644 --- a/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart +++ b/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart @@ -45,6 +45,23 @@ class C extends S {} @reflectiveTest class SubtypeOfStructClassInImplementsTest extends PubPackageResolutionTest { + test_implements_abi_specific_int() async { + await assertErrorsInCode(r''' +import 'dart:ffi'; +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), +}) +class AbiSpecificInteger1 extends AbiSpecificInteger { + const AbiSpecificInteger1(); +} +class AbiSpecificInteger4 implements AbiSpecificInteger1 { + const AbiSpecificInteger4(); +} +''', [ + error(FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS, 204, 19), + ]); + } + test_implements_struct() async { await assertErrorsInCode(r''' import 'dart:ffi'; diff --git a/pkg/front_end/lib/src/api_unstable/vm.dart b/pkg/front_end/lib/src/api_unstable/vm.dart index b9b4ca702d0..b9158478e6d 100644 --- a/pkg/front_end/lib/src/api_unstable/vm.dart +++ b/pkg/front_end/lib/src/api_unstable/vm.dart @@ -54,6 +54,8 @@ export '../fasta/compiler_context.dart' show CompilerContext; export '../fasta/fasta_codes.dart' show LocatedMessage, + messageFfiAbiSpecificIntegerInvalid, + messageFfiAbiSpecificIntegerMappingInvalid, messageFfiExceptionalReturnNull, messageFfiExpectedConstant, messageFfiLeafCallMustNotReturnHandle, diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status index ceaa2a11f9a..10ad152843f 100644 --- a/pkg/front_end/messages.status +++ b/pkg/front_end/messages.status @@ -334,6 +334,8 @@ FastaUsageLong/analyzerCode: Fail FastaUsageLong/example: Fail FastaUsageShort/analyzerCode: Fail FastaUsageShort/example: Fail +FfiAbiSpecificIntegerInvalid/analyzerCode: Fail +FfiAbiSpecificIntegerMappingInvalid/analyzerCode: Fail FfiDartTypeMismatch/analyzerCode: Fail FfiEmptyStruct/analyzerCode: Fail FfiExceptionalReturnNull/analyzerCode: Fail diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index 3c8ec81fe5b..6632763c38c 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -4554,6 +4554,16 @@ StaticAndInstanceConflictCause: problemMessage: "This is the instance member." severity: CONTEXT +FfiAbiSpecificIntegerInvalid: + # Used by dart:ffi + problemMessage: "Classes extending 'AbiSpecificInteger' must have exactly one const constructor, no other members, and no type arguments." + external: test/ffi_test.dart + +FfiAbiSpecificIntegerMappingInvalid: + # Used by dart:ffi + problemMessage: "Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a NativeType integer with a fixed size." + external: test/ffi_test.dart + FfiTypeMismatch: # Used by dart:ffi problemMessage: "Expected type '#type' to be '#type2', which is the Dart type corresponding to '#type3'." diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt index 28be9b12535..39c9d6c6e9b 100644 --- a/pkg/front_end/test/spell_checking_list_common.txt +++ b/pkg/front_end/test/spell_checking_list_common.txt @@ -11,6 +11,7 @@ a abbreviations +abi ability able abort diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt index ffc3264e3a3..30bf90dd2b9 100644 --- a/pkg/front_end/test/spell_checking_list_messages.txt +++ b/pkg/front_end/test/spell_checking_list_messages.txt @@ -10,6 +10,8 @@ # automatic tools might move it to the top of the file. JS +abispecificinteger +abispecificintegermapping adjusting api argument(s) @@ -65,6 +67,7 @@ placing pubspec.yaml re sdksummary +size solutions stacktrace staticinterop diff --git a/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.1.expect b/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.1.expect index 4e4f7644a9a..ffb841a99fd 100644 --- a/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.1.expect +++ b/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.1.expect @@ -40,6 +40,10 @@ additionalExports = (ffi::nullptr, ffi::sizeOf, ffi::Dart_NativeMessageHandler, ffi::Abi, + ffi::AbiSpecificInteger, + ffi::AbiSpecificIntegerArray, + ffi::AbiSpecificIntegerMapping, + ffi::AbiSpecificIntegerPointer, ffi::Allocator, ffi::AllocatorAlloc, ffi::Array, @@ -114,6 +118,10 @@ additionalExports = (ffi::nullptr, ffi::sizeOf, ffi::Dart_NativeMessageHandler, ffi::Abi, + ffi::AbiSpecificInteger, + ffi::AbiSpecificIntegerArray, + ffi::AbiSpecificIntegerMapping, + ffi::AbiSpecificIntegerPointer, ffi::Allocator, ffi::AllocatorAlloc, ffi::Array, diff --git a/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.2.expect b/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.2.expect index ead3ad4df50..40804c4adfd 100644 --- a/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.2.expect +++ b/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.2.expect @@ -40,6 +40,10 @@ additionalExports = (ffi::nullptr, ffi::sizeOf, ffi::Dart_NativeMessageHandler, ffi::Abi, + ffi::AbiSpecificInteger, + ffi::AbiSpecificIntegerArray, + ffi::AbiSpecificIntegerMapping, + ffi::AbiSpecificIntegerPointer, ffi::Allocator, ffi::AllocatorAlloc, ffi::Array, @@ -114,6 +118,10 @@ additionalExports = (ffi::nullptr, ffi::sizeOf, ffi::Dart_NativeMessageHandler, ffi::Abi, + ffi::AbiSpecificInteger, + ffi::AbiSpecificIntegerArray, + ffi::AbiSpecificIntegerMapping, + ffi::AbiSpecificIntegerPointer, ffi::Allocator, ffi::AllocatorAlloc, ffi::Array, 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 0f9dc6dcf34..0db513dff49 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 @@ -25,5 +25,5 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:138:9) - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) 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 edf8c4063f1..b41e17171c2 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 @@ -52,5 +52,5 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:138:9) - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect index 732d28224fe..e6c0f958734 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect @@ -25,5 +25,5 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:138:9) - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.modular.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.modular.expect index 732d28224fe..e6c0f958734 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.modular.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.modular.expect @@ -25,5 +25,5 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:138:9) - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect index a445d6240f8..c21a02f2fb0 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect @@ -52,5 +52,5 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:138:9) - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) 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 eaeea6341f1..0e058b10c06 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 @@ -33,5 +33,5 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:138:9) - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) 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 c77db0df40a..dec871457d4 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 @@ -84,5 +84,5 @@ Extra constant evaluation: evaluated: 110, effectively constant: 2 Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:138:9) - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect index b6819cb9805..5e4b756023e 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect @@ -33,5 +33,5 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:138:9) - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.modular.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.modular.expect index b6819cb9805..5e4b756023e 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.modular.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.modular.expect @@ -33,5 +33,5 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:138:9) - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect index 7de8504337c..e5881fb4355 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect @@ -84,5 +84,5 @@ Extra constant evaluation: evaluated: 110, effectively constant: 2 Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:138:9) - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9) diff --git a/pkg/vm/lib/transformations/ffi/common.dart b/pkg/vm/lib/transformations/ffi/common.dart index 42235ff6354..b50d0a2c509 100644 --- a/pkg/vm/lib/transformations/ffi/common.dart +++ b/pkg/vm/lib/transformations/ffi/common.dart @@ -18,6 +18,7 @@ import 'package:kernel/type_environment.dart' show TypeEnvironment, SubtypeCheckMode; import 'abi.dart'; +import 'native_type_cfe.dart'; /// Represents the (instantiated) ffi.NativeType. enum NativeType { @@ -44,7 +45,7 @@ enum NativeType { kBool, } -const Set nativeIntTypes = { +const Set nativeIntTypesFixedSize = { NativeType.kInt8, NativeType.kInt16, NativeType.kInt32, @@ -53,6 +54,10 @@ const Set nativeIntTypes = { NativeType.kUint16, NativeType.kUint32, NativeType.kUint64, +}; + +const Set nativeIntTypes = { + ...nativeIntTypesFixedSize, NativeType.kIntptr, }; @@ -178,11 +183,15 @@ class FfiTransformer extends Transformer { final Class compoundClass; final Class structClass; final Class unionClass; + final Class abiSpecificIntegerClass; + final Class abiSpecificIntegerMappingClass; final Class ffiNativeClass; final Class nativeFieldWrapperClass1Class; final Class ffiStructLayoutClass; final Field ffiStructLayoutTypesField; final Field ffiStructLayoutPackingField; + final Class ffiAbiSpecificMappingClass; + final Field ffiAbiSpecificMappingNativeTypesField; final Class ffiInlineArrayClass; final Field ffiInlineArrayElementTypeField; final Field ffiInlineArrayLengthField; @@ -202,6 +211,12 @@ class FfiTransformer extends Transformer { final Procedure unionArrayElemAt; final Procedure arrayArrayElemAt; final Procedure arrayArrayAssignAt; + final Procedure abiSpecificIntegerPointerGetValue; + final Procedure abiSpecificIntegerPointerSetValue; + final Procedure abiSpecificIntegerPointerElemAt; + final Procedure abiSpecificIntegerPointerSetElemAt; + final Procedure abiSpecificIntegerArrayElemAt; + final Procedure abiSpecificIntegerArraySetElemAt; final Procedure asFunctionMethod; final Procedure asFunctionInternal; final Procedure sizeOfMethod; @@ -228,6 +243,12 @@ class FfiTransformer extends Transformer { final Map storeMethods; final Map storeUnalignedMethods; final Map elementAtMethods; + final Procedure loadAbiSpecificIntMethod; + final Procedure loadAbiSpecificIntAtIndexMethod; + final Procedure storeAbiSpecificIntMethod; + final Procedure storeAbiSpecificIntAtIndexMethod; + final Procedure abiCurrentMethod; + final Map constantAbis; final Procedure memCopy; final Procedure allocationTearoff; final Procedure asFunctionTearoff; @@ -301,6 +322,10 @@ class FfiTransformer extends Transformer { compoundClass = index.getClass('dart:ffi', '_Compound'), structClass = index.getClass('dart:ffi', 'Struct'), unionClass = index.getClass('dart:ffi', 'Union'), + abiSpecificIntegerClass = + index.getClass('dart:ffi', 'AbiSpecificInteger'), + abiSpecificIntegerMappingClass = + index.getClass('dart:ffi', 'AbiSpecificIntegerMapping'), ffiNativeClass = index.getClass('dart:ffi', 'FfiNative'), nativeFieldWrapperClass1Class = index.getClass('dart:nativewrappers', 'NativeFieldWrapperClass1'), @@ -309,6 +334,10 @@ class FfiTransformer extends Transformer { index.getField('dart:ffi', '_FfiStructLayout', 'fieldTypes'), ffiStructLayoutPackingField = index.getField('dart:ffi', '_FfiStructLayout', 'packing'), + ffiAbiSpecificMappingClass = + index.getClass('dart:ffi', '_FfiAbiSpecificMapping'), + ffiAbiSpecificMappingNativeTypesField = + index.getField('dart:ffi', '_FfiAbiSpecificMapping', 'nativeTypes'), ffiInlineArrayClass = index.getClass('dart:ffi', '_FfiInlineArray'), ffiInlineArrayElementTypeField = index.getField('dart:ffi', '_FfiInlineArray', 'elementType'), @@ -362,6 +391,18 @@ class FfiTransformer extends Transformer { arrayArrayElemAt = index.getProcedure('dart:ffi', 'ArrayArray', '[]'), arrayArrayAssignAt = index.getProcedure('dart:ffi', 'ArrayArray', '[]='), + abiSpecificIntegerPointerGetValue = index.getProcedure( + 'dart:ffi', 'AbiSpecificIntegerPointer', 'get:value'), + abiSpecificIntegerPointerSetValue = index.getProcedure( + 'dart:ffi', 'AbiSpecificIntegerPointer', 'set:value'), + abiSpecificIntegerPointerElemAt = + index.getProcedure('dart:ffi', 'AbiSpecificIntegerPointer', '[]'), + abiSpecificIntegerPointerSetElemAt = + index.getProcedure('dart:ffi', 'AbiSpecificIntegerPointer', '[]='), + abiSpecificIntegerArrayElemAt = + index.getProcedure('dart:ffi', 'AbiSpecificIntegerArray', '[]'), + abiSpecificIntegerArraySetElemAt = + index.getProcedure('dart:ffi', 'AbiSpecificIntegerArray', '[]='), asFunctionMethod = index.getProcedure( 'dart:ffi', 'NativeFunctionPointer', 'asFunction'), asFunctionInternal = @@ -406,6 +447,20 @@ class FfiTransformer extends Transformer { final name = nativeTypeClassNames[t]; return index.getTopLevelProcedure('dart:ffi', "_elementAt$name"); }), + loadAbiSpecificIntMethod = + index.getTopLevelProcedure('dart:ffi', "_loadAbiSpecificInt"), + loadAbiSpecificIntAtIndexMethod = index.getTopLevelProcedure( + 'dart:ffi', "_loadAbiSpecificIntAtIndex"), + storeAbiSpecificIntMethod = + index.getTopLevelProcedure('dart:ffi', "_storeAbiSpecificInt"), + storeAbiSpecificIntAtIndexMethod = index.getTopLevelProcedure( + 'dart:ffi', "_storeAbiSpecificIntAtIndex"), + abiCurrentMethod = index.getProcedure('dart:ffi', 'Abi', 'current'), + constantAbis = abiNames.map((abi, name) => MapEntry( + (index.getField('dart:ffi', 'Abi', name).initializer + as ConstantExpression) + .constant, + abi)), memCopy = index.getTopLevelProcedure('dart:ffi', '_memCopy'), allocationTearoff = index.getProcedure( 'dart:ffi', 'AllocatorAlloc', LibraryIndex.tearoffPrefix + 'call'), @@ -451,6 +506,7 @@ class FfiTransformer extends Transformer { /// [Uint32] -> [int] /// [Uint64] -> [int] /// [IntPtr] -> [int] + /// T extends [AbiSpecificInteger] -> [int] /// [Double] -> [double] /// [Float] -> [double] /// [Bool] -> [bool] @@ -477,6 +533,9 @@ class FfiTransformer extends Transformer { } return nativeType; } + if (hierarchy.isSubclassOf(nativeClass, abiSpecificIntegerClass)) { + return InterfaceType(intClass, Nullability.legacy); + } if (hierarchy.isSubclassOf(nativeClass, compoundClass)) { if (nativeClass == structClass || nativeClass == unionClass) { return null; @@ -773,6 +832,24 @@ class FfiTransformer extends Transformer { return dimensions; } + bool isAbiSpecificIntegerSubtype(DartType type) { + if (type is InvalidType) { + return false; + } + if (type is NullType) { + return false; + } + if (type is InterfaceType) { + if (type.classNode == abiSpecificIntegerClass) { + return false; + } + } + return env.isSubtypeOf( + type, + InterfaceType(abiSpecificIntegerClass, Nullability.legacy), + SubtypeCheckMode.ignoringNullabilities); + } + bool isCompoundSubtype(DartType type) { if (type is InvalidType) { return false; @@ -822,6 +899,76 @@ class FfiTransformer extends Transformer { interfaceTarget: numMultiplication, functionType: numMultiplication.getterType as FunctionType); } + + Iterable getAbiSpecificIntegerMappingAnnotations(Class node) { + return node.annotations + .whereType() + .map((e) => e.constant) + .whereType() + .where((e) => e.classNode == abiSpecificIntegerMappingClass) + .map((instanceConstant) => + instanceConstant.fieldValues.values.single as MapConstant); + } + + /// Generates an expression performing an Abi specific integer load or store. + /// + /// If [value] is provided, it is a store, otherwise a load. + /// + /// Provide either [index], or [offsetInBytes], or none for an offset of 0. + /// + /// Generates an expression: + /// + /// ```dart + /// _storeAbiSpecificInt( + /// [8, 8, 4][_abi()], + /// typedDataBase, + /// index * [8, 8, 4][_abi()], + /// value, + /// ) + /// ``` + Expression abiSpecificLoadOrStoreExpression( + AbiSpecificNativeTypeCfe nativeTypeCfe, { + required Expression typedDataBase, + Expression? offsetInBytes, + Expression? index, + Expression? value, + required fileOffset, + }) { + assert(index == null || offsetInBytes == null); + final method = () { + if (value != null) { + if (index != null) { + return storeAbiSpecificIntAtIndexMethod; + } + return storeAbiSpecificIntMethod; + } + if (index != null) { + return loadAbiSpecificIntAtIndexMethod; + } + return loadAbiSpecificIntMethod; + }(); + + final Expression offsetOrIndex = () { + if (offsetInBytes != null) { + return offsetInBytes; + } + if (index != null) { + return index; + } + return ConstantExpression(IntConstant(0)); + }(); + + return StaticInvocation( + method, + Arguments([ + typedDataBase, + offsetOrIndex, + if (value != null) value, + ], types: [ + InterfaceType(nativeTypeCfe.clazz, Nullability.nonNullable) + ]), + )..fileOffset = fileOffset; + } } /// Checks if any library depends on dart:ffi. diff --git a/pkg/vm/lib/transformations/ffi/definitions.dart b/pkg/vm/lib/transformations/ffi/definitions.dart index fbf2052c988..fe38da05496 100644 --- a/pkg/vm/lib/transformations/ffi/definitions.dart +++ b/pkg/vm/lib/transformations/ffi/definitions.dart @@ -4,6 +4,8 @@ import 'package:front_end/src/api_unstable/vm.dart' show + messageFfiAbiSpecificIntegerInvalid, + messageFfiAbiSpecificIntegerMappingInvalid, messageFfiPackedAnnotationAlignment, messageNonPositiveArrayDimensions, templateFfiEmptyStruct, @@ -248,8 +250,36 @@ class _FfiDefinitionTransformer extends FfiTransformer { return true; } + bool _isUserAbiSpecificInteger(Class node) => + hierarchy.isSubclassOf(node, abiSpecificIntegerClass) && + node != abiSpecificIntegerClass; + @override visitClass(Class node) { + if (_isUserAbiSpecificInteger(node)) { + final nativeTypeCfe = NativeTypeCfe( + this, node.getThisType(coreTypes, Nullability.nonNullable)) + as AbiSpecificNativeTypeCfe; + if (nativeTypeCfe.abiSpecificTypes.length == 0) { + // Annotation missing, multiple annotations, or invalid mapping. + diagnosticReporter.report(messageFfiAbiSpecificIntegerMappingInvalid, + node.fileOffset, node.name.length, node.location!.file); + } + if (node.typeParameters.length != 0 || + node.procedures.where((Procedure e) => !e.isSynthetic).length != 0 || + node.fields.length != 0 || + node.redirectingFactories.length != 0 || + node.constructors.length != 1 || + !node.constructors.single.isConst) { + // We want exactly one constructor, no other members and no type arguments. + diagnosticReporter.report(messageFfiAbiSpecificIntegerInvalid, + node.fileOffset, node.name.length, node.location!.file); + } + final IndexedClass? indexedClass = + currentLibraryIndex?.lookupIndexedClass(node.name); + _addSizeOfField(node, indexedClass, nativeTypeCfe.size); + _annotateAbiSpecificTypeWithMapping(node, nativeTypeCfe); + } if (!_isUserCompound(node)) { return node; } @@ -594,8 +624,13 @@ class _FfiDefinitionTransformer extends FfiTransformer { final nativeTypeAnnos = _getNativeTypeAnnotations(m).toList(); if (nativeTypeAnnos.length == 1) { final clazz = nativeTypeAnnos.first; - final nativeType = _getFieldType(clazz)!; - type = PrimitiveNativeTypeCfe(nativeType, clazz); + if (_isUserAbiSpecificInteger(clazz)) { + type = NativeTypeCfe( + this, clazz.getThisType(coreTypes, Nullability.nonNullable)); + } else { + final nativeType = _getFieldType(clazz)!; + type = PrimitiveNativeTypeCfe(nativeType, clazz); + } } } @@ -791,6 +826,33 @@ class _FfiDefinitionTransformer extends FfiTransformer { InterfaceType(pragmaClass, Nullability.nonNullable, []))); } + static const vmFfiAbiSpecificIntMapping = 'vm:ffi:abi-specific-mapping'; + + void _annotateAbiSpecificTypeWithMapping( + Class node, AbiSpecificNativeTypeCfe nativeTypeCfe) { + final constants = [ + for (final abi in Abi.values) + nativeTypeCfe.abiSpecificTypes[abi]?.generateConstant(this) ?? + NullConstant() + ]; + node.addAnnotation(ConstantExpression( + InstanceConstant(pragmaClass.reference, [], { + pragmaName.fieldReference: StringConstant(vmFfiAbiSpecificIntMapping), + pragmaOptions.fieldReference: InstanceConstant( + ffiAbiSpecificMappingClass.reference, + [], + { + ffiAbiSpecificMappingNativeTypesField.fieldReference: + ListConstant( + InterfaceType(typeClass, Nullability.nullable), + constants, + ), + }, + ) + }), + InterfaceType(pragmaClass, Nullability.nonNullable, []))); + } + void _generateMethodsForField( Class node, Field field, @@ -890,7 +952,8 @@ class _FfiDefinitionTransformer extends FfiTransformer { .map((expr) => expr.constant) .whereType() .map((constant) => constant.classNode) - .where((klass) => _getFieldType(klass) != null); + .where((klass) => + _getFieldType(klass) != null || _isUserAbiSpecificInteger(klass)); } Iterable> _getArraySizeAnnotations(Member node) { diff --git a/pkg/vm/lib/transformations/ffi/native_type_cfe.dart b/pkg/vm/lib/transformations/ffi/native_type_cfe.dart index 862d1bd49d1..bb73a945570 100644 --- a/pkg/vm/lib/transformations/ffi/native_type_cfe.dart +++ b/pkg/vm/lib/transformations/ffi/native_type_cfe.dart @@ -16,7 +16,8 @@ import 'common.dart'; abstract class NativeTypeCfe { factory NativeTypeCfe(FfiTransformer transformer, DartType dartType, {List? arrayDimensions, - Map compoundCache = const {}}) { + Map compoundCache = const {}, + alreadyInAbiSpecificType = false}) { if (transformer.isPrimitiveType(dartType)) { final clazz = (dartType as InterfaceType).classNode; final nativeType = transformer.getType(clazz)!; @@ -48,6 +49,32 @@ abstract class NativeTypeCfe { } return ArrayNativeTypeCfe.multi(elementCfeType, arrayDimensions); } + if (transformer.isAbiSpecificIntegerSubtype(dartType)) { + final clazz = (dartType as InterfaceType).classNode; + final mappingConstants = + transformer.getAbiSpecificIntegerMappingAnnotations(clazz); + if (alreadyInAbiSpecificType || mappingConstants.length != 1) { + // Unsupported mapping. + return AbiSpecificNativeTypeCfe({}, clazz); + } + final mapping = + Map.fromEntries(mappingConstants.first.entries.map((e) => MapEntry( + transformer.constantAbis[e.key]!, + NativeTypeCfe( + transformer, + (e.value as InstanceConstant).classNode.getThisType( + transformer.coreTypes, Nullability.nonNullable), + alreadyInAbiSpecificType: true, + )))); + for (final value in mapping.values) { + if (value is! PrimitiveNativeTypeCfe || + !nativeIntTypesFixedSize.contains(value.nativeType)) { + // Unsupported mapping. + return AbiSpecificNativeTypeCfe({}, clazz); + } + } + return AbiSpecificNativeTypeCfe(mapping, clazz); + } throw "Invalid type $dartType"; } @@ -557,6 +584,66 @@ class ArrayNativeTypeCfe implements NativeTypeCfe { ..fileOffset = fileOffset); } +class AbiSpecificNativeTypeCfe implements NativeTypeCfe { + final Map abiSpecificTypes; + + final Class clazz; + + AbiSpecificNativeTypeCfe(this.abiSpecificTypes, this.clazz); + + @override + Map get size => abiSpecificTypes + .map((abi, nativeTypeCfe) => MapEntry(abi, nativeTypeCfe.size[abi])); + + @override + Map get alignment => abiSpecificTypes + .map((abi, nativeTypeCfe) => MapEntry(abi, nativeTypeCfe.alignment[abi])); + + @override + Constant generateConstant(FfiTransformer transformer) => + TypeLiteralConstant(InterfaceType(clazz, Nullability.nonNullable)); + + @override + ReturnStatement generateGetterStatement( + DartType dartType, + int fileOffset, + Map offsets, + bool unalignedAccess, + FfiTransformer transformer, + ) { + return ReturnStatement( + transformer.abiSpecificLoadOrStoreExpression( + this, + typedDataBase: transformer.getCompoundTypedDataBaseField( + ThisExpression(), fileOffset), + offsetInBytes: transformer.runtimeBranchOnLayout(offsets), + fileOffset: fileOffset, + ), + ); + } + + @override + ReturnStatement generateSetterStatement( + DartType dartType, + int fileOffset, + Map offsets, + bool unalignedAccess, + VariableDeclaration argument, + FfiTransformer transformer, + ) { + return ReturnStatement( + transformer.abiSpecificLoadOrStoreExpression( + this, + typedDataBase: transformer.getCompoundTypedDataBaseField( + ThisExpression(), fileOffset), + offsetInBytes: transformer.runtimeBranchOnLayout(offsets), + value: VariableGet(argument), + fileOffset: fileOffset, + ), + ); + } +} + extension on int? { int? align(int? alignment) => ((this + alignment - 1) ~/ alignment) * alignment; diff --git a/pkg/vm/lib/transformations/ffi/use_sites.dart b/pkg/vm/lib/transformations/ffi/use_sites.dart index 69a52e53583..d0e44333d11 100644 --- a/pkg/vm/lib/transformations/ffi/use_sites.dart +++ b/pkg/vm/lib/transformations/ffi/use_sites.dart @@ -27,6 +27,7 @@ import 'package:kernel/type_algebra.dart' show Substitution; import 'package:kernel/type_environment.dart'; import 'abi.dart' show wordSize; +import 'native_type_cfe.dart'; import 'common.dart' show NativeType, FfiTransformer, nativeTypeSizes, WORD_SIZE, UNKNOWN; @@ -132,6 +133,42 @@ class _FfiUseSiteTransformer extends FfiTransformer { final Member target = node.target; try { + if (target == abiSpecificIntegerPointerGetValue || + target == abiSpecificIntegerPointerSetValue || + target == abiSpecificIntegerPointerElemAt || + target == abiSpecificIntegerPointerSetElemAt || + target == abiSpecificIntegerArrayElemAt || + target == abiSpecificIntegerArraySetElemAt) { + final pointer = node.arguments.positional[0]; + final pointerType = + pointer.getStaticType(_staticTypeContext!) as InterfaceType; + _ensureNativeTypeValid(pointerType, pointer, + allowCompounds: true, allowInlineArray: true); + + final typeArg = pointerType.typeArguments.single; + final nativeTypeCfe = + NativeTypeCfe(this, typeArg) as AbiSpecificNativeTypeCfe; + + return abiSpecificLoadOrStoreExpression( + nativeTypeCfe, + typedDataBase: (target == abiSpecificIntegerArrayElemAt || + target == abiSpecificIntegerArraySetElemAt) + ? getArrayTypedDataBaseField(node.arguments.positional[0]) + : node.arguments.positional[0], + index: (target == abiSpecificIntegerPointerElemAt || + target == abiSpecificIntegerPointerSetElemAt || + target == abiSpecificIntegerArrayElemAt || + target == abiSpecificIntegerArraySetElemAt) + ? node.arguments.positional[1] + : null, + value: (target == abiSpecificIntegerPointerSetValue || + target == abiSpecificIntegerPointerSetElemAt || + target == abiSpecificIntegerArraySetElemAt) + ? node.arguments.positional.last + : null, + fileOffset: node.fileOffset, + ); + } if (target == structPointerRef || target == structPointerElemAt || target == unionPointerRef || @@ -785,13 +822,19 @@ class _FfiUseSiteTransformer extends FfiTransformer { klass == opaqueClass || klass == structClass || klass == unionClass || + klass == abiSpecificIntegerClass || classNativeTypes[klass] != null) { return null; } // The Opaque and Struct classes can be extended, but subclasses // cannot be (nor implemented). - final onlyDirectExtendsClasses = [opaqueClass, structClass, unionClass]; + final onlyDirectExtendsClasses = [ + opaqueClass, + structClass, + unionClass, + abiSpecificIntegerClass, + ]; final superClass = klass.superclass; for (final onlyDirectExtendsClass in onlyDirectExtendsClasses) { if (hierarchy.isSubtypeOf(klass, onlyDirectExtendsClass)) { diff --git a/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart b/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart new file mode 100644 index 00000000000..50266bda3d1 --- /dev/null +++ b/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart @@ -0,0 +1,106 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:ffi'; + +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), + Abi.androidArm64: Uint32(), + Abi.androidIA32: Uint32(), + Abi.androidX64: Uint32(), + Abi.fuchsiaArm64: Uint64(), + Abi.fuchsiaX64: Uint32(), + Abi.iosArm: Uint32(), + Abi.iosArm64: Uint32(), + Abi.iosX64: Uint32(), + Abi.linuxArm: Uint32(), + Abi.linuxArm64: Uint32(), + Abi.linuxIA32: Uint32(), + Abi.linuxX64: Uint32(), + Abi.macosArm64: Uint32(), + Abi.macosX64: Uint32(), + Abi.windowsArm64: Uint16(), + Abi.windowsIA32: Uint16(), + Abi.windowsX64: Uint16(), +}) +class WChar extends AbiSpecificInteger { + const WChar(); +} + +void main() { + testSizeOf(); + testStoreLoad(); + testStoreLoadIndexed(); + testStruct(); + testInlineArray(); +} + +void testSizeOf() { + final size = sizeOf(); + print(size); +} + +void testStoreLoad() { + final p = noAlloc(); + p.value = 10; + print(p.value); + noAlloc.free(p); +} + +void testStoreLoadIndexed() { + final p = noAlloc(2); + p[0] = 10; + p[1] = 3; + print(p[0]); + print(p[1]); + noAlloc.free(p); +} + +class WCharStruct extends Struct { + @WChar() + external int a0; + + @WChar() + external int a1; +} + +void testStruct() { + final p = noAlloc(); + p.ref.a0 = 1; + print(p.ref.a0); + p.ref.a0 = 2; + print(p.ref.a0); + noAlloc.free(p); +} + +class WCharArrayStruct extends Struct { + @Array(100) + external Array a0; +} + +void testInlineArray() { + final p = noAlloc(); + final array = p.ref.a0; + for (int i = 0; i < 100; i++) { + array[i] = i; + } + for (int i = 0; i < 100; i++) { + print(array[i]); + } + noAlloc.free(p); +} + +const noAlloc = _DummyAllocator(); + +class _DummyAllocator implements Allocator { + const _DummyAllocator(); + + @override + Pointer allocate(int byteCount, {int? alignment}) { + return Pointer.fromAddress(0); + } + + @override + void free(Pointer pointer) {} +} diff --git a/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart.expect b/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart.expect new file mode 100644 index 00000000000..d8ba7e5655e --- /dev/null +++ b/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart.expect @@ -0,0 +1,205 @@ +library #lib /*isNonNullableByDefault*/; +import self as self; +import "dart:ffi" as ffi; +import "dart:core" as core; +import "dart:typed_data" as typ; +import "dart:_internal" as _in; + +import "dart:ffi"; + +@#C49 +@#C56 +class WChar extends ffi::AbiSpecificInteger /*hasConstConstructor*/ { + const constructor •() → self::WChar + : super ffi::AbiSpecificInteger::•() + ; + @#C59 + static get #sizeOf() → core::int* + return #C61.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}; +} +@#C66 +class WCharStruct extends ffi::Struct { + synthetic constructor •() → self::WCharStruct + : super ffi::Struct::•() + ; + constructor #fromTypedDataBase(core::Object #typedDataBase) → self::WCharStruct + : super ffi::Struct::_fromTypedDataBase(#typedDataBase) + ; + @#C67 + get a0() → core::int + return ffi::_loadAbiSpecificInt(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C68.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}); + @#C67 + set a0(core::int #externalFieldValue) → void + return ffi::_storeAbiSpecificInt(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C68.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue); + @#C67 + get a1() → core::int + return ffi::_loadAbiSpecificInt(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C61.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}); + @#C67 + set a1(core::int #externalFieldValue) → void + return ffi::_storeAbiSpecificInt(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C61.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue); + @#C59 + static get #sizeOf() → core::int* + return #C70.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}; +} +@#C75 +class WCharArrayStruct extends ffi::Struct { + synthetic constructor •() → self::WCharArrayStruct + : super ffi::Struct::•() + ; + constructor #fromTypedDataBase(core::Object #typedDataBase) → self::WCharArrayStruct + : super ffi::Struct::_fromTypedDataBase(#typedDataBase) + ; + @#C76 + get a0() → ffi::Array + return new ffi::Array::_( block { + core::Object #typedDataBase = this.{ffi::_Compound::_typedDataBase}{core::Object}; + core::int #offset = #C68.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}; + } =>#typedDataBase is ffi::Pointer ?{core::Object} ffi::_fromAddress(#typedDataBase.{ffi::Pointer::address}{core::int}.{core::num::+}(#offset){(core::num) → core::num}) : let typ::TypedData #typedData = _in::unsafeCast(#typedDataBase) in #typedData.{typ::TypedData::buffer}{typ::ByteBuffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}{core::int}.{core::num::+}(#offset){(core::num) → core::num}, #C80.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}){([core::int, core::int?]) → typ::Uint8List}, #C71, #C81); + @#C76 + set a0(ffi::Array #externalFieldValue) → void + return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C68.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue.{ffi::Array::_typedDataBase}{core::Object}, #C1, #C80.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}); + @#C59 + static get #sizeOf() → core::int* + return #C80.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}; +} +class _DummyAllocator extends core::Object implements ffi::Allocator /*hasConstConstructor*/ { + const constructor •() → self::_DummyAllocator + : super core::Object::•() + ; + @#C82 + method allocate(core::int byteCount, {core::int? alignment = #C58}) → ffi::Pointer { + return ffi::Pointer::fromAddress(0); + } + @#C82 + method free(ffi::Pointer pointer) → void {} +} +static const field self::_DummyAllocator noAlloc = #C83; +static method main() → void { + self::testSizeOf(); + self::testStoreLoad(); + self::testStoreLoadIndexed(); + self::testStruct(); + self::testInlineArray(); +} +static method testSizeOf() → void { + final core::int size = self::WChar::#sizeOf; + core::print(size); +} +static method testStoreLoad() → void { + final ffi::Pointer p = #C83.{ffi::Allocator::allocate}(self::WChar::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; + ffi::_storeAbiSpecificInt(p, #C1, 10); + core::print(ffi::_loadAbiSpecificInt(p, #C1)); + #C83.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; +} +static method testStoreLoadIndexed() → void { + final ffi::Pointer p = #C83.{ffi::Allocator::allocate}(2.{core::num::*}(self::WChar::#sizeOf){(core::num) → core::num}){(core::int, {alignment: core::int?}) → ffi::Pointer}; + ffi::_storeAbiSpecificIntAtIndex(p, 0, 10); + ffi::_storeAbiSpecificIntAtIndex(p, 1, 3); + core::print(ffi::_loadAbiSpecificIntAtIndex(p, 0)); + core::print(ffi::_loadAbiSpecificIntAtIndex(p, 1)); + #C83.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; +} +static method testStruct() → void { + final ffi::Pointer p = #C83.{ffi::Allocator::allocate}(self::WCharStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; + new self::WCharStruct::#fromTypedDataBase(p!).{self::WCharStruct::a0} = 1; + core::print(new self::WCharStruct::#fromTypedDataBase(p!).{self::WCharStruct::a0}{core::int}); + new self::WCharStruct::#fromTypedDataBase(p!).{self::WCharStruct::a0} = 2; + core::print(new self::WCharStruct::#fromTypedDataBase(p!).{self::WCharStruct::a0}{core::int}); + #C83.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; +} +static method testInlineArray() → void { + final ffi::Pointer p = #C83.{ffi::Allocator::allocate}(self::WCharArrayStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer}; + final ffi::Array array = new self::WCharArrayStruct::#fromTypedDataBase(p!).{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}) { + ffi::_storeAbiSpecificIntAtIndex(array.{ffi::Array::_typedDataBase}{core::Object}, i, i); + } + for (core::int i = 0; i.{core::num::<}(100){(core::num) → core::bool}; i = i.{core::num::+}(1){(core::num) → core::int}) { + core::print(ffi::_loadAbiSpecificIntAtIndex(array.{ffi::Array::_typedDataBase}{core::Object}, i)); + } + #C83.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; +} +constants { + #C1 = 0 + #C2 = "android" + #C3 = ffi::_OS {index:#C1, _name:#C2} + #C4 = "arm" + #C5 = ffi::_Architecture {index:#C1, _name:#C4} + #C6 = ffi::Abi {_os:#C3, _architecture:#C5} + #C7 = ffi::Uint32 {} + #C8 = 1 + #C9 = "arm64" + #C10 = ffi::_Architecture {index:#C8, _name:#C9} + #C11 = ffi::Abi {_os:#C3, _architecture:#C10} + #C12 = 2 + #C13 = "ia32" + #C14 = ffi::_Architecture {index:#C12, _name:#C13} + #C15 = ffi::Abi {_os:#C3, _architecture:#C14} + #C16 = 3 + #C17 = "x64" + #C18 = ffi::_Architecture {index:#C16, _name:#C17} + #C19 = ffi::Abi {_os:#C3, _architecture:#C18} + #C20 = "fuchsia" + #C21 = ffi::_OS {index:#C8, _name:#C20} + #C22 = ffi::Abi {_os:#C21, _architecture:#C10} + #C23 = ffi::Uint64 {} + #C24 = ffi::Abi {_os:#C21, _architecture:#C18} + #C25 = "ios" + #C26 = ffi::_OS {index:#C12, _name:#C25} + #C27 = ffi::Abi {_os:#C26, _architecture:#C5} + #C28 = ffi::Abi {_os:#C26, _architecture:#C10} + #C29 = ffi::Abi {_os:#C26, _architecture:#C18} + #C30 = "linux" + #C31 = ffi::_OS {index:#C16, _name:#C30} + #C32 = ffi::Abi {_os:#C31, _architecture:#C5} + #C33 = ffi::Abi {_os:#C31, _architecture:#C10} + #C34 = ffi::Abi {_os:#C31, _architecture:#C14} + #C35 = ffi::Abi {_os:#C31, _architecture:#C18} + #C36 = 4 + #C37 = "macos" + #C38 = ffi::_OS {index:#C36, _name:#C37} + #C39 = ffi::Abi {_os:#C38, _architecture:#C10} + #C40 = ffi::Abi {_os:#C38, _architecture:#C18} + #C41 = 5 + #C42 = "windows" + #C43 = ffi::_OS {index:#C41, _name:#C42} + #C44 = ffi::Abi {_os:#C43, _architecture:#C10} + #C45 = ffi::Uint16 {} + #C46 = ffi::Abi {_os:#C43, _architecture:#C14} + #C47 = ffi::Abi {_os:#C43, _architecture:#C18} + #C48 = {#C6:#C7, #C11:#C7, #C15:#C7, #C19:#C7, #C22:#C23, #C24:#C7, #C27:#C7, #C28:#C7, #C29:#C7, #C32:#C7, #C33:#C7, #C34:#C7, #C35:#C7, #C39:#C7, #C40:#C7, #C44:#C45, #C46:#C45, #C47:#C45) + #C49 = ffi::AbiSpecificIntegerMapping {mapping:#C48} + #C50 = "vm:ffi:abi-specific-mapping" + #C51 = TypeLiteralConstant(ffi::Uint32) + #C52 = TypeLiteralConstant(ffi::Uint64) + #C53 = TypeLiteralConstant(ffi::Uint16) + #C54 = [#C51, #C51, #C51, #C51, #C52, #C51, #C51, #C51, #C51, #C51, #C51, #C51, #C51, #C51, #C51, #C53, #C53, #C53] + #C55 = ffi::_FfiAbiSpecificMapping {nativeTypes:#C54} + #C56 = core::pragma {name:#C50, options:#C55} + #C57 = "vm:prefer-inline" + #C58 = null + #C59 = core::pragma {name:#C57, options:#C58} + #C60 = 8 + #C61 = [#C36, #C36, #C36, #C36, #C60, #C36, #C36, #C36, #C36, #C36, #C36, #C36, #C36, #C36, #C36, #C12, #C12, #C12] + #C62 = "vm:ffi:struct-fields" + #C63 = TypeLiteralConstant(self::WChar) + #C64 = [#C63, #C63] + #C65 = ffi::_FfiStructLayout {fieldTypes:#C64, packing:#C58} + #C66 = core::pragma {name:#C62, options:#C65} + #C67 = self::WChar {} + #C68 = [#C1, #C1, #C1, #C1, #C1, #C1, #C1, #C1, #C1, #C1, #C1, #C1, #C1, #C1, #C1, #C1, #C1, #C1] + #C69 = 16 + #C70 = [#C60, #C60, #C60, #C60, #C69, #C60, #C60, #C60, #C60, #C60, #C60, #C60, #C60, #C60, #C60, #C36, #C36, #C36] + #C71 = 100 + #C72 = ffi::_FfiInlineArray {elementType:#C63, length:#C71} + #C73 = [#C72] + #C74 = ffi::_FfiStructLayout {fieldTypes:#C73, packing:#C58} + #C75 = core::pragma {name:#C62, options:#C74} + #C76 = ffi::_ArraySize {dimension1:#C71, dimension2:#C58, dimension3:#C58, dimension4:#C58, dimension5:#C58, dimensions:#C58} + #C77 = 400 + #C78 = 800 + #C79 = 200 + #C80 = [#C77, #C77, #C77, #C77, #C78, #C77, #C77, #C77, #C77, #C77, #C77, #C77, #C77, #C77, #C77, #C79, #C79, #C79] + #C81 = [] + #C82 = core::_Override {} + #C83 = self::_DummyAllocator {} +} diff --git a/pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart b/pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart new file mode 100644 index 00000000000..b9759e0e52e --- /dev/null +++ b/pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart @@ -0,0 +1,92 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:ffi'; + +@AbiSpecificIntegerMapping({ + Abi.linuxArm: Uint32(), + Abi.linuxArm64: Uint32(), + Abi.linuxIA32: Uint32(), + Abi.linuxX64: Uint32(), +}) +class Incomplete extends AbiSpecificInteger { + const Incomplete(); +} + +void main() { + testSizeOf(); + testStoreLoad(); + testStoreLoadIndexed(); + testStruct(); + testInlineArray(); +} + +void testSizeOf() { + final size = sizeOf(); + print(size); +} + +void testStoreLoad() { + final p = noAlloc(); + p.value = 10; + print(p.value); + noAlloc.free(p); +} + +void testStoreLoadIndexed() { + final p = noAlloc(2); + p[0] = 10; + p[1] = 3; + print(p[0]); + print(p[1]); + noAlloc.free(p); +} + +class IncompleteStruct extends Struct { + @Incomplete() + external int a0; + + @Incomplete() + external int a1; +} + +void testStruct() { + final p = noAlloc(); + p.ref.a0 = 1; + print(p.ref.a0); + p.ref.a0 = 2; + print(p.ref.a0); + noAlloc.free(p); +} + +class IncompleteArrayStruct extends Struct { + @Array(100) + external Array a0; +} + +void testInlineArray() { + final p = noAlloc(); + final array = p.ref.a0; + for (int i = 0; i < 100; i++) { + array[i] = i; + } + for (int i = 0; i < 100; i++) { + print(array[i]); + } + noAlloc.free(p); +} + +const noAlloc = _DummyAllocator(); + +class _DummyAllocator implements Allocator { + const _DummyAllocator(); + + @override + Pointer allocate(int byteCount, {int? alignment}) { + return Pointer.fromAddress(0); + } + + @override + void free(Pointer pointer) {} +} 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 new file mode 100644 index 00000000000..2acfbf64bc0 --- /dev/null +++ b/pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart.expect @@ -0,0 +1,173 @@ +library #lib /*isNonNullableByDefault*/; +import self as self; +import "dart:ffi" as ffi; +import "dart:core" as core; +import "dart:typed_data" as typ; +import "dart:_internal" as _in; + +import "dart:ffi"; + +@#C21 +@#C27 +class Incomplete extends ffi::AbiSpecificInteger /*hasConstConstructor*/ { + const constructor •() → self::Incomplete + : super ffi::AbiSpecificInteger::•() + ; + @#C29 + static get #sizeOf() → core::int* + return ffi::_checkAbiSpecificIntegerMapping(#C31.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}); +} +@#C36 +class IncompleteStruct extends ffi::Struct { + synthetic constructor •() → self::IncompleteStruct + : super ffi::Struct::•() + ; + constructor #fromTypedDataBase(core::Object #typedDataBase) → self::IncompleteStruct + : super ffi::Struct::_fromTypedDataBase(#typedDataBase) + ; + @#C37 + get a0() → core::int + return ffi::_loadAbiSpecificInt(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C38.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}); + @#C37 + set a0(core::int #externalFieldValue) → void + return ffi::_storeAbiSpecificInt(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C38.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue); + @#C37 + get a1() → core::int + return ffi::_loadAbiSpecificInt(this.{ffi::_Compound::_typedDataBase}{core::Object}, ffi::_checkAbiSpecificIntegerMapping(#C31.{core::List::[]}(ffi::_abi()){(core::int) → core::int*})); + @#C37 + set a1(core::int #externalFieldValue) → void + return ffi::_storeAbiSpecificInt(this.{ffi::_Compound::_typedDataBase}{core::Object}, ffi::_checkAbiSpecificIntegerMapping(#C31.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}), #externalFieldValue); + @#C29 + static get #sizeOf() → core::int* + return ffi::_checkAbiSpecificIntegerMapping(#C40.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}); +} +@#C45 +class IncompleteArrayStruct extends ffi::Struct { + synthetic constructor •() → self::IncompleteArrayStruct + : super ffi::Struct::•() + ; + constructor #fromTypedDataBase(core::Object #typedDataBase) → self::IncompleteArrayStruct + : super ffi::Struct::_fromTypedDataBase(#typedDataBase) + ; + @#C46 + get a0() → ffi::Array + return new ffi::Array::_( block { + core::Object #typedDataBase = this.{ffi::_Compound::_typedDataBase}{core::Object}; + core::int #offset = #C38.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}; + } =>#typedDataBase is ffi::Pointer ?{core::Object} ffi::_fromAddress(#typedDataBase.{ffi::Pointer::address}{core::int}.{core::num::+}(#offset){(core::num) → core::num}) : let typ::TypedData #typedData = _in::unsafeCast(#typedDataBase) in #typedData.{typ::TypedData::buffer}{typ::ByteBuffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}{core::int}.{core::num::+}(#offset){(core::num) → core::num}, ffi::_checkAbiSpecificIntegerMapping(#C48.{core::List::[]}(ffi::_abi()){(core::int) → core::int*})){([core::int, core::int?]) → typ::Uint8List}, #C41, #C49); + @#C46 + set a0(ffi::Array #externalFieldValue) → void + return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C38.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue.{ffi::Array::_typedDataBase}{core::Object}, #C4, ffi::_checkAbiSpecificIntegerMapping(#C48.{core::List::[]}(ffi::_abi()){(core::int) → core::int*})); + @#C29 + static get #sizeOf() → core::int* + return ffi::_checkAbiSpecificIntegerMapping(#C48.{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 + method allocate(core::int byteCount, {core::int? alignment = #C23}) → ffi::Pointer { + return ffi::Pointer::fromAddress(0); + } + @#C50 + method free(ffi::Pointer pointer) → void {} +} +static const field self::_DummyAllocator noAlloc = #C51; +static method main() → void { + self::testSizeOf(); + self::testStoreLoad(); + self::testStoreLoadIndexed(); + self::testStruct(); + self::testInlineArray(); +} +static method testSizeOf() → void { + final core::int size = self::Incomplete::#sizeOf; + 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}; + ffi::_storeAbiSpecificInt(p, #C4, 10); + core::print(ffi::_loadAbiSpecificInt(p, #C4)); + #C51.{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}; + ffi::_storeAbiSpecificIntAtIndex(p, 0, 10); + ffi::_storeAbiSpecificIntAtIndex(p, 1, 3); + core::print(ffi::_loadAbiSpecificIntAtIndex(p, 0)); + core::print(ffi::_loadAbiSpecificIntAtIndex(p, 1)); + #C51.{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}; + new self::IncompleteStruct::#fromTypedDataBase(p!).{self::IncompleteStruct::a0} = 1; + core::print(new self::IncompleteStruct::#fromTypedDataBase(p!).{self::IncompleteStruct::a0}{core::int}); + new self::IncompleteStruct::#fromTypedDataBase(p!).{self::IncompleteStruct::a0} = 2; + core::print(new self::IncompleteStruct::#fromTypedDataBase(p!).{self::IncompleteStruct::a0}{core::int}); + #C51.{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::Array array = new self::IncompleteArrayStruct::#fromTypedDataBase(p!).{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}) { + ffi::_storeAbiSpecificIntAtIndex(array.{ffi::Array::_typedDataBase}{core::Object}, i, i); + } + for (core::int i = 0; i.{core::num::<}(100){(core::num) → core::bool}; i = i.{core::num::+}(1){(core::num) → core::int}) { + core::print(ffi::_loadAbiSpecificIntAtIndex(array.{ffi::Array::_typedDataBase}{core::Object}, i)); + } + #C51.{self::_DummyAllocator::free}(p){(ffi::Pointer) → void}; +} +constants { + #C1 = 3 + #C2 = "linux" + #C3 = ffi::_OS {index:#C1, _name:#C2} + #C4 = 0 + #C5 = "arm" + #C6 = ffi::_Architecture {index:#C4, _name:#C5} + #C7 = ffi::Abi {_os:#C3, _architecture:#C6} + #C8 = ffi::Uint32 {} + #C9 = 1 + #C10 = "arm64" + #C11 = ffi::_Architecture {index:#C9, _name:#C10} + #C12 = ffi::Abi {_os:#C3, _architecture:#C11} + #C13 = 2 + #C14 = "ia32" + #C15 = ffi::_Architecture {index:#C13, _name:#C14} + #C16 = ffi::Abi {_os:#C3, _architecture:#C15} + #C17 = "x64" + #C18 = ffi::_Architecture {index:#C1, _name:#C17} + #C19 = ffi::Abi {_os:#C3, _architecture:#C18} + #C20 = {#C7:#C8, #C12:#C8, #C16:#C8, #C19:#C8) + #C21 = ffi::AbiSpecificIntegerMapping {mapping:#C20} + #C22 = "vm:ffi:abi-specific-mapping" + #C23 = null + #C24 = TypeLiteralConstant(ffi::Uint32) + #C25 = [#C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C24, #C24, #C24, #C24, #C23, #C23, #C23, #C23, #C23] + #C26 = ffi::_FfiAbiSpecificMapping {nativeTypes:#C25} + #C27 = core::pragma {name:#C22, options:#C26} + #C28 = "vm:prefer-inline" + #C29 = core::pragma {name:#C28, options:#C23} + #C30 = 4 + #C31 = [#C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C30, #C30, #C30, #C30, #C23, #C23, #C23, #C23, #C23] + #C32 = "vm:ffi:struct-fields" + #C33 = TypeLiteralConstant(self::Incomplete) + #C34 = [#C33, #C33] + #C35 = ffi::_FfiStructLayout {fieldTypes:#C34, packing:#C23} + #C36 = core::pragma {name:#C32, options:#C35} + #C37 = self::Incomplete {} + #C38 = [#C4, #C4, #C4, #C4, #C4, #C4, #C4, #C4, #C4, #C4, #C4, #C4, #C4, #C4, #C4, #C4, #C4, #C4] + #C39 = 8 + #C40 = [#C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C39, #C39, #C39, #C39, #C23, #C23, #C23, #C23, #C23] + #C41 = 100 + #C42 = ffi::_FfiInlineArray {elementType:#C33, length:#C41} + #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 = 400 + #C48 = [#C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C23, #C47, #C47, #C47, #C47, #C23, #C23, #C23, #C23, #C23] + #C49 = [] + #C50 = core::_Override {} + #C51 = self::_DummyAllocator {} +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/enum_from_lib_used_as_type.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/enum_from_lib_used_as_type.dart.expect index ebc13da74c1..c7a1c76a498 100644 --- a/pkg/vm/testcases/transformations/type_flow/transformer/enum_from_lib_used_as_type.dart.expect +++ b/pkg/vm/testcases/transformations/type_flow/transformer/enum_from_lib_used_as_type.dart.expect @@ -22,6 +22,6 @@ class Class extends core::Object { synthetic constructor •() → self::Class : super core::Object::•() ; -[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3303,getterSelectorId:3304] method method([@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::Enum e) → core::int +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3304,getterSelectorId:3305] method method([@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::Enum e) → core::int return [@vm.inferred-type.metadata=!] e.{core::_Enum::index}{core::int}; } diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/tree_shake_enum_from_lib.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/tree_shake_enum_from_lib.dart.expect index d0ed61e6ba5..2fc50fac48f 100644 --- a/pkg/vm/testcases/transformations/type_flow/transformer/tree_shake_enum_from_lib.dart.expect +++ b/pkg/vm/testcases/transformations/type_flow/transformer/tree_shake_enum_from_lib.dart.expect @@ -51,6 +51,6 @@ class ConstClass extends core::Object { synthetic constructor •() → self::ConstClass : super core::Object::•() ; -[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3307,getterSelectorId:3308] method method([@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::ConstEnum e) → core::int +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3308,getterSelectorId:3309] method method([@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::ConstEnum e) → core::int return [@vm.inferred-type.metadata=!] e.{core::_Enum::index}{core::int}; } diff --git a/runtime/bin/ffi_test/ffi_test_functions.cc b/runtime/bin/ffi_test/ffi_test_functions.cc index 1784af3c170..dd5e41fdb7a 100644 --- a/runtime/bin/ffi_test/ffi_test_functions.cc +++ b/runtime/bin/ffi_test/ffi_test_functions.cc @@ -1133,4 +1133,38 @@ DART_EXPORT uint64_t SizeOfStruct3BytesPackedInt() { return sizeof(Struct3BytesPackedIntCopy); } +// Define ssize_t for Windows as intptr_t. +#if defined(_WIN32) +typedef intptr_t ssize_t; +#endif + +#define DEFINE_SIZE_OF(type_modifier, type) \ + DART_EXPORT uint64_t FfiSizeOf_##type_modifier##_##type() { \ + return sizeof(type_modifier type); \ + } + +#define SIZES(F) \ + F(, intptr_t) \ + F(, uintptr_t) \ + F(, int) \ + F(unsigned, int) \ + F(, long) /* NOLINT */ \ + F(unsigned, long) /* NOLINT */ \ + F(, wchar_t) \ + F(, size_t) \ + F(, ssize_t) \ + F(, off_t) + +SIZES(DEFINE_SIZE_OF) + +#undef DEFINE_SIZE_OF +#undef SIZES + +DART_EXPORT int64_t WCharMinValue() { + return WCHAR_MIN; +} +DART_EXPORT int64_t WCharMaxValue() { + return WCHAR_MAX; +} + } // namespace dart diff --git a/runtime/bin/ffi_test/ffi_test_functions_generated.cc b/runtime/bin/ffi_test/ffi_test_functions_generated.cc index dc17bb0ae82..724fe558092 100644 --- a/runtime/bin/ffi_test/ffi_test_functions_generated.cc +++ b/runtime/bin/ffi_test/ffi_test_functions_generated.cc @@ -566,6 +566,10 @@ union Union16BytesNestedFloat { Struct16BytesHomogeneousFloat a2; }; +struct StructInlineArrayInt { + wchar_t a0[10]; +}; + // Used for testing structs and unions by value. // Smallest struct with data. // 10 struct arguments will exhaust available registers. @@ -4894,6 +4898,46 @@ DART_EXPORT bool PassUint8Struct1ByteBool(uint8_t a0, Struct1ByteBool a1) { return result % 2 != 0; } +// Used for testing structs and unions by value. +// Returning a wchar. +DART_EXPORT wchar_t PassWCharStructInlineArrayIntUintPtrx2LongUnsigned( + wchar_t a0, + StructInlineArrayInt a1, + uintptr_t a2, + uintptr_t a3, + /* NOLINT(runtime/int) */ long a4, + /* NOLINT(runtime/int) */ unsigned long a5) { + std::cout << "PassWCharStructInlineArrayIntUintPtrx2LongUnsigned" + << "(" << a0 << ", ([" << a1.a0[0] << ", " << a1.a0[1] << ", " + << a1.a0[2] << ", " << a1.a0[3] << ", " << a1.a0[4] << ", " + << a1.a0[5] << ", " << a1.a0[6] << ", " << a1.a0[7] << ", " + << a1.a0[8] << ", " << a1.a0[9] << "]), " << a2 << ", " << a3 + << ", " << a4 << ", " << a5 << ")" + << "\n"; + + wchar_t result = 0; + + result += a0; + result += a1.a0[0]; + result += a1.a0[1]; + result += a1.a0[2]; + result += a1.a0[3]; + result += a1.a0[4]; + result += a1.a0[5]; + result += a1.a0[6]; + result += a1.a0[7]; + result += a1.a0[8]; + result += a1.a0[9]; + result += a2; + result += a3; + result += a4; + result += a5; + + 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) { @@ -12599,21 +12643,85 @@ DART_EXPORT intptr_t TestPassUint8Struct1ByteBool( std::cout << "result = " << result << "\n"; - CHECK_APPROX(1, result); + CHECK_EQ(1, result); // Pass argument that will make the Dart callback throw. a0 = 42; result = f(a0, a1); - CHECK_APPROX(0.0, result); + CHECK_EQ(0, result); // Pass argument that will make the Dart callback return null. a0 = 84; result = f(a0, a1); - CHECK_APPROX(0.0, result); + CHECK_EQ(0, result); + + return 0; +} + +// Used for testing structs and unions by value. +// Returning a wchar. +DART_EXPORT intptr_t TestPassWCharStructInlineArrayIntUintPtrx2LongUnsigned( + // NOLINTNEXTLINE(whitespace/parens) + wchar_t (*f)(wchar_t a0, + StructInlineArrayInt a1, + uintptr_t a2, + uintptr_t a3, + /* NOLINT(runtime/int) */ long a4, + /* NOLINT(runtime/int) */ unsigned long a5)) { + wchar_t a0; + StructInlineArrayInt a1 = {}; + uintptr_t a2; + uintptr_t a3; + /* NOLINT(runtime/int) */ long a4; + /* NOLINT(runtime/int) */ unsigned long a5; + + a0 = 1; + a1.a0[0] = 2; + a1.a0[1] = 3; + a1.a0[2] = 4; + a1.a0[3] = 5; + a1.a0[4] = 6; + a1.a0[5] = 7; + a1.a0[6] = 8; + a1.a0[7] = 9; + a1.a0[8] = 10; + a1.a0[9] = 11; + a2 = 12; + a3 = 13; + a4 = 14; + a5 = 15; + + std::cout << "Calling TestPassWCharStructInlineArrayIntUintPtrx2LongUnsigned(" + << "(" << a0 << ", ([" << a1.a0[0] << ", " << a1.a0[1] << ", " + << a1.a0[2] << ", " << a1.a0[3] << ", " << a1.a0[4] << ", " + << a1.a0[5] << ", " << a1.a0[6] << ", " << a1.a0[7] << ", " + << a1.a0[8] << ", " << a1.a0[9] << "]), " << a2 << ", " << a3 + << ", " << a4 << ", " << a5 << ")" + << ")\n"; + + wchar_t result = f(a0, a1, a2, a3, a4, a5); + + std::cout << "result = " << result << "\n"; + + CHECK_EQ(120, result); + + // Pass argument that will make the Dart callback throw. + a0 = 42; + + result = f(a0, a1, a2, a3, a4, a5); + + CHECK_EQ(0, result); + + // Pass argument that will make the Dart callback return null. + a0 = 84; + + result = f(a0, a1, a2, a3, a4, a5); + + CHECK_EQ(0, result); return 0; } diff --git a/runtime/lib/ffi.cc b/runtime/lib/ffi.cc index 9f0bf7544b8..2c54160800e 100644 --- a/runtime/lib/ffi.cc +++ b/runtime/lib/ffi.cc @@ -26,6 +26,7 @@ #include "vm/compiler/assembler/assembler.h" #include "vm/compiler/ffi/call.h" #include "vm/compiler/ffi/callback.h" +#include "vm/compiler/ffi/marshaller.h" #include "vm/compiler/jit/compiler.h" #endif // !defined(DART_PRECOMPILED_RUNTIME) @@ -98,6 +99,15 @@ DEFINE_NATIVE_ENTRY(Ffi_nativeCallbackFunction, 1, 2) { func = func.parent_function(); ASSERT(func.is_static()); + // AbiSpecificTypes can have an incomplete mapping. + const char* error = nullptr; + compiler::ffi::NativeFunctionTypeFromFunctionType(zone, native_signature, + &error); + if (error != nullptr) { + Exceptions::ThrowCompileTimeError(LanguageError::Handle( + zone, LanguageError::New(String::Handle(zone, String::New(error))))); + } + // We are returning an object which is not an Instance here. This is only OK // because we know that the result will be passed directly to // _pointerFromFunction and will not leak out into user code. diff --git a/runtime/vm/app_snapshot.cc b/runtime/vm/app_snapshot.cc index d1d09c7a2bf..de02c85863a 100644 --- a/runtime/vm/app_snapshot.cc +++ b/runtime/vm/app_snapshot.cc @@ -6695,6 +6695,10 @@ SerializationCluster* Serializer::NewClusterForClass(intptr_t cid, case kStringCid: return new (Z) StringSerializationCluster( is_canonical, cluster_represents_canonical_set && !vm_); +#define CASE_FFI_CID(name) case kFfi##name##Cid: + CLASS_LIST_FFI_TYPE_MARKER(CASE_FFI_CID) +#undef CASE_FFI_CID + return new (Z) InstanceSerializationCluster(is_canonical, cid); case kWeakSerializationReferenceCid: #if defined(DART_PRECOMPILER) ASSERT(kind_ == Snapshot::kFullAOT); @@ -7850,6 +7854,10 @@ DeserializationCluster* Deserializer::ReadCluster() { return new (Z) StringDeserializationCluster( is_canonical, !is_non_root_unit_ && isolate_group() != Dart::vm_isolate_group()); +#define CASE_FFI_CID(name) case kFfi##name##Cid: + CLASS_LIST_FFI_TYPE_MARKER(CASE_FFI_CID) +#undef CASE_FFI_CID + return new (Z) InstanceDeserializationCluster(cid, is_canonical); default: break; } diff --git a/runtime/vm/compiler/ffi/abi.cc b/runtime/vm/compiler/ffi/abi.cc index 25900e43aa6..c902a89959b 100644 --- a/runtime/vm/compiler/ffi/abi.cc +++ b/runtime/vm/compiler/ffi/abi.cc @@ -47,42 +47,58 @@ static_assert(offsetof(AbiAlignmentUint64, i) == 8, #if defined(DART_TARGET_OS_ANDROID) #define DART_TARGET_OS_NAME Android +#define DART_TARGET_OS_NAME_LC android #elif defined(DART_TARGET_OS_FUCHSIA) #define DART_TARGET_OS_NAME Fuchsia +#define DART_TARGET_OS_NAME_LC fuchsia #elif defined(DART_TARGET_OS_LINUX) #define DART_TARGET_OS_NAME Linux +#define DART_TARGET_OS_NAME_LC linux #elif defined(DART_TARGET_OS_MACOS) #if DART_TARGET_OS_MACOS_IOS #define DART_TARGET_OS_NAME IOS +#define DART_TARGET_OS_NAME_LC ios #else #define DART_TARGET_OS_NAME MacOS +#define DART_TARGET_OS_NAME_LC macos #endif #elif defined(DART_TARGET_OS_WINDOWS) #define DART_TARGET_OS_NAME Windows +#define DART_TARGET_OS_NAME_LC windows #else #error Unknown OS #endif #if defined(TARGET_ARCH_IA32) #define TARGET_ARCH_NAME IA32 +#define TARGET_ARCH_NAME_LC ia32 #elif defined(TARGET_ARCH_X64) #define TARGET_ARCH_NAME X64 +#define TARGET_ARCH_NAME_LC x64 #elif defined(TARGET_ARCH_ARM) #define TARGET_ARCH_NAME Arm +#define TARGET_ARCH_NAME_LC arm #elif defined(TARGET_ARCH_ARM64) #define TARGET_ARCH_NAME Arm64 +#define TARGET_ARCH_NAME_LC arm64 #else #error Unknown arch #endif -#define ABI_NAME1(os, arch) k##os##arch -#define ABI_NAME2(os, arch) ABI_NAME1(os, arch) -#define ABI_NAME3 ABI_NAME2(DART_TARGET_OS_NAME, TARGET_ARCH_NAME) +#define ABI_ENUM_VALUE1(os, arch) k##os##arch +#define ABI_ENUM_VALUE2(os, arch) ABI_ENUM_VALUE1(os, arch) +#define ABI_ENUM_VALUE3 ABI_ENUM_VALUE2(DART_TARGET_OS_NAME, TARGET_ARCH_NAME) Abi TargetAbi() { - return Abi::ABI_NAME3; + return Abi::ABI_ENUM_VALUE3; } +#define STRINGIFY2(s) STRINGIFY(s) +#define STRINGIFY(s) #s + +const char* target_abi_name = + STRINGIFY2(DART_TARGET_OS_NAME_LC) "_" STRINGIFY2(TARGET_ARCH_NAME_LC); + } // namespace ffi } // namespace compiler diff --git a/runtime/vm/compiler/ffi/abi.h b/runtime/vm/compiler/ffi/abi.h index 118054faea3..81b76d91bc4 100644 --- a/runtime/vm/compiler/ffi/abi.h +++ b/runtime/vm/compiler/ffi/abi.h @@ -39,16 +39,23 @@ enum class Abi { kWindowsIA32, kWindowsX64, }; + +const int64_t num_abis = static_cast(Abi::kWindowsX64) + 1; + // We use the integer values of this enum in -// runtime/vm/compiler/frontend/kernel_to_il.cc +// - runtime/vm/compiler/ffi/native_type.cc +// - runtime/vm/compiler/frontend/kernel_to_il.cc static_assert(static_cast(Abi::kAndroidArm) == 0, "Enum value unexpected."); static_assert(static_cast(Abi::kWindowsX64) == 17, "Enum value unexpected."); +static_assert(num_abis == 18, "Enum value unexpected."); // The target ABI. Defines sizes and alignment of native types. Abi TargetAbi(); +extern const char* target_abi_name; + } // namespace ffi } // namespace compiler diff --git a/runtime/vm/compiler/ffi/marshaller.cc b/runtime/vm/compiler/ffi/marshaller.cc index 6cf5ece10dc..ca4f6d6f329 100644 --- a/runtime/vm/compiler/ffi/marshaller.cc +++ b/runtime/vm/compiler/ffi/marshaller.cc @@ -91,17 +91,22 @@ bool BaseMarshaller::IsCompound(intptr_t arg_index) const { if (IsFfiTypeClassId(type.type_class_id())) { return false; } -#ifdef DEBUG const auto& cls = Class::Handle(this->zone_, type.type_class()); const auto& superClass = Class::Handle(this->zone_, cls.SuperClass()); - // TODO(http://dartbug.com/42563): Implement AbiSpecificInt. + const bool is_abi_specific_int = + String::Handle(this->zone_, superClass.UserVisibleName()) + .Equals(Symbols::AbiSpecificInteger()); + if (is_abi_specific_int) { + return false; + } +#ifdef DEBUG const bool is_struct = String::Handle(this->zone_, superClass.UserVisibleName()) .Equals(Symbols::Struct()); const bool is_union = String::Handle(this->zone_, superClass.UserVisibleName()) .Equals(Symbols::Union()); - RELEASE_ASSERT(is_struct || is_union); + ASSERT(is_struct || is_union); #endif return true; } diff --git a/runtime/vm/compiler/ffi/native_type.cc b/runtime/vm/compiler/ffi/native_type.cc index bf4d452a8df..ac49f391320 100644 --- a/runtime/vm/compiler/ffi/native_type.cc +++ b/runtime/vm/compiler/ffi/native_type.cc @@ -474,7 +474,33 @@ static const NativeType* CompoundFromPragma(Zone* zone, } } -// TODO(http://dartbug.com/42563): Implement AbiSpecificInt. +static const NativeType* AbiSpecificFromPragma(Zone* zone, + const Instance& pragma, + const Class& abi_specific_int, + const char** error) { + const auto& clazz = Class::Handle(zone, pragma.clazz()); + const auto& fields = Array::Handle(zone, clazz.fields()); + ASSERT(fields.Length() == 1); + const auto& native_types_field = + Field::Handle(zone, Field::RawCast(fields.At(0))); + ASSERT(String::Handle(zone, native_types_field.name()) + .Equals(Symbols::FfiNativeTypes())); + const auto& native_types = + Array::Handle(zone, Array::RawCast(pragma.GetField(native_types_field))); + + ASSERT(native_types.Length() == num_abis); + const int64_t abi_index = static_cast(TargetAbi()); + const auto& abi_abstract_type = AbstractType::Handle( + zone, AbstractType::RawCast(native_types.At(abi_index))); + if (abi_abstract_type.IsNull()) { + *error = zone->PrintToString( + "AbiSpecificInteger '%s' is missing mapping for '%s'.", + abi_specific_int.UserVisibleNameCString(), target_abi_name); + return nullptr; + } + return NativeType::FromAbstractType(zone, abi_abstract_type, error); +} + const NativeType* NativeType::FromAbstractType(Zone* zone, const AbstractType& type, const char** error) { @@ -483,18 +509,26 @@ const NativeType* NativeType::FromAbstractType(Zone* zone, return &NativeType::FromTypedDataClassId(zone, class_id); } - // User-defined structs or unions. + // User-defined structs, unions, or Abi-specific integers. const auto& cls = Class::Handle(zone, type.type_class()); const auto& superClass = Class::Handle(zone, cls.SuperClass()); const bool is_struct = String::Handle(zone, superClass.UserVisibleName()) .Equals(Symbols::Struct()); const bool is_union = String::Handle(zone, superClass.UserVisibleName()) .Equals(Symbols::Union()); - RELEASE_ASSERT(is_struct || is_union); + const bool is_abi_specific_int = + String::Handle(zone, superClass.UserVisibleName()) + .Equals(Symbols::AbiSpecificInteger()); + RELEASE_ASSERT(is_struct || is_union || is_abi_specific_int); auto& pragmas = Object::Handle(zone); String& pragma_name = String::Handle(zone); - pragma_name = Symbols::vm_ffi_struct_fields().ptr(); + if (is_struct || is_union) { + pragma_name = Symbols::vm_ffi_struct_fields().ptr(); + } else { + ASSERT(is_abi_specific_int); + pragma_name = Symbols::vm_ffi_abi_specific_mapping().ptr(); + } Library::FindPragma(dart::Thread::Current(), /*only_core=*/false, cls, pragma_name, /*multiple=*/true, &pragmas); ASSERT(!pragmas.IsNull()); @@ -503,7 +537,13 @@ const NativeType* NativeType::FromAbstractType(Zone* zone, auto& pragma = Instance::Handle(zone); auto& clazz = Class::Handle(zone); auto& library = Library::Handle(zone); - const String& class_symbol = Symbols::FfiStructLayout(); + String& class_symbol = String::Handle(zone); + if (is_struct || is_union) { + class_symbol = Symbols::FfiStructLayout().ptr(); + } else { + ASSERT(is_abi_specific_int); + class_symbol = Symbols::FfiAbiSpecificMapping().ptr(); + } for (intptr_t i = 0; i < pragmas_array.Length(); i++) { pragma ^= pragmas_array.At(i); clazz ^= pragma.clazz(); @@ -514,7 +554,11 @@ const NativeType* NativeType::FromAbstractType(Zone* zone, } } - return CompoundFromPragma(zone, pragma, is_struct, error); + if (is_struct || is_union) { + return CompoundFromPragma(zone, pragma, is_struct, error); + } + ASSERT(is_abi_specific_int); + return AbiSpecificFromPragma(zone, pragma, cls, error); } #endif diff --git a/runtime/vm/compiler/ffi/recognized_method.cc b/runtime/vm/compiler/ffi/recognized_method.cc index f2fd7b6034e..323fcc83456 100644 --- a/runtime/vm/compiler/ffi/recognized_method.cc +++ b/runtime/vm/compiler/ffi/recognized_method.cc @@ -118,6 +118,36 @@ AlignmentType RecognizedMethodAlignment(MethodRecognizer::Kind kind) { } } +MethodRecognizer::Kind FfiLoad(const NativeType& native_type) { + if (native_type.IsPrimitive()) { + switch (native_type.AsPrimitive().representation()) { +#define CASE(type) \ + case k##type: \ + return MethodRecognizer::kFfiLoad##type; + CLASS_LIST_FFI_NUMERIC_FIXED_SIZE(CASE) +#undef CASE + default: + break; + } + } + UNIMPLEMENTED(); +} + +MethodRecognizer::Kind FfiStore(const NativeType& native_type) { + if (native_type.IsPrimitive()) { + switch (native_type.AsPrimitive().representation()) { +#define CASE(type) \ + case k##type: \ + return MethodRecognizer::kFfiStore##type; + CLASS_LIST_FFI_NUMERIC_FIXED_SIZE(CASE) +#undef CASE + default: + break; + } + } + UNIMPLEMENTED(); +} + } // namespace ffi } // namespace compiler diff --git a/runtime/vm/compiler/ffi/recognized_method.h b/runtime/vm/compiler/ffi/recognized_method.h index 98f4a315552..ebb02a15853 100644 --- a/runtime/vm/compiler/ffi/recognized_method.h +++ b/runtime/vm/compiler/ffi/recognized_method.h @@ -32,6 +32,9 @@ classid_t RecognizedMethodTypeArgCid(MethodRecognizer::Kind kind); AlignmentType RecognizedMethodAlignment(MethodRecognizer::Kind kind); +MethodRecognizer::Kind FfiLoad(const NativeType& native_type); +MethodRecognizer::Kind FfiStore(const NativeType& native_type); + } // namespace ffi } // namespace compiler diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc index 00d4adfbaed..f510129c0ac 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc @@ -881,6 +881,19 @@ JoinEntryInstr* BaseFlowGraphBuilder::BuildThrowNoSuchMethod() { return nsm; } +Fragment BaseFlowGraphBuilder::ThrowException(TokenPosition position) { + Fragment instructions; + Value* exception = Pop(); + instructions += Fragment(new (Z) ThrowInstr(InstructionSource(position), + GetNextDeoptId(), exception)) + .closed(); + // Use its side effect of leaving a constant on the stack (does not change + // the graph). + NullConstant(); + + return instructions; +} + Fragment BaseFlowGraphBuilder::AssertBool(TokenPosition position) { Value* value = Pop(); AssertBooleanInstr* instr = new (Z) @@ -1000,14 +1013,23 @@ Fragment BaseFlowGraphBuilder::BuildFfiAsFunctionInternalCall( ASSERT(signatures.IsInstantiated()); ASSERT(signatures.Length() == 2); - const AbstractType& dart_type = AbstractType::Handle(signatures.TypeAt(0)); - const AbstractType& native_type = AbstractType::Handle(signatures.TypeAt(1)); + const auto& dart_type = + FunctionType::Cast(AbstractType::Handle(signatures.TypeAt(0))); + const auto& native_type = + FunctionType::Cast(AbstractType::Handle(signatures.TypeAt(1))); - ASSERT(dart_type.IsFunctionType() && native_type.IsFunctionType()); - const Function& target = - Function::ZoneHandle(compiler::ffi::TrampolineFunction( - FunctionType::Cast(dart_type), FunctionType::Cast(native_type), - is_leaf)); + // AbiSpecificTypes can have an incomplete mapping. + const char* error = nullptr; + compiler::ffi::NativeFunctionTypeFromFunctionType(zone_, native_type, &error); + if (error != nullptr) { + const auto& language_error = Error::Handle( + LanguageError::New(String::Handle(String::New(error, Heap::kOld)), + Report::kError, Heap::kOld)); + Report::LongJump(language_error); + } + + const Function& target = Function::ZoneHandle( + compiler::ffi::TrampolineFunction(dart_type, native_type, is_leaf)); Fragment code; // Store the pointer in the context, we cannot load the untagged address diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.h b/runtime/vm/compiler/frontend/base_flow_graph_builder.h index 5aa008b3838..37651ab290f 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.h +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.h @@ -348,6 +348,7 @@ class BaseFlowGraphBuilder { Fragment TestAnyTypeArgs(Fragment present, Fragment absent); JoinEntryInstr* BuildThrowNoSuchMethod(); + Fragment ThrowException(TokenPosition position); Fragment AssertBool(TokenPosition position); Fragment BooleanNegate(); diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index 314505e0f8b..a5693f82968 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -6,6 +6,7 @@ #include "vm/closure_functions_cache.h" #include "vm/compiler/ffi/callback.h" +#include "vm/compiler/ffi/recognized_method.h" #include "vm/compiler/frontend/flow_graph_builder.h" // For dart::FlowGraphBuilder::SimpleInstanceOfType. #include "vm/compiler/frontend/prologue_builder.h" #include "vm/compiler/jit/compiler.h" @@ -3416,13 +3417,26 @@ Fragment StreamingFlowGraphBuilder::BuildStaticInvocation(TokenPosition* p) { } const auto recognized_kind = target.recognized_kind(); - if (recognized_kind == MethodRecognizer::kNativeEffect) { - return BuildNativeEffect(); - } else if (recognized_kind == MethodRecognizer::kFfiAsFunctionInternal) { - return BuildFfiAsFunctionInternal(); - } else if (CompilerState::Current().is_aot() && - recognized_kind == MethodRecognizer::kFfiNativeCallbackFunction) { - return BuildFfiNativeCallbackFunction(); + switch (recognized_kind) { + case MethodRecognizer::kNativeEffect: + return BuildNativeEffect(); + case MethodRecognizer::kFfiAsFunctionInternal: + return BuildFfiAsFunctionInternal(); + case MethodRecognizer::kFfiNativeCallbackFunction: + if (CompilerState::Current().is_aot()) { + return BuildFfiNativeCallbackFunction(); + } + break; + case MethodRecognizer::kFfiLoadAbiSpecificInt: + return BuildLoadAbiSpecificInt(/*at_index=*/false); + case MethodRecognizer::kFfiLoadAbiSpecificIntAtIndex: + return BuildLoadAbiSpecificInt(/*at_index=*/true); + case MethodRecognizer::kFfiStoreAbiSpecificInt: + return BuildStoreAbiSpecificInt(/*at_index=*/false); + case MethodRecognizer::kFfiStoreAbiSpecificIntAtIndex: + return BuildStoreAbiSpecificInt(/*at_index=*/true); + default: + break; } Fragment instructions; @@ -5517,6 +5531,109 @@ Fragment StreamingFlowGraphBuilder::BuildNativeEffect() { return code; } +static void ReportIfNotNull(const char* error) { + if (error != nullptr) { + const auto& language_error = Error::Handle( + LanguageError::New(String::Handle(String::New(error, Heap::kOld)), + Report::kError, Heap::kOld)); + Report::LongJump(language_error); + } +} + +Fragment StreamingFlowGraphBuilder::BuildLoadAbiSpecificInt(bool at_index) { + const intptr_t argument_count = ReadUInt(); // Read argument count. + ASSERT(argument_count == 2); // TypedDataBase, offset/index + const intptr_t list_length = ReadListLength(); // Read types list length. + ASSERT(list_length == 1); // AbiSpecificInt. + // Read types. + const TypeArguments& type_arguments = T.BuildTypeArguments(list_length); + const AbstractType& type_argument = + AbstractType::Handle(type_arguments.TypeAt(0)); + + // AbiSpecificTypes can have an incomplete mapping. + const char* error = nullptr; + const auto* native_type = + compiler::ffi::NativeType::FromAbstractType(zone_, type_argument, &error); + ReportIfNotNull(error); + + Fragment code; + // Read positional argument count. + const intptr_t positional_count = ReadListLength(); + ASSERT(positional_count == 2); + code += BuildExpression(); // Argument 1: typedDataBase. + code += BuildExpression(); // Argument 2: offsetInBytes or index. + if (at_index) { + code += IntConstant(native_type->SizeInBytes()); + code += B->BinaryIntegerOp(Token::kMUL, kTagged, /* truncate= */ true); + } + + // Skip (empty) named arguments list. + const intptr_t named_args_len = ReadListLength(); + ASSERT(named_args_len == 0); + + // This call site is not guaranteed to be optimized. So, do a call to the + // correct force optimized function instead of compiling the body. + MethodRecognizer::Kind kind = compiler::ffi::FfiLoad(*native_type); + const char* function_name = MethodRecognizer::KindToFunctionNameCString(kind); + const Library& ffi_library = Library::Handle(Z, Library::FfiLibrary()); + const Function& target = Function::ZoneHandle( + Z, ffi_library.LookupFunctionAllowPrivate( + String::Handle(Z, String::New(function_name)))); + Array& argument_names = Array::ZoneHandle(Z); + code += StaticCall(TokenPosition::kNoSource, target, argument_count, + argument_names, ICData::kStatic); + + return code; +} + +Fragment StreamingFlowGraphBuilder::BuildStoreAbiSpecificInt(bool at_index) { + const intptr_t argument_count = ReadUInt(); + ASSERT(argument_count == 3); + const intptr_t list_length = ReadListLength(); + ASSERT(list_length == 1); + // Read types. + const TypeArguments& type_arguments = T.BuildTypeArguments(list_length); + const AbstractType& type_argument = + AbstractType::Handle(type_arguments.TypeAt(0)); + + // AbiSpecificTypes can have an incomplete mapping. + const char* error = nullptr; + const auto* native_type = + compiler::ffi::NativeType::FromAbstractType(zone_, type_argument, &error); + ReportIfNotNull(error); + + Fragment code; + // Read positional argument count. + const intptr_t positional_count = ReadListLength(); + ASSERT(positional_count == 3); + code += BuildExpression(); // Argument 1: typedDataBase. + code += BuildExpression(); // Argument 2: offsetInBytes or index. + if (at_index) { + code += IntConstant(native_type->SizeInBytes()); + code += B->BinaryIntegerOp(Token::kMUL, kTagged, /* truncate= */ true); + } + code += BuildExpression(); // Argument 3: value + + // Skip (empty) named arguments list. + const intptr_t named_args_len = ReadListLength(); + ASSERT(named_args_len == 0); + + // This call site is not guaranteed to be optimized. So, do a call to the + // correct force optimized function instead of compiling the body. + MethodRecognizer::Kind kind = compiler::ffi::FfiStore(*native_type); + const char* function_name = MethodRecognizer::KindToFunctionNameCString(kind); + const Library& ffi_library = Library::Handle(Z, Library::FfiLibrary()); + const Function& target = Function::ZoneHandle( + Z, ffi_library.LookupFunctionAllowPrivate( + String::Handle(Z, String::New(function_name)))); + ASSERT(!target.IsNull()); + Array& argument_names = Array::ZoneHandle(Z); + code += StaticCall(TokenPosition::kNoSource, target, argument_count, + argument_names, ICData::kStatic); + + return code; +} + Fragment StreamingFlowGraphBuilder::BuildFfiAsFunctionInternal() { const intptr_t argc = ReadUInt(); // Read argument count. ASSERT(argc == 2); // Pointer, isLeaf. @@ -5595,6 +5712,11 @@ Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction() { ReadListLength(); // Skip (empty) named arguments list. ASSERT(named_args_len == 0); + // AbiSpecificTypes can have an incomplete mapping. + const char* error = nullptr; + compiler::ffi::NativeFunctionTypeFromFunctionType(zone_, native_sig, &error); + ReportIfNotNull(error); + const Function& result = Function::ZoneHandle(Z, compiler::ffi::NativeCallbackFunction( native_sig, target, exceptional_return)); diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h index ddfabe208e3..68d77944be6 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h @@ -362,6 +362,15 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper { // Build flow graph for '_nativeEffect'. Fragment BuildNativeEffect(); + // Build flow graph for '_loadAbiSpecificInt' and + // '_loadAbiSpecificIntAtIndex', '_storeAbiSpecificInt', and + // '_storeAbiSpecificIntAtIndex' call sites. + // + // The second argument is either offsetInBytes (at_index==false), or + // index (at_index==true). + Fragment BuildLoadAbiSpecificInt(bool at_index); + Fragment BuildStoreAbiSpecificInt(bool at_index); + // Build FG for '_asFunctionInternal'. Reads an Arguments from the // Kernel buffer and pushes the resulting closure. Fragment BuildFfiAsFunctionInternal(); diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index 07a2898984e..dba179a857b 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -402,19 +402,6 @@ Fragment FlowGraphBuilder::FfiCall( return body; } -Fragment FlowGraphBuilder::ThrowException(TokenPosition position) { - Fragment instructions; - Value* exception = Pop(); - instructions += Fragment(new (Z) ThrowInstr(InstructionSource(position), - GetNextDeoptId(), exception)) - .closed(); - // Use its side effect of leaving a constant on the stack (does not change - // the graph). - NullConstant(); - - return instructions; -} - Fragment FlowGraphBuilder::RethrowException(TokenPosition position, int catch_try_index) { Fragment instructions; @@ -4491,6 +4478,8 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfFfiNative(const Function& function) { const char* error = nullptr; const auto marshaller_ptr = compiler::ffi::CallMarshaller::FromFunction(Z, function, &error); + // AbiSpecific integers can be incomplete causing us to not know the calling + // convention. However, this is caught in asFunction in both JIT/AOT. RELEASE_ASSERT(error == nullptr); RELEASE_ASSERT(marshaller_ptr != nullptr); const auto& marshaller = *marshaller_ptr; @@ -4642,6 +4631,8 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfFfiCallback(const Function& function) { const char* error = nullptr; const auto marshaller_ptr = compiler::ffi::CallbackMarshaller::FromFunction(Z, function, &error); + // AbiSpecific integers can be incomplete causing us to not know the calling + // convention. However, this is caught fromFunction in both JIT/AOT. RELEASE_ASSERT(error == nullptr); RELEASE_ASSERT(marshaller_ptr != nullptr); const auto& marshaller = *marshaller_ptr; diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h index 42573d0500f..cb66e59bbb4 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.h +++ b/runtime/vm/compiler/frontend/kernel_to_il.h @@ -196,7 +196,6 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder { Fragment FfiCall(const compiler::ffi::CallMarshaller& marshaller); - Fragment ThrowException(TokenPosition position); Fragment RethrowException(TokenPosition position, int catch_try_index); Fragment LoadLocal(LocalVariable* variable); Fragment IndirectGoto(intptr_t target_count); diff --git a/runtime/vm/compiler/method_recognizer.cc b/runtime/vm/compiler/method_recognizer.cc index af012549a5b..d8472519b68 100644 --- a/runtime/vm/compiler/method_recognizer.cc +++ b/runtime/vm/compiler/method_recognizer.cc @@ -197,6 +197,12 @@ const char* MethodRecognizer::KindToCString(Kind kind) { return "?"; } +const char* MethodRecognizer::KindToFunctionNameCString(Kind kind) { + if (kind >= kUnknown && kind < kNumRecognizedMethods) + return recognized_methods[kind].function_name; + return "?"; +} + // Is this method marked with the vm:recognized pragma? bool MethodRecognizer::IsMarkedAsRecognized(const Function& function, const char* kind) { diff --git a/runtime/vm/compiler/method_recognizer.h b/runtime/vm/compiler/method_recognizer.h index e9a1067659d..3ea42479c7e 100644 --- a/runtime/vm/compiler/method_recognizer.h +++ b/runtime/vm/compiler/method_recognizer.h @@ -52,6 +52,7 @@ class MethodRecognizer : public AllStatic { static intptr_t MethodKindToReceiverCid(Kind kind); static const char* KindToCString(Kind kind); + static const char* KindToFunctionNameCString(Kind kind); static bool IsMarkedAsRecognized(const Function& function, const char* kind = nullptr); diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h index e2f2083b28a..5cb46fc8840 100644 --- a/runtime/vm/compiler/recognized_methods_list.h +++ b/runtime/vm/compiler/recognized_methods_list.h @@ -191,28 +191,32 @@ namespace dart { V(::, _asFunctionInternal, FfiAsFunctionInternal, 0x92ae104f) \ V(::, _nativeCallbackFunction, FfiNativeCallbackFunction, 0x3ff5ae9c) \ V(::, _nativeEffect, NativeEffect, 0x61e00b59) \ - V(::, _loadInt8, FfiLoadInt8, 0x0f04dfd6) \ - V(::, _loadInt16, FfiLoadInt16, 0xec44312d) \ - V(::, _loadInt32, FfiLoadInt32, 0xee223fc3) \ - V(::, _loadInt64, FfiLoadInt64, 0xdeefbfa3) \ - V(::, _loadUint8, FfiLoadUint8, 0xe14e1cd1) \ - V(::, _loadUint16, FfiLoadUint16, 0x0cd65cea) \ - V(::, _loadUint32, FfiLoadUint32, 0xf66e9055) \ - V(::, _loadUint64, FfiLoadUint64, 0x0505fdcc) \ + V(::, _loadAbiSpecificInt, FfiLoadAbiSpecificInt, 0x7807e872) \ + V(::, _loadAbiSpecificIntAtIndex, FfiLoadAbiSpecificIntAtIndex, 0x6aa4cab4) \ + V(::, _loadInt8, FfiLoadInt8, 0x0f04e397) \ + V(::, _loadInt16, FfiLoadInt16, 0xec4434ee) \ + V(::, _loadInt32, FfiLoadInt32, 0xee224384) \ + V(::, _loadInt64, FfiLoadInt64, 0xdeefc364) \ + V(::, _loadUint8, FfiLoadUint8, 0xe14e2092) \ + V(::, _loadUint16, FfiLoadUint16, 0x0cd660ab) \ + V(::, _loadUint32, FfiLoadUint32, 0xf66e9416) \ + V(::, _loadUint64, FfiLoadUint64, 0x0506018d) \ V(::, _loadIntPtr, FfiLoadIntPtr, 0xebd9b43e) \ V(::, _loadFloat, FfiLoadFloat, 0xf8d9845d) \ V(::, _loadFloatUnaligned, FfiLoadFloatUnaligned, 0xc8c8dfff) \ V(::, _loadDouble, FfiLoadDouble, 0xf70cc619) \ V(::, _loadDoubleUnaligned, FfiLoadDoubleUnaligned, 0xc99ebd39) \ V(::, _loadPointer, FfiLoadPointer, 0x4e79d0fc) \ - V(::, _storeInt8, FfiStoreInt8, 0xdf50af0c) \ - V(::, _storeInt16, FfiStoreInt16, 0xd84df332) \ - V(::, _storeInt32, FfiStoreInt32, 0xfbe62c5d) \ - V(::, _storeInt64, FfiStoreInt64, 0xf1d40d7a) \ - V(::, _storeUint8, FfiStoreUint8, 0x056dd2f6) \ - V(::, _storeUint16, FfiStoreUint16, 0xe2fdaade) \ - V(::, _storeUint32, FfiStoreUint32, 0xe5d7e8c5) \ - V(::, _storeUint64, FfiStoreUint64, 0xe2d93239) \ + V(::, _storeAbiSpecificInt, FfiStoreAbiSpecificInt, 0xc70954c0) \ + V(::, _storeAbiSpecificIntAtIndex, FfiStoreAbiSpecificIntAtIndex, 0xc64efe4b)\ + V(::, _storeInt8, FfiStoreInt8, 0xdf50b2cd) \ + V(::, _storeInt16, FfiStoreInt16, 0xd84df6f3) \ + V(::, _storeInt32, FfiStoreInt32, 0xfbe6301e) \ + V(::, _storeInt64, FfiStoreInt64, 0xf1d4113b) \ + V(::, _storeUint8, FfiStoreUint8, 0x056dd6b7) \ + V(::, _storeUint16, FfiStoreUint16, 0xe2fdae9f) \ + V(::, _storeUint32, FfiStoreUint32, 0xe5d7ec86) \ + V(::, _storeUint64, FfiStoreUint64, 0xe2d935fa) \ V(::, _storeIntPtr, FfiStoreIntPtr, 0x080266a8) \ V(::, _storeFloat, FfiStoreFloat, 0x6484f07e) \ V(::, _storeFloatUnaligned, FfiStoreFloatUnaligned, 0x600a9203) \ diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc index 30188c27d13..39689f69759 100644 --- a/runtime/vm/kernel_loader.cc +++ b/runtime/vm/kernel_loader.cc @@ -1588,6 +1588,25 @@ void KernelLoader::FinishClassLoading(const Class& klass, fields_[0]->set_is_nullable(true); } + // Check that subclasses of AbiSpecificInteger have a mapping for the + // current ABI. + // + // TODO(https://github.com/dart-lang/language/issues/1889): If we make + // kernel know about the target platform, we can move this check to the + // frontend. + const auto& super_class = Class::Handle(Z, klass.SuperClass()); + if (!super_class.IsNull() && + super_class.UserVisibleName() == Symbols::AbiSpecificInteger().ptr() && + Library::Handle(Z, super_class.library()).url() == + Symbols::DartFfi().ptr()) { + const char* error = nullptr; + compiler::ffi::NativeType::FromAbstractType( + Z, AbstractType::Handle(Z, klass.DeclarationType()), &error); + if (error != nullptr) { + H.ReportError("%s", error); + } + } + // Due to ReadVMAnnotations(), the klass may have been loaded at this point // (loading the class while evaluating annotations). if (klass.is_loaded()) { diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index f18ef85684c..87ff2075cb4 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -7567,15 +7567,20 @@ bool Function::FfiCSignatureReturnsStruct() const { if (IsFfiTypeClassId(type.type_class_id())) { return false; } - // TODO(http://dartbug.com/42563): Implement AbiSpecificInt. -#ifdef DEBUG const auto& cls = Class::Handle(zone, type.type_class()); const auto& superClass = Class::Handle(zone, cls.SuperClass()); + const bool is_abi_specific_int = + String::Handle(zone, superClass.UserVisibleName()) + .Equals(Symbols::AbiSpecificInteger()); + if (is_abi_specific_int) { + return false; + } +#ifdef DEBUG const bool is_struct = String::Handle(zone, superClass.UserVisibleName()) .Equals(Symbols::Struct()); const bool is_union = String::Handle(zone, superClass.UserVisibleName()) .Equals(Symbols::Union()); - RELEASE_ASSERT(is_struct || is_union); + ASSERT(is_struct || is_union); #endif return true; } diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index 880bddecf15..75bb9680d55 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -16,6 +16,7 @@ class ObjectPointerVisitor; // One-character symbols are added implicitly. #define PREDEFINED_SYMBOLS_LIST(V) \ + V(AbiSpecificInteger, "AbiSpecificInteger") \ V(AbstractClassInstantiationError, "AbstractClassInstantiationError") \ V(AllocateInvocationMirror, "_allocateInvocationMirror") \ V(AllocateInvocationMirrorForClosure, "_allocateInvocationMirrorForClosure") \ @@ -109,6 +110,7 @@ class ObjectPointerVisitor; V(ExternalOneByteString, "_ExternalOneByteString") \ V(ExternalTwoByteString, "_ExternalTwoByteString") \ V(FallThroughError, "FallThroughError") \ + V(FfiAbiSpecificMapping, "_FfiAbiSpecificMapping") \ V(FfiBool, "Bool") \ V(FfiCallback, "_FfiCallback") \ V(FfiDouble, "Double") \ @@ -124,6 +126,7 @@ class ObjectPointerVisitor; V(FfiIntPtr, "IntPtr") \ V(FfiNativeFunction, "NativeFunction") \ V(FfiNativeType, "NativeType") \ + V(FfiNativeTypes, "nativeTypes") \ V(FfiPointer, "Pointer") \ V(FfiStructLayout, "_FfiStructLayout") \ V(FfiStructLayoutArray, "_FfiInlineArray") \ @@ -467,6 +470,7 @@ class ObjectPointerVisitor; V(vm_notify_debugger_on_exception, "vm:notify-debugger-on-exception") \ V(vm_recognized, "vm:recognized") \ V(vm_trace_entrypoints, "vm:testing.unsafe.trace-entrypoints-fn") \ + V(vm_ffi_abi_specific_mapping, "vm:ffi:abi-specific-mapping") \ V(vm_ffi_struct_fields, "vm:ffi:struct-fields") \ V(vm_unsafe_no_interrupts, "vm:unsafe:no-interrupts") \ V(vm_external_name, "vm:external-name") \ diff --git a/sdk/lib/_internal/vm/lib/ffi_native_type_patch.dart b/sdk/lib/_internal/vm/lib/ffi_native_type_patch.dart index 426a170919c..92e23c1cf18 100644 --- a/sdk/lib/_internal/vm/lib/ffi_native_type_patch.dart +++ b/sdk/lib/_internal/vm/lib/ffi_native_type_patch.dart @@ -55,6 +55,7 @@ class Uint32 extends _NativeInteger {} @pragma("vm:entry-point") class Uint64 extends _NativeInteger {} +// TODO(http://dartbug.com/47938): Implement as AbiSpecificInteger. @patch @pragma("vm:entry-point") class IntPtr extends _NativeInteger {} diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart index 3af29c34ffb..73d08b7152e 100644 --- a/sdk/lib/_internal/vm/lib/ffi_patch.dart +++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart @@ -236,6 +236,15 @@ class Abi { factory Abi.current() => values[_abi()]; } +@pragma("vm:entry-point") +class _FfiAbiSpecificMapping { + /// Indexed by [_abi]. + @pragma("vm:entry-point") + final List nativeTypes; + + const _FfiAbiSpecificMapping(this.nativeTypes); +} + /// Copies data byte-wise from [source] to [target]. /// /// [source] and [target] should either be [Pointer] or [TypedData]. @@ -284,34 +293,42 @@ void _memCopy(Object target, int targetOffsetInBytes, Object source, // allocating a Pointer with in elementAt/offsetBy. Allocating these pointers // and GCing new spaces takes a lot of the benchmark time. The next speedup is // getting rid of these allocations by inlining these functions. +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_loadInt8") external int _loadInt8(Object typedDataBase, int offsetInBytes); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_loadInt16") external int _loadInt16(Object typedDataBase, int offsetInBytes); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_loadInt32") external int _loadInt32(Object typedDataBase, int offsetInBytes); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_loadInt64") external int _loadInt64(Object typedDataBase, int offsetInBytes); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_loadUint8") external int _loadUint8(Object typedDataBase, int offsetInBytes); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_loadUint16") external int _loadUint16(Object typedDataBase, int offsetInBytes); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_loadUint32") external int _loadUint32(Object typedDataBase, int offsetInBytes); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_loadUint64") external int _loadUint64(Object typedDataBase, int offsetInBytes); @@ -320,6 +337,14 @@ external int _loadUint64(Object typedDataBase, int offsetInBytes); @pragma("vm:external-name", "Ffi_loadIntPtr") external int _loadIntPtr(Object typedDataBase, int offsetInBytes); +@pragma("vm:recognized", "other") +external int _loadAbiSpecificInt( + Object typedDataBase, int offsetInBytes); + +@pragma("vm:recognized", "other") +external int _loadAbiSpecificIntAtIndex( + Object typedDataBase, int index); + @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_loadFloat") external double _loadFloat(Object typedDataBase, int offsetInBytes); @@ -341,34 +366,42 @@ external double _loadDoubleUnaligned(Object typedDataBase, int offsetInBytes); external Pointer _loadPointer( Object typedDataBase, int offsetInBytes); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_storeInt8") external void _storeInt8(Object typedDataBase, int offsetInBytes, int value); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_storeInt16") external void _storeInt16(Object typedDataBase, int offsetInBytes, int value); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_storeInt32") external void _storeInt32(Object typedDataBase, int offsetInBytes, int value); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_storeInt64") external void _storeInt64(Object typedDataBase, int offsetInBytes, int value); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_storeUint8") external void _storeUint8(Object typedDataBase, int offsetInBytes, int value); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_storeUint16") external void _storeUint16(Object typedDataBase, int offsetInBytes, int value); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_storeUint32") external void _storeUint32(Object typedDataBase, int offsetInBytes, int value); +@pragma("vm:entry-point") @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_storeUint64") external void _storeUint64(Object typedDataBase, int offsetInBytes, int value); @@ -377,6 +410,14 @@ external void _storeUint64(Object typedDataBase, int offsetInBytes, int value); @pragma("vm:external-name", "Ffi_storeIntPtr") external void _storeIntPtr(Object typedDataBase, int offsetInBytes, int value); +@pragma("vm:recognized", "other") +external int _storeAbiSpecificInt( + Object typedDataBase, int offsetInBytes, int value); + +@pragma("vm:recognized", "other") +external int _storeAbiSpecificIntAtIndex( + Object typedDataBase, int index, int value); + @pragma("vm:recognized", "other") @pragma("vm:external-name", "Ffi_storeFloat") external void _storeFloat( @@ -937,6 +978,25 @@ extension UnionPointer on Pointer { throw "UNREACHABLE: This case should have been rewritten in the CFE."; } +extension AbiSpecificIntegerPointer + on Pointer { + @patch + int get value => + throw "UNREACHABLE: This case should have been rewritten in the CFE."; + + @patch + void set value(int value) => + throw "UNREACHABLE: This case should have been rewritten in the CFE."; + + @patch + int operator [](int index) => + throw "UNREACHABLE: This case should have been rewritten in the CFE."; + + @patch + void operator []=(int index, int value) => + throw "UNREACHABLE: This case should have been rewritten in the CFE."; +} + extension PointerArray on Array> { @patch Pointer operator [](int index) => @@ -972,6 +1032,20 @@ extension UnionArray on Array { } } +extension AbiSpecificIntegerArray on Array { + @patch + int operator [](int index) { + throw ArgumentError( + "Receiver should be a subtype of AbiSpecificInteger at compile-time."); + } + + @patch + void operator []=(int index, int value) { + throw ArgumentError( + "Receiver should be a subtype of AbiSpecificInteger at compile-time."); + } +} + extension NativePort on SendPort { @patch @pragma("vm:external-name", "SendPortImpl_get_id") diff --git a/sdk/lib/ffi/abi_specific.dart b/sdk/lib/ffi/abi_specific.dart new file mode 100644 index 00000000000..560044a383e --- /dev/null +++ b/sdk/lib/ffi/abi_specific.dart @@ -0,0 +1,53 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of dart.ffi; + +/// The supertype of all [Abi]-specific integer types. +/// +/// [Abi]-specific integers should extend this class and annotate it with +/// [AbiSpecificIntegerMapping] to declare the integer size and signedness +/// for [Abi.values]. +/// +/// For example: +/// +/// ``` +/// /// Represents a `uintptr_t` in C. +/// /// +/// /// [UintPtr] is not constructible in the Dart code and serves purely as +/// /// marker in type signatures. +/// @AbiSpecificIntegerMapping({ +/// Abi.androidArm: Uint32(), +/// Abi.androidArm64: Uint64(), +/// Abi.androidIA32: Uint32(), +/// Abi.androidX64: Uint64(), +/// Abi.fuchsiaArm64: Uint64(), +/// Abi.fuchsiaX64: Uint64(), +/// Abi.iosArm: Uint32(), +/// Abi.iosArm64: Uint64(), +/// Abi.linuxArm: Uint32(), +/// Abi.linuxArm64: Uint64(), +/// Abi.linuxIA32: Uint32(), +/// Abi.linuxX64: Uint64(), +/// Abi.macosArm64: Uint64(), +/// Abi.macosX64: Uint64(), +/// Abi.windowsIA32: Uint32(), +/// Abi.windowsX64: Uint64(), +/// }) +/// class UintPtr extends AbiSpecificInteger { +/// const UintPtr(); +/// } +/// ``` +class AbiSpecificInteger extends NativeType { + const AbiSpecificInteger(); +} + +/// Mapping for a subtype of [AbiSpecificInteger]. +/// +/// See documentation on [AbiSpecificInteger]. +class AbiSpecificIntegerMapping { + final Map mapping; + + const AbiSpecificIntegerMapping(this.mapping); +} diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart index 00eee44966e..eba4ea8525f 100644 --- a/sdk/lib/ffi/ffi.dart +++ b/sdk/lib/ffi/ffi.dart @@ -16,6 +16,7 @@ import 'dart:isolate'; import 'dart:typed_data'; part 'abi.dart'; +part 'abi_specific.dart'; part 'native_type.dart'; part 'allocation.dart'; part 'annotations.dart'; @@ -738,6 +739,22 @@ extension UnionPointer on Pointer { external T operator [](int index); } +/// Extension on [Pointer] specialized for the type argument +/// [AbiSpecificInteger]. +extension AbiSpecificIntegerPointer + on Pointer { + /// The integer at [address]. + external int get value; + + external void set value(int value); + + /// The integer at `address + sizeOf() * index`. + external int operator [](int index); + + /// The integer at `address + sizeOf() * index`. + external void operator []=(int index, int value); +} + /// Bounds checking indexing methods on [Array]s of [Pointer]. extension PointerArray on Array> { external Pointer operator [](int index); @@ -764,6 +781,13 @@ extension ArrayArray on Array> { external void operator []=(int index, Array value); } +/// Bounds checking indexing methods on [Array]s of [AbiSpecificInteger]. +extension AbiSpecificIntegerArray on Array { + external int operator [](int index); + + external void operator []=(int index, int value); +} + /// Extension to retrieve the native `Dart_Port` from a [SendPort]. extension NativePort on SendPort { /// The native port of this [SendPort]. diff --git a/sdk/lib/ffi/ffi_sources.gni b/sdk/lib/ffi/ffi_sources.gni index f7b2838f0e1..b725fe02eab 100644 --- a/sdk/lib/ffi/ffi_sources.gni +++ b/sdk/lib/ffi/ffi_sources.gni @@ -7,6 +7,7 @@ ffi_sdk_sources = [ # The above file needs to be first as it lists the parts below. "abi.dart", + "abi_specific.dart", "allocation.dart", "annotations.dart", "dynamic_library.dart", diff --git a/tests/ffi/abi_specific_int_incomplete_aot_test.dart b/tests/ffi/abi_specific_int_incomplete_aot_test.dart new file mode 100644 index 00000000000..0b77d4368e7 --- /dev/null +++ b/tests/ffi/abi_specific_int_incomplete_aot_test.dart @@ -0,0 +1,23 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// SharedObjects=ffi_test_functions + +import 'dart:ffi'; + +// We want at least 1 mapping to satisfy the static checks. +const notTestingOn = Abi.fuchsiaArm64; + +@AbiSpecificIntegerMapping({ + notTestingOn: Int8(), +}) +class Incomplete extends AbiSpecificInteger { + const Incomplete(); +} + +void main() { + // Any use that causes the class to be used, causes a compile-time error + // during loading of the class. + nullptr.cast(); //# 1: compile-time error +} diff --git a/tests/ffi/abi_specific_int_incomplete_jit_test.dart b/tests/ffi/abi_specific_int_incomplete_jit_test.dart new file mode 100644 index 00000000000..2ecb4c97194 --- /dev/null +++ b/tests/ffi/abi_specific_int_incomplete_jit_test.dart @@ -0,0 +1,160 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// SharedObjects=ffi_test_functions + +import 'dart:ffi'; + +import 'package:expect/expect.dart'; +import 'package:ffi/ffi.dart'; + +// We want at least 1 mapping to satisfy the static checks. +const notTestingOn = Abi.fuchsiaArm64; + +@AbiSpecificIntegerMapping({ + notTestingOn: Int8(), +}) +class Incomplete extends AbiSpecificInteger { + const Incomplete(); +} + +void main() { + if (Abi.current() == notTestingOn) { + return; + } + testSizeOf(); + testStoreLoad(); + testStoreLoadIndexed(); + testStruct(); + testInlineArray(); + testInlineArray2(); + testAsFunction(); + testFromFunction(); +} + +void testSizeOf() { + Expect.throws(() { + sizeOf(); + }); +} + +void testStoreLoad() { + final p = calloc().cast(); + Expect.throws(() { + p.value = 10; + }); + Expect.throws(() { + p.value; + }); + calloc.free(p); +} + +void testStoreLoadIndexed() { + final p = calloc().cast(); + Expect.throws(() { + p[0] = 10; + }); + Expect.throws(() { + p[1]; + }); + calloc.free(p); +} + +class IncompleteStruct extends Struct { + @Incomplete() + external int a0; + + @Incomplete() + external int a1; +} + +void testStruct() { + final p = calloc(2).cast(); + Expect.throws(() { + p.ref.a0 = 1; + }); + Expect.throws(() { + p.ref.a0; + }); + calloc.free(p); +} + +class IncompleteArrayStruct extends Struct { + @Array(100) + external Array a0; +} + +void testInlineArray() { + final p = calloc(100).cast(); + final array = p.ref.a0; + Expect.throws(() { + array[3] = 4; + }); + Expect.throws(() { + array[3]; + }); + calloc.free(p); +} + +const _dim1 = 8; +const _dim2 = 4; + +class IncompleteArrayArrayStruct extends Struct { + @Array(_dim1, _dim2) + external Array> a0; +} + +void testInlineArray2() { + final p = calloc(100).cast(); + Expect.throws(() { + p.elementAt(3); + }); + calloc.free(p); +} + +void testAsFunction() { + Expect.throws(() { + nullptr + .cast>() + .asFunction(); + }); + Expect.throws(() { + nullptr + .cast>() + .asFunction(); + }); + Expect.throws(() { + nullptr + .cast>() + .asFunction(); + }); + Expect.throws(() { + nullptr + .cast>() + .asFunction(); + }); +} + +int myIncr(int a) => a + 1; + +IncompleteArrayStruct myIncompleteReturn() => + nullptr.cast().ref; + +int myIncompleteArg(IncompleteArrayStruct a) => 5; + +void testFromFunction() { + Expect.throws(() { + Pointer.fromFunction(myIncr, 3); + }); + Expect.throws(() { + Pointer.fromFunction(myIncr, 3); + }); + Expect.throws(() { + Pointer.fromFunction(myIncompleteReturn); + }); + Expect.throws(() { + Pointer.fromFunction( + myIncompleteArg, 3); + }); +} diff --git a/tests/ffi/abi_specific_int_test.dart b/tests/ffi/abi_specific_int_test.dart new file mode 100644 index 00000000000..266655cce6d --- /dev/null +++ b/tests/ffi/abi_specific_int_test.dart @@ -0,0 +1,112 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:expect/expect.dart'; +import 'package:ffi/ffi.dart'; + +import 'abi_specific_ints.dart'; + +void main() { + testSizeOf(); + testStoreLoad(); + testStoreLoadIndexed(); + testStruct(); + testInlineArray(); + testInlineArray2(); +} + +void testSizeOf() { + final size = sizeOf(); + if (Platform.isWindows) { + Expect.equals(2, size); + } else { + Expect.equals(4, size); + } +} + +void testStoreLoad() { + final p = calloc(); + p.value = 10; + Expect.equals(10, p.value); + calloc.free(p); +} + +void testStoreLoadIndexed() { + final p = calloc(2); + p[0] = 10; + p[1] = 3; + Expect.equals(10, p[0]); + Expect.equals(3, p[1]); + calloc.free(p); +} + +class WCharStruct extends Struct { + @WChar() + external int a0; + + @WChar() + external int a1; +} + +void testStruct() { + final p = calloc(); + p.ref.a0 = 1; + Expect.equals(1, p.ref.a0); + p.ref.a0 = 2; + Expect.equals(2, p.ref.a0); + calloc.free(p); +} + +class WCharArrayStruct extends Struct { + @Array(100) + external Array a0; +} + +void testInlineArray() { + final p = calloc(); + final array = p.ref.a0; + for (int i = 0; i < 100; i++) { + array[i] = i; + } + for (int i = 0; i < 100; i++) { + Expect.equals(i, array[i]); + } + calloc.free(p); +} + +const _dim0 = 3; +const _dim1 = 8; +const _dim2 = 4; + +class WCharArrayArrayStruct extends Struct { + @Array(_dim1, _dim2) + external Array> a0; +} + +void testInlineArray2() { + int someValue(int a, int b, int c) => a * 1337 + b * 42 + c; + final p = calloc(_dim0); + for (int i0 = 0; i0 < _dim0; i0++) { + final array = p.elementAt(i0).ref.a0; + for (int i1 = 0; i1 < _dim1; i1++) { + final array2 = array[i1]; + for (int i2 = 0; i2 < _dim2; i2++) { + array2[i2] = someValue(i0, i1, i2); + } + } + } + for (int i0 = 0; i0 < _dim0; i0++) { + final array = p.elementAt(i0).ref.a0; + for (int i1 = 0; i1 < _dim1; i1++) { + final array2 = array[i1]; + for (int i2 = 0; i2 < _dim2; i2++) { + Expect.equals(someValue(i0, i1, i2), array2[i2]); + } + } + } + calloc.free(p); +} diff --git a/tests/ffi/abi_specific_ints.dart b/tests/ffi/abi_specific_ints.dart new file mode 100644 index 00000000000..94e05a49fa8 --- /dev/null +++ b/tests/ffi/abi_specific_ints.dart @@ -0,0 +1,137 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:ffi'; + +// TODO(dacoharkes): These should move to `package:ffi`. + +// `int` in C. +typedef Int = Int32; + +// `unsigned int` in C. +typedef UnsignedInt = Uint32; + +// `size_t` in C. +typedef Size = UintPtr; + +// `ssize_t` in C. +typedef SSize = IntPtr; + +// `off_t` in C. +typedef Off = Long; + +/// Represents a native unsigned pointer-sized integer in C. +/// +/// [UintPtr] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), + Abi.androidArm64: Uint64(), + Abi.androidIA32: Uint32(), + Abi.androidX64: Uint64(), + Abi.fuchsiaArm64: Uint64(), + Abi.fuchsiaX64: Uint64(), + Abi.iosArm: Uint32(), + Abi.iosArm64: Uint64(), + Abi.iosX64: Uint64(), + Abi.linuxArm: Uint32(), + Abi.linuxArm64: Uint64(), + Abi.linuxIA32: Uint32(), + Abi.linuxX64: Uint64(), + Abi.macosArm64: Uint64(), + Abi.macosX64: Uint64(), + Abi.windowsArm64: Uint64(), + Abi.windowsIA32: Uint32(), + Abi.windowsX64: Uint64(), +}) +class UintPtr extends AbiSpecificInteger { + const UintPtr(); +} + +/// `long` in C. +/// +/// [Long] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +@AbiSpecificIntegerMapping({ + Abi.androidArm: Int32(), + Abi.androidArm64: Int64(), + Abi.androidIA32: Int32(), + Abi.androidX64: Int64(), + Abi.fuchsiaArm64: Int64(), + Abi.fuchsiaX64: Int64(), + Abi.iosArm: Int32(), + Abi.iosArm64: Int64(), + Abi.iosX64: Int64(), + Abi.linuxArm: Int32(), + Abi.linuxArm64: Int64(), + Abi.linuxIA32: Int32(), + Abi.linuxX64: Int64(), + Abi.macosArm64: Int64(), + Abi.macosX64: Int64(), + Abi.windowsArm64: Int32(), + Abi.windowsIA32: Int32(), + Abi.windowsX64: Int32(), +}) +class Long extends AbiSpecificInteger { + const Long(); +} + +/// `unsigned long` in C. +/// +/// [UnsignedLong] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), + Abi.androidArm64: Uint64(), + Abi.androidIA32: Uint32(), + Abi.androidX64: Uint64(), + Abi.fuchsiaArm64: Uint64(), + Abi.fuchsiaX64: Uint64(), + Abi.iosArm: Uint32(), + Abi.iosArm64: Uint64(), + Abi.iosX64: Uint64(), + Abi.linuxArm: Uint32(), + Abi.linuxArm64: Uint64(), + Abi.linuxIA32: Uint32(), + Abi.linuxX64: Uint64(), + Abi.macosArm64: Uint64(), + Abi.macosX64: Uint64(), + Abi.windowsArm64: Uint32(), + Abi.windowsIA32: Uint32(), + Abi.windowsX64: Uint32(), +}) +class UnsignedLong extends AbiSpecificInteger { + const UnsignedLong(); +} + +/// `wchar_t` in C. +/// +/// The signedness of `wchar_t` is undefined in C. Here, it is exposed as an +/// unsigned integer. +/// +/// [WChar] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), + Abi.androidArm64: Uint32(), + Abi.androidIA32: Uint32(), + Abi.androidX64: Uint32(), + Abi.fuchsiaArm64: Uint32(), + Abi.fuchsiaX64: Uint32(), + Abi.iosArm: Uint32(), + Abi.iosArm64: Uint32(), + Abi.iosX64: Uint32(), + Abi.linuxArm: Uint32(), + Abi.linuxArm64: Uint32(), + Abi.linuxIA32: Uint32(), + Abi.linuxX64: Uint32(), + Abi.macosArm64: Uint32(), + Abi.macosX64: Uint32(), + Abi.windowsArm64: Uint16(), + Abi.windowsIA32: Uint16(), + Abi.windowsX64: Uint16(), +}) +class WChar extends AbiSpecificInteger { + const WChar(); +} diff --git a/tests/ffi/c_types_test.dart b/tests/ffi/c_types_test.dart new file mode 100644 index 00000000000..d423c2c0390 --- /dev/null +++ b/tests/ffi/c_types_test.dart @@ -0,0 +1,134 @@ +// Copyright (c) 2019, 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. +// +// Tests the sizes of c types from https://dartbug.com/36140. +// +// SharedObjects=ffi_test_functions + +import 'dart:ffi'; + +import "package:expect/expect.dart"; +import 'dart:io' show Platform; + +import 'abi_specific_ints.dart'; +import 'ffi_test_helpers.dart'; + +void main() { + printSizes(); + testSizes(); + testIntAssumptions(); + testSizeTAssumptions(); + testLongAssumptions(); + testOffTAssumptions(); + testWCharTAssumptions(); +} + +class CType { + final int ffiSize; + final String modifier; + final String type; + + CType(this.ffiSize, this.type, [this.modifier = ""]); + + String get cRepresentation => "$modifier $type".trim(); + + String get _getSizeName => "FfiSizeOf_$modifier\_$type"; + + int Function() get sizeFunction => ffiTestFunctions + .lookupFunction(_getSizeName); + + int get size => sizeFunction(); + + String toString() => cRepresentation; +} + +final intptr_t = CType(sizeOf(), "intptr_t"); +final uintptr_t = CType(sizeOf(), "uintptr_t"); +final int_ = CType(sizeOf(), "int"); +final uint = CType(sizeOf(), "int", "unsigned"); +final long = CType(sizeOf(), "long"); +final ulong = CType(sizeOf(), "long", "unsigned"); +final wchar_t = CType(sizeOf(), "wchar_t"); +final size_t = CType(sizeOf(), "size_t"); +final ssize_t = CType(sizeOf(), "ssize_t"); +final off_t = CType(sizeOf(), "off_t"); + +final cTypes = [ + intptr_t, + uintptr_t, + int_, + uint, + long, + ulong, + wchar_t, + size_t, + ssize_t, + off_t +]; + +void printSizes() { + cTypes.forEach((element) { + print("${element.cRepresentation.padRight(20)}: ${element.size}"); + }); +} + +void testSizes() { + cTypes.forEach((element) { + Expect.equals(element.size, element.ffiSize); + }); +} + +void testIntAssumptions() { + Expect.equals(4, int_.size); + Expect.equals(4, uint.size); +} + +void testSizeTAssumptions() { + Expect.equals(intptr_t.size, size_t.size); + Expect.equals(intptr_t.size, ssize_t.size); +} + +void testLongAssumptions() { + if (Platform.isWindows) { + Expect.equals(4, long.size); + Expect.equals(4, ulong.size); + } else { + Expect.equals(intptr_t.size, long.size); + Expect.equals(intptr_t.size, ulong.size); + } +} + +void testOffTAssumptions() { + Expect.equals(long.size, off_t.size); +} + +void testWCharTAssumptions() { + final bool isSigned = wCharMinValue() != 0; + print("wchar_t isSigned $isSigned"); + if (Platform.isWindows) { + Expect.equals(2, wchar_t.size); + if (isSigned) { + Expect.equals(-0x8000, wCharMinValue()); + Expect.equals(0x7fff, wCharMaxValue()); + } else { + Expect.equals(0, wCharMinValue()); + Expect.equals(0xffff, wCharMaxValue()); + } + } else { + Expect.equals(4, wchar_t.size); + if (isSigned) { + Expect.equals(-0x80000000, wCharMinValue()); + Expect.equals(0x7fffffff, wCharMaxValue()); + } else { + Expect.equals(0, wCharMinValue()); + Expect.equals(0xffffffff, wCharMaxValue()); + } + } +} + +int Function() wCharMinValue = ffiTestFunctions + .lookupFunction('WCharMinValue'); + +int Function() wCharMaxValue = ffiTestFunctions + .lookupFunction('WCharMaxValue'); diff --git a/tests/ffi/ffi.status b/tests/ffi/ffi.status index 82ef75cfe47..b119eb807b5 100644 --- a/tests/ffi/ffi.status +++ b/tests/ffi/ffi.status @@ -47,3 +47,9 @@ vmspecific_function_callbacks_exit_test: SkipByDesign [ $compiler == dart2analyzer || $compiler == fasta ] vmspecific_enable_ffi_test: SkipByDesign # This is a check for VM only. + +[ $compiler == dartkp || $arch == arm64 && $system == fuchsia ] +abi_specific_int_incomplete_jit_test: SkipByDesign # Only intended to run in JIT mode. + +[ $compiler != dartkp || $arch == arm64 && $system == fuchsia ] +abi_specific_int_incomplete_aot_test: SkipByDesign # Only intended to run in AOT mode. 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 232b7a565f6..2276d2f01e6 100644 --- a/tests/ffi/function_callbacks_structs_by_value_generated_test.dart +++ b/tests/ffi/function_callbacks_structs_by_value_generated_test.dart @@ -16,6 +16,9 @@ import 'dart:ffi'; import "package:expect/expect.dart"; import "package:ffi/ffi.dart"; +// Reuse the AbiSpecificInts. +import 'abi_specific_ints.dart'; + import 'callback_tests_utils.dart'; import 'dylib_utils.dart'; @@ -365,6 +368,12 @@ final testCases = [ Pointer.fromFunction( passUint8Struct1ByteBool, false), passUint8Struct1ByteBoolAfterCallback), + CallbackTest.withCheck( + "PassWCharStructInlineArrayIntUintPtrx2LongUnsigned", + Pointer.fromFunction< + PassWCharStructInlineArrayIntUintPtrx2LongUnsignedType>( + passWCharStructInlineArrayIntUintPtrx2LongUnsigned, 0), + passWCharStructInlineArrayIntUintPtrx2LongUnsignedAfterCallback), CallbackTest.withCheck( "ReturnStruct1ByteInt", Pointer.fromFunction(returnStruct1ByteInt), @@ -7891,6 +7900,84 @@ void passUint8Struct1ByteBoolAfterCallback() { Expect.equals(1 % 2 != 0, result); } +typedef PassWCharStructInlineArrayIntUintPtrx2LongUnsignedType = WChar Function( + WChar, StructInlineArrayInt, UintPtr, UintPtr, Long, UnsignedLong); + +// Global variables to be able to test inputs after callback returned. +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a0 = 0; +StructInlineArrayInt passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1 = + Pointer.fromAddress(0).ref; +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a2 = 0; +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a3 = 0; +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a4 = 0; +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a5 = 0; + +// Result variable also global, so we can delete it after the callback. +int passWCharStructInlineArrayIntUintPtrx2LongUnsignedResult = 0; + +int passWCharStructInlineArrayIntUintPtrx2LongUnsignedCalculateResult() { + int result = 0; + + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a0; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[0]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[1]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[2]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[3]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[4]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[5]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[6]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[7]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[8]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[9]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a2; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a3; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a4; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a5; + + passWCharStructInlineArrayIntUintPtrx2LongUnsignedResult = result; + + return result; +} + +/// Returning a wchar. +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned( + int a0, StructInlineArrayInt a1, int a2, int a3, int a4, int a5) { + print( + "passWCharStructInlineArrayIntUintPtrx2LongUnsigned(${a0}, ${a1}, ${a2}, ${a3}, ${a4}, ${a5})"); + + // In legacy mode, possibly return null. + + // In both nnbd and legacy mode, possibly throw. + if (a0 == 42 || a0 == 84) { + print("throwing!"); + throw Exception( + "PassWCharStructInlineArrayIntUintPtrx2LongUnsigned throwing on purpose!"); + } + + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a0 = a0; + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1 = a1; + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a2 = a2; + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a3 = a3; + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a4 = a4; + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a5 = a5; + + final result = + passWCharStructInlineArrayIntUintPtrx2LongUnsignedCalculateResult(); + + print("result = $result"); + + return result; +} + +void passWCharStructInlineArrayIntUintPtrx2LongUnsignedAfterCallback() { + final result = + passWCharStructInlineArrayIntUintPtrx2LongUnsignedCalculateResult(); + + print("after callback result = $result"); + + Expect.equals(120, 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_compounds.dart b/tests/ffi/function_structs_by_value_generated_compounds.dart index 2f41b7550b2..439e9a349b7 100644 --- a/tests/ffi/function_structs_by_value_generated_compounds.dart +++ b/tests/ffi/function_structs_by_value_generated_compounds.dart @@ -7,6 +7,9 @@ import 'dart:ffi'; +// Reuse the AbiSpecificInts. +import 'abi_specific_ints.dart'; + class Struct1ByteBool extends Struct { @Bool() external bool a0; @@ -1257,3 +1260,10 @@ class Union16BytesNestedFloat extends Union { String toString() => "(${a0}, ${a1}, ${a2})"; } + +class StructInlineArrayInt extends Struct { + @Array(10) + external Array a0; + + String toString() => "(${[for (var i0 = 0; i0 < 10; i0 += 1) a0[i0]]})"; +} diff --git a/tests/ffi/function_structs_by_value_generated_leaf_test.dart b/tests/ffi/function_structs_by_value_generated_leaf_test.dart index 2067463658b..283faad9d6a 100644 --- a/tests/ffi/function_structs_by_value_generated_leaf_test.dart +++ b/tests/ffi/function_structs_by_value_generated_leaf_test.dart @@ -16,6 +16,9 @@ import 'dart:ffi'; import "package:expect/expect.dart"; import "package:ffi/ffi.dart"; +// Reuse the AbiSpecificInts. +import 'abi_specific_ints.dart'; + import 'dylib_utils.dart'; // Reuse the compound classes. @@ -89,6 +92,7 @@ void main() { testPassUint8Boolx9Struct10BytesHomogeneousBoolBoolLeaf(); testPassUint8Boolx9Struct10BytesInlineArrayBoolBoolLeaf(); testPassUint8Struct1ByteBoolLeaf(); + testPassWCharStructInlineArrayIntUintPtrx2LongUnsignedLeaf(); testReturnStruct1ByteIntLeaf(); testReturnStruct3BytesHomogeneousUint8Leaf(); testReturnStruct3BytesInt2ByteAlignedLeaf(); @@ -5461,6 +5465,50 @@ void testPassUint8Struct1ByteBoolLeaf() { calloc.free(a1Pointer); } +final passWCharStructInlineArrayIntUintPtrx2LongUnsignedLeaf = ffiTestFunctions + .lookupFunction< + WChar Function(WChar, StructInlineArrayInt, UintPtr, UintPtr, Long, + UnsignedLong), + int Function(int, StructInlineArrayInt, int, int, int, int)>( + "PassWCharStructInlineArrayIntUintPtrx2LongUnsigned", + isLeaf: true); + +/// Returning a wchar. +void testPassWCharStructInlineArrayIntUintPtrx2LongUnsignedLeaf() { + int a0; + final a1Pointer = calloc(); + final StructInlineArrayInt a1 = a1Pointer.ref; + int a2; + int a3; + int a4; + int a5; + + a0 = 1; + a1.a0[0] = 2; + a1.a0[1] = 3; + a1.a0[2] = 4; + a1.a0[3] = 5; + a1.a0[4] = 6; + a1.a0[5] = 7; + a1.a0[6] = 8; + a1.a0[7] = 9; + a1.a0[8] = 10; + a1.a0[9] = 11; + a2 = 12; + a3 = 13; + a4 = 14; + a5 = 15; + + final result = passWCharStructInlineArrayIntUintPtrx2LongUnsignedLeaf( + a0, a1, a2, a3, a4, a5); + + print("result = $result"); + + Expect.equals(120, result); + + calloc.free(a1Pointer); +} + final returnStruct1ByteIntLeaf = ffiTestFunctions.lookupFunction< Struct1ByteInt Function(Int8), Struct1ByteInt Function(int)>("ReturnStruct1ByteInt", isLeaf: true); diff --git a/tests/ffi/function_structs_by_value_generated_test.dart b/tests/ffi/function_structs_by_value_generated_test.dart index 463118d788d..1f4c5d3df7a 100644 --- a/tests/ffi/function_structs_by_value_generated_test.dart +++ b/tests/ffi/function_structs_by_value_generated_test.dart @@ -16,6 +16,9 @@ import 'dart:ffi'; import "package:expect/expect.dart"; import "package:ffi/ffi.dart"; +// Reuse the AbiSpecificInts. +import 'abi_specific_ints.dart'; + import 'dylib_utils.dart'; // Reuse the compound classes. @@ -89,6 +92,7 @@ void main() { testPassUint8Boolx9Struct10BytesHomogeneousBoolBool(); testPassUint8Boolx9Struct10BytesInlineArrayBoolBool(); testPassUint8Struct1ByteBool(); + testPassWCharStructInlineArrayIntUintPtrx2LongUnsigned(); testReturnStruct1ByteInt(); testReturnStruct3BytesHomogeneousUint8(); testReturnStruct3BytesInt2ByteAligned(); @@ -5385,6 +5389,49 @@ void testPassUint8Struct1ByteBool() { calloc.free(a1Pointer); } +final passWCharStructInlineArrayIntUintPtrx2LongUnsigned = + ffiTestFunctions.lookupFunction< + WChar Function( + WChar, StructInlineArrayInt, UintPtr, UintPtr, Long, UnsignedLong), + int Function(int, StructInlineArrayInt, int, int, int, + int)>("PassWCharStructInlineArrayIntUintPtrx2LongUnsigned"); + +/// Returning a wchar. +void testPassWCharStructInlineArrayIntUintPtrx2LongUnsigned() { + int a0; + final a1Pointer = calloc(); + final StructInlineArrayInt a1 = a1Pointer.ref; + int a2; + int a3; + int a4; + int a5; + + a0 = 1; + a1.a0[0] = 2; + a1.a0[1] = 3; + a1.a0[2] = 4; + a1.a0[3] = 5; + a1.a0[4] = 6; + a1.a0[5] = 7; + a1.a0[6] = 8; + a1.a0[7] = 9; + a1.a0[8] = 10; + a1.a0[9] = 11; + a2 = 12; + a3 = 13; + a4 = 14; + a5 = 15; + + final result = passWCharStructInlineArrayIntUintPtrx2LongUnsigned( + a0, a1, a2, a3, a4, a5); + + print("result = $result"); + + Expect.equals(120, result); + + calloc.free(a1Pointer); +} + final returnStruct1ByteInt = ffiTestFunctions.lookupFunction< Struct1ByteInt Function(Int8), Struct1ByteInt Function(int)>("ReturnStruct1ByteInt"); diff --git a/tests/ffi/generator/c_types.dart b/tests/ffi/generator/c_types.dart index d0825f6c948..bb92bc472ef 100644 --- a/tests/ffi/generator/c_types.dart +++ b/tests/ffi/generator/c_types.dart @@ -18,6 +18,10 @@ const uint64 = FundamentalType(PrimitiveType.uint64); const intptr = FundamentalType(PrimitiveType.intptr); const float = FundamentalType(PrimitiveType.float); const double_ = FundamentalType(PrimitiveType.double_); +const long = FundamentalType(PrimitiveType.long); +const ulong = FundamentalType(PrimitiveType.ulong); +const uintptr = FundamentalType(PrimitiveType.uintptr); +const wchar = FundamentalType(PrimitiveType.wchar); enum PrimitiveType { bool_, @@ -32,25 +36,96 @@ enum PrimitiveType { intptr, float, double_, + long, + ulong, + uintptr, + wchar, } -const primitiveNames = [ - "bool", - "int8", - "int16", - "int32", - "int64", - "uint8", - "uint16", - "uint32", - "uint64", - "intptr", - "float", - "double", -]; +const primitiveNames = { + PrimitiveType.bool_: "bool", + PrimitiveType.int8: "int8", + PrimitiveType.int16: "int16", + PrimitiveType.int32: "int32", + PrimitiveType.int64: "int64", + PrimitiveType.uint8: "uint8", + PrimitiveType.uint16: "uint16", + PrimitiveType.uint32: "uint32", + PrimitiveType.uint64: "uint64", + PrimitiveType.intptr: "intptr", + PrimitiveType.float: "float", + PrimitiveType.double_: "double", + PrimitiveType.long: "long", + PrimitiveType.ulong: "ulong", + PrimitiveType.uintptr: "uintptr", + PrimitiveType.wchar: "wchar", +}; -const intptrSize = -1; -const primitiveSizesInBytes = [1, 1, 2, 4, 8, 1, 2, 4, 8, intptrSize, 4, 8]; +final primitiveCType = { + PrimitiveType.bool_: "bool", + PrimitiveType.int8: "int8_t", + PrimitiveType.int16: "int16_t", + PrimitiveType.int32: "int32_t", + PrimitiveType.int64: "int64_t", + PrimitiveType.uint8: "uint8_t", + PrimitiveType.uint16: "uint16_t", + PrimitiveType.uint32: "uint32_t", + PrimitiveType.uint64: "uint64_t", + PrimitiveType.intptr: "intptr", + PrimitiveType.float: "float", + PrimitiveType.double_: "double", + // People should use explicit sizes. But we also want to test `long`. + // Surpressing lint. + PrimitiveType.long: "/* NOLINT(runtime/int) */long", + PrimitiveType.ulong: "/* NOLINT(runtime/int) */unsigned long", + PrimitiveType.uintptr: "uintptr_t", + PrimitiveType.wchar: "wchar_t", +}; + +final primitiveDartCType = { + PrimitiveType.bool_: "Bool", + PrimitiveType.int8: "Int8", + PrimitiveType.int16: "Int16", + PrimitiveType.int32: "Int32", + PrimitiveType.int64: "Int64", + PrimitiveType.uint8: "Uint8", + PrimitiveType.uint16: "Uint16", + PrimitiveType.uint32: "Uint32", + PrimitiveType.uint64: "Uint64", + PrimitiveType.intptr: "Intptr", + PrimitiveType.float: "Float", + PrimitiveType.double_: "Double", + PrimitiveType.long: "Long", + PrimitiveType.ulong: "UnsignedLong", + PrimitiveType.uintptr: "UintPtr", + PrimitiveType.wchar: "WChar", +}; + +/// Sizes equal on all platforms. +const primitiveSizesInBytes = { + PrimitiveType.bool_: 1, + PrimitiveType.int8: 1, + PrimitiveType.int16: 2, + PrimitiveType.int32: 4, + PrimitiveType.int64: 8, + PrimitiveType.uint8: 1, + PrimitiveType.uint16: 2, + PrimitiveType.uint32: 4, + PrimitiveType.uint64: 8, + PrimitiveType.float: 4, + PrimitiveType.double_: 8, +}; + +const primitivesUnsigned = { + PrimitiveType.bool_, + PrimitiveType.uint8, + PrimitiveType.uint16, + PrimitiveType.uint32, + PrimitiveType.uint64, + PrimitiveType.uintptr, + PrimitiveType.ulong, + PrimitiveType.wchar, +}; abstract class CType { String get cType; @@ -90,18 +165,13 @@ class FundamentalType extends CType { bool get isOnlyFloatingPoint => isFloatingPoint; bool get isOnlyInteger => isInteger; bool get isOnlyBool => isBool; - bool get isUnsigned => - primitive == PrimitiveType.bool_ || - primitive == PrimitiveType.uint8 || - primitive == PrimitiveType.uint16 || - primitive == PrimitiveType.uint32 || - primitive == PrimitiveType.uint64; + bool get isUnsigned => primitivesUnsigned.contains(primitive); bool get isSigned => !isUnsigned; - String get name => primitiveNames[primitive.index]; + String get name => primitiveNames[primitive]!; - String get cType => "${name}${isInteger ? "_t" : ""}"; - String get dartCType => name.upperCaseFirst(); + String get cType => primitiveCType[primitive]!; + String get dartCType => primitiveDartCType[primitive]!; String get dartType { if (isInteger) return 'int'; if (isOnlyFloatingPoint) return 'double'; @@ -110,12 +180,12 @@ class FundamentalType extends CType { } String get dartStructFieldAnnotation => "@${dartCType}()"; - bool get hasSize => primitive != PrimitiveType.intptr; + bool get hasSize => primitiveSizesInBytes.containsKey(primitive); int get size { if (!hasSize) { throw "Size unknown."; } - return primitiveSizesInBytes[primitive.index]; + return primitiveSizesInBytes[primitive]!; } } diff --git a/tests/ffi/generator/structs_by_value_tests_configuration.dart b/tests/ffi/generator/structs_by_value_tests_configuration.dart index a5d62af3fef..9179d50ecc9 100644 --- a/tests/ffi/generator/structs_by_value_tests_configuration.dart +++ b/tests/ffi/generator/structs_by_value_tests_configuration.dart @@ -422,6 +422,18 @@ stack."""), bool_, """ Returning a bool."""), + FunctionType( + [ + wchar, + structArrayWChar, + uintptr, + uintptr, + long, + ulong, + ], + wchar, + """ +Returning a wchar."""), ]; final functionsStructReturn = [ @@ -672,6 +684,7 @@ final compounds = [ union12bytesInt, union16bytesFloat, union16bytesFloat2, + structArrayWChar, ]; final struct1byteBool = StructType([bool_]); @@ -863,3 +876,6 @@ final union16bytesFloat = /// This union has homogenous floats of different sizes. final union16bytesFloat2 = UnionType([struct8bytesFloat, struct12bytesFloat, struct16bytesFloat]); + +/// This struct contains an AbiSpecificInt type. +final structArrayWChar = StructType([FixedLengthArrayType(wchar, 10)]); diff --git a/tests/ffi/generator/structs_by_value_tests_generator.dart b/tests/ffi/generator/structs_by_value_tests_generator.dart index 51de2db153c..4c260b9d6b8 100644 --- a/tests/ffi/generator/structs_by_value_tests_generator.dart +++ b/tests/ffi/generator/structs_by_value_tests_generator.dart @@ -399,7 +399,7 @@ extension on CType { switch (this.runtimeType) { case FundamentalType: final this_ = this as FundamentalType; - if (this_.isInteger) { + if (this_.isInteger || this_.isBool) { return "CHECK_EQ(${expected}, ${actual});"; } assert(this_.isFloatingPoint); @@ -426,7 +426,7 @@ for (intptr_t i = 0; i < ${this_.length}; i++){ switch (this.runtimeType) { case FundamentalType: final this_ = this as FundamentalType; - if (this_.isInteger) { + if (this_.isInteger || this_.isBool) { return "CHECK_EQ(0, ${actual});"; } assert(this_.isFloatingPoint); @@ -921,6 +921,9 @@ ${headerCommon(copyrightYear: copyrightYear)} $dartVersion import 'dart:ffi'; + +// Reuse the AbiSpecificInts. +import 'abi_specific_ints.dart'; """; } @@ -929,7 +932,7 @@ String compoundsPath({required bool isNnbd}) { return Platform.script .resolve( "../../$folder/function_structs_by_value_generated_compounds.dart") - .path; + .toFilePath(); } Future writeDartCompounds() async { @@ -964,6 +967,9 @@ import 'dart:ffi'; import "package:expect/expect.dart"; import "package:ffi/ffi.dart"; +// Reuse the AbiSpecificInts. +import 'abi_specific_ints.dart'; + import 'dylib_utils.dart'; // Reuse the compound classes. @@ -1001,7 +1007,7 @@ String callTestPath({required bool isNnbd, required bool isLeaf}) { return Platform.script .resolve( "../../$folder/function_structs_by_value_generated${suffix}_test.dart") - .path; + .toFilePath(); } headerDartCallbackTest({required bool isNnbd, required int copyrightYear}) { @@ -1023,6 +1029,9 @@ import 'dart:ffi'; import "package:expect/expect.dart"; import "package:ffi/ffi.dart"; +// Reuse the AbiSpecificInts. +import 'abi_specific_ints.dart'; + import 'callback_tests_utils.dart'; import 'dylib_utils.dart'; @@ -1067,7 +1076,7 @@ String callbackTestPath({required bool isNnbd}) { return Platform.script .resolve( "../../$folder/function_callbacks_structs_by_value_generated_test.dart") - .path; + .toFilePath(); } headerC({required int copyrightYear}) { @@ -1128,7 +1137,7 @@ Future writeC() async { final ccPath = Platform.script .resolve("../../../runtime/bin/ffi_test/ffi_test_functions_generated.cc") - .path; + .toFilePath(); void printUsage() { print(""" diff --git a/tests/ffi/vmspecific_static_checks_test.dart b/tests/ffi/vmspecific_static_checks_test.dart index e77b9417986..ba7f5ab16ad 100644 --- a/tests/ffi/vmspecific_static_checks_test.dart +++ b/tests/ffi/vmspecific_static_checks_test.dart @@ -848,3 +848,35 @@ class TestStruct1802 extends Struct { @Array.multi([2, 2, 2, 2, 2, 2, -1]) //# 1802: compile-time error external Array inlineArray; //# 1802: compile-time error } + +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), + Abi.androidArm64: IntPtr(), //# 1900: compile-time error + Abi.androidIA32: AbiSpecificInteger1(), //# 1901: compile-time error +}) +@AbiSpecificIntegerMapping({}) //# 1902: compile-time error +class AbiSpecificInteger1 extends AbiSpecificInteger { + const AbiSpecificInteger1(); + + int get a => 4; //# 1910: compile-time error + + external int b; //# 1911: compile-time error +} + +class AbiSpecificInteger2 + implements AbiSpecificInteger //# 1903: compile-time error +{ + const AbiSpecificInteger2(); +} + +class AbiSpecificInteger3 + extends AbiSpecificInteger1 //# 1904: compile-time error +{ + const AbiSpecificInteger3(); +} + +class AbiSpecificInteger4 + implements AbiSpecificInteger1 //# 1905: compile-time error +{ + const AbiSpecificInteger4(); +} diff --git a/tests/ffi_2/abi_specific_int_incomplete_aot_test.dart b/tests/ffi_2/abi_specific_int_incomplete_aot_test.dart new file mode 100644 index 00000000000..84296529abf --- /dev/null +++ b/tests/ffi_2/abi_specific_int_incomplete_aot_test.dart @@ -0,0 +1,25 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// @dart = 2.9 + +// SharedObjects=ffi_test_functions + +import 'dart:ffi'; + +// We want at least 1 mapping to satisfy the static checks. +const notTestingOn = Abi.fuchsiaArm64; + +@AbiSpecificIntegerMapping({ + notTestingOn: Int8(), +}) +class Incomplete extends AbiSpecificInteger { + const Incomplete(); +} + +void main() { + // Any use that causes the class to be used, causes a compile-time error + // during loading of the class. + nullptr.cast(); //# 1: compile-time error +} diff --git a/tests/ffi_2/abi_specific_int_incomplete_jit_test.dart b/tests/ffi_2/abi_specific_int_incomplete_jit_test.dart new file mode 100644 index 00000000000..b507ed91370 --- /dev/null +++ b/tests/ffi_2/abi_specific_int_incomplete_jit_test.dart @@ -0,0 +1,162 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// @dart = 2.9 + +// SharedObjects=ffi_test_functions + +import 'dart:ffi'; + +import 'package:expect/expect.dart'; +import 'package:ffi/ffi.dart'; + +// We want at least 1 mapping to satisfy the static checks. +const notTestingOn = Abi.fuchsiaArm64; + +@AbiSpecificIntegerMapping({ + notTestingOn: Int8(), +}) +class Incomplete extends AbiSpecificInteger { + const Incomplete(); +} + +void main() { + if (Abi.current() == notTestingOn) { + return; + } + testSizeOf(); + testStoreLoad(); + testStoreLoadIndexed(); + testStruct(); + testInlineArray(); + testInlineArray2(); + testAsFunction(); + testFromFunction(); +} + +void testSizeOf() { + Expect.throws(() { + sizeOf(); + }); +} + +void testStoreLoad() { + final p = calloc().cast(); + Expect.throws(() { + p.value = 10; + }); + Expect.throws(() { + p.value; + }); + calloc.free(p); +} + +void testStoreLoadIndexed() { + final p = calloc().cast(); + Expect.throws(() { + p[0] = 10; + }); + Expect.throws(() { + p[1]; + }); + calloc.free(p); +} + +class IncompleteStruct extends Struct { + @Incomplete() + int a0; + + @Incomplete() + int a1; +} + +void testStruct() { + final p = calloc(2).cast(); + Expect.throws(() { + p.ref.a0 = 1; + }); + Expect.throws(() { + p.ref.a0; + }); + calloc.free(p); +} + +class IncompleteArrayStruct extends Struct { + @Array(100) + Array a0; +} + +void testInlineArray() { + final p = calloc(100).cast(); + final array = p.ref.a0; + Expect.throws(() { + array[3] = 4; + }); + Expect.throws(() { + array[3]; + }); + calloc.free(p); +} + +const _dim1 = 8; +const _dim2 = 4; + +class IncompleteArrayArrayStruct extends Struct { + @Array(_dim1, _dim2) + Array> a0; +} + +void testInlineArray2() { + final p = calloc(100).cast(); + Expect.throws(() { + p.elementAt(3); + }); + calloc.free(p); +} + +void testAsFunction() { + Expect.throws(() { + nullptr + .cast>() + .asFunction(); + }); + Expect.throws(() { + nullptr + .cast>() + .asFunction(); + }); + Expect.throws(() { + nullptr + .cast>() + .asFunction(); + }); + Expect.throws(() { + nullptr + .cast>() + .asFunction(); + }); +} + +int myIncr(int a) => a + 1; + +IncompleteArrayStruct myIncompleteReturn() => + nullptr.cast().ref; + +int myIncompleteArg(IncompleteArrayStruct a) => 5; + +void testFromFunction() { + Expect.throws(() { + Pointer.fromFunction(myIncr, 3); + }); + Expect.throws(() { + Pointer.fromFunction(myIncr, 3); + }); + Expect.throws(() { + Pointer.fromFunction(myIncompleteReturn); + }); + Expect.throws(() { + Pointer.fromFunction( + myIncompleteArg, 3); + }); +} diff --git a/tests/ffi_2/abi_specific_int_test.dart b/tests/ffi_2/abi_specific_int_test.dart new file mode 100644 index 00000000000..152c159034c --- /dev/null +++ b/tests/ffi_2/abi_specific_int_test.dart @@ -0,0 +1,114 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// @dart = 2.9 + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:expect/expect.dart'; +import 'package:ffi/ffi.dart'; + +import 'abi_specific_ints.dart'; + +void main() { + testSizeOf(); + testStoreLoad(); + testStoreLoadIndexed(); + testStruct(); + testInlineArray(); + testInlineArray2(); +} + +void testSizeOf() { + final size = sizeOf(); + if (Platform.isWindows) { + Expect.equals(2, size); + } else { + Expect.equals(4, size); + } +} + +void testStoreLoad() { + final p = calloc(); + p.value = 10; + Expect.equals(10, p.value); + calloc.free(p); +} + +void testStoreLoadIndexed() { + final p = calloc(2); + p[0] = 10; + p[1] = 3; + Expect.equals(10, p[0]); + Expect.equals(3, p[1]); + calloc.free(p); +} + +class WCharStruct extends Struct { + @WChar() + int a0; + + @WChar() + int a1; +} + +void testStruct() { + final p = calloc(); + p.ref.a0 = 1; + Expect.equals(1, p.ref.a0); + p.ref.a0 = 2; + Expect.equals(2, p.ref.a0); + calloc.free(p); +} + +class WCharArrayStruct extends Struct { + @Array(100) + Array a0; +} + +void testInlineArray() { + final p = calloc(); + final array = p.ref.a0; + for (int i = 0; i < 100; i++) { + array[i] = i; + } + for (int i = 0; i < 100; i++) { + Expect.equals(i, array[i]); + } + calloc.free(p); +} + +const _dim0 = 3; +const _dim1 = 8; +const _dim2 = 4; + +class WCharArrayArrayStruct extends Struct { + @Array(_dim1, _dim2) + Array> a0; +} + +void testInlineArray2() { + int someValue(int a, int b, int c) => a * 1337 + b * 42 + c; + final p = calloc(_dim0); + for (int i0 = 0; i0 < _dim0; i0++) { + final array = p.elementAt(i0).ref.a0; + for (int i1 = 0; i1 < _dim1; i1++) { + final array2 = array[i1]; + for (int i2 = 0; i2 < _dim2; i2++) { + array2[i2] = someValue(i0, i1, i2); + } + } + } + for (int i0 = 0; i0 < _dim0; i0++) { + final array = p.elementAt(i0).ref.a0; + for (int i1 = 0; i1 < _dim1; i1++) { + final array2 = array[i1]; + for (int i2 = 0; i2 < _dim2; i2++) { + Expect.equals(someValue(i0, i1, i2), array2[i2]); + } + } + } + calloc.free(p); +} diff --git a/tests/ffi_2/abi_specific_ints.dart b/tests/ffi_2/abi_specific_ints.dart new file mode 100644 index 00000000000..91c9875fd43 --- /dev/null +++ b/tests/ffi_2/abi_specific_ints.dart @@ -0,0 +1,124 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// @dart = 2.9 + +import 'dart:ffi'; + +// TODO(dacoharkes): These should move to `package:ffi`. + +/// Represents a native unsigned pointer-sized integer in C. +/// +/// [UintPtr] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), + Abi.androidArm64: Uint64(), + Abi.androidIA32: Uint32(), + Abi.androidX64: Uint64(), + Abi.fuchsiaArm64: Uint64(), + Abi.fuchsiaX64: Uint64(), + Abi.iosArm: Uint32(), + Abi.iosArm64: Uint64(), + Abi.iosX64: Uint64(), + Abi.linuxArm: Uint32(), + Abi.linuxArm64: Uint64(), + Abi.linuxIA32: Uint32(), + Abi.linuxX64: Uint64(), + Abi.macosArm64: Uint64(), + Abi.macosX64: Uint64(), + Abi.windowsArm64: Uint64(), + Abi.windowsIA32: Uint32(), + Abi.windowsX64: Uint64(), +}) +class UintPtr extends AbiSpecificInteger { + const UintPtr(); +} + +/// `long` in C. +/// +/// [Long] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +@AbiSpecificIntegerMapping({ + Abi.androidArm: Int32(), + Abi.androidArm64: Int64(), + Abi.androidIA32: Int32(), + Abi.androidX64: Int64(), + Abi.fuchsiaArm64: Int64(), + Abi.fuchsiaX64: Int64(), + Abi.iosArm: Int32(), + Abi.iosArm64: Int64(), + Abi.iosX64: Int64(), + Abi.linuxArm: Int32(), + Abi.linuxArm64: Int64(), + Abi.linuxIA32: Int32(), + Abi.linuxX64: Int64(), + Abi.macosArm64: Int64(), + Abi.macosX64: Int64(), + Abi.windowsArm64: Int32(), + Abi.windowsIA32: Int32(), + Abi.windowsX64: Int32(), +}) +class Long extends AbiSpecificInteger { + const Long(); +} + +/// `unsigned long` in C. +/// +/// [UnsignedLong] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), + Abi.androidArm64: Uint64(), + Abi.androidIA32: Uint32(), + Abi.androidX64: Uint64(), + Abi.fuchsiaArm64: Uint64(), + Abi.fuchsiaX64: Uint64(), + Abi.iosArm: Uint32(), + Abi.iosArm64: Uint64(), + Abi.iosX64: Uint64(), + Abi.linuxArm: Uint32(), + Abi.linuxArm64: Uint64(), + Abi.linuxIA32: Uint32(), + Abi.linuxX64: Uint64(), + Abi.macosArm64: Uint64(), + Abi.macosX64: Uint64(), + Abi.windowsArm64: Uint32(), + Abi.windowsIA32: Uint32(), + Abi.windowsX64: Uint32(), +}) +class UnsignedLong extends AbiSpecificInteger { + const UnsignedLong(); +} + +/// `wchar_t` in C. +/// +/// The signedness of `wchar_t` is undefined in C. Here, it is exposed as an +/// unsigned integer. +/// +/// [WChar] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), + Abi.androidArm64: Uint32(), + Abi.androidIA32: Uint32(), + Abi.androidX64: Uint32(), + Abi.fuchsiaArm64: Uint32(), + Abi.fuchsiaX64: Uint32(), + Abi.iosArm: Uint32(), + Abi.iosArm64: Uint32(), + Abi.iosX64: Uint32(), + Abi.linuxArm: Uint32(), + Abi.linuxArm64: Uint32(), + Abi.linuxIA32: Uint32(), + Abi.linuxX64: Uint32(), + Abi.macosArm64: Uint32(), + Abi.macosX64: Uint32(), + Abi.windowsArm64: Uint16(), + Abi.windowsIA32: Uint16(), + Abi.windowsX64: Uint16(), +}) +class WChar extends AbiSpecificInteger { + const WChar(); +} diff --git a/tests/ffi_2/c_types_test.dart b/tests/ffi_2/c_types_test.dart new file mode 100644 index 00000000000..fa762bdf7ba --- /dev/null +++ b/tests/ffi_2/c_types_test.dart @@ -0,0 +1,109 @@ +// Copyright (c) 2019, 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. +// +// Tests the sizes of c types from https://dartbug.com/36140. +// +// SharedObjects=ffi_test_functions + +// @dart = 2.9 + +import 'dart:ffi'; + +import "package:expect/expect.dart"; +import 'dart:io' show Platform; + +import 'abi_specific_ints.dart'; +import 'ffi_test_helpers.dart'; + +void main() { + printSizes(); + testSizes(); + testLongAssumptions(); + testWCharTAssumptions(); +} + +class CType { + final int ffiSize; + final String modifier; + final String type; + + CType(this.ffiSize, this.type, [this.modifier = ""]); + + String get cRepresentation => "$modifier $type".trim(); + + String get _getSizeName => "FfiSizeOf_$modifier\_$type"; + + int Function() get sizeFunction => ffiTestFunctions + .lookupFunction(_getSizeName); + + int get size => sizeFunction(); + + String toString() => cRepresentation; +} + +final intptr_t = CType(sizeOf(), "intptr_t"); +final uintptr_t = CType(sizeOf(), "uintptr_t"); +final long = CType(sizeOf(), "long"); +final ulong = CType(sizeOf(), "long", "unsigned"); +final wchar_t = CType(sizeOf(), "wchar_t"); + +final cTypes = [ + intptr_t, + uintptr_t, + long, + ulong, + wchar_t, +]; + +void printSizes() { + cTypes.forEach((element) { + print("${element.cRepresentation.padRight(20)}: ${element.size}"); + }); +} + +void testSizes() { + cTypes.forEach((element) { + Expect.equals(element.size, element.ffiSize); + }); +} + +void testLongAssumptions() { + if (Platform.isWindows) { + Expect.equals(4, long.size); + Expect.equals(4, ulong.size); + } else { + Expect.equals(intptr_t.size, long.size); + Expect.equals(intptr_t.size, ulong.size); + } +} + +void testWCharTAssumptions() { + final bool isSigned = wCharMinValue() != 0; + print("wchar_t isSigned $isSigned"); + if (Platform.isWindows) { + Expect.equals(2, wchar_t.size); + if (isSigned) { + Expect.equals(-0x8000, wCharMinValue()); + Expect.equals(0x7fff, wCharMaxValue()); + } else { + Expect.equals(0, wCharMinValue()); + Expect.equals(0xffff, wCharMaxValue()); + } + } else { + Expect.equals(4, wchar_t.size); + if (isSigned) { + Expect.equals(-0x80000000, wCharMinValue()); + Expect.equals(0x7fffffff, wCharMaxValue()); + } else { + Expect.equals(0, wCharMinValue()); + Expect.equals(0xffffffff, wCharMaxValue()); + } + } +} + +int Function() wCharMinValue = ffiTestFunctions + .lookupFunction('WCharMinValue'); + +int Function() wCharMaxValue = ffiTestFunctions + .lookupFunction('WCharMaxValue'); diff --git a/tests/ffi_2/ffi_2.status b/tests/ffi_2/ffi_2.status index 82ef75cfe47..b119eb807b5 100644 --- a/tests/ffi_2/ffi_2.status +++ b/tests/ffi_2/ffi_2.status @@ -47,3 +47,9 @@ vmspecific_function_callbacks_exit_test: SkipByDesign [ $compiler == dart2analyzer || $compiler == fasta ] vmspecific_enable_ffi_test: SkipByDesign # This is a check for VM only. + +[ $compiler == dartkp || $arch == arm64 && $system == fuchsia ] +abi_specific_int_incomplete_jit_test: SkipByDesign # Only intended to run in JIT mode. + +[ $compiler != dartkp || $arch == arm64 && $system == fuchsia ] +abi_specific_int_incomplete_aot_test: SkipByDesign # Only intended to run in AOT mode. diff --git a/tests/ffi_2/function_callbacks_structs_by_value_generated_test.dart b/tests/ffi_2/function_callbacks_structs_by_value_generated_test.dart index d593bd11b2a..f4100fbf115 100644 --- a/tests/ffi_2/function_callbacks_structs_by_value_generated_test.dart +++ b/tests/ffi_2/function_callbacks_structs_by_value_generated_test.dart @@ -18,6 +18,9 @@ import 'dart:ffi'; import "package:expect/expect.dart"; import "package:ffi/ffi.dart"; +// Reuse the AbiSpecificInts. +import 'abi_specific_ints.dart'; + import 'callback_tests_utils.dart'; import 'dylib_utils.dart'; @@ -367,6 +370,12 @@ final testCases = [ Pointer.fromFunction( passUint8Struct1ByteBool, false), passUint8Struct1ByteBoolAfterCallback), + CallbackTest.withCheck( + "PassWCharStructInlineArrayIntUintPtrx2LongUnsigned", + Pointer.fromFunction< + PassWCharStructInlineArrayIntUintPtrx2LongUnsignedType>( + passWCharStructInlineArrayIntUintPtrx2LongUnsigned, 0), + passWCharStructInlineArrayIntUintPtrx2LongUnsignedAfterCallback), CallbackTest.withCheck( "ReturnStruct1ByteInt", Pointer.fromFunction(returnStruct1ByteInt), @@ -8153,6 +8162,88 @@ void passUint8Struct1ByteBoolAfterCallback() { Expect.equals(1 % 2 != 0, result); } +typedef PassWCharStructInlineArrayIntUintPtrx2LongUnsignedType = WChar Function( + WChar, StructInlineArrayInt, UintPtr, UintPtr, Long, UnsignedLong); + +// Global variables to be able to test inputs after callback returned. +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a0 = 0; +StructInlineArrayInt passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1 = + Pointer.fromAddress(0).ref; +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a2 = 0; +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a3 = 0; +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a4 = 0; +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a5 = 0; + +// Result variable also global, so we can delete it after the callback. +int passWCharStructInlineArrayIntUintPtrx2LongUnsignedResult = 0; + +int passWCharStructInlineArrayIntUintPtrx2LongUnsignedCalculateResult() { + int result = 0; + + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a0; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[0]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[1]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[2]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[3]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[4]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[5]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[6]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[7]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[8]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1.a0[9]; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a2; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a3; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a4; + result += passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a5; + + passWCharStructInlineArrayIntUintPtrx2LongUnsignedResult = result; + + return result; +} + +/// Returning a wchar. +int passWCharStructInlineArrayIntUintPtrx2LongUnsigned( + int a0, StructInlineArrayInt a1, int a2, int a3, int a4, int a5) { + print( + "passWCharStructInlineArrayIntUintPtrx2LongUnsigned(${a0}, ${a1}, ${a2}, ${a3}, ${a4}, ${a5})"); + + // In legacy mode, possibly return null. + if (a0 == 84) { + print("returning null!"); + return null; + } + + // In both nnbd and legacy mode, possibly throw. + if (a0 == 42 || a0 == 84) { + print("throwing!"); + throw Exception( + "PassWCharStructInlineArrayIntUintPtrx2LongUnsigned throwing on purpose!"); + } + + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a0 = a0; + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a1 = a1; + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a2 = a2; + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a3 = a3; + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a4 = a4; + passWCharStructInlineArrayIntUintPtrx2LongUnsigned_a5 = a5; + + final result = + passWCharStructInlineArrayIntUintPtrx2LongUnsignedCalculateResult(); + + print("result = $result"); + + return result; +} + +void passWCharStructInlineArrayIntUintPtrx2LongUnsignedAfterCallback() { + final result = + passWCharStructInlineArrayIntUintPtrx2LongUnsignedCalculateResult(); + + print("after callback result = $result"); + + Expect.equals(120, result); +} + typedef ReturnStruct1ByteIntType = Struct1ByteInt Function(Int8); // Global variables to be able to test inputs after callback returned. diff --git a/tests/ffi_2/function_structs_by_value_generated_compounds.dart b/tests/ffi_2/function_structs_by_value_generated_compounds.dart index 8b7931fb389..69081770c04 100644 --- a/tests/ffi_2/function_structs_by_value_generated_compounds.dart +++ b/tests/ffi_2/function_structs_by_value_generated_compounds.dart @@ -9,6 +9,9 @@ import 'dart:ffi'; +// Reuse the AbiSpecificInts. +import 'abi_specific_ints.dart'; + class Struct1ByteBool extends Struct { @Bool() bool a0; @@ -1259,3 +1262,10 @@ class Union16BytesNestedFloat extends Union { String toString() => "(${a0}, ${a1}, ${a2})"; } + +class StructInlineArrayInt extends Struct { + @Array(10) + Array a0; + + String toString() => "(${[for (var i0 = 0; i0 < 10; i0 += 1) a0[i0]]})"; +} diff --git a/tests/ffi_2/function_structs_by_value_generated_leaf_test.dart b/tests/ffi_2/function_structs_by_value_generated_leaf_test.dart index 3a75d811232..7c642963cbc 100644 --- a/tests/ffi_2/function_structs_by_value_generated_leaf_test.dart +++ b/tests/ffi_2/function_structs_by_value_generated_leaf_test.dart @@ -18,6 +18,9 @@ import 'dart:ffi'; import "package:expect/expect.dart"; import "package:ffi/ffi.dart"; +// Reuse the AbiSpecificInts. +import 'abi_specific_ints.dart'; + import 'dylib_utils.dart'; // Reuse the compound classes. @@ -91,6 +94,7 @@ void main() { testPassUint8Boolx9Struct10BytesHomogeneousBoolBoolLeaf(); testPassUint8Boolx9Struct10BytesInlineArrayBoolBoolLeaf(); testPassUint8Struct1ByteBoolLeaf(); + testPassWCharStructInlineArrayIntUintPtrx2LongUnsignedLeaf(); testReturnStruct1ByteIntLeaf(); testReturnStruct3BytesHomogeneousUint8Leaf(); testReturnStruct3BytesInt2ByteAlignedLeaf(); @@ -5463,6 +5467,50 @@ void testPassUint8Struct1ByteBoolLeaf() { calloc.free(a1Pointer); } +final passWCharStructInlineArrayIntUintPtrx2LongUnsignedLeaf = ffiTestFunctions + .lookupFunction< + WChar Function(WChar, StructInlineArrayInt, UintPtr, UintPtr, Long, + UnsignedLong), + int Function(int, StructInlineArrayInt, int, int, int, int)>( + "PassWCharStructInlineArrayIntUintPtrx2LongUnsigned", + isLeaf: true); + +/// Returning a wchar. +void testPassWCharStructInlineArrayIntUintPtrx2LongUnsignedLeaf() { + int a0; + final a1Pointer = calloc(); + final StructInlineArrayInt a1 = a1Pointer.ref; + int a2; + int a3; + int a4; + int a5; + + a0 = 1; + a1.a0[0] = 2; + a1.a0[1] = 3; + a1.a0[2] = 4; + a1.a0[3] = 5; + a1.a0[4] = 6; + a1.a0[5] = 7; + a1.a0[6] = 8; + a1.a0[7] = 9; + a1.a0[8] = 10; + a1.a0[9] = 11; + a2 = 12; + a3 = 13; + a4 = 14; + a5 = 15; + + final result = passWCharStructInlineArrayIntUintPtrx2LongUnsignedLeaf( + a0, a1, a2, a3, a4, a5); + + print("result = $result"); + + Expect.equals(120, result); + + calloc.free(a1Pointer); +} + final returnStruct1ByteIntLeaf = ffiTestFunctions.lookupFunction< Struct1ByteInt Function(Int8), Struct1ByteInt Function(int)>("ReturnStruct1ByteInt", isLeaf: true); diff --git a/tests/ffi_2/function_structs_by_value_generated_test.dart b/tests/ffi_2/function_structs_by_value_generated_test.dart index f817df09395..ef24ad2ce7a 100644 --- a/tests/ffi_2/function_structs_by_value_generated_test.dart +++ b/tests/ffi_2/function_structs_by_value_generated_test.dart @@ -18,6 +18,9 @@ import 'dart:ffi'; import "package:expect/expect.dart"; import "package:ffi/ffi.dart"; +// Reuse the AbiSpecificInts. +import 'abi_specific_ints.dart'; + import 'dylib_utils.dart'; // Reuse the compound classes. @@ -91,6 +94,7 @@ void main() { testPassUint8Boolx9Struct10BytesHomogeneousBoolBool(); testPassUint8Boolx9Struct10BytesInlineArrayBoolBool(); testPassUint8Struct1ByteBool(); + testPassWCharStructInlineArrayIntUintPtrx2LongUnsigned(); testReturnStruct1ByteInt(); testReturnStruct3BytesHomogeneousUint8(); testReturnStruct3BytesInt2ByteAligned(); @@ -5387,6 +5391,49 @@ void testPassUint8Struct1ByteBool() { calloc.free(a1Pointer); } +final passWCharStructInlineArrayIntUintPtrx2LongUnsigned = + ffiTestFunctions.lookupFunction< + WChar Function( + WChar, StructInlineArrayInt, UintPtr, UintPtr, Long, UnsignedLong), + int Function(int, StructInlineArrayInt, int, int, int, + int)>("PassWCharStructInlineArrayIntUintPtrx2LongUnsigned"); + +/// Returning a wchar. +void testPassWCharStructInlineArrayIntUintPtrx2LongUnsigned() { + int a0; + final a1Pointer = calloc(); + final StructInlineArrayInt a1 = a1Pointer.ref; + int a2; + int a3; + int a4; + int a5; + + a0 = 1; + a1.a0[0] = 2; + a1.a0[1] = 3; + a1.a0[2] = 4; + a1.a0[3] = 5; + a1.a0[4] = 6; + a1.a0[5] = 7; + a1.a0[6] = 8; + a1.a0[7] = 9; + a1.a0[8] = 10; + a1.a0[9] = 11; + a2 = 12; + a3 = 13; + a4 = 14; + a5 = 15; + + final result = passWCharStructInlineArrayIntUintPtrx2LongUnsigned( + a0, a1, a2, a3, a4, a5); + + print("result = $result"); + + Expect.equals(120, result); + + calloc.free(a1Pointer); +} + final returnStruct1ByteInt = ffiTestFunctions.lookupFunction< Struct1ByteInt Function(Int8), Struct1ByteInt Function(int)>("ReturnStruct1ByteInt"); diff --git a/tests/ffi_2/vmspecific_static_checks_test.dart b/tests/ffi_2/vmspecific_static_checks_test.dart index 165b25cd045..ecb15cb615d 100644 --- a/tests/ffi_2/vmspecific_static_checks_test.dart +++ b/tests/ffi_2/vmspecific_static_checks_test.dart @@ -847,3 +847,35 @@ class TestStruct1802 extends Struct { @Array.multi([2, 2, 2, 2, 2, 2, -1]) //# 1802: compile-time error Array inlineArray; //# 1802: compile-time error } + +@AbiSpecificIntegerMapping({ + Abi.androidArm: Uint32(), + Abi.androidArm64: IntPtr(), //# 1900: compile-time error + Abi.androidIA32: AbiSpecificInteger1(), //# 1901: compile-time error +}) +@AbiSpecificIntegerMapping({}) //# 1902: compile-time error +class AbiSpecificInteger1 extends AbiSpecificInteger { + const AbiSpecificInteger1(); + + int get a => 4; //# 1910: compile-time error + + int b; //# 1911: compile-time error +} + +class AbiSpecificInteger2 + implements AbiSpecificInteger //# 1903: compile-time error +{ + const AbiSpecificInteger2(); +} + +class AbiSpecificInteger3 + extends AbiSpecificInteger1 //# 1904: compile-time error +{ + const AbiSpecificInteger3(); +} + +class AbiSpecificInteger4 + implements AbiSpecificInteger1 //# 1905: compile-time error +{ + const AbiSpecificInteger4(); +}