From acdf82de1714a6a16c5f604e233c990588b98a4c Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Thu, 16 Dec 2021 22:07:00 +0000 Subject: [PATCH] [vm/ffi] ABI-specific integers This CL adds support for users defining integers which are mapped to differing sizes and signedness based on the application binary interface the Dart VM is running on. Notable implementation design decisions: - ABIs are open world, so that adding an ABI to the Dart VM does not break existing definitions. Thus, we only figure out in the VM that we're missing a mapping. We throw compile-time errors. - In AOT, these show up in the precompilation step. - In JIT, these show up as `_CompileTimeError` at runtime. Note that these can be caught. So in subsequent compilation steps we need to ensure that we also throw the same compile-time error. - We match on the call-sites (streaming_flowgraph_builder) rather than method bodies (kernel_to_il) of AbiSpecific loads and stores so that we can compile for the int-size of the call site. API design decisions: https://github.com/dart-lang/sdk/issues/42563#issuecomment-981774001 Closes: https://github.com/dart-lang/sdk/issues/42563 TEST=tests/ffi_2/abi_*_test.dart TEST=tests/ffi/function_*_generated_test.dart TEST=tests/ffi/vmspecific_static_checks_test.dart Change-Id: I8c8df36fab939b6fb614c5f1ee8e1bf46b6e9521 Cq-Include-Trybots: luci.dart.try:analyzer-linux-release-try,analyzer-nnbd-linux-release-try,app-kernel-linux-debug-x64-try,benchmark-linux-try,dart-sdk-linux-try,front-end-linux-release-x64-try,front-end-nnbd-linux-release-x64-try,pkg-linux-debug-try,vm-canary-linux-debug-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-fuchsia-release-x64-try,vm-kernel-checked-linux-release-x64-try,vm-kernel-gcc-linux-try,vm-kernel-linux-debug-x64c-try,vm-kernel-mac-debug-x64-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-msan-linux-release-x64-try,vm-kernel-nnbd-linux-debug-ia32-try,vm-kernel-nnbd-win-debug-x64-try,vm-kernel-nnbd-win-release-ia32-try,vm-kernel-nnbd-linux-debug-x64-try,vm-kernel-precomp-asan-linux-release-x64-try,vm-kernel-precomp-android-release-arm_x64-try,vm-kernel-precomp-android-release-arm64c-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-win-debug-x64c-try,vm-kernel-reload-linux-debug-x64-try,vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-kernel-win-debug-x64-try,vm-precomp-ffi-qemu-linux-release-arm-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/221501 Reviewed-by: Martin Kustermann Reviewed-by: Ryan Macnak --- benchmarks/FfiMemory/dart/FfiMemory.dart | 62 ++++++ benchmarks/FfiMemory/dart2/FfiMemory.dart | 64 +++++- .../lib/src/messages/codes_generated.dart | 20 ++ pkg/analyzer/lib/error/error.dart | 1 + .../lib/src/dart/error/ffi_code.g.dart | 31 ++- .../lib/src/generated/ffi_verifier.dart | 37 +++- pkg/analyzer/messages.yaml | 16 +- .../subtype_of_struct_class_test.dart | 17 ++ pkg/front_end/lib/src/api_unstable/vm.dart | 2 + pkg/front_end/messages.status | 2 + pkg/front_end/messages.yaml | 10 + .../test/spell_checking_list_common.txt | 1 + .../test/spell_checking_list_messages.txt | 3 + ..._outline_change_50_ffi.yaml.world.1.expect | 8 + ..._outline_change_50_ffi.yaml.world.2.expect | 8 + ...ffi_struct_inline_array.dart.strong.expect | 2 +- ...nline_array.dart.strong.transformed.expect | 2 +- .../ffi_struct_inline_array.dart.weak.expect | 2 +- ...ruct_inline_array.dart.weak.modular.expect | 2 +- ..._inline_array.dart.weak.transformed.expect | 2 +- ...array_multi_dimensional.dart.strong.expect | 2 +- ...dimensional.dart.strong.transformed.expect | 2 +- ...e_array_multi_dimensional.dart.weak.expect | 2 +- ...multi_dimensional.dart.weak.modular.expect | 2 +- ...i_dimensional.dart.weak.transformed.expect | 2 +- pkg/vm/lib/transformations/ffi/common.dart | 149 ++++++++++++- .../lib/transformations/ffi/definitions.dart | 69 +++++- .../transformations/ffi/native_type_cfe.dart | 89 +++++++- pkg/vm/lib/transformations/ffi/use_sites.dart | 45 +++- .../transformations/ffi/abi_specific_int.dart | 106 +++++++++ .../ffi/abi_specific_int.dart.expect | 205 ++++++++++++++++++ .../ffi/abi_specific_int_incomplete.dart | 92 ++++++++ .../abi_specific_int_incomplete.dart.expect | 173 +++++++++++++++ .../enum_from_lib_used_as_type.dart.expect | 2 +- .../tree_shake_enum_from_lib.dart.expect | 2 +- runtime/bin/ffi_test/ffi_test_functions.cc | 34 +++ .../ffi_test/ffi_test_functions_generated.cc | 114 +++++++++- runtime/lib/ffi.cc | 10 + runtime/vm/app_snapshot.cc | 8 + runtime/vm/compiler/ffi/abi.cc | 24 +- runtime/vm/compiler/ffi/abi.h | 9 +- runtime/vm/compiler/ffi/marshaller.cc | 11 +- runtime/vm/compiler/ffi/native_type.cc | 56 ++++- runtime/vm/compiler/ffi/recognized_method.cc | 30 +++ runtime/vm/compiler/ffi/recognized_method.h | 3 + .../frontend/base_flow_graph_builder.cc | 36 ++- .../frontend/base_flow_graph_builder.h | 1 + .../frontend/kernel_binary_flowgraph.cc | 136 +++++++++++- .../frontend/kernel_binary_flowgraph.h | 9 + runtime/vm/compiler/frontend/kernel_to_il.cc | 17 +- runtime/vm/compiler/frontend/kernel_to_il.h | 1 - runtime/vm/compiler/method_recognizer.cc | 6 + runtime/vm/compiler/method_recognizer.h | 1 + runtime/vm/compiler/recognized_methods_list.h | 36 +-- runtime/vm/kernel_loader.cc | 19 ++ runtime/vm/object.cc | 11 +- runtime/vm/symbols.h | 4 + .../vm/lib/ffi_native_type_patch.dart | 1 + sdk/lib/_internal/vm/lib/ffi_patch.dart | 74 +++++++ sdk/lib/ffi/abi_specific.dart | 53 +++++ sdk/lib/ffi/ffi.dart | 24 ++ sdk/lib/ffi/ffi_sources.gni | 1 + .../abi_specific_int_incomplete_aot_test.dart | 23 ++ .../abi_specific_int_incomplete_jit_test.dart | 160 ++++++++++++++ tests/ffi/abi_specific_int_test.dart | 112 ++++++++++ tests/ffi/abi_specific_ints.dart | 137 ++++++++++++ tests/ffi/c_types_test.dart | 134 ++++++++++++ tests/ffi/ffi.status | 6 + ...backs_structs_by_value_generated_test.dart | 87 ++++++++ ..._structs_by_value_generated_compounds.dart | 10 + ..._structs_by_value_generated_leaf_test.dart | 48 ++++ ...ction_structs_by_value_generated_test.dart | 47 ++++ tests/ffi/generator/c_types.dart | 124 ++++++++--- .../structs_by_value_tests_configuration.dart | 16 ++ .../structs_by_value_tests_generator.dart | 21 +- tests/ffi/vmspecific_static_checks_test.dart | 32 +++ .../abi_specific_int_incomplete_aot_test.dart | 25 +++ .../abi_specific_int_incomplete_jit_test.dart | 162 ++++++++++++++ tests/ffi_2/abi_specific_int_test.dart | 114 ++++++++++ tests/ffi_2/abi_specific_ints.dart | 124 +++++++++++ tests/ffi_2/c_types_test.dart | 109 ++++++++++ tests/ffi_2/ffi_2.status | 6 + ...backs_structs_by_value_generated_test.dart | 91 ++++++++ ..._structs_by_value_generated_compounds.dart | 10 + ..._structs_by_value_generated_leaf_test.dart | 48 ++++ ...ction_structs_by_value_generated_test.dart | 47 ++++ .../ffi_2/vmspecific_static_checks_test.dart | 32 +++ 87 files changed, 3556 insertions(+), 132 deletions(-) create mode 100644 pkg/vm/testcases/transformations/ffi/abi_specific_int.dart create mode 100644 pkg/vm/testcases/transformations/ffi/abi_specific_int.dart.expect create mode 100644 pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart create mode 100644 pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart.expect create mode 100644 sdk/lib/ffi/abi_specific.dart create mode 100644 tests/ffi/abi_specific_int_incomplete_aot_test.dart create mode 100644 tests/ffi/abi_specific_int_incomplete_jit_test.dart create mode 100644 tests/ffi/abi_specific_int_test.dart create mode 100644 tests/ffi/abi_specific_ints.dart create mode 100644 tests/ffi/c_types_test.dart create mode 100644 tests/ffi_2/abi_specific_int_incomplete_aot_test.dart create mode 100644 tests/ffi_2/abi_specific_int_incomplete_jit_test.dart create mode 100644 tests/ffi_2/abi_specific_int_test.dart create mode 100644 tests/ffi_2/abi_specific_ints.dart create mode 100644 tests/ffi_2/c_types_test.dart 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(); +}