From 7d46d4b5cb72e4d957a614ff2b35551dc06b6560 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 13 Feb 2019 12:42:47 +0000 Subject: [PATCH] [vm / library] Foreign function interface prototype Prototype for `dart:ffi` on Linux/MacOS x64 in JIT mode. `dart:ffi` is experimental and its API is likely to change in the future. Progress and design decisions are tracked in https://github.com/dart-lang/sdk/projects/13 issue: https://github.com/dart-lang/sdk/issues/34452 Change-Id: Ifa4566388e42c8757f154741d11e303465ef305d Cq-Include-Trybots: luci.dart.try:vm-kernel-optcounter-threshold-linux-release-x64-try, vm-kernel-precomp-linux-debug-x64-try, vm-kernel-precomp-linux-release-simarm-try, vm-kernel-precomp-linux-release-simarm64-try, vm-kernel-precomp-linux-release-x64-try, vm-kernel-precomp-mac-release-simarm64-try, vm-kernel-precomp-win-release-x64-try, vm-kernel-mac-debug-x64-try, vm-kernel-asan-linux-release-x64 Reviewed-on: https://dart-review.googlesource.com/c/80124 Reviewed-by: Samir Jindel Auto-Submit: Daco Harkes --- .gitignore | 4 + BUILD.gn | 2 + .../tool/input_sdk/libraries.dart | 5 + pkg/front_end/lib/src/api_unstable/vm.dart | 7 +- .../lib/src/fasta/fasta_codes_generated.dart | 117 ++- .../lib/src/fasta/kernel/kernel_target.dart | 5 +- pkg/front_end/messages.status | 7 +- pkg/front_end/messages.yaml | 25 +- pkg/kernel/lib/ast.dart | 4 + pkg/kernel/lib/core_types.dart | 96 +++ pkg/kernel/lib/transformations/ffi.dart | 197 +++++ .../lib/transformations/ffi_definitions.dart | 372 ++++++++++ .../lib/transformations/ffi_use_sites.dart | 270 +++++++ pkg/vm/lib/target/vm.dart | 14 + runtime/bin/BUILD.gn | 44 ++ runtime/bin/ffi_test_dynamic_library.cc | 13 + runtime/bin/ffi_test_functions.cc | 368 ++++++++++ runtime/lib/ffi.cc | 680 ++++++++++++++++++ runtime/lib/ffi_dynamic_library.cc | 114 +++ runtime/lib/ffi_dynamic_library_patch.dart | 35 + runtime/lib/ffi_native_type_patch.dart | 69 ++ runtime/lib/ffi_patch.dart | 55 ++ runtime/lib/ffi_sources.gni | 17 + runtime/vm/BUILD.gn | 19 +- runtime/vm/bootstrap_natives.cc | 5 + runtime/vm/bootstrap_natives.h | 17 +- runtime/vm/class_finalizer.cc | 10 + runtime/vm/class_id.h | 28 + runtime/vm/compiler/assembler/assembler_x64.h | 4 + .../frontend/kernel_binary_flowgraph.cc | 1 + runtime/vm/compiler/frontend/scope_builder.cc | 1 + runtime/vm/compiler/jit/compiler.cc | 3 + runtime/vm/constants_x64.cc | 27 + runtime/vm/constants_x64.h | 37 + runtime/vm/dart_api_impl.cc | 4 + runtime/vm/dart_api_impl.h | 19 + runtime/vm/ffi_trampoline_stubs_x64.cc | 564 +++++++++++++++ runtime/vm/flag_list.h | 1 + runtime/vm/kernel_loader.cc | 4 + runtime/vm/native_arguments.h | 2 +- runtime/vm/native_entry.cc | 8 + runtime/vm/native_entry.h | 15 +- runtime/vm/object.cc | 208 +++++- runtime/vm/object.h | 123 ++++ runtime/vm/object_service.cc | 18 + runtime/vm/object_store.h | 6 +- runtime/vm/raw_object.cc | 16 + runtime/vm/raw_object.h | 94 +++ runtime/vm/raw_object_fields.cc | 6 +- runtime/vm/raw_object_snapshot.cc | 48 ++ runtime/vm/runtime_entry.cc | 5 + runtime/vm/runtime_entry_list.h | 1 + runtime/vm/service.cc | 7 + runtime/vm/snapshot.cc | 8 + runtime/vm/snapshot.h | 1 + runtime/vm/symbols.h | 22 +- runtime/vm/vm_sources.gni | 2 + samples/ffi/coordinate.dart | 40 ++ samples/ffi/sample_ffi_data.dart | 274 +++++++ samples/ffi/sample_ffi_dynamic_library.dart | 21 + samples/ffi/sample_ffi_functions.dart | 267 +++++++ .../ffi/sample_ffi_functions_callbacks.dart | 80 +++ samples/ffi/sample_ffi_functions_structs.dart | 64 ++ samples/ffi/sample_ffi_structs.dart | 63 ++ sdk/BUILD.gn | 1 + .../sdk_library_metadata/lib/libraries.dart | 5 + sdk/lib/ffi/annotations.dart | 50 ++ sdk/lib/ffi/dynamic_library.dart | 32 + sdk/lib/ffi/ffi.dart | 102 +++ sdk/lib/ffi/ffi_sources.gni | 12 + sdk/lib/ffi/native_type.dart | 133 ++++ sdk/lib/libraries.json | 8 + sdk/lib/libraries.yaml | 7 + tests/standalone_2/ffi/coordinate.dart | 40 ++ tests/standalone_2/ffi/coordinate_bare.dart | 20 + tests/standalone_2/ffi/coordinate_manual.dart | 44 ++ tests/standalone_2/ffi/cstring.dart | 32 + .../standalone_2/ffi/data_not_asan_test.dart | 29 + tests/standalone_2/ffi/data_test.dart | 492 +++++++++++++ .../ffi/dynamic_library_test.dart | 63 ++ tests/standalone_2/ffi/enable_ffi_test.dart | 20 + .../ffi/function_callbacks_test.dart | 90 +++ .../ffi/function_structs_test.dart | 116 +++ tests/standalone_2/ffi/function_test.dart | 284 ++++++++ .../standalone_2/ffi/static_checks_test.dart | 365 ++++++++++ tests/standalone_2/ffi/structs_test.dart | 136 ++++ tests/standalone_2/ffi/subtype_test.dart | 19 + tests/standalone_2/ffi/very_large_struct.dart | 67 ++ tests/standalone_2/standalone_2_vm.status | 14 + 89 files changed, 6792 insertions(+), 52 deletions(-) create mode 100644 pkg/kernel/lib/transformations/ffi.dart create mode 100644 pkg/kernel/lib/transformations/ffi_definitions.dart create mode 100644 pkg/kernel/lib/transformations/ffi_use_sites.dart create mode 100644 runtime/bin/ffi_test_dynamic_library.cc create mode 100644 runtime/bin/ffi_test_functions.cc create mode 100644 runtime/lib/ffi.cc create mode 100644 runtime/lib/ffi_dynamic_library.cc create mode 100644 runtime/lib/ffi_dynamic_library_patch.dart create mode 100644 runtime/lib/ffi_native_type_patch.dart create mode 100644 runtime/lib/ffi_patch.dart create mode 100644 runtime/lib/ffi_sources.gni create mode 100644 runtime/vm/constants_x64.cc create mode 100644 runtime/vm/ffi_trampoline_stubs_x64.cc create mode 100644 samples/ffi/coordinate.dart create mode 100644 samples/ffi/sample_ffi_data.dart create mode 100644 samples/ffi/sample_ffi_dynamic_library.dart create mode 100644 samples/ffi/sample_ffi_functions.dart create mode 100644 samples/ffi/sample_ffi_functions_callbacks.dart create mode 100644 samples/ffi/sample_ffi_functions_structs.dart create mode 100644 samples/ffi/sample_ffi_structs.dart create mode 100644 sdk/lib/ffi/annotations.dart create mode 100644 sdk/lib/ffi/dynamic_library.dart create mode 100644 sdk/lib/ffi/ffi.dart create mode 100644 sdk/lib/ffi/ffi_sources.gni create mode 100644 sdk/lib/ffi/native_type.dart create mode 100644 tests/standalone_2/ffi/coordinate.dart create mode 100644 tests/standalone_2/ffi/coordinate_bare.dart create mode 100644 tests/standalone_2/ffi/coordinate_manual.dart create mode 100644 tests/standalone_2/ffi/cstring.dart create mode 100644 tests/standalone_2/ffi/data_not_asan_test.dart create mode 100644 tests/standalone_2/ffi/data_test.dart create mode 100644 tests/standalone_2/ffi/dynamic_library_test.dart create mode 100644 tests/standalone_2/ffi/enable_ffi_test.dart create mode 100644 tests/standalone_2/ffi/function_callbacks_test.dart create mode 100644 tests/standalone_2/ffi/function_structs_test.dart create mode 100644 tests/standalone_2/ffi/function_test.dart create mode 100644 tests/standalone_2/ffi/static_checks_test.dart create mode 100644 tests/standalone_2/ffi/structs_test.dart create mode 100644 tests/standalone_2/ffi/subtype_test.dart create mode 100644 tests/standalone_2/ffi/very_large_struct.dart diff --git a/.gitignore b/.gitignore index 3e23d6d9db6..2e3efaa3ae6 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ .idea CMakeLists.txt .clang_complete +cmake-build-debug # VSCode project files .vscode @@ -49,6 +50,9 @@ compile_commands.json # GDB files .gdb_history +# https://github.com/Dart-Code/Dart-Code/issues/1295 +analysis_options.yaml + # Built by chromebot and downloaded from Google Storage client/tests/drt diff --git a/BUILD.gn b/BUILD.gn index ae4c230871b..6d899eecadf 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -47,6 +47,8 @@ group("runtime") { "runtime/bin:sample_extension", "runtime/bin:test_extension", "runtime/bin:entrypoints_verification_test_extension", + "runtime/bin:ffi_test_dynamic_library", + "runtime/bin:ffi_test_functions", "runtime/vm:kernel_platform_files($host_toolchain)", "utils/kernel-service:kernel-service", ] diff --git a/pkg/dev_compiler/tool/input_sdk/libraries.dart b/pkg/dev_compiler/tool/input_sdk/libraries.dart index 6009d0c3e10..1a08e4269df 100644 --- a/pkg/dev_compiler/tool/input_sdk/libraries.dart +++ b/pkg/dev_compiler/tool/input_sdk/libraries.dart @@ -61,6 +61,11 @@ const Map libraries = const { categories: "Client,Server,Embedded", maturity: Maturity.UNSTABLE, dart2jsPatchPath: "_internal/js_runtime/lib/developer_patch.dart"), + "ffi": const LibraryInfo("ffi/ffi.dart", + categories: "Server", + // TODO(dacoharkes): Update maturity when we release dart:ffi. + // https://github.com/dart-lang/sdk/issues/34452 + maturity: Maturity.EXPERIMENTAL), "html": const LibraryInfo("html/dart2js/html_dart2js.dart", categories: "Client", maturity: Maturity.WEB_STABLE, diff --git a/pkg/front_end/lib/src/api_unstable/vm.dart b/pkg/front_end/lib/src/api_unstable/vm.dart index 5d7cd8c0f5a..e7c5bcd8c45 100644 --- a/pkg/front_end/lib/src/api_unstable/vm.dart +++ b/pkg/front_end/lib/src/api_unstable/vm.dart @@ -53,10 +53,13 @@ export '../fasta/fasta_codes.dart' templateConstEvalNonConstantLiteral, templateConstEvalNonConstantVariableGet, templateConstEvalZeroDivisor, - templateFfiAnnotationMissing, + templateFfiFieldAnnotation, + templateFfiStructAnnotation, + templateFfiNotStatic, templateFfiTypeInvalid, templateFfiTypeMismatch, - templateFfiTypeOpaque; + templateFfiTypeUnsized, + templateFfiFieldInitializer; export '../fasta/hybrid_file_system.dart' show HybridFileSystem; diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart index d62184ff55c..e57910c6d5b 100644 --- a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart +++ b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart @@ -3348,28 +3348,101 @@ const MessageCode messageFastaUsageShort = // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const Template< - Message Function( - String - name)> templateFfiAnnotationMissing = const Template< + Message Function(String name)> templateFfiFieldAnnotation = const Template< Message Function(String name)>( messageTemplate: - r"""Field '#name' is missing an annotation to declare its C++ type,dart:ffi structs (Pointer) cannot have regular Dart fields.""", - withArguments: _withArgumentsFfiAnnotationMissing); + r"""Field '#name' requires exactly one annotation to declare its C++ type, which cannot be Void. dart:ffi structs (Pointer) cannot have regular Dart fields.""", + withArguments: _withArgumentsFfiFieldAnnotation); // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. -const Code codeFfiAnnotationMissing = +const Code codeFfiFieldAnnotation = const Code( - "FfiAnnotationMissing", - templateFfiAnnotationMissing, + "FfiFieldAnnotation", + templateFfiFieldAnnotation, ); // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. -Message _withArgumentsFfiAnnotationMissing(String name) { +Message _withArgumentsFfiFieldAnnotation(String name) { if (name.isEmpty) throw 'No name provided'; name = demangleMixinApplicationName(name); - return new Message(codeFfiAnnotationMissing, + return new Message(codeFfiFieldAnnotation, message: - """Field '${name}' is missing an annotation to declare its C++ type,dart:ffi structs (Pointer) cannot have regular Dart fields.""", + """Field '${name}' requires exactly one annotation to declare its C++ type, which cannot be Void. dart:ffi structs (Pointer) cannot have regular Dart fields.""", + arguments: {'name': name}); +} + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Template< + Message Function(String name)> templateFfiFieldInitializer = const Template< + Message Function(String name)>( + messageTemplate: + r"""Field '#name' is a dart:ffi Pointer to a struct field and therefore cannot be initialized before constructor execution.""", + withArguments: _withArgumentsFfiFieldInitializer); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeFfiFieldInitializer = + const Code( + "FfiFieldInitializer", + templateFfiFieldInitializer, +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +Message _withArgumentsFfiFieldInitializer(String name) { + if (name.isEmpty) throw 'No name provided'; + name = demangleMixinApplicationName(name); + return new Message(codeFfiFieldInitializer, + message: + """Field '${name}' is a dart:ffi Pointer to a struct field and therefore cannot be initialized before constructor execution.""", + arguments: {'name': name}); +} + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Template< + Message Function(String name)> templateFfiNotStatic = const Template< + Message Function(String name)>( + messageTemplate: + r"""#name expects a static function as parameter. dart:ffi only supports calling static Dart functions from c.""", + withArguments: _withArgumentsFfiNotStatic); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeFfiNotStatic = + const Code( + "FfiNotStatic", + templateFfiNotStatic, +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +Message _withArgumentsFfiNotStatic(String name) { + if (name.isEmpty) throw 'No name provided'; + name = demangleMixinApplicationName(name); + return new Message(codeFfiNotStatic, + message: + """${name} expects a static function as parameter. dart:ffi only supports calling static Dart functions from c.""", + arguments: {'name': name}); +} + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Template< + Message Function(String name)> templateFfiStructAnnotation = const Template< + Message Function(String name)>( + messageTemplate: + r"""Class '#name' is a dart:ffi Pointer but has no struct annotation. Only struct Pointers can have fields.""", + withArguments: _withArgumentsFfiStructAnnotation); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeFfiStructAnnotation = + const Code( + "FfiStructAnnotation", + templateFfiStructAnnotation, +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +Message _withArgumentsFfiStructAnnotation(String name) { + if (name.isEmpty) throw 'No name provided'; + name = demangleMixinApplicationName(name); + return new Message(codeFfiStructAnnotation, + message: + """Class '${name}' is a dart:ffi Pointer but has no struct annotation. Only struct Pointers can have fields.""", arguments: {'name': name}); } @@ -3378,7 +3451,7 @@ const Template< Message Function(DartType _type)> templateFfiTypeInvalid = const Template< Message Function(DartType _type)>( messageTemplate: - r"""Expected type '#type' to be a valid and instantiated subtype of '_NativeType'.""", + r"""Expected type '#type' to be a valid and instantiated subtype of 'NativeType'.""", withArguments: _withArgumentsFfiTypeInvalid); // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. @@ -3395,7 +3468,7 @@ Message _withArgumentsFfiTypeInvalid(DartType _type) { String type = typeParts.join(); return new Message(codeFfiTypeInvalid, message: - """Expected type '${type}' to be a valid and instantiated subtype of '_NativeType'.""" + + """Expected type '${type}' to be a valid and instantiated subtype of 'NativeType'.""" + labeler.originMessages, arguments: {'type': _type}); } @@ -3442,29 +3515,29 @@ const Template< Message Function( String name, DartType - _type)> templateFfiTypeOpaque = const Template< + _type)> templateFfiTypeUnsized = const Template< Message Function(String name, DartType _type)>( messageTemplate: - r"""Method '#name' cannot be called on something of type '#type' as this type is opaque.""", - withArguments: _withArgumentsFfiTypeOpaque); + r"""Method '#name' cannot be called on something of type '#type' as this type is unsized.""", + withArguments: _withArgumentsFfiTypeUnsized); // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. -const Code codeFfiTypeOpaque = +const Code codeFfiTypeUnsized = const Code( - "FfiTypeOpaque", - templateFfiTypeOpaque, + "FfiTypeUnsized", + templateFfiTypeUnsized, ); // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. -Message _withArgumentsFfiTypeOpaque(String name, DartType _type) { +Message _withArgumentsFfiTypeUnsized(String name, DartType _type) { if (name.isEmpty) throw 'No name provided'; name = demangleMixinApplicationName(name); TypeLabeler labeler = new TypeLabeler(); List typeParts = labeler.labelType(_type); String type = typeParts.join(); - return new Message(codeFfiTypeOpaque, + return new Message(codeFfiTypeUnsized, message: - """Method '${name}' cannot be called on something of type '${type}' as this type is opaque.""" + + """Method '${name}' cannot be called on something of type '${type}' as this type is unsized.""" + labeler.originMessages, arguments: {'name': name, 'type': _type}); } diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart index bbb6e4f566e..6e0d8ae7ac8 100644 --- a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart +++ b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart @@ -554,6 +554,7 @@ class KernelTarget extends TargetImplementation { "dart:_internal", "dart:async", "dart:core", + "dart:ffi", "dart:mirrors" ]) { Uri uri = Uri.parse(platformLibrary); @@ -569,8 +570,8 @@ class KernelTarget extends TargetImplementation { break; } } - if (!found && uri.path != "mirrors") { - // dart:mirrors is optional. + if (!found && uri.path != "mirrors" && uri.path != "ffi") { + // dart:mirrors and dart:ffi are optional. throw "Can't find $uri"; } } else { diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status index 05dc6220479..9303b17bb7c 100644 --- a/pkg/front_end/messages.status +++ b/pkg/front_end/messages.status @@ -173,10 +173,13 @@ FastaUsageLong/analyzerCode: Fail FastaUsageLong/example: Fail FastaUsageShort/analyzerCode: Fail FastaUsageShort/example: Fail -FfiAnnotationMissing/analyzerCode : Fail +FfiFieldAnnotation/analyzerCode: Fail +FfiFieldInitializer/analyzerCode: Fail +FfiNotStatic/analyzerCode: Fail +FfiStructAnnotation/analyzerCode: Fail FfiTypeInvalid/analyzerCode: Fail FfiTypeMismatch/analyzerCode: Fail -FfiTypeOpaque/analyzerCode: Fail +FfiTypeUnsized/analyzerCode: Fail FieldInitializedOutsideDeclaringClass/script1: Fail FieldInitializerOutsideConstructor/script1: Fail FinalAndCovariant/script2: Fail diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index 9dc289adff5..023e1d2fe7f 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -3434,15 +3434,30 @@ FfiTypeMismatch: FfiTypeInvalid: # Used by dart:ffi - template: "Expected type '#type' to be a valid and instantiated subtype of '_NativeType'." + template: "Expected type '#type' to be a valid and instantiated subtype of 'NativeType'." external: test/ffi_test.dart -FfiTypeOpaque: +FfiTypeUnsized: # Used by dart:ffi - template: "Method '#name' cannot be called on something of type '#type' as this type is opaque." + template: "Method '#name' cannot be called on something of type '#type' as this type is unsized." external: test/ffi_test.dart -FfiAnnotationMissing: +FfiFieldAnnotation: # Used by dart:ffi - template: "Field '#name' is missing an annotation to declare its C++ type,dart:ffi structs (Pointer) cannot have regular Dart fields." + template: "Field '#name' requires exactly one annotation to declare its C++ type, which cannot be Void. dart:ffi structs (Pointer) cannot have regular Dart fields." + external: test/ffi_test.dart + +FfiNotStatic: + # Used by dart:ffi + template: "#name expects a static function as parameter. dart:ffi only supports calling static Dart functions from c." + external: test/ffi_test.dart + +FfiFieldInitializer: + # Used by dart:ffi + template: "Field '#name' is a dart:ffi Pointer to a struct field and therefore cannot be initialized before constructor execution." + external: test/ffi_test.dart + +FfiStructAnnotation: + # Used by dart:ffi + template: "Class '#name' is a dart:ffi Pointer but has no struct annotation. Only struct Pointers can have fields." external: test/ffi_test.dart diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart index 2da3cd52561..d738ec503c6 100644 --- a/pkg/kernel/lib/ast.dart +++ b/pkg/kernel/lib/ast.dart @@ -155,6 +155,8 @@ abstract class TreeNode extends Node { Component get enclosingComponent => parent?.enclosingComponent; + Library get enclosingLibrary => parent?.enclosingLibrary; + /// Returns the best known source location of the given AST node, or `null` if /// the node is orphaned. /// @@ -466,6 +468,8 @@ class Library extends NamedNode implements Comparable, FileUriNode { Location _getLocationInEnclosingFile(int offset) { return _getLocationInComponent(enclosingComponent, fileUri, offset); } + + Library get enclosingLibraray => this; } /// An import or export declaration in a library. diff --git a/pkg/kernel/lib/core_types.dart b/pkg/kernel/lib/core_types.dart index 504eea5f032..ddc4aac49ea 100644 --- a/pkg/kernel/lib/core_types.dart +++ b/pkg/kernel/lib/core_types.dart @@ -33,6 +33,11 @@ class CoreTypes { 'dart:async': [ 'Future', 'Stream', + ], + 'dart:ffi': [ + 'DynamicLibrary', + 'Pointer', + 'Struct', ] }; @@ -93,10 +98,97 @@ class CoreTypes { Class _pragmaClass; Field _pragmaName; Field _pragmaOptions; + Constructor _pragmaConstructor; + + Library _ffiLibrary; + Class _ffiPointerClass; + Procedure _ffiPointerLoadProcedure; + Procedure _ffiPointerStoreProcedure; + Procedure _ffiPointerCastProcedure; + Procedure _ffiPointerOffsetByProcedure; + Procedure _ffiPointerAsFunctionProcedure; + Field _ffiStructField; + Class _ffiDynamicLibraryClass; + Procedure _ffiDynamicLibraryLookupFunctionProcedure; + Procedure _ffiAllocateProcedure; + Procedure _ffiSizeOfProcedure; + Procedure _ffiFromFunctionProcedure; + Class _ffiNativeFunctionClass; CoreTypes(Component component) : index = new LibraryIndex.coreLibraries(component); + Library get ffiLibrary { + return _ffiLibrary ??= index.getLibrary('dart:ffi'); + } + + Class get ffiPointerClass { + return _ffiPointerClass ??= index.getClass('dart:ffi', 'Pointer'); + } + + Procedure get ffiPointerLoadProcedure { + return _ffiPointerLoadProcedure ??= + index.getMember('dart:ffi', 'Pointer', 'load'); + } + + Procedure get ffiPointerStoreProcedure { + return _ffiPointerStoreProcedure ??= + index.getMember('dart:ffi', 'Pointer', 'store'); + } + + Procedure get ffiPointerCastProcedure { + return _ffiPointerCastProcedure ??= + index.getMember('dart:ffi', 'Pointer', 'cast'); + } + + Procedure get ffiPointerOffsetByProcedure { + return _ffiPointerOffsetByProcedure ??= + index.getMember('dart:ffi', 'Pointer', 'offsetBy'); + } + + Procedure get ffiPointerAsFunctionProcedure { + return _ffiPointerAsFunctionProcedure ??= + index.getMember('dart:ffi', 'Pointer', 'asFunction'); + } + + Field get ffiStructField { + return _ffiStructField ??= index.getTopLevelMember('dart:ffi', 'struct'); + } + + Class get ffiDynamicLibraryClass { + return _ffiDynamicLibraryClass ??= + index.getClass('dart:ffi', 'DynamicLibrary'); + } + + Procedure get ffiDynamicLibraryLookupFunctionProcedure { + return _ffiDynamicLibraryLookupFunctionProcedure ??= + index.getMember('dart:ffi', 'DynamicLibrary', 'lookupFunction'); + } + + Procedure get ffiAllocateProcedure { + return _ffiAllocateProcedure ??= + index.getTopLevelMember('dart:ffi', 'allocate'); + } + + Procedure get ffiSizeOfProcedure { + return _ffiSizeOfProcedure ??= + index.getTopLevelMember('dart:ffi', 'sizeOf'); + } + + Procedure get ffiFromFunctionProcedure { + return _ffiFromFunctionProcedure ??= + index.getTopLevelMember('dart:ffi', 'fromFunction'); + } + + Class get ffiNativeFunctionClass { + return _ffiNativeFunctionClass ??= + index.getClass('dart:ffi', 'NativeFunction'); + } + + Class ffiNativeTypeClass(String name) { + return index.getClass('dart:ffi', name); + } + Procedure get asyncErrorWrapperHelperProcedure { return _asyncErrorWrapperHelperProcedure ??= index.getTopLevelMember('dart:async', '_asyncErrorWrapperHelper'); @@ -312,6 +404,10 @@ class CoreTypes { return _pragmaOptions ??= index.getMember('dart:core', 'pragma', 'options'); } + Constructor get pragmaConstructor { + return _pragmaConstructor ??= index.getMember('dart:core', 'pragma', '_'); + } + Class get stackTraceClass { return _stackTraceClass ??= index.getClass('dart:core', 'StackTrace'); } diff --git a/pkg/kernel/lib/transformations/ffi.dart b/pkg/kernel/lib/transformations/ffi.dart new file mode 100644 index 00000000000..b592f2f67e6 --- /dev/null +++ b/pkg/kernel/lib/transformations/ffi.dart @@ -0,0 +1,197 @@ +// 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. + +// This file contains logic which is shared between the ffi_definition and +// ffi_use_site transformers. + +library kernel.transformations.ffi; + +import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; +import 'package:kernel/type_environment.dart' show TypeEnvironment; + +import '../ast.dart'; +import '../core_types.dart'; +import '../target/targets.dart' show DiagnosticReporter; + +/// Represents the (instantiated) ffi.NativeType. +enum NativeType { + kPointer, + kNativeFunction, + kInt8, + kInt16, + kInt32, + kInt64, + kUint8, + kUint16, + kUint32, + kUnit64, + kIntptr, + kFloat, + kDouble, + kVoid +} + +const NativeType kNativeTypeIntStart = NativeType.kInt8; +const NativeType kNativeTypeIntEnd = NativeType.kIntptr; + +/// The [NativeType] class names, indexed by [NativeType]. +const List nativeTypeClassNames = [ + 'Pointer', + 'NativeFunction', + 'Int8', + 'Int16', + 'Int32', + 'Int64', + 'Uint8', + 'Uint16', + 'Uint32', + 'Uint64', + 'IntPtr', + 'Float', + 'Double', + 'Void' +]; + +const int UNKNOWN = 0; +const int WORD_SIZE = -1; + +/// The [NativeType] sizes in bytes, indexed by [NativeType]. +const List nativeTypeSizes = [ + WORD_SIZE, // Pointer + UNKNOWN, // NativeFunction + 1, // Int8 + 2, // Int16 + 4, // Int32 + 8, // Int64 + 1, // Uint8 + 2, // Uint16 + 4, // Uint32 + 8, // Uint64 + WORD_SIZE, // IntPtr + 4, // Float + 8, // Double + UNKNOWN, // Void +]; + +/// [FfiTransformer] contains logic which is shared between +/// _FfiUseSiteTransformer and _FfiDefinitionTransformer. +class FfiTransformer extends Transformer { + final TypeEnvironment env; + final ClassHierarchy hierarchy; + final DiagnosticReporter diagnosticReporter; + + final Class intClass; + final Class doubleClass; + final Constructor pragmaConstructor; + + final Library ffiLibrary; + final Class nativeFunctionClass; + final Class pointerClass; + final Procedure castMethod; + final Procedure loadMethod; + final Procedure storeMethod; + final Procedure offsetByMethod; + final Procedure asFunctionMethod; + final Procedure lookupFunctionMethod; + final Procedure fromFunctionMethod; + final Field structField; + + /// Classes corresponding to [NativeType], indexed by [NativeType]. + final List nativeTypesClasses; + + FfiTransformer(this.hierarchy, CoreTypes coreTypes, this.diagnosticReporter) + : env = new TypeEnvironment(coreTypes, hierarchy), + intClass = coreTypes.intClass, + doubleClass = coreTypes.doubleClass, + ffiLibrary = coreTypes.ffiLibrary, + nativeFunctionClass = coreTypes.ffiNativeFunctionClass, + pointerClass = coreTypes.ffiPointerClass, + castMethod = coreTypes.ffiPointerCastProcedure, + loadMethod = coreTypes.ffiPointerLoadProcedure, + storeMethod = coreTypes.ffiPointerStoreProcedure, + offsetByMethod = coreTypes.ffiPointerOffsetByProcedure, + asFunctionMethod = coreTypes.ffiPointerAsFunctionProcedure, + lookupFunctionMethod = + coreTypes.ffiDynamicLibraryLookupFunctionProcedure, + fromFunctionMethod = coreTypes.ffiFromFunctionProcedure, + structField = coreTypes.ffiStructField, + pragmaConstructor = coreTypes.pragmaConstructor, + nativeTypesClasses = + nativeTypeClassNames.map(coreTypes.ffiNativeTypeClass).toList() {} + + /// Computes the Dart type corresponding to a ffi.[NativeType], returns null + /// if it is not a valid NativeType. + /// + /// [Int8] -> [int] + /// [Int16] -> [int] + /// [Int32] -> [int] + /// [Int64] -> [int] + /// [Uint8] -> [int] + /// [Uint16] -> [int] + /// [Uint32] -> [int] + /// [Uint64] -> [int] + /// [IntPtr] -> [int] + /// [Double] -> [double] + /// [Float] -> [double] + /// [Pointer] -> [Pointer] + /// T extends [Pointer] -> T + /// [NativeFunction] S1 Function(S2, S3) + /// where DartRepresentationOf(Tn) -> Sn + DartType convertNativeTypeToDartType(DartType nativeType) { + if (nativeType is! InterfaceType) { + return null; + } + Class nativeClass = (nativeType as InterfaceType).classNode; + if (env.isSubtypeOf( + InterfaceType(nativeClass), InterfaceType(pointerClass))) { + return nativeType; + } + NativeType nativeType_ = getType(nativeClass); + if (nativeType_ == null) { + return null; + } + if (kNativeTypeIntStart.index <= nativeType_.index && + nativeType_.index <= kNativeTypeIntEnd.index) { + return InterfaceType(intClass); + } + if (nativeType_ == NativeType.kFloat || nativeType_ == NativeType.kDouble) { + return InterfaceType(doubleClass); + } + if (nativeType_ == NativeType.kNativeFunction) { + DartType fun = (nativeType as InterfaceType).typeArguments[0]; + if (fun is FunctionType) { + if (fun.namedParameters.isNotEmpty) return null; + if (fun.positionalParameters.length != fun.requiredParameterCount) + return null; + if (fun.typeParameters.length != 0) return null; + DartType returnType = convertNativeTypeToDartType(fun.returnType); + if (returnType == null) return null; + List argumentTypes = fun.positionalParameters + .map(this.convertNativeTypeToDartType) + .toList(); + if (argumentTypes.contains(null)) return null; + return FunctionType(argumentTypes, returnType); + } + } + return null; + } + + NativeType getType(Class c) { + int index = nativeTypesClasses.indexOf(c); + if (index == -1) { + return null; + } + return NativeType.values[index]; + } +} + +/// Contains replaced members, of which all the call sites need to be replaced. +/// +/// [ReplacedMembers] is populated by _FfiDefinitionTransformer and consumed by +/// _FfiUseSiteTransformer. +class ReplacedMembers { + final Map replacedGetters; + final Map replacedSetters; + ReplacedMembers(this.replacedGetters, this.replacedSetters); +} diff --git a/pkg/kernel/lib/transformations/ffi_definitions.dart b/pkg/kernel/lib/transformations/ffi_definitions.dart new file mode 100644 index 00000000000..e79a5f11e61 --- /dev/null +++ b/pkg/kernel/lib/transformations/ffi_definitions.dart @@ -0,0 +1,372 @@ +// 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. + +library kernel.transformations.ffi_definitions; + +import 'dart:math' as math; + +import 'package:front_end/src/api_unstable/vm.dart' + show + templateFfiFieldAnnotation, + templateFfiStructAnnotation, + templateFfiTypeMismatch, + templateFfiFieldInitializer; + +import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; + +import '../ast.dart'; +import '../core_types.dart'; +import '../target/targets.dart' show DiagnosticReporter; + +import 'ffi.dart' + show + ReplacedMembers, + NativeType, + FfiTransformer, + nativeTypeSizes, + WORD_SIZE; + +/// Checks and expands the dart:ffi @struct and field annotations. +/// +/// Sample input: +/// @ffi.struct +/// class Coord extends ffi.Pointer { +/// @ffi.Double() +/// double x; +/// +/// @ffi.Double() +/// double y; +/// +/// @ffi.Pointer() +/// Coord next; +/// +/// external static int sizeOf(); +/// } +/// +/// Sample output: +/// class Coordinate extends ffi.Pointer { +/// ffi.Pointer get _xPtr => cast(); +/// set x(double v) => _xPtr.store(v); +/// double get x => _xPtr.load(); +/// +/// ffi.Pointer get _yPtr => +/// offsetBy(ffi.sizeOf() * 1).cast(); +/// set y(double v) => _yPtr.store(v); +/// double get y => _yPtr.load(); +/// +/// ffi.Pointer get _nextPtr => +/// offsetBy(ffi.sizeOf() * 2).cast(); +/// set next(Coordinate v) => _nextPtr.store(v); +/// Coordinate get next => _nextPtr.load(); +/// +/// static int sizeOf() => 24; +/// } +ReplacedMembers transformLibraries( + CoreTypes coreTypes, + ClassHierarchy hierarchy, + List libraries, + DiagnosticReporter diagnosticReporter) { + final transformer = + new _FfiDefinitionTransformer(hierarchy, coreTypes, diagnosticReporter); + libraries.forEach(transformer.visitLibrary); + return ReplacedMembers( + transformer.replacedGetters, transformer.replacedSetters); +} + +/// Checks and expands the dart:ffi @struct and field annotations. +class _FfiDefinitionTransformer extends FfiTransformer { + Map replacedGetters = {}; + Map replacedSetters = {}; + + _FfiDefinitionTransformer(ClassHierarchy hierarchy, CoreTypes coreTypes, + DiagnosticReporter diagnosticReporter) + : super(hierarchy, coreTypes, diagnosticReporter) {} + + @override + visitClass(Class node) { + if (node == pointerClass || !hierarchy.isSubtypeOf(node, pointerClass)) { + return node; + } + + // Because subtypes of Pointer are only allocated by allocate>() + // and fromAddress>() which are not recognized as constructor + // calls, we need to prevent these classes from being tree shaken out. + _preventTreeShaking(node); + + _checkFieldAnnotations(node); + _checkConstructors(node); + + bool isStruct = _checkStructAnnotation(node); + if (isStruct) { + int size = _replaceFields(node); + _replaceSizeOfMethod(node, size); + } + + return node; + } + + bool _checkStructAnnotation(Class node) { + bool isStruct = _hasAnnotation(node); + if (!isStruct && node.fields.isNotEmpty) { + diagnosticReporter.report( + templateFfiStructAnnotation.withArguments(node.name), + node.fileOffset, + 1, + node.fileUri); + } + return isStruct; + } + + void _checkFieldAnnotations(Class node) { + for (Field f in node.fields) { + if (f.initializer is! NullLiteral) { + diagnosticReporter.report( + templateFfiFieldInitializer.withArguments(f.name.name), + f.fileOffset, + f.name.name.length, + f.fileUri); + } + List annos = _getAnnotations(f).toList(); + if (annos.length != 1) { + diagnosticReporter.report( + templateFfiFieldAnnotation.withArguments(f.name.name), + f.fileOffset, + f.name.name.length, + f.fileUri); + } else { + DartType dartType = f.type; + DartType nativeType = + InterfaceType(nativeTypesClasses[annos.first.index]); + DartType shouldBeDartType = convertNativeTypeToDartType(nativeType); + if (!env.isSubtypeOf(dartType, shouldBeDartType)) { + diagnosticReporter.report( + templateFfiTypeMismatch.withArguments( + dartType, shouldBeDartType, nativeType), + f.fileOffset, + 1, + f.location.file); + } + } + } + } + + void _checkConstructors(Class node) { + List toRemove = []; + for (Constructor c in node.constructors) { + for (Initializer i in c.initializers) { + if (i is FieldInitializer) { + toRemove.add(i); + diagnosticReporter.report( + templateFfiFieldInitializer.withArguments(i.field.name.name), + i.fileOffset, + 1, + i.location.file); + } + } + } + // Remove initializers referring to fields to prevent cascading errors. + for (Initializer i in toRemove) { + i.remove(); + } + } + + /// Computes the field offsets in the struct and replaces the fields with + /// getters and setters using these offsets. + /// + /// Returns the total size of the struct. + int _replaceFields(Class node) { + List fields = []; + List types = []; + + for (Field f in node.fields) { + List annos = _getAnnotations(f).toList(); + if (annos.length == 1) { + NativeType t = annos.first; + fields.add(f); + types.add(t); + } + } + + List offsets = _calculateOffsets(types); + int size = _calculateSize(offsets, types); + + for (int i = 0; i < fields.length; i++) { + List methods = + _generateMethodsForField(fields[i], types[i], offsets[i]); + for (Procedure p in methods) { + node.addMember(p); + } + } + + for (Field f in fields) { + f.remove(); + } + + return size; + } + + /// Sample output: + /// ffi.Pointer get _xPtr => cast(); + /// double get x => _xPtr.load(); + /// set x(double v) => _xPtr.store(v); + List _generateMethodsForField( + Field field, NativeType type, int offset) { + DartType nativeType = type == NativeType.kPointer + ? field.type + : InterfaceType(nativeTypesClasses[type.index]); + DartType pointerType = InterfaceType(pointerClass, [nativeType]); + Name pointerName = Name('#_ptr_${field.name.name}'); + + // Sample output for primitives: + // ffi.Pointer get _xPtr => cast>(); + // Sample output for structs: + // ffi.Pointer get _xPtr => offsetBy(16).cast<...>(); + Expression offsetExpression = ThisExpression(); + if (offset != 0) { + offsetExpression = MethodInvocation(offsetExpression, offsetByMethod.name, + Arguments([IntLiteral(offset)]), offsetByMethod); + } + Procedure pointerGetter = Procedure( + pointerName, + ProcedureKind.Getter, + FunctionNode( + ReturnStatement(MethodInvocation(offsetExpression, castMethod.name, + Arguments([], types: [pointerType]), castMethod)), + returnType: pointerType)); + + // Sample output: + // double get x => _xPtr.load(); + Procedure getter = Procedure( + field.name, + ProcedureKind.Getter, + FunctionNode( + ReturnStatement(MethodInvocation( + PropertyGet(ThisExpression(), pointerName, pointerGetter), + loadMethod.name, + Arguments([], types: [field.type]), + loadMethod)), + returnType: field.type)); + + // Sample output: + // set x(double v) => _xPtr.store(v); + VariableDeclaration argument = VariableDeclaration('#v', type: field.type); + Procedure setter = Procedure( + field.name, + ProcedureKind.Setter, + FunctionNode( + ReturnStatement(MethodInvocation( + PropertyGet(ThisExpression(), pointerName, pointerGetter), + storeMethod.name, + Arguments([VariableGet(argument)]), + storeMethod)), + returnType: VoidType(), + positionalParameters: [argument])); + + replacedGetters[field] = getter; + replacedSetters[field] = setter; + + return [pointerGetter, getter, setter]; + } + + /// Sample input: + /// external static int sizeOf(); + /// + /// Sample output: + /// static int sizeOf() => 24; + void _replaceSizeOfMethod(Class struct, int size) { + Procedure sizeOf = _findProcedure(struct, 'sizeOf'); + if (sizeOf == null || !sizeOf.isExternal || !sizeOf.isStatic) { + return; + } + + // replace in place to avoid going over use sites + sizeOf.function = FunctionNode(ReturnStatement(IntLiteral(size)), + returnType: InterfaceType(intClass)); + sizeOf.isExternal = false; + } + + // TODO(dacoharkes): move to VM, take into account architecture + // https://github.com/dart-lang/sdk/issues/35768 + int _sizeInBytes(NativeType t) { + int size = nativeTypeSizes[t.index]; + if (size == WORD_SIZE) { + size = 8; + } + return size; + } + + int _align(int offset, int size) { + int remainder = offset % size; + if (remainder != 0) { + offset -= remainder; + offset += size; + } + return offset; + } + + // TODO(dacoharkes): move to VM, take into account architecture + // https://github.com/dart-lang/sdk/issues/35768 + List _calculateOffsets(List types) { + int offset = 0; + List offsets = []; + for (NativeType t in types) { + int size = _sizeInBytes(t); + offset = _align(offset, size); + offsets.add(offset); + offset += size; + } + return offsets; + } + + // TODO(dacoharkes): move to VM, take into account architecture + // https://github.com/dart-lang/sdk/issues/35768 + int _calculateSize(List offsets, List types) { + if (offsets.isEmpty) { + return 0; + } + int largestElement = types.map((e) => _sizeInBytes(e)).reduce(math.max); + int highestOffsetIndex = types.length - 1; + int highestOffset = offsets[highestOffsetIndex]; + int highestOffsetSize = _sizeInBytes(types[highestOffsetIndex]); + return _align(highestOffset + highestOffsetSize, largestElement); + } + + bool _hasAnnotation(Class node) { + for (Expression e in node.annotations) { + if (e is StaticGet) { + if (e.target == structField) { + return true; + } + } + } + return false; + } + + void _preventTreeShaking(Class node) { + node.addAnnotation(ConstructorInvocation( + pragmaConstructor, Arguments([StringLiteral("vm:entry-point")]))); + } + + NativeType _getFieldType(Class c) { + NativeType fieldType = getType(c); + + if (fieldType == NativeType.kVoid) { + // Fields cannot have Void types. + return null; + } + return fieldType; + } + + Iterable _getAnnotations(Field node) { + return node.annotations + .whereType() + .map((expr) => expr.target.parent) + .map((klass) => _getFieldType(klass)) + .where((type) => type != null); + } +} + +/// Finds procedure with name, otherwise returns null. +Procedure _findProcedure(Class c, String name) => + c.procedures.firstWhere((p) => p.name.name == name, orElse: () => null); diff --git a/pkg/kernel/lib/transformations/ffi_use_sites.dart b/pkg/kernel/lib/transformations/ffi_use_sites.dart new file mode 100644 index 00000000000..54d9b734b29 --- /dev/null +++ b/pkg/kernel/lib/transformations/ffi_use_sites.dart @@ -0,0 +1,270 @@ +// 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. + +library kernel.transformations.ffi_use_sites; + +import 'package:front_end/src/api_unstable/vm.dart' + show + templateFfiTypeInvalid, + templateFfiTypeMismatch, + templateFfiTypeUnsized, + templateFfiNotStatic; + +import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; + +import '../ast.dart'; +import '../core_types.dart'; +import '../target/targets.dart' show DiagnosticReporter; + +import 'ffi.dart' + show + ReplacedMembers, + NativeType, + kNativeTypeIntStart, + kNativeTypeIntEnd, + FfiTransformer; + +/// Checks and replaces calls to dart:ffi struct fields and methods. +void transformLibraries( + CoreTypes coreTypes, + ClassHierarchy hierarchy, + List libraries, + DiagnosticReporter diagnosticReporter, + ReplacedMembers replacedFields) { + final transformer = new _FfiUseSiteTransformer( + hierarchy, + coreTypes, + diagnosticReporter, + replacedFields.replacedGetters, + replacedFields.replacedSetters); + libraries.forEach(transformer.visitLibrary); +} + +/// Checks and replaces calls to dart:ffi struct fields and methods. +class _FfiUseSiteTransformer extends FfiTransformer { + final Map replacedGetters; + final Map replacedSetters; + + _FfiUseSiteTransformer( + ClassHierarchy hierarchy, + CoreTypes coreTypes, + DiagnosticReporter diagnosticReporter, + this.replacedGetters, + this.replacedSetters) + : super(hierarchy, coreTypes, diagnosticReporter) {} + + @override + visitClass(Class node) { + env.thisType = InterfaceType(node); + try { + return super.visitClass(node); + } finally { + env.thisType = null; + } + } + + @override + visitPropertyGet(PropertyGet node) { + super.visitPropertyGet(node); + + Procedure replacedWith = replacedGetters[node.interfaceTarget]; + if (replacedWith != null) { + node = PropertyGet(node.receiver, replacedWith.name, replacedWith); + } + + return node; + } + + @override + visitPropertySet(PropertySet node) { + super.visitPropertySet(node); + + Procedure replacedWith = replacedSetters[node.interfaceTarget]; + if (replacedWith != null) { + node = PropertySet( + node.receiver, replacedWith.name, node.value, replacedWith); + } + + return node; + } + + @override + visitStaticInvocation(StaticInvocation node) { + super.visitStaticInvocation(node); + + Member target = node.target; + try { + if (target == fromFunctionMethod) { + DartType nativeType = + InterfaceType(nativeFunctionClass, [node.arguments.types[0]]); + Expression func = node.arguments.positional[0]; + DartType dartType = func.getStaticType(env); + + _ensureIsStatic(func); + _ensureNativeTypeValid(nativeType, node); + _ensureNativeTypeToDartType(nativeType, dartType, node); + } + } catch (_FfiStaticTypeError) {} + + return node; + } + + @override + visitMethodInvocation(MethodInvocation node) { + super.visitMethodInvocation(node); + + Member target = node.interfaceTarget; + try { + if (target == lookupFunctionMethod) { + DartType nativeType = + InterfaceType(nativeFunctionClass, [node.arguments.types[0]]); + DartType dartType = node.arguments.types[1]; + + _ensureNativeTypeValid(nativeType, node); + _ensureNativeTypeToDartType(nativeType, dartType, node); + } else if (target == asFunctionMethod) { + if (node.enclosingLibrary == ffiLibrary) { + // Library code of dart:ffi uses asFunction to implement + // lookupFunction. Since we treat lookupFunction as well, this call + // can be generic and still support AOT. + return node; + } + + DartType dartType = node.arguments.types[0]; + DartType pointerType = node.receiver.getStaticType(env); + DartType nativeType = _pointerTypeGetTypeArg(pointerType); + + _ensureNativeTypeValid(pointerType, node); + _ensureNativeTypeValid(nativeType, node); + _ensureNativeTypeToDartType(nativeType, dartType, node); + } else if (target == loadMethod) { + // TODO(dacoharkes): should load and store permitted to be generic? + // https://github.com/dart-lang/sdk/issues/35902 + DartType dartType = node.arguments.types[0]; + DartType pointerType = node.receiver.getStaticType(env); + DartType nativeType = _pointerTypeGetTypeArg(pointerType); + + _ensureNativeTypeValid(pointerType, node); + _ensureNativeTypeValid(nativeType, node); + _ensureNativeTypeSized(nativeType, node, target.name); + _ensureNativeTypeToDartType(nativeType, dartType, node); + } else if (target == storeMethod) { + // TODO(dacoharkes): should load and store permitted to be generic? + // https://github.com/dart-lang/sdk/issues/35902 + DartType dartType = node.arguments.positional[0].getStaticType(env); + DartType pointerType = node.receiver.getStaticType(env); + DartType nativeType = _pointerTypeGetTypeArg(pointerType); + + _ensureNativeTypeValid(pointerType, node); + _ensureNativeTypeValid(nativeType, node); + _ensureNativeTypeSized(nativeType, node, target.name); + _ensureNativeTypeToDartType(nativeType, dartType, node); + } + } catch (_FfiStaticTypeError) {} + + return node; + } + + DartType _pointerTypeGetTypeArg(DartType pointerType) { + if (pointerType is InterfaceType) { + InterfaceType superType = + hierarchy.getTypeAsInstanceOf(pointerType, pointerClass); + return superType?.typeArguments[0]; + } + return null; + } + + void _ensureNativeTypeToDartType( + DartType nativeType, DartType dartType, Expression node) { + DartType shouldBeDartType = convertNativeTypeToDartType(nativeType); + if (dartType != shouldBeDartType) { + diagnosticReporter.report( + templateFfiTypeMismatch.withArguments( + dartType, shouldBeDartType, nativeType), + node.fileOffset, + 1, + node.location.file); + throw _FfiStaticTypeError(); + } + } + + void _ensureNativeTypeValid(DartType nativeType, Expression node) { + if (!_nativeTypeValid(nativeType)) { + diagnosticReporter.report( + templateFfiTypeInvalid.withArguments(nativeType), + node.fileOffset, + 1, + node.location.file); + throw _FfiStaticTypeError(); + } + } + + /// The Dart type system does not enforce that NativeFunction return and + /// parameter types are only NativeTypes, so we need to check this. + bool _nativeTypeValid(DartType nativeType) { + return convertNativeTypeToDartType(nativeType) != null; + } + + void _ensureNativeTypeSized( + DartType nativeType, Expression node, Name targetName) { + if (!_nativeTypeSized(nativeType)) { + diagnosticReporter.report( + templateFfiTypeUnsized.withArguments(targetName.name, nativeType), + node.fileOffset, + 1, + node.location.file); + throw _FfiStaticTypeError(); + } + } + + /// Unsized NativeTypes do not support [sizeOf] because their size is unknown. + /// Consequently, [allocate], [Pointer.load], [Pointer.store], and + /// [Pointer.elementAt] are not available. + bool _nativeTypeSized(DartType nativeType) { + if (!(nativeType is InterfaceType)) { + return false; + } + Class nativeClass = (nativeType as InterfaceType).classNode; + if (env.isSubtypeOf( + InterfaceType(nativeClass), InterfaceType(pointerClass))) { + return true; + } + NativeType nativeType_ = getType(nativeClass); + if (nativeType_ == null) { + return false; + } + if (kNativeTypeIntStart.index <= nativeType_.index && + nativeType_.index <= kNativeTypeIntEnd.index) { + return true; + } + if (nativeType_ == NativeType.kFloat || nativeType_ == NativeType.kDouble) { + return true; + } + if (nativeType_ == NativeType.kPointer) { + return true; + } + return false; + } + + void _ensureIsStatic(Expression node) { + if (!_isStatic(node)) { + diagnosticReporter.report( + templateFfiNotStatic.withArguments(fromFunctionMethod.name.name), + node.fileOffset, + 1, + node.location.file); + throw _FfiStaticTypeError(); + } + } + + bool _isStatic(Expression node) { + if (node is! StaticGet) return false; + + return (node as StaticGet).target is Procedure; + } +} + +/// Used internally for abnormal control flow to prevent cascading error +/// messages. +class _FfiStaticTypeError implements Exception {} diff --git a/pkg/vm/lib/target/vm.dart b/pkg/vm/lib/target/vm.dart index c57ae3440af..3719f095ec2 100644 --- a/pkg/vm/lib/target/vm.dart +++ b/pkg/vm/lib/target/vm.dart @@ -9,6 +9,12 @@ import 'package:kernel/ast.dart'; import 'package:kernel/class_hierarchy.dart'; import 'package:kernel/core_types.dart'; import 'package:kernel/target/targets.dart'; +import 'package:kernel/transformations/ffi.dart' as transformFfi + show ReplacedMembers; +import 'package:kernel/transformations/ffi_definitions.dart' + as transformFfiDefinitions show transformLibraries; +import 'package:kernel/transformations/ffi_use_sites.dart' + as transformFfiUseSites show transformLibraries; import 'package:kernel/transformations/mixin_full_resolution.dart' as transformMixins show transformLibraries; import 'package:kernel/transformations/constants.dart' show ConstantsBackend; @@ -68,6 +74,7 @@ class VmTarget extends Target { 'dart:nativewrappers', 'dart:io', 'dart:cli', + 'dart:ffi', ]; @override @@ -82,6 +89,13 @@ class VmTarget extends Target { doSuperResolution: false /* resolution is done in Dart VM */); logger?.call("Transformed mixin applications"); + transformFfi.ReplacedMembers replacedFields = + transformFfiDefinitions.transformLibraries( + coreTypes, hierarchy, libraries, diagnosticReporter); + transformFfiUseSites.transformLibraries( + coreTypes, hierarchy, libraries, diagnosticReporter, replacedFields); + logger?.call("Transformed ffi annotations"); + // TODO(kmillikin): Make this run on a per-method basis. transformAsync.transformLibraries(coreTypes, libraries); logger?.call("Transformed async methods"); diff --git a/runtime/bin/BUILD.gn b/runtime/bin/BUILD.gn index ec64042508f..41a4b0aa5c8 100644 --- a/runtime/bin/BUILD.gn +++ b/runtime/bin/BUILD.gn @@ -1004,6 +1004,50 @@ shared_library("entrypoints_verification_test_extension") { } } +shared_library("ffi_test_dynamic_library") { + deps = [ + ":dart", + ] + sources = [ + "ffi_test_dynamic_library.cc", + ] + include_dirs = [ ".." ] + defines = [ + # The only effect of DART_SHARED_LIB is to export the Dart API. + "DART_SHARED_LIB", + ] + if (is_linux || is_android) { + cflags = [ "-fPIC" ] + } + if (is_win) { + libs = [ "dart.lib" ] + abs_root_out_dir = rebase_path(root_out_dir) + ldflags = [ "/LIBPATH:$abs_root_out_dir" ] + } +} + +shared_library("ffi_test_functions") { + deps = [ + ":dart", + ] + sources = [ + "ffi_test_functions.cc", + ] + include_dirs = [ ".." ] + defines = [ + # The only effect of DART_SHARED_LIB is to export the Dart API. + "DART_SHARED_LIB", + ] + if (is_linux || is_android) { + cflags = [ "-fPIC" ] + } + if (is_win) { + libs = [ "dart.lib" ] + abs_root_out_dir = rebase_path(root_out_dir) + ldflags = [ "/LIBPATH:$abs_root_out_dir" ] + } +} + shared_library("sample_extension") { deps = [ ":dart", diff --git a/runtime/bin/ffi_test_dynamic_library.cc b/runtime/bin/ffi_test_dynamic_library.cc new file mode 100644 index 00000000000..c925300eaf1 --- /dev/null +++ b/runtime/bin/ffi_test_dynamic_library.cc @@ -0,0 +1,13 @@ +// 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. + +#include "include/dart_api.h" + +DART_EXPORT int return42() { + return 42; +} + +DART_EXPORT double timesFour(double d) { + return d * 4.0; +} diff --git a/runtime/bin/ffi_test_functions.cc b/runtime/bin/ffi_test_functions.cc new file mode 100644 index 00000000000..0248829ab71 --- /dev/null +++ b/runtime/bin/ffi_test_functions.cc @@ -0,0 +1,368 @@ +// 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. + +// This file contains test functions for the dart:ffi test cases. + +#include +#include + +#include "include/dart_api.h" + +namespace dart { + +// Sums two ints and adds 42. +// Simple function to test trampolines. +// Also used for testing argument exception on passing null instead of a Dart +// int. +DART_EXPORT int32_t SumPlus42(int32_t a, int32_t b) { + std::cout << "SumPlus42(" << a << ", " << b << ")\n"; + int32_t retval = 42 + a + b; + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Performs some computation on various sized signed ints. +// Used for testing value ranges for signed ints. +DART_EXPORT int64_t IntComputation(int8_t a, int16_t b, int32_t c, int64_t d) { + std::cout << "IntComputation(" << static_cast(a) << ", " << b << ", " + << c << ", " << d << ")\n"; + int64_t retval = d - c + b - a; + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Performs some computation on various sized unsigned ints. +// Used for testing value ranges for unsigned ints. +DART_EXPORT int64_t UintComputation(uint8_t a, + uint16_t b, + uint32_t c, + uint64_t d) { + std::cout << "UintComputation(" << static_cast(a) << ", " << b << ", " + << c << ", " << d << ")\n"; + uint64_t retval = d - c + b - a; + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Multiplies pointer sized int by three. +// Used for testing pointer sized parameter and return value. +DART_EXPORT intptr_t Times3(intptr_t a) { + std::cout << "Times3(" << a << ")\n"; + intptr_t retval = a * 3; + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Multiples a double by 1.337. +// Used for testing double parameter and return value. +// Also used for testing argument exception on passing null instead of a Dart +// double. +DART_EXPORT double Times1_337Double(double a) { + std::cout << "Times1_337Double(" << a << ")\n"; + double retval = a * 1.337; + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Multiples a float by 1.337. +// Used for testing float parameter and return value. +DART_EXPORT float Times1_337Float(float a) { + std::cout << "Times1_337Float(" << a << ")\n"; + float retval = a * 1.337f; + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Sums many ints. +// Used for testing calling conventions. With so many doubles we are using all +// normal parameter registers and some stack slots. +DART_EXPORT intptr_t SumManyInts(intptr_t a, + intptr_t b, + intptr_t c, + intptr_t d, + intptr_t e, + intptr_t f, + intptr_t g, + intptr_t h, + intptr_t i, + intptr_t j) { + std::cout << "SumManyInts(" << a << ", " << b << ", " << c << ", " << d + << ", " << e << ", " << f << ", " << g << ", " << h << ", " << i + << ", " << j << ")\n"; + intptr_t retval = a + b + c + d + e + f + g + h + i + j; + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Sums many doubles. +// Used for testing calling conventions. With so many doubles we are using all +// xmm parameter registers and some stack slots. +DART_EXPORT double SumManyDoubles(double a, + double b, + double c, + double d, + double e, + double f, + double g, + double h, + double i, + double j) { + std::cout << "SumManyDoubles(" << a << ", " << b << ", " << c << ", " << d + << ", " << e << ", " << f << ", " << g << ", " << h << ", " << i + << ", " << j << ")\n"; + double retval = a + b + c + d + e + f + g + h + i + j; + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Sums many numbers. +// Used for testing calling conventions. With so many parameters we are using +// both registers and stack slots. +DART_EXPORT double SumManyNumbers(int a, + float b, + int c, + double d, + int e, + float f, + int g, + double h, + int i, + float j, + int k, + double l, + int m, + float n, + int o, + double p, + int q, + float r, + int s, + double t) { + std::cout << "SumManyNumbers(" << a << ", " << b << ", " << c << ", " << d + << ", " << e << ", " << f << ", " << g << ", " << h << ", " << i + << ", " << j << ", " << k << ", " << l << ", " << m << ", " << n + << ", " << o << ", " << p << ", " << q << ", " << r << ", " << s + << ", " << t << ")\n"; + double retval = a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + + p + q + r + s + t; + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Assigns 1337 to the second element and returns the address of that element. +// Used for testing Pointer parameters and return values. +DART_EXPORT int64_t* Assign1337Index1(int64_t* a) { + std::cout << "Assign1337Index1(" << a << ")\n"; + std::cout << "val[0] = " << a[0] << "\n"; + std::cout << "val[1] = " << a[1] << "\n"; + a[1] = 1337; + std::cout << "val[1] = " << a[1] << "\n"; + int64_t* retval = a + 1; + std::cout << "returning " << retval << "\n"; + return retval; +} + +struct Coord { + double x; + double y; + Coord* next; +}; + +// Transposes Coordinate by (10, 10) and returns next Coordinate. +// Used for testing struct pointer parameter, struct pointer return value, +// struct field access, and struct pointer field dereference. +DART_EXPORT Coord* TransposeCoordinate(Coord* coord) { + std::cout << "TransposeCoordinate(" << coord << " {" << coord->x << ", " + << coord->y << ", " << coord->next << "})\n"; + coord->x = coord->x + 10.0; + coord->y = coord->y + 10.0; + std::cout << "returning " << coord->next << "\n"; + return coord->next; +} + +// Takes a Coordinate array and returns a Coordinate pointer to the next +// element. +// Used for testing struct arrays. +DART_EXPORT Coord* CoordinateElemAt1(Coord* coord) { + std::cout << "CoordinateElemAt1(" << coord << ")\n"; + std::cout << "sizeof(Coord): " << sizeof(Coord) << "\n"; + std::cout << "coord[0] = {" << coord[0].x << ", " << coord[0].y << ", " + << coord[0].next << "}\n"; + std::cout << "coord[1] = {" << coord[1].x << ", " << coord[1].y << ", " + << coord[1].next << "}\n"; + Coord* retval = coord + 1; + std::cout << "returning " << retval << "\n"; + return retval; +} + +typedef Coord* (*CoordUnOp)(Coord* coord); + +// Takes a Coordinate Function(Coordinate) and applies it three times to a +// Coordinate. +// Used for testing function pointers with structs. +DART_EXPORT Coord* CoordinateUnOpTrice(CoordUnOp unop, Coord* coord) { + std::cout << "CoordinateUnOpTrice(" << unop << ", " << coord << ")\n"; + Coord* retval = unop(unop(unop(coord))); + std::cout << "returning " << retval << "\n"; + return retval; +} + +typedef intptr_t (*IntptrBinOp)(intptr_t a, intptr_t b); + +// Returns a closure. +// Note this closure is not properly marked as DART_EXPORT or extern "C". +// Used for testing passing a pointer to a closure to Dart. +// TODO(dacoharkes): is this a supported use case? +DART_EXPORT IntptrBinOp IntptrAdditionClosure() { + std::cout << "IntptrAdditionClosure()\n"; + IntptrBinOp retval = [](intptr_t a, intptr_t b) { return a + b; }; + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Applies an intptr binop function to 42 and 74. +// Used for testing passing a function pointer to C. +DART_EXPORT intptr_t ApplyTo42And74(IntptrBinOp binop) { + std::cout << "ApplyTo42And74()\n"; + intptr_t retval = binop(42, 74); + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Returns next element in the array, unless a null pointer is passed. +// When a null pointer is passed, a null pointer is returned. +// Used for testing null pointers. +DART_EXPORT int64_t* NullableInt64ElemAt1(int64_t* a) { + std::cout << "NullableInt64ElemAt1(" << a << ")\n"; + int64_t* retval; + if (a) { + std::cout << "not null pointer, address: " << a << "\n"; + retval = a + 1; + } else { + std::cout << "null pointer, address: " << a << "\n"; + retval = nullptr; + } + std::cout << "returning " << retval << "\n"; + return retval; +} + +struct VeryLargeStruct { + int8_t a; + int16_t b; + int32_t c; + int64_t d; + uint8_t e; + uint16_t f; + uint32_t g; + uint64_t h; + intptr_t i; + float j; + double k; + VeryLargeStruct* parent; + intptr_t numChildren; + VeryLargeStruct* children; + int8_t smallLastField; +}; + +// Sums the fields of a very large struct, including the first field (a) from +// the parent and children. +// Used for testing alignment and padding in structs. +DART_EXPORT int64_t SumVeryLargeStruct(VeryLargeStruct* vls) { + std::cout << "SumVeryLargeStruct(" << vls << ")\n"; + std::cout << "offsetof(a): " << offsetof(VeryLargeStruct, a) << "\n"; + std::cout << "offsetof(b): " << offsetof(VeryLargeStruct, b) << "\n"; + std::cout << "offsetof(c): " << offsetof(VeryLargeStruct, c) << "\n"; + std::cout << "offsetof(d): " << offsetof(VeryLargeStruct, d) << "\n"; + std::cout << "offsetof(e): " << offsetof(VeryLargeStruct, e) << "\n"; + std::cout << "offsetof(f): " << offsetof(VeryLargeStruct, f) << "\n"; + std::cout << "offsetof(g): " << offsetof(VeryLargeStruct, g) << "\n"; + std::cout << "offsetof(h): " << offsetof(VeryLargeStruct, h) << "\n"; + std::cout << "offsetof(i): " << offsetof(VeryLargeStruct, i) << "\n"; + std::cout << "offsetof(j): " << offsetof(VeryLargeStruct, j) << "\n"; + std::cout << "offsetof(k): " << offsetof(VeryLargeStruct, k) << "\n"; + std::cout << "offsetof(parent): " << offsetof(VeryLargeStruct, parent) + << "\n"; + std::cout << "offsetof(numChildren): " + << offsetof(VeryLargeStruct, numChildren) << "\n"; + std::cout << "offsetof(children): " << offsetof(VeryLargeStruct, children) + << "\n"; + std::cout << "offsetof(smallLastField): " + << offsetof(VeryLargeStruct, smallLastField) << "\n"; + std::cout << "sizeof(VeryLargeStruct): " << sizeof(VeryLargeStruct) << "\n"; + + std::cout << "vls->a: " << static_cast(vls->a) << "\n"; + std::cout << "vls->b: " << vls->b << "\n"; + std::cout << "vls->c: " << vls->c << "\n"; + std::cout << "vls->d: " << vls->d << "\n"; + std::cout << "vls->e: " << static_cast(vls->e) << "\n"; + std::cout << "vls->f: " << vls->f << "\n"; + std::cout << "vls->g: " << vls->g << "\n"; + std::cout << "vls->h: " << vls->h << "\n"; + std::cout << "vls->i: " << vls->i << "\n"; + std::cout << "vls->j: " << vls->j << "\n"; + std::cout << "vls->k: " << vls->k << "\n"; + std::cout << "vls->parent: " << vls->parent << "\n"; + std::cout << "vls->numChildren: " << vls->numChildren << "\n"; + std::cout << "vls->children: " << vls->children << "\n"; + std::cout << "vls->smallLastField: " << static_cast(vls->smallLastField) + << "\n"; + + int64_t retval = 0; + retval += 0x0L + vls->a; + retval += vls->b; + retval += vls->c; + retval += vls->d; + retval += vls->e; + retval += vls->f; + retval += vls->g; + retval += vls->h; + retval += vls->i; + retval += vls->j; + retval += vls->k; + retval += vls->smallLastField; + std::cout << retval << "\n"; + if (vls->parent) { + std::cout << "has parent\n"; + retval += vls->parent->a; + } + std::cout << "has " << vls->numChildren << " children\n"; + for (int i = 0; i < vls->numChildren; i++) { + retval += vls->children[i].a; + } + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Sums numbers of various sizes. +// Used for testing truncation and sign extension of non 64 bit parameters. +DART_EXPORT int64_t SumSmallNumbers(int8_t a, + int16_t b, + int32_t c, + uint8_t d, + uint16_t e, + uint32_t f) { + std::cout << "SumSmallNumbers(" << static_cast(a) << ", " << b << ", " + << c << ", " << static_cast(d) << ", " << e << ", " << f + << ")\n"; + int64_t retval = 0; + retval += a; + retval += b; + retval += c; + retval += d; + retval += e; + retval += f; + std::cout << "returning " << retval << "\n"; + return retval; +} + +// Checks whether the float is between 1336.0f and 1338.0f. +// Used for testing rounding of Dart Doubles to floats in Pointer.store(). +DART_EXPORT uint8_t IsRoughly1337(float* a) { + std::cout << "IsRoughly1337(" << a[0] << ")\n"; + uint8_t retval = (1336.0f < a[0] && a[0] < 1338.0f) ? 1 : 0; + std::cout << "returning " << static_cast(retval) << "\n"; + return retval; +} + +} // namespace dart diff --git a/runtime/lib/ffi.cc b/runtime/lib/ffi.cc new file mode 100644 index 00000000000..0621990d57c --- /dev/null +++ b/runtime/lib/ffi.cc @@ -0,0 +1,680 @@ +// 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. + +#include "include/dart_api.h" +#include "vm/bootstrap_natives.h" +#include "vm/class_finalizer.h" +#include "vm/compiler/assembler/assembler.h" +#include "vm/exceptions.h" +#include "vm/log.h" +#include "vm/native_arguments.h" +#include "vm/native_entry.h" +#include "vm/object.h" +#include "vm/object_store.h" +#include "vm/symbols.h" + +namespace dart { + +// The following functions are runtime checks on type arguments. +// Some checks are also performed in kernel transformation, these are asserts. +// Some checks are only performed at runtime to allow for generic code, these +// throw ArgumentExceptions. + +static void ThrowTypeArgumentError(const AbstractType& type_arg, + const char* expected) { + const String& error = String::Handle(String::NewFormatted( + "Type argument (%s) should be a %s", + String::Handle(type_arg.UserVisibleName()).ToCString(), expected)); + Exceptions::ThrowArgumentError(error); +} + +static bool IsPointerType(const AbstractType& type) { + // Do a fast check for predefined types. + classid_t type_cid = type.type_class_id(); + if (RawObject::IsFfiPointerClassId(type_cid)) { + return true; + } + + // Do a slow check for subtyping. + const Class& pointer_class = + Class::Handle(Isolate::Current()->object_store()->ffi_pointer_class()); + AbstractType& pointer_type = + AbstractType::Handle(pointer_class.DeclarationType()); + pointer_type ^= pointer_type.InstantiateFrom(Object::null_type_arguments(), + Object::null_type_arguments(), + kNoneFree, NULL, Heap::kNew); + ASSERT(pointer_type.IsInstantiated()); + ASSERT(type.IsInstantiated()); + return type.IsSubtypeOf(pointer_type, Heap::kNew); +} + +static bool IsConcreteNativeType(const AbstractType& type) { + // Do a fast check for predefined types. + classid_t type_cid = type.type_class_id(); + if (RawObject::IsFfiNativeTypeTypeClassId(type_cid)) { + return false; + } + if (RawObject::IsFfiTypeClassId(type_cid)) { + return true; + } + + // Do a slow check for subtyping. + const Class& native_type_class = Class::Handle( + Isolate::Current()->object_store()->ffi_native_type_class()); + AbstractType& native_type_type = + AbstractType::Handle(native_type_class.DeclarationType()); + return type.IsSubtypeOf(native_type_type, Heap::kNew); +} + +static void CheckIsConcreteNativeType(const AbstractType& type) { + if (!IsConcreteNativeType(type)) { + ThrowTypeArgumentError(type, "concrete sub type of NativeType"); + } +} + +static bool IsNativeFunction(const AbstractType& type_arg) { + classid_t type_cid = type_arg.type_class_id(); + return RawObject::IsFfiTypeNativeFunctionClassId(type_cid); +} + +static void CheckSized(const AbstractType& type_arg) { + classid_t type_cid = type_arg.type_class_id(); + if (RawObject::IsFfiTypeVoidClassId(type_cid) || + RawObject::IsFfiTypeNativeFunctionClassId(type_cid)) { + const String& error = String::Handle(String::NewFormatted( + "%s does not have a predefined size (@unsized). " + "Unsized NativeTypes do not support [sizeOf] because their size " + "is unknown. " + "Consequently, [allocate], [Pointer.load], [Pointer.store], and " + "[Pointer.elementAt] are not available.", + String::Handle(type_arg.UserVisibleName()).ToCString())); + Exceptions::ThrowArgumentError(error); + } +} + +// Checks that a dart type correspond to a [NativeType]. +// Because this is checked already in a kernel transformation, it does not throw +// an ArgumentException but a boolean which should be asserted. +// +// [Int8] -> [int] +// [Int16] -> [int] +// [Int32] -> [int] +// [Int64] -> [int] +// [Uint8] -> [int] +// [Uint16] -> [int] +// [Uint32] -> [int] +// [Uint64] -> [int] +// [IntPtr] -> [int] +// [Double] -> [double] +// [Float] -> [double] +// [Pointer] -> [Pointer] +// T extends [Pointer] -> T +// [NativeFunction] S1 Function(S2, S3) +// where DartRepresentationOf(Tn) -> Sn +static bool DartAndCTypeCorrespond(const AbstractType& native_type, + const AbstractType& dart_type) { + classid_t native_type_cid = native_type.type_class_id(); + if (RawObject::IsFfiTypeIntClassId(native_type_cid)) { + return dart_type.IsSubtypeOf(AbstractType::Handle(Type::IntType()), + Heap::kNew); + } + if (RawObject::IsFfiTypeDoubleClassId(native_type_cid)) { + return dart_type.IsSubtypeOf(AbstractType::Handle(Type::Double()), + Heap::kNew); + } + if (RawObject::IsFfiPointerClassId(native_type_cid)) { + return native_type.Equals(dart_type) || dart_type.IsNullType(); + } + if (RawObject::IsFfiTypeNativeFunctionClassId(native_type_cid)) { + if (!dart_type.IsFunctionType()) { + return false; + } + TypeArguments& nativefunction_type_args = + TypeArguments::Handle(native_type.arguments()); + AbstractType& nativefunction_type_arg = + AbstractType::Handle(nativefunction_type_args.TypeAt(0)); + if (!nativefunction_type_arg.IsFunctionType()) { + return false; + } + Function& dart_function = Function::Handle(((Type&)dart_type).signature()); + if (dart_function.NumTypeParameters() != 0 || + dart_function.HasOptionalPositionalParameters() || + dart_function.HasOptionalNamedParameters()) { + return false; + } + Function& nativefunction_function = + Function::Handle(((Type&)nativefunction_type_arg).signature()); + if (nativefunction_function.NumTypeParameters() != 0 || + nativefunction_function.HasOptionalPositionalParameters() || + nativefunction_function.HasOptionalNamedParameters()) { + return false; + } + if (!(dart_function.NumParameters() == + nativefunction_function.NumParameters())) { + return false; + } + if (!DartAndCTypeCorrespond( + AbstractType::Handle(nativefunction_function.result_type()), + AbstractType::Handle(dart_function.result_type()))) { + return false; + } + for (intptr_t i = 0; i < dart_function.NumParameters(); i++) { + if (!DartAndCTypeCorrespond( + AbstractType::Handle(nativefunction_function.ParameterTypeAt(i)), + AbstractType::Handle(dart_function.ParameterTypeAt(i)))) { + return false; + } + } + } + return true; +} + +// The following functions are runtime checks on arguments. + +// Note that expected_from and expected_to are inclusive. +static void CheckRange(const Integer& argument_value, + intptr_t expected_from, + intptr_t expected_to, + const char* argument_name) { + int64_t value = argument_value.AsInt64Value(); + if (value < expected_from || expected_to < value) { + Exceptions::ThrowRangeError(argument_name, argument_value, expected_from, + expected_to); + } +} + +static const Pointer& AsPointer(const Instance& instance) { + if (!instance.IsPointer()) { + const String& error = String::Handle(String::NewFormatted( + "Expected a Pointer object but found %s", instance.ToCString())); + Exceptions::ThrowArgumentError(error); + } + return Pointer::Cast(instance); +} + +static const Integer& AsInteger(const Instance& instance) { + if (!instance.IsInteger()) { + const String& error = String::Handle(String::NewFormatted( + "Expected an int but found %s", instance.ToCString())); + Exceptions::ThrowArgumentError(error); + } + return Integer::Cast(instance); +} + +static const Double& AsDouble(const Instance& instance) { + if (!instance.IsDouble()) { + const String& error = String::Handle(String::NewFormatted( + "Expected a double but found %s", instance.ToCString())); + Exceptions::ThrowArgumentError(error); + } + return Double::Cast(instance); +} + +// Native data types sizes in bytes + +static const size_t kSizeUnknown = 0; + +static const intptr_t kNumElementSizes = kFfiVoidCid - kFfiPointerCid + 1; + +static const size_t element_size_table[kNumElementSizes] = { + sizeof(intptr_t), // kFfiPointerCid + kSizeUnknown, // kFfiNativeFunctionCid + 1, // kFfiInt8Cid + 2, // kFfiInt16Cid + 4, // kFfiInt32Cid + 8, // kFfiInt64Cid + 1, // kFfiUint8Cid + 2, // kFfiUint16Cid + 4, // kFfiUint32Cid + 8, // kFfiUint64Cid + sizeof(intptr_t), // kFfiIntPtrCid + 4, // kFfiFloatCid + 8, // kFfiDoubleCid + kSizeUnknown, // kFfiVoidCid +}; + +static size_t ElementSizeInBytes(intptr_t class_id) { + ASSERT(RawObject::IsFfiTypeClassId(class_id)); + ASSERT(class_id != kFfiNativeFunctionCid); + ASSERT(class_id != kFfiVoidCid); + intptr_t index = class_id - kFfiPointerCid; + return element_size_table[index]; +} + +// The remainder of this file implements the dart:ffi native methods. + +DEFINE_NATIVE_ENTRY(Ffi_allocate, 1, 1) { + // TODO(dacoharkes): When we have a way of determining the size of structs in + // the VM, change the signature so we can allocate structs, subtype of + // Pointer. https://github.com/dart-lang/sdk/issues/35782 + GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); + + CheckIsConcreteNativeType(type_arg); + CheckSized(type_arg); + + GET_NON_NULL_NATIVE_ARGUMENT(Integer, argCount, arguments->NativeArgAt(0)); + int64_t count = argCount.AsInt64Value(); + classid_t type_cid = type_arg.type_class_id(); + int64_t max_count = INTPTR_MAX / ElementSizeInBytes(type_cid); + CheckRange(argCount, 1, max_count, "count"); + + size_t size = ElementSizeInBytes(type_cid) * count; + uint8_t* memory = reinterpret_cast(malloc(size)); + if (memory == NULL) { + const String& error = String::Handle(String::NewFormatted( + "allocating (%" Pd ") bytes of memory failed", size)); + Exceptions::ThrowArgumentError(error); + } + + RawPointer* result = Pointer::New(type_arg, memory); + return result; +} + +DEFINE_NATIVE_ENTRY(Ffi_fromAddress, 1, 1) { + GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); + TypeArguments& type_args = TypeArguments::Handle(type_arg.arguments()); + AbstractType& native_type = AbstractType::Handle( + type_args.TypeAtNullSafe(Pointer::kNativeTypeArgPos)); + CheckIsConcreteNativeType(native_type); + GET_NON_NULL_NATIVE_ARGUMENT(Integer, arg_ptr, arguments->NativeArgAt(0)); + + uint8_t* address = reinterpret_cast(arg_ptr.AsInt64Value()); + // TODO(dacoharkes): should this return NULL if addres is 0? + // https://github.com/dart-lang/sdk/issues/35756 + + RawPointer* result = + Pointer::New(native_type, address, type_arg.type_class_id()); + return result; +} + +DEFINE_NATIVE_ENTRY(Ffi_elementAt, 0, 2) { + GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(Integer, index, arguments->NativeArgAt(1)); + AbstractType& pointer_type_arg = + AbstractType::Handle(pointer.type_argument()); + CheckSized(pointer_type_arg); + + classid_t class_id = pointer_type_arg.type_class_id(); + uint8_t* address = pointer.GetCMemoryAddress(); + uint8_t* address_new = + address + index.AsInt64Value() * ElementSizeInBytes(class_id); + RawPointer* result = Pointer::New(pointer_type_arg, address_new); + return result; +} + +DEFINE_NATIVE_ENTRY(Ffi_offsetBy, 0, 2) { + GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(Integer, offset, arguments->NativeArgAt(1)); + AbstractType& pointer_type_arg = + AbstractType::Handle(pointer.type_argument()); + + uint8_t* address = pointer.GetCMemoryAddress(); + uint8_t* address_new = address + offset.AsInt64Value(); + RawPointer* result = Pointer::New(pointer_type_arg, address_new); + return result; +} + +DEFINE_NATIVE_ENTRY(Ffi_cast, 1, 1) { + GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); + GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); + TypeArguments& type_args = TypeArguments::Handle(type_arg.arguments()); + AbstractType& native_type = AbstractType::Handle( + type_args.TypeAtNullSafe(Pointer::kNativeTypeArgPos)); + CheckIsConcreteNativeType(native_type); + + uint8_t* address = pointer.GetCMemoryAddress(); + RawPointer* result = + Pointer::New(native_type, address, type_arg.type_class_id()); + return result; +} + +DEFINE_NATIVE_ENTRY(Ffi_free, 0, 1) { + GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); + + uint8_t* address = pointer.GetCMemoryAddress(); + free(address); + pointer.SetCMemoryAddress(0); + return Object::null(); +} + +DEFINE_NATIVE_ENTRY(Ffi_address, 0, 1) { + GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); + + uint8_t* address = pointer.GetCMemoryAddress(); + intptr_t int_ptr = reinterpret_cast(address); + return Integer::NewFromUint64(int_ptr); +} + +static RawInstance* BoxLoadPointer(uint8_t* address, + const AbstractType& instance_type_arg, + intptr_t type_cid) { + // TODO(dacoharkes): should this return NULL if addres is 0? + // https://github.com/dart-lang/sdk/issues/35756 + if (address == 0) { // 0 is the c++ null pointer + return Instance::null(); + } + AbstractType& type_arg = + AbstractType::Handle(TypeArguments::Handle(instance_type_arg.arguments()) + .TypeAt(Pointer::kNativeTypeArgPos)); + return Pointer::New(type_arg, address, type_cid); +} + +static RawInstance* LoadValue(uint8_t* address, + const AbstractType& instance_type_arg) { + classid_t type_cid = instance_type_arg.type_class_id(); + switch (type_cid) { + case kFfiInt8Cid: + return Integer::New(*reinterpret_cast(address)); + case kFfiInt16Cid: + return Integer::New(*reinterpret_cast(address)); + case kFfiInt32Cid: + return Integer::New(*reinterpret_cast(address)); + case kFfiInt64Cid: + return Integer::New(*reinterpret_cast(address)); + case kFfiUint8Cid: + return Integer::NewFromUint64(*reinterpret_cast(address)); + case kFfiUint16Cid: + return Integer::NewFromUint64(*reinterpret_cast(address)); + case kFfiUint32Cid: + return Integer::NewFromUint64(*reinterpret_cast(address)); + case kFfiUint64Cid: + return Integer::NewFromUint64(*reinterpret_cast(address)); + case kFfiIntPtrCid: + return Integer::New(*reinterpret_cast(address)); + case kFfiFloatCid: + return Double::New(*reinterpret_cast(address)); + case kFfiDoubleCid: + return Double::New(*reinterpret_cast(address)); + case kFfiPointerCid: + default: + ASSERT(IsPointerType(instance_type_arg)); + return BoxLoadPointer(*reinterpret_cast(address), + instance_type_arg, type_cid); + } +} + +DEFINE_NATIVE_ENTRY(Ffi_load, 1, 1) { + GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); + GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); + AbstractType& pointer_type_arg = + AbstractType::Handle(pointer.type_argument()); + CheckSized(pointer_type_arg); + ASSERT(DartAndCTypeCorrespond(pointer_type_arg, type_arg)); + + uint8_t* address = pointer.GetCMemoryAddress(); + return LoadValue(address, pointer_type_arg); +} + +static void StoreValue(const Pointer& pointer, + classid_t type_cid, + const Instance& new_value) { + uint8_t* address = pointer.GetCMemoryAddress(); + AbstractType& pointer_type_arg = + AbstractType::Handle(pointer.type_argument()); + switch (type_cid) { + case kFfiInt8Cid: + *reinterpret_cast(address) = AsInteger(new_value).AsInt64Value(); + break; + case kFfiInt16Cid: + *reinterpret_cast(address) = + AsInteger(new_value).AsInt64Value(); + break; + case kFfiInt32Cid: + *reinterpret_cast(address) = + AsInteger(new_value).AsInt64Value(); + break; + case kFfiInt64Cid: + *reinterpret_cast(address) = + AsInteger(new_value).AsInt64Value(); + break; + case kFfiUint8Cid: + *reinterpret_cast(address) = + AsInteger(new_value).AsInt64Value(); + break; + case kFfiUint16Cid: + *reinterpret_cast(address) = + AsInteger(new_value).AsInt64Value(); + break; + case kFfiUint32Cid: + *reinterpret_cast(address) = + AsInteger(new_value).AsInt64Value(); + break; + case kFfiUint64Cid: + *reinterpret_cast(address) = + AsInteger(new_value).AsInt64Value(); + break; + case kFfiIntPtrCid: + *reinterpret_cast(address) = + AsInteger(new_value).AsInt64Value(); + break; + case kFfiFloatCid: + *reinterpret_cast(address) = AsDouble(new_value).value(); + break; + case kFfiDoubleCid: + *reinterpret_cast(address) = AsDouble(new_value).value(); + break; + case kFfiPointerCid: + default: { + ASSERT(IsPointerType(pointer_type_arg)); + uint8_t* new_value_unwrapped = nullptr; + if (!new_value.IsNull()) { + ASSERT(new_value.IsPointer()); + new_value_unwrapped = AsPointer(new_value).GetCMemoryAddress(); + // TODO(dacoharkes): should this return NULL if addres is 0? + // https://github.com/dart-lang/sdk/issues/35756 + } + *reinterpret_cast(address) = new_value_unwrapped; + } break; + } +} + +DEFINE_NATIVE_ENTRY(Ffi_store, 0, 2) { + GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); + GET_NATIVE_ARGUMENT(Instance, new_value, arguments->NativeArgAt(1)); + AbstractType& arg_type = AbstractType::Handle(new_value.GetType(Heap::kNew)); + AbstractType& pointer_type_arg = + AbstractType::Handle(pointer.type_argument()); + CheckSized(pointer_type_arg); + ASSERT(DartAndCTypeCorrespond(pointer_type_arg, arg_type)); + + classid_t type_cid = pointer_type_arg.type_class_id(); + StoreValue(pointer, type_cid, new_value); + return Object::null(); +} + +DEFINE_NATIVE_ENTRY(Ffi_sizeOf, 1, 0) { + GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); + CheckIsConcreteNativeType(type_arg); + CheckSized(type_arg); + + classid_t type_cid = type_arg.type_class_id(); + return Smi::New(ElementSizeInBytes(type_cid)); +} + +// Generates assembly to trampoline from Dart into C++. +// +// Attaches assembly code to the function with the folling features: +// - unboxes arguments +// - puts the arguments on the c stack +// - invokes the c function +// - reads the the result +// - boxes the result and returns it. +// +// It inspects the signature to know what to box/unbox +// Parameter `function` has the Dart types in its signature +// Parameter `c_signature` has the C++ types in its signature +static RawCode* TrampolineCode(const Function& function, + const Function& c_signature) { +#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER) + // Currently we generate the trampoline when calling asFunction(), this means + // the ffi cannot be used in AOT. + // In order make it work in AOT we need to: + // - collect all asFunction signatures ahead of time + // - generate trampolines for those + // - store these in the object store + // - and read these from the object store when calling asFunction() + // https://github.com/dart-lang/sdk/issues/35765 + UNREACHABLE(); +#elif !defined(TARGET_ARCH_X64) + // https://github.com/dart-lang/sdk/issues/35774 + UNREACHABLE(); +#elif !defined(TARGET_OS_LINUX) && !defined(TARGET_OS_MACOS) + // https://github.com/dart-lang/sdk/issues/35760 Arm32 && Android + // https://github.com/dart-lang/sdk/issues/35771 Windows + // https://github.com/dart-lang/sdk/issues/35772 Arm64 + // https://github.com/dart-lang/sdk/issues/35773 DBC + UNREACHABLE(); +#else + extern void GenerateFfiTrampoline(Assembler * assembler, + const Function& signature); + ObjectPoolBuilder object_pool_builder; + Assembler assembler(&object_pool_builder); + GenerateFfiTrampoline(&assembler, c_signature); + const Code& code = Code::Handle(Code::FinalizeCode( + function, nullptr, &assembler, Code::PoolAttachment::kAttachPool)); + code.set_exception_handlers( + ExceptionHandlers::Handle(ExceptionHandlers::New(0))); + return code.raw(); +#endif +} + +// TODO(dacoharkes): Cache the trampolines. +// We can possibly address simultaniously with 'precaching' in AOT. +static RawFunction* TrampolineFunction(const Function& dart_signature, + const Function& c_signature) { + Thread* thread = Thread::Current(); + Zone* zone = thread->zone(); + const String& name = + String::ZoneHandle(Symbols::New(Thread::Current(), "FfiTrampoline")); + const Library& lib = Library::Handle(Library::FfiLibrary()); + const Class& owner_class = Class::Handle(lib.toplevel_class()); + Function& function = Function::ZoneHandle( + zone, Function::New(name, RawFunction::kFfiTrampoline, + true, // is_static + false, // is_const + false, // is_abstract + false, // is_external + true, // is_native + owner_class, TokenPosition::kMinSource)); + + function.set_num_fixed_parameters(dart_signature.num_fixed_parameters()); + function.set_result_type(AbstractType::Handle(dart_signature.result_type())); + function.set_parameter_types(Array::Handle(dart_signature.parameter_types())); + + const Code& code = Code::Handle(TrampolineCode(function, c_signature)); + function.AttachCode(code); + + return function.raw(); +} + +DEFINE_NATIVE_ENTRY(Ffi_asFunction, 1, 1) { + GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); + AbstractType& pointer_type_arg = + AbstractType::Handle(pointer.type_argument()); + ASSERT(IsNativeFunction(pointer_type_arg)); + GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); + ASSERT(DartAndCTypeCorrespond(pointer_type_arg, type_arg)); + + Function& dart_signature = Function::Handle(Type::Cast(type_arg).signature()); + TypeArguments& nativefunction_type_args = + TypeArguments::Handle(pointer_type_arg.arguments()); + AbstractType& nativefunction_type_arg = + AbstractType::Handle(nativefunction_type_args.TypeAt(0)); + Function& c_signature = + Function::Handle(Type::Cast(nativefunction_type_arg).signature()); + Function& function = + Function::Handle(TrampolineFunction(dart_signature, c_signature)); + + // Set the c function pointer in the context of the closure rather than in + // the function so that we can reuse the function for each c function with + // the same signature. + Context& context = Context::Handle(Context::New(1)); + context.SetAt(0, + Object::Handle(Integer::NewFromUint64( + reinterpret_cast(pointer.GetCMemoryAddress())))); + + RawClosure* raw_closure = + Closure::New(Object::null_type_arguments(), Object::null_type_arguments(), + function, context, Heap::kOld); + + return raw_closure; +} + +// Generates assembly to trampoline from C++ back into Dart. +static void* GenerateFfiInverseTrampoline(const Function& signature, + void* dart_entry_point) { +#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER) + UNREACHABLE(); +#elif !defined(TARGET_ARCH_X64) + // https://github.com/dart-lang/sdk/issues/35774 + UNREACHABLE(); +#elif !defined(TARGET_OS_LINUX) && !defined(TARGET_OS_MACOS) + // https://github.com/dart-lang/sdk/issues/35760 Arm32 && Android + // https://github.com/dart-lang/sdk/issues/35771 Windows + // https://github.com/dart-lang/sdk/issues/35772 Arm64 + // https://github.com/dart-lang/sdk/issues/35773 DBC + UNREACHABLE(); +#else + extern void GenerateFfiInverseTrampoline( + Assembler * assembler, const Function& signature, void* dart_entry_point); + ObjectPoolBuilder object_pool_builder; + Assembler assembler(&object_pool_builder); + GenerateFfiInverseTrampoline(&assembler, signature, dart_entry_point); + const Code& code = Code::Handle( + Code::FinalizeCode("inverse trampoline", nullptr, &assembler, + Code::PoolAttachment::kAttachPool, false)); + + uword entryPoint = code.EntryPoint(); + + return reinterpret_cast(entryPoint); +#endif +} + +// TODO(dacoharkes): Implement this feature. +// https://github.com/dart-lang/sdk/issues/35761 +// For now, it always returns Pointer with address 0. +DEFINE_NATIVE_ENTRY(Ffi_fromFunction, 1, 1) { + GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(Closure, closure, arguments->NativeArgAt(0)); + + Function& c_signature = Function::Handle(((Type&)type_arg).signature()); + + Function& func = Function::Handle(closure.function()); + Code& code = Code::Handle(func.EnsureHasCode()); + void* entryPoint = reinterpret_cast(code.EntryPoint()); + + THR_Print("Ffi_fromFunction: %s\n", type_arg.ToCString()); + THR_Print("Ffi_fromFunction: %s\n", c_signature.ToCString()); + THR_Print("Ffi_fromFunction: %s\n", closure.ToCString()); + THR_Print("Ffi_fromFunction: %s\n", func.ToCString()); + THR_Print("Ffi_fromFunction: %s\n", code.ToCString()); + THR_Print("Ffi_fromFunction: %p\n", entryPoint); + THR_Print("Ffi_fromFunction: %" Pd "\n", code.Size()); + + void* address = GenerateFfiInverseTrampoline(c_signature, entryPoint); + + TypeArguments& type_args = TypeArguments::Handle(zone); + type_args = TypeArguments::New(1); + type_args.SetTypeAt(Pointer::kNativeTypeArgPos, type_arg); + type_args ^= type_args.Canonicalize(); + + Class& native_function_class = Class::Handle( + Isolate::Current()->class_table()->At(kFfiNativeFunctionCid)); + native_function_class.EnsureIsFinalized(Thread::Current()); + + Type& native_function_type = Type::Handle( + Type::New(native_function_class, type_args, TokenPosition::kNoSource)); + native_function_type ^= + ClassFinalizer::FinalizeType(Class::Handle(), native_function_type); + native_function_type ^= native_function_type.Canonicalize(); + + address = 0; // https://github.com/dart-lang/sdk/issues/35761 + + Pointer& result = Pointer::Handle( + Pointer::New(native_function_type, reinterpret_cast(address))); + + return result.raw(); +} + +} // namespace dart diff --git a/runtime/lib/ffi_dynamic_library.cc b/runtime/lib/ffi_dynamic_library.cc new file mode 100644 index 00000000000..0d6a614c982 --- /dev/null +++ b/runtime/lib/ffi_dynamic_library.cc @@ -0,0 +1,114 @@ +// 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. + +#if !defined(TARGET_OS_LINUX) && !defined(TARGET_OS_MACOS) +// TODO(dacoharkes): implement dynamic libraries for other targets. +// see +// - runtime/vm/native_symbol.h +// - runtime/vm/native_symbol_linux.cc +// - runtime/bin/extensions.h (but we cannot import from bin) +// - runtime/bin/extensions_linux.cc +#else +#include +#endif +#include "include/dart_api.h" +#include "vm/bootstrap_natives.h" +#include "vm/exceptions.h" +#include "vm/native_entry.h" + +namespace dart { + +// Concatenates a NULL terminated array of strings. +// The returned string is scope allocated. +// TODO(dacoharkes): Can we share this with runtime/bin/extensions.cc? +const char* Concatenate(const char** strings) { + int size = 1; // null termination. + for (int i = 0; strings[i] != NULL; i++) { + size += strlen(strings[i]); + } + char* result = reinterpret_cast(Dart_ScopeAllocate(size)); + int index = 0; + for (int i = 0; strings[i] != NULL; i++) { + index += snprintf(result + index, size - index, "%s", strings[i]); + } + ASSERT(index == size - 1); + ASSERT(result[size - 1] == '\0'); + return result; +} + +// TODO(dacoharkes): Can we share this with runtime/bin/extensions.cc? +const char* LibraryPath(const char* library_name) { + const char* library_prefix = "lib"; +#if defined(TARGET_OS_LINUX) + const char* library_extension = "so"; +#elif defined(TARGET_OS_MACOS) + const char* library_extension = "dylib"; +#else + const char* library_extension = ""; + UNREACHABLE(); +#endif + + const char* path_components[] = { + library_prefix, library_name, ".", library_extension, NULL, + }; + + return Concatenate(path_components); +} + +DEFINE_NATIVE_ENTRY(Ffi_dl_open, 0, 1) { + GET_NON_NULL_NATIVE_ARGUMENT(String, argName, arguments->NativeArgAt(0)); + +#if !defined(TARGET_OS_LINUX) && !defined(TARGET_OS_MACOS) + UNREACHABLE(); +#else + dlerror(); // Clear any errors. + void* handle = dlopen(LibraryPath(argName.ToCString()), RTLD_LAZY); + if (handle == nullptr) { + char* error = dlerror(); + const String& msg = String::Handle( + String::NewFormatted("Failed to load dynamic library(%s)", error)); + Exceptions::ThrowArgumentError(msg); + } + + return DynamicLibrary::New(handle); +#endif +} + +DEFINE_NATIVE_ENTRY(Ffi_dl_lookup, 1, 2) { + GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); + + GET_NON_NULL_NATIVE_ARGUMENT(DynamicLibrary, dlib, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(String, argSymbolName, + arguments->NativeArgAt(1)); + +#if !defined(TARGET_OS_LINUX) && !defined(TARGET_OS_MACOS) + UNREACHABLE(); +#else + void* handle = dlib.GetHandle(); + + dlerror(); // Clear any errors. + uint8_t* pointer = + reinterpret_cast(dlsym(handle, argSymbolName.ToCString())); + char* error; + if ((error = dlerror()) != NULL) { + const String& msg = String::Handle( + String::NewFormatted("Failed to lookup symbol (%s)", error)); + Exceptions::ThrowArgumentError(msg); + } + + // TODO(dacoharkes): should this return NULL if addres is 0? + // https://github.com/dart-lang/sdk/issues/35756 + RawPointer* result = Pointer::New(type_arg, pointer); + return result; +#endif +} + +DEFINE_NATIVE_ENTRY(Ffi_dl_getHandle, 0, 1) { + GET_NON_NULL_NATIVE_ARGUMENT(DynamicLibrary, dlib, arguments->NativeArgAt(0)); + + intptr_t handle = reinterpret_cast(dlib.GetHandle()); + return Integer::NewFromUint64(handle); +} + +} // namespace dart diff --git a/runtime/lib/ffi_dynamic_library_patch.dart b/runtime/lib/ffi_dynamic_library_patch.dart new file mode 100644 index 00000000000..db458055e88 --- /dev/null +++ b/runtime/lib/ffi_dynamic_library_patch.dart @@ -0,0 +1,35 @@ +// 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. + +import "dart:_internal" show patch; + +DynamicLibrary _open(String name) native "Ffi_dl_open"; + +@patch +class DynamicLibrary { + @patch + @pragma("vm:entry-point") + factory DynamicLibrary.open(String name) { + return _open(name); + } + + @patch + Pointer lookup(String symbolName) + native "Ffi_dl_lookup"; + + // TODO(dacoharkes): Expose this to users, or extend Pointer? + // https://github.com/dart-lang/sdk/issues/35881 + int getHandle() native "Ffi_dl_getHandle"; + + @patch + bool operator ==(other) { + if (other == null) return false; + return getHandle() == other.getHandle(); + } + + @patch + int get hashCode { + return getHandle().hashCode; + } +} diff --git a/runtime/lib/ffi_native_type_patch.dart b/runtime/lib/ffi_native_type_patch.dart new file mode 100644 index 00000000000..b12f0b8eb00 --- /dev/null +++ b/runtime/lib/ffi_native_type_patch.dart @@ -0,0 +1,69 @@ +// 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. + +import "dart:_internal" show patch; + +@patch +@pragma("vm:entry-point") +class NativeType {} + +@patch +@pragma("vm:entry-point") +class _NativeInteger extends NativeType {} + +@patch +@pragma("vm:entry-point") +class _NativeDouble extends NativeType {} + +@patch +@pragma("vm:entry-point") +class Int8 extends _NativeInteger {} + +@patch +@pragma("vm:entry-point") +class Int16 extends _NativeInteger {} + +@patch +@pragma("vm:entry-point") +class Int32 extends _NativeInteger {} + +@patch +@pragma("vm:entry-point") +class Int64 extends _NativeInteger {} + +@patch +@pragma("vm:entry-point") +class Uint8 extends _NativeInteger {} + +@patch +@pragma("vm:entry-point") +class Uint16 extends _NativeInteger {} + +@patch +@pragma("vm:entry-point") +class Uint32 extends _NativeInteger {} + +@patch +@pragma("vm:entry-point") +class Uint64 extends _NativeInteger {} + +@patch +@pragma("vm:entry-point") +class IntPtr extends _NativeInteger {} + +@patch +@pragma("vm:entry-point") +class Float extends _NativeDouble {} + +@patch +@pragma("vm:entry-point") +class Double extends _NativeDouble {} + +@patch +@pragma("vm:entry-point") +class Void extends NativeType {} + +@patch +@pragma("vm:entry-point") +class NativeFunction extends NativeType {} diff --git a/runtime/lib/ffi_patch.dart b/runtime/lib/ffi_patch.dart new file mode 100644 index 00000000000..c8fde6c3bbc --- /dev/null +++ b/runtime/lib/ffi_patch.dart @@ -0,0 +1,55 @@ +// 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. + +import "dart:_internal" show patch; + +@patch +Pointer allocate({int count: 1}) native "Ffi_allocate"; + +@patch +T fromAddress(int ptr) native "Ffi_fromAddress"; + +@patch +int sizeOf() native "Ffi_sizeOf"; + +@patch +Pointer> fromFunction( + @DartRepresentationOf("T") Function f) native "Ffi_fromFunction"; + +@patch +@pragma("vm:entry-point") +class Pointer { + @patch + void store(Object value) native "Ffi_store"; + + @patch + R load() native "Ffi_load"; + + @patch + int get address native "Ffi_address"; + + // Note this could also be implmented without an extra native as offsetBy + // (elementSize()*index). This would be 2 native calls rather than one. What + // would be better? + @patch + Pointer elementAt(int index) native "Ffi_elementAt"; + + // Note this could also be implmented without an extra native as + // fromAddress(address). This would be 2 native calls rather than one. + // What would be better? + @patch + Pointer offsetBy(int offsetInBytes) native "Ffi_offsetBy"; + + // Note this could also be implemented without an extra native as + // fromAddress(address). This would be 2 native calls rather than one. + // What would be better? + @patch + U cast() native "Ffi_cast"; + + @patch + R asFunction() native "Ffi_asFunction"; + + @patch + void free() native "Ffi_free"; +} diff --git a/runtime/lib/ffi_sources.gni b/runtime/lib/ffi_sources.gni new file mode 100644 index 00000000000..fd625f40913 --- /dev/null +++ b/runtime/lib/ffi_sources.gni @@ -0,0 +1,17 @@ +# 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. + +# Sources visible via dart:ffi library. +ffi_runtime_cc_files = [ + "ffi.cc", + "ffi_dynamic_library.cc", +] + +ffi_runtime_dart_files = [ + "ffi_patch.dart", + "ffi_dynamic_library_patch.dart", + "ffi_native_type_patch.dart", +] + +ffi_runtime_sources = ffi_runtime_cc_files + ffi_runtime_dart_files diff --git a/runtime/vm/BUILD.gn b/runtime/vm/BUILD.gn index caaad921e00..d018eb17027 100644 --- a/runtime/vm/BUILD.gn +++ b/runtime/vm/BUILD.gn @@ -8,6 +8,7 @@ import("../../sdk/lib/collection/collection_sources.gni") import("../../sdk/lib/convert/convert_sources.gni") import("../../sdk/lib/core/core_sources.gni") import("../../sdk/lib/developer/developer_sources.gni") +import("../../sdk/lib/ffi/ffi_sources.gni") import("../../sdk/lib/internal/internal_sources.gni") import("../../sdk/lib/isolate/isolate_sources.gni") import("../../sdk/lib/math/math_sources.gni") @@ -16,13 +17,15 @@ import("../../sdk/lib/profiler/profiler_sources.gni") import("../../sdk/lib/typed_data/typed_data_sources.gni") import("../../sdk/lib/vmservice/vmservice_sources.gni") import("../../utils/compile_platform.gni") -import("../bin/io_sources.gni") import("../bin/cli_sources.gni") +import("../bin/io_sources.gni") +import("../configs.gni") import("../lib/async_sources.gni") import("../lib/collection_sources.gni") import("../lib/convert_sources.gni") import("../lib/core_sources.gni") import("../lib/developer_sources.gni") +import("../lib/ffi_sources.gni") import("../lib/internal_sources.gni") import("../lib/isolate_sources.gni") import("../lib/math_sources.gni") @@ -30,7 +33,6 @@ import("../lib/mirrors_sources.gni") import("../lib/profiler_sources.gni") import("../lib/typed_data_sources.gni") import("../lib/vmservice_sources.gni") -import("../configs.gni") import("../runtime_args.gni") import("compiler/compiler_sources.gni") import("heap/heap_sources.gni") @@ -84,12 +86,13 @@ library_for_all_configs("libdart_vm") { library_for_all_configs("libdart_lib") { target_type = "source_set" include_dirs = [ ".." ] - allsources = async_runtime_sources + collection_runtime_sources + - convert_runtime_sources + core_runtime_sources + - developer_runtime_sources + internal_runtime_sources + - isolate_runtime_sources + math_runtime_sources + - mirrors_runtime_sources + profiler_runtime_sources + - typed_data_runtime_sources + vmservice_runtime_sources + allsources = + async_runtime_sources + collection_runtime_sources + + convert_runtime_sources + core_runtime_sources + + developer_runtime_sources + internal_runtime_sources + + isolate_runtime_sources + math_runtime_sources + mirrors_runtime_sources + + profiler_runtime_sources + typed_data_runtime_sources + + vmservice_runtime_sources + ffi_runtime_sources sources = [ "bootstrap.cc" ] + rebase_path(allsources, ".", "../lib") snapshot_sources = [] nosnapshot_sources = [] diff --git a/runtime/vm/bootstrap_natives.cc b/runtime/vm/bootstrap_natives.cc index e02c6fa0384..a0e99e3f55d 100644 --- a/runtime/vm/bootstrap_natives.cc +++ b/runtime/vm/bootstrap_natives.cc @@ -100,6 +100,11 @@ void Bootstrap::SetupNativeResolver() { library.set_native_entry_resolver(resolver); library.set_native_entry_symbol_resolver(symbol_resolver); + library = Library::FfiLibrary(); + ASSERT(!library.IsNull()); + library.set_native_entry_resolver(resolver); + library.set_native_entry_symbol_resolver(symbol_resolver); + library = Library::InternalLibrary(); ASSERT(!library.IsNull()); library.set_native_entry_resolver(resolver); diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index 551784dc994..194a86c496c 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -350,7 +350,22 @@ namespace dart { V(VMService_CancelStream, 1) \ V(VMService_RequestAssets, 0) \ V(VMService_DecodeAssets, 1) \ - V(VMService_spawnUriNotify, 2) + V(VMService_spawnUriNotify, 2) \ + V(Ffi_allocate, 1) \ + V(Ffi_free, 1) \ + V(Ffi_load, 1) \ + V(Ffi_store, 2) \ + V(Ffi_address, 1) \ + V(Ffi_fromAddress, 1) \ + V(Ffi_elementAt, 2) \ + V(Ffi_offsetBy, 2) \ + V(Ffi_cast, 1) \ + V(Ffi_sizeOf, 0) \ + V(Ffi_asFunction, 1) \ + V(Ffi_fromFunction, 1) \ + V(Ffi_dl_open, 1) \ + V(Ffi_dl_lookup, 2) \ + V(Ffi_dl_getHandle, 1) // List of bootstrap native entry points used in the dart:mirror library. #define MIRRORS_BOOTSTRAP_NATIVE_LIST(V) \ diff --git a/runtime/vm/class_finalizer.cc b/runtime/vm/class_finalizer.cc index 967a1989b3f..a60217bacf5 100644 --- a/runtime/vm/class_finalizer.cc +++ b/runtime/vm/class_finalizer.cc @@ -1385,6 +1385,7 @@ void ClassFinalizer::VerifyImplicitFieldOffsets() { String& name = String::Handle(zone); String& expected_name = String::Handle(zone); Error& error = Error::Handle(zone); + TypeParameter& type_param = TypeParameter::Handle(zone); // First verify field offsets of all the TypedDataView classes. for (intptr_t cid = kTypedDataInt8ArrayViewCid; @@ -1443,6 +1444,15 @@ void ClassFinalizer::VerifyImplicitFieldOffsets() { name ^= field.name(); expected_name ^= String::New("_data"); ASSERT(String::EqualsIgnoringPrivateKey(name, expected_name)); + + // Now verify field offsets of 'Pointer' class. + cls = class_table.At(kFfiPointerCid); + error = cls.EnsureIsFinalized(thread); + ASSERT(error.IsNull()); + ASSERT(cls.NumOwnTypeArguments() == 1); + type_param ^= TypeParameter::RawCast( + TypeArguments::Handle(cls.type_parameters()).TypeAt(0)); + ASSERT(Pointer::kNativeTypeArgPos == type_param.index()); #endif } diff --git a/runtime/vm/class_id.h b/runtime/vm/class_id.h index 90b1988ce34..ddbd7a05633 100644 --- a/runtime/vm/class_id.h +++ b/runtime/vm/class_id.h @@ -19,6 +19,7 @@ namespace dart { V(ClosureData) \ V(SignatureData) \ V(RedirectionData) \ + V(FfiTrampolineData) \ V(Field) \ V(Script) \ V(Library) \ @@ -65,6 +66,8 @@ namespace dart { V(Float64x2) \ V(TypedData) \ V(ExternalTypedData) \ + V(Pointer) \ + V(DynamicLibrary) \ V(Capability) \ V(ReceivePort) \ V(SendPort) \ @@ -102,6 +105,27 @@ namespace dart { V(Int32x4Array) \ V(Float64x2Array) +#define CLASS_LIST_FFI_TYPE_MARKER(V) \ + V(Int8) \ + V(Int16) \ + V(Int32) \ + V(Int64) \ + V(Uint8) \ + V(Uint16) \ + V(Uint32) \ + V(Uint64) \ + V(IntPtr) \ + V(Float) \ + V(Double) \ + V(Void) + +#define CLASS_LIST_FFI(V) \ + V(Pointer) \ + V(NativeFunction) \ + CLASS_LIST_FFI_TYPE_MARKER(V) \ + V(NativeType) \ + V(DynamicLibrary) + #define DART_CLASS_LIST_TYPED_DATA(V) \ V(Int8) \ V(Uint8) \ @@ -152,6 +176,10 @@ enum ClassId { CLASS_LIST(DEFINE_OBJECT_KIND) #undef DEFINE_OBJECT_KIND +#define DEFINE_OBJECT_KIND(clazz) kFfi##clazz##Cid, + CLASS_LIST_FFI(DEFINE_OBJECT_KIND) +#undef DEFINE_OBJECT_KIND + // clang-format off #define DEFINE_OBJECT_KIND(clazz) kTypedData##clazz##Cid, CLASS_LIST_TYPED_DATA(DEFINE_OBJECT_KIND) diff --git a/runtime/vm/compiler/assembler/assembler_x64.h b/runtime/vm/compiler/assembler/assembler_x64.h index c69bef3fb8b..db70602716c 100644 --- a/runtime/vm/compiler/assembler/assembler_x64.h +++ b/runtime/vm/compiler/assembler/assembler_x64.h @@ -474,6 +474,10 @@ class Assembler : public AssemblerBase { // for proper unwinding of Dart frames (use --generate_gdb_symbols and -O0). void movq(Register dst, Register src) { EmitQ(src, dst, 0x89); } + void movq(XmmRegister dst, Register src) { + EmitQ(dst, src, 0x6E, 0x0F, 0x66); + } + void movd(XmmRegister dst, Register src) { EmitL(dst, src, 0x6E, 0x0F, 0x66); } diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index 1af6d53fa2e..bf8977afaf2 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -2030,6 +2030,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraph() { return flow_graph_builder_->BuildGraphOfInvokeFieldDispatcher(function); case RawFunction::kSignatureFunction: case RawFunction::kIrregexpFunction: + case RawFunction::kFfiTrampoline: break; } UNREACHABLE(); diff --git a/runtime/vm/compiler/frontend/scope_builder.cc b/runtime/vm/compiler/frontend/scope_builder.cc index a5f322a0b01..d585002d25a 100644 --- a/runtime/vm/compiler/frontend/scope_builder.cc +++ b/runtime/vm/compiler/frontend/scope_builder.cc @@ -383,6 +383,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() { break; case RawFunction::kSignatureFunction: case RawFunction::kIrregexpFunction: + case RawFunction::kFfiTrampoline: UNREACHABLE(); } if (needs_expr_temp_) { diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc index b70280eee17..66292faeb30 100644 --- a/runtime/vm/compiler/jit/compiler.cc +++ b/runtime/vm/compiler/jit/compiler.cc @@ -103,6 +103,9 @@ static void PrecompilationModeHandler(bool value) { FLAG_background_compilation = false; FLAG_collect_code = false; FLAG_enable_mirrors = false; + // TODO(dacoharkes): Ffi support in AOT + // https://github.com/dart-lang/sdk/issues/35765 + FLAG_enable_ffi = false; FLAG_fields_may_be_reset = true; FLAG_interpret_irregexp = true; FLAG_lazy_dispatchers = false; diff --git a/runtime/vm/constants_x64.cc b/runtime/vm/constants_x64.cc new file mode 100644 index 00000000000..df6fb759a7d --- /dev/null +++ b/runtime/vm/constants_x64.cc @@ -0,0 +1,27 @@ +// 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. + +#include "vm/constants_x64.h" + +namespace dart { + +#if defined(_WIN64) +const Register CallingConventions::ArgumentRegisters[] = { + CallingConventions::kArg1Reg, CallingConventions::kArg2Reg, + CallingConventions::kArg3Reg, CallingConventions::kArg4Reg}; + +const XmmRegister CallingConventions::XmmArgumentRegisters[] = { + XmmRegister::XMM0, XmmRegister::XMM1, XmmRegister::XMM2, XmmRegister::XMM3}; +#else +const Register CallingConventions::ArgumentRegisters[] = { + CallingConventions::kArg1Reg, CallingConventions::kArg2Reg, + CallingConventions::kArg3Reg, CallingConventions::kArg4Reg, + CallingConventions::kArg5Reg, CallingConventions::kArg6Reg}; + +const XmmRegister CallingConventions::XmmArgumentRegisters[] = { + XmmRegister::XMM0, XmmRegister::XMM1, XmmRegister::XMM2, XmmRegister::XMM3, + XmmRegister::XMM4, XmmRegister::XMM5, XmmRegister::XMM6, XmmRegister::XMM7}; +#endif + +} // namespace dart diff --git a/runtime/vm/constants_x64.h b/runtime/vm/constants_x64.h index 512ff982ddb..5de7ddc2191 100644 --- a/runtime/vm/constants_x64.h +++ b/runtime/vm/constants_x64.h @@ -5,6 +5,9 @@ #ifndef RUNTIME_VM_CONSTANTS_X64_H_ #define RUNTIME_VM_CONSTANTS_X64_H_ +#include "platform/assert.h" +#include "platform/globals.h" + namespace dart { enum Register { @@ -149,6 +152,20 @@ class CallingConventions { static const Register kArg2Reg = RDX; static const Register kArg3Reg = R8; static const Register kArg4Reg = R9; + static const Register ArgumentRegisters[]; + static const intptr_t kArgumentRegisters = + R(kArg1Reg) | R(kArg2Reg) | R(kArg3Reg) | R(kArg4Reg); + static const intptr_t kNumArgRegs = 4; + + static const XmmRegister XmmArgumentRegisters[]; + static const intptr_t kXmmArgumentRegisters = + R(XMM0) | R(XMM1) | R(XMM2) | R(XMM3); + static const intptr_t kNumXmmArgRegs = 4; + + // can ArgumentRegisters[i] and XmmArgumentRegisters[i] both be used at the + // same time? (Windows no, rest yes) + static const bool kArgumentIntRegXorXmmReg = true; + static const intptr_t kShadowSpaceBytes = 4 * kWordSize; static const intptr_t kVolatileCpuRegisters = @@ -164,6 +181,8 @@ class CallingConventions { R(XMM6) | R(XMM7) | R(XMM8) | R(XMM9) | R(XMM10) | R(XMM11) | R(XMM12) | R(XMM13) | R(XMM14) | R(XMM15); + static const XmmRegister xmmFirstNonParameterReg = XMM4; + // Windows x64 ABI specifies that small objects are passed in registers. // Otherwise they are passed by reference. static const size_t kRegisterTransferLimit = 16; @@ -177,6 +196,22 @@ class CallingConventions { static const Register kArg4Reg = RCX; static const Register kArg5Reg = R8; static const Register kArg6Reg = R9; + static const Register ArgumentRegisters[]; + static const intptr_t kArgumentRegisters = R(kArg1Reg) | R(kArg2Reg) | + R(kArg3Reg) | R(kArg4Reg) | + R(kArg5Reg) | R(kArg6Reg); + static const intptr_t kNumArgRegs = 6; + + static const XmmRegister XmmArgumentRegisters[]; + static const intptr_t kXmmArgumentRegisters = R(XMM0) | R(XMM1) | R(XMM2) | + R(XMM3) | R(XMM4) | R(XMM5) | + R(XMM6) | R(XMM7); + static const intptr_t kNumXmmArgRegs = 8; + + // can ArgumentRegisters[i] and XmmArgumentRegisters[i] both be used at the + // same time? (Windows no, rest yes) + static const bool kArgumentIntRegXorXmmReg = false; + static const intptr_t kShadowSpaceBytes = 0; static const intptr_t kVolatileCpuRegisters = R(RAX) | R(RCX) | R(RDX) | @@ -192,6 +227,8 @@ class CallingConventions { R(RBX) | R(R12) | R(R13) | R(R14) | R(R15); static const intptr_t kCalleeSaveXmmRegisters = 0; + + static const XmmRegister xmmFirstNonParameterReg = XMM8; }; #endif diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc index 9cd844a5f06..8e45f57d565 100644 --- a/runtime/vm/dart_api_impl.cc +++ b/runtime/vm/dart_api_impl.cc @@ -4774,6 +4774,10 @@ RawString* Api::GetEnvironmentValue(Thread* thread, const String& name) { return Symbols::False().raw(); } + if (!Api::ffiEnabled() && name.Equals(Symbols::DartLibraryFfi())) { + return Symbols::False().raw(); + } + if (name.Equals(Symbols::DartVMProduct())) { #ifdef PRODUCT return Symbols::True().raw(); diff --git a/runtime/vm/dart_api_impl.h b/runtime/vm/dart_api_impl.h index 9c3518ab02d..570a855dfe3 100644 --- a/runtime/vm/dart_api_impl.h +++ b/runtime/vm/dart_api_impl.h @@ -294,6 +294,25 @@ class Api : AllStatic { static RawString* GetEnvironmentValue(Thread* thread, const String& name); + static bool ffiEnabled() { + // dart:ffi is not implemented for the following configurations +#if !defined(TARGET_ARCH_X64) + // https://github.com/dart-lang/sdk/issues/35774 + return false; +#elif !defined(TARGET_OS_LINUX) && !defined(TARGET_OS_MACOS) + // https://github.com/dart-lang/sdk/issues/35760 Arm32 && Android + // https://github.com/dart-lang/sdk/issues/35771 Windows + // https://github.com/dart-lang/sdk/issues/35772 Arm64 + // https://github.com/dart-lang/sdk/issues/35773 DBC + return false; +#else + // dart:ffi is also not implemented for precompiled in which case + // FLAG_enable_ffi is set to false by --precompilation. + // Once dart:ffi is supported on all targets, only users will set this flag + return FLAG_enable_ffi; +#endif + } + private: static Dart_Handle InitNewHandle(Thread* thread, RawObject* raw); diff --git a/runtime/vm/ffi_trampoline_stubs_x64.cc b/runtime/vm/ffi_trampoline_stubs_x64.cc new file mode 100644 index 00000000000..9374f61784b --- /dev/null +++ b/runtime/vm/ffi_trampoline_stubs_x64.cc @@ -0,0 +1,564 @@ +// 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. + +// TODO(dacoharkes): Move this into compiler namespace. + +#include "vm/globals.h" + +#include "vm/stub_code.h" + +#if defined(TARGET_ARCH_X64) && !defined(DART_PRECOMPILED_RUNTIME) + +#include "vm/compiler/assembler/assembler.h" +#include "vm/compiler/assembler/disassembler.h" +#include "vm/compiler/backend/flow_graph_compiler.h" +#include "vm/compiler/jit/compiler.h" +#include "vm/constants_x64.h" +#include "vm/dart_entry.h" +#include "vm/heap/heap.h" +#include "vm/heap/scavenger.h" +#include "vm/instructions.h" +#include "vm/object_store.h" +#include "vm/resolver.h" +#include "vm/stack_frame.h" +#include "vm/tags.h" +#include "vm/type_testing_stubs.h" + +#define __ assembler-> + +namespace dart { + +static Representation TypeRepresentation(const AbstractType& result_type) { + switch (result_type.type_class_id()) { + case kFfiFloatCid: + case kFfiDoubleCid: + return kUnboxedDouble; + case kFfiInt8Cid: + case kFfiInt16Cid: + case kFfiInt32Cid: + case kFfiInt64Cid: + case kFfiUint8Cid: + case kFfiUint16Cid: + case kFfiUint32Cid: + case kFfiUint64Cid: + case kFfiIntPtrCid: + case kFfiPointerCid: + default: // Subtypes of Pointer. + return kUnboxedInt64; + } +} + +// Converts a Ffi [signature] to a list of Representations. +// Note that this ignores first argument (receiver) which is dynamic. +static ZoneGrowableArray* ArgumentRepresentations( + const Function& signature) { + intptr_t num_arguments = signature.num_fixed_parameters() - 1; + auto result = new ZoneGrowableArray(num_arguments); + for (intptr_t i = 0; i < num_arguments; i++) { + AbstractType& arg_type = + AbstractType::Handle(signature.ParameterTypeAt(i + 1)); + result->Add(TypeRepresentation(arg_type)); + } + return result; +} + +// Takes a list of argument representations, and converts it to a list of +// argument locations based on calling convention. +static ZoneGrowableArray* ArgumentLocations( + const ZoneGrowableArray& arg_representations) { + intptr_t num_arguments = arg_representations.length(); + auto result = new ZoneGrowableArray(num_arguments); + result->FillWith(Location(), 0, num_arguments); + Location* data = result->data(); + + // Loop through all arguments and assign a register or a stack location. + intptr_t int_regs_used = 0; + intptr_t xmm_regs_used = 0; + intptr_t nth_stack_argument = 0; + bool on_stack; + for (intptr_t i = 0; i < num_arguments; i++) { + on_stack = true; + switch (arg_representations.At(i)) { + case kUnboxedInt64: + if (int_regs_used < CallingConventions::kNumArgRegs) { + data[i] = Location::RegisterLocation( + CallingConventions::ArgumentRegisters[int_regs_used]); + int_regs_used++; + if (CallingConventions::kArgumentIntRegXorXmmReg) { + xmm_regs_used++; + } + on_stack = false; + } + break; + case kUnboxedDouble: + if (xmm_regs_used < CallingConventions::kNumXmmArgRegs) { + data[i] = Location::FpuRegisterLocation( + CallingConventions::XmmArgumentRegisters[xmm_regs_used]); + xmm_regs_used++; + if (CallingConventions::kArgumentIntRegXorXmmReg) { + int_regs_used++; + } + on_stack = false; + } + break; + default: + UNREACHABLE(); + } + if (on_stack) { + data[i] = Location::StackSlot(nth_stack_argument, RSP); + nth_stack_argument++; + } + } + return result; +} + +static intptr_t NumStackArguments( + const ZoneGrowableArray& locations) { + intptr_t num_arguments = locations.length(); + intptr_t num_stack_arguments = 0; + for (intptr_t i = 0; i < num_arguments; i++) { + if (locations.At(i).IsStackSlot()) { + num_stack_arguments++; + } + } + return num_stack_arguments; +} + +// Input parameters: +// Register reg : a Null, or something else +static void GenerateNotNullCheck(Assembler* assembler, Register reg) { + Label not_null; + Address throw_null_pointer_address = + Address(THR, Thread::OffsetFromThread(&kArgumentNullErrorRuntimeEntry)); + + __ CompareObject(reg, Object::null_object()); + __ j(NOT_EQUAL, ¬_null, Assembler::kNearJump); + + // TODO(dacoharkes): Create the message here and use + // kArgumentErrorRuntimeEntry to report which argument was null. + __ movq(CODE_REG, Address(THR, Thread::call_to_runtime_stub_offset())); + __ movq(RBX, throw_null_pointer_address); + __ movq(R10, Immediate(0)); + __ call(Address(THR, Thread::call_to_runtime_entry_point_offset())); + + __ Bind(¬_null); +} + +// Saves an int64 in the thread so GC does not trip. +// +// Input parameters: +// Register src : a C int64 +static void GenerateSaveInt64GCSafe(Assembler* assembler, Register src) { + __ movq(Address(THR, Thread::unboxed_int64_runtime_arg_offset()), src); +} + +// Loads an int64 from the thread. +static void GenerateLoadInt64GCSafe(Assembler* assembler, Register dst) { + __ movq(dst, Address(THR, Thread::unboxed_int64_runtime_arg_offset())); +} + +// Takes a Dart int and converts it to a C int64. +// +// Input parameters: +// Register reg : a Dart Null, Smi, or Mint +// Output parameters: +// Register reg : a C int64 +// Invariant: keeps ArgumentRegisters and XmmArgumentRegisters intact +void GenerateMarshalInt64(Assembler* assembler, Register reg) { + ASSERT(reg != TMP); + ASSERT((1 << TMP & CallingConventions::kArgumentRegisters) == 0); + Label done, not_smi; + + // Exception on Null + GenerateNotNullCheck(assembler, reg); + + // Smi or Mint? + __ movq(TMP, reg); + __ testq(TMP, Immediate(kSmiTagMask)); + __ j(NOT_ZERO, ¬_smi, Assembler::kNearJump); + + // Smi + __ SmiUntag(reg); + __ jmp(&done, Assembler::kNearJump); + + // Mint + __ Bind(¬_smi); + __ movq(reg, FieldAddress(reg, Mint::value_offset())); + __ Bind(&done); +} + +// Takes a C int64 and converts it to a Dart int. +// +// Input parameters: +// RAX : a C int64 +// Output paramaters: +// RAX : a Dart Smi or Mint +static void GenerateUnmarshalInt64(Assembler* assembler) { + const Class& mint_class = + Class::ZoneHandle(Isolate::Current()->object_store()->mint_class()); + ASSERT(!mint_class.IsNull()); + const auto& mint_allocation_stub = + Code::ZoneHandle(StubCode::GetAllocationStubForClass(mint_class)); + ASSERT(!mint_allocation_stub.IsNull()); + Label done; + + // Try whether it fits in a Smi. + __ movq(TMP, RAX); + __ SmiTag(RAX); + __ j(NO_OVERFLOW, &done, Assembler::kNearJump); + + // Mint + // Backup result value (to avoid GC). + GenerateSaveInt64GCSafe(assembler, TMP); + + // Allocate object (can call into runtime). + __ Call(mint_allocation_stub); + + // Store result value. + GenerateLoadInt64GCSafe(assembler, TMP); + __ movq(FieldAddress(RAX, Mint::value_offset()), TMP); + + __ Bind(&done); +} + +// Takes a Dart double and converts it into a C double. +// +// Input parameters: +// Register reg : a Dart Null or Double +// Output parameters: +// XmmRegister xmm_reg : a C double +// Invariant: keeps ArgumentRegisters and other XmmArgumentRegisters intact +static void GenerateMarshalDouble(Assembler* assembler, + Register reg, + XmmRegister xmm_reg) { + ASSERT((1 << reg & CallingConventions::kArgumentRegisters) == 0); + + // Throw a Dart Exception on Null. + GenerateNotNullCheck(assembler, reg); + + __ movq(reg, FieldAddress(reg, Double::value_offset())); + __ movq(xmm_reg, reg); +} + +// Takes a C double and converts it into a Dart double. +// +// Input parameters: +// XMM0 : a C double +// Output parameters: +// RAX : a Dart Double +static void GenerateUnmarshalDouble(Assembler* assembler) { + const auto& double_class = + Class::ZoneHandle(Isolate::Current()->object_store()->double_class()); + ASSERT(!double_class.IsNull()); + const auto& double_allocation_stub = + Code::ZoneHandle(StubCode::GetAllocationStubForClass(double_class)); + ASSERT(!double_allocation_stub.IsNull()); + + // Backup result value (to avoid GC). + __ movq(RAX, XMM0); + GenerateSaveInt64GCSafe(assembler, RAX); + + // Allocate object (can call into runtime). + __ Call(double_allocation_stub); + + // Store the result value. + GenerateLoadInt64GCSafe(assembler, TMP); + __ movq(FieldAddress(RAX, Double::value_offset()), TMP); +} + +// Takes a Dart double and converts into a C float. +// +// Input parameters: +// Register reg : a Dart double +// Output parameters: +// XmmRegister xxmReg : a C float +// Invariant: keeps ArgumentRegisters and other XmmArgumentRegisters intact +static void GenerateMarshalFloat(Assembler* assembler, + Register reg, + XmmRegister xmm_reg) { + ASSERT((1 << reg & CallingConventions::kArgumentRegisters) == 0); + + GenerateMarshalDouble(assembler, reg, xmm_reg); + + __ cvtsd2ss(xmm_reg, xmm_reg); +} + +// Takes a C float and converts it into a Dart double. +// +// Input parameters: +// XMM0 : a C float +// Output paramaters: +// RAX : a Dart Double +static void GenerateUnmarshalFloat(Assembler* assembler) { + __ cvtss2sd(XMM0, XMM0); + GenerateUnmarshalDouble(assembler); +} + +// Takes a Dart ffi.Pointer and converts it into a C pointer. +// +// Input parameters: +// Register reg : a Dart ffi.Pointer or Null +// Output parameters: +// Register reg : a C pointer +static void GenerateMarshalPointer(Assembler* assembler, Register reg) { + Label done, not_null; + + __ CompareObject(reg, Object::null_object()); + __ j(NOT_EQUAL, ¬_null, Assembler::kNearJump); + + // If null, the address is 0. + __ movq(reg, Immediate(0)); + __ jmp(&done); + + // If not null but a Pointer, load the address. + __ Bind(¬_null); + __ movq(reg, FieldAddress(reg, Pointer::address_offset())); + __ Bind(&done); +} + +// Takes a C pointer and converts it into a Dart ffi.Pointer or Null. +// +// Input parameters: +// RAX : a C pointer +// Outpot paramaters: +// RAX : a Dart ffi.Pointer or Null +static void GenerateUnmarshalPointer(Assembler* assembler, + Address closure_dart, + const Class& pointer_class) { + Label done, not_null; + ASSERT(!pointer_class.IsNull()); + const auto& pointer_allocation_stub = + Code::ZoneHandle(StubCode::GetAllocationStubForClass(pointer_class)); + ASSERT(!pointer_allocation_stub.IsNull()); + + // If the address is 0, return a Dart Null. + __ cmpq(RAX, Immediate(0)); + __ j(NOT_EQUAL, ¬_null, Assembler::kNearJump); + __ LoadObject(RAX, Object::null_object()); + __ jmp(&done); + + // Backup result value (to avoid GC). + __ Bind(¬_null); + GenerateSaveInt64GCSafe(assembler, RAX); + + // Allocate object (can call into runtime). + __ movq(TMP, closure_dart); + __ movq(TMP, FieldAddress(TMP, Closure::function_offset())); + __ movq(TMP, FieldAddress(TMP, Function::result_type_offset())); + __ pushq(FieldAddress(TMP, Type::arguments_offset())); + __ Call(pointer_allocation_stub); + __ popq(TMP); // Pop type arguments. + + // Store the result value. + GenerateLoadInt64GCSafe(assembler, RDX); + __ movq(FieldAddress(RAX, Pointer::address_offset()), RDX); + __ Bind(&done); +} + +static void GenerateMarshalArgument(Assembler* assembler, + const AbstractType& arg_type, + Register reg, + XmmRegister xmm_reg) { + switch (arg_type.type_class_id()) { + case kFfiInt8Cid: + case kFfiInt16Cid: + case kFfiInt32Cid: + case kFfiInt64Cid: + case kFfiUint8Cid: + case kFfiUint16Cid: + case kFfiUint32Cid: + case kFfiUint64Cid: + case kFfiIntPtrCid: + // TODO(dacoharkes): Truncate and sign extend 8 bit and 16 bit, and write + // tests. https://github.com/dart-lang/sdk/issues/35787 + GenerateMarshalInt64(assembler, reg); + return; + case kFfiFloatCid: + GenerateMarshalFloat(assembler, reg, xmm_reg); + return; + case kFfiDoubleCid: + GenerateMarshalDouble(assembler, reg, xmm_reg); + return; + case kFfiPointerCid: + default: // Subtypes of Pointer. + GenerateMarshalPointer(assembler, reg); + return; + } +} + +static void GenerateUnmarshalResult(Assembler* assembler, + const AbstractType& result_type, + Address closure_dart) { + switch (result_type.type_class_id()) { + case kFfiInt8Cid: + case kFfiInt16Cid: + case kFfiInt32Cid: + case kFfiInt64Cid: + case kFfiUint8Cid: + case kFfiUint16Cid: + case kFfiUint32Cid: + case kFfiUint64Cid: + case kFfiIntPtrCid: + GenerateUnmarshalInt64(assembler); + return; + case kFfiFloatCid: + GenerateUnmarshalFloat(assembler); + return; + case kFfiDoubleCid: + GenerateUnmarshalDouble(assembler); + return; + case kFfiPointerCid: + default: // subtypes of Pointer + break; + } + Class& cls = Class::ZoneHandle(Thread::Current()->zone(), + Type::Cast(result_type).type_class()); + + GenerateUnmarshalPointer(assembler, closure_dart, cls); +} + +// Generates a assembly for dart:ffi trampolines: +// - marshal arguments +// - put the arguments in registers and on the c stack +// - invoke the c function +// - (c result register is the same as dart, so keep in place) +// - unmarshal c result +// - return +// +// Input parameters: +// RSP + kWordSize * num_arguments : closure. +// RSP + kWordSize * (num_arguments - 1) : arg 1. +// RSP + kWordSize * (num_arguments - 2) : arg 2. +// RSP + kWordSize : arg n. +// After entering stub: +// RBP = RSP (before stub) - kWordSize +// RBP + kWordSize * (num_arguments + 1) : closure. +// RBP + kWordSize * num_arguments : arg 1. +// RBP + kWordSize * (num_arguments - 1) : arg 2. +// RBP + kWordSize * 2 : arg n. +// +// TODO(dacoharkes): Test truncation on non 64 bits ints and floats. +void GenerateFfiTrampoline(Assembler* assembler, const Function& signature) { + ZoneGrowableArray* arg_representations = + ArgumentRepresentations(signature); + ZoneGrowableArray* arg_locations = + ArgumentLocations(*arg_representations); + + intptr_t num_dart_arguments = signature.num_fixed_parameters(); + intptr_t num_arguments = num_dart_arguments - 1; // ignore closure + + __ EnterStubFrame(); + + // Save exit frame information to enable stack walking as we are about + // to transition to Dart VM C++ code. + __ movq(Address(THR, Thread::top_exit_frame_info_offset()), RBP); + +#if defined(DEBUG) + { + Label ok; + // Check that we are always entering from Dart code. + __ movq(TMP, Immediate(VMTag::kDartCompiledTagId)); + __ cmpq(TMP, Assembler::VMTagAddress()); + __ j(EQUAL, &ok, Assembler::kNearJump); + __ Stop("Not coming from Dart code."); + __ Bind(&ok); + } +#endif + + // Reserve space for arguments and align frame before entering C++ world. + __ subq(RSP, Immediate(NumStackArguments(*arg_locations) * kWordSize)); + if (OS::ActivationFrameAlignment() > 1) { + __ andq(RSP, Immediate(~(OS::ActivationFrameAlignment() - 1))); + } + + // Prepare address for calling the C function. + Address closure_dart = Address(RBP, (num_dart_arguments + 1) * kWordSize); + __ movq(RBX, closure_dart); + __ movq(RBX, FieldAddress(RBX, Closure::context_offset())); + __ movq(RBX, FieldAddress(RBX, Context::variable_offset(0))); + GenerateMarshalInt64(assembler, RBX); // Address is a Smi or Mint. + + // Marshal arguments and store in the right register. + for (intptr_t i = 0; i < num_arguments; i++) { + Representation rep = arg_representations->At(i); + Location loc = arg_locations->At(i); + + // We do marshalling in the the target register or in RAX. + Register reg = loc.IsRegister() ? loc.reg() : RAX; + // For doubles and floats we use target xmm register or first non param reg. + FpuRegister xmm_reg = loc.IsFpuRegister() + ? loc.fpu_reg() + : CallingConventions::xmmFirstNonParameterReg; + + // Load parameter from Dart stack. + __ movq(reg, Address(RBP, (num_arguments + 1 - i) * kWordSize)); + + // Marshal argument. + AbstractType& arg_type = + AbstractType::Handle(signature.ParameterTypeAt(i + 1)); + GenerateMarshalArgument(assembler, arg_type, reg, xmm_reg); + + // Store marshalled argument where c expects value. + if (loc.IsStackSlot()) { + if (rep == kUnboxedDouble) { + __ movq(reg, xmm_reg); + } + __ movq(loc.ToStackSlotAddress(), reg); + } + } + + // Mark that the thread is executing VM code. + __ movq(Assembler::VMTagAddress(), RBX); + + __ CallCFunction(RBX); + + // Mark that the thread is executing Dart code. + __ movq(Assembler::VMTagAddress(), Immediate(VMTag::kDartCompiledTagId)); + + // Unmarshal result. + AbstractType& return_type = AbstractType::Handle(signature.result_type()); + GenerateUnmarshalResult(assembler, return_type, closure_dart); + + // Reset exit frame information in Isolate structure. + __ movq(Address(THR, Thread::top_exit_frame_info_offset()), Immediate(0)); + + __ LeaveStubFrame(); + + __ ret(); +} + +void GenerateFfiInverseTrampoline(Assembler* assembler, + const Function& signature, + void* dart_entry_point) { + ZoneGrowableArray* arg_representations = + ArgumentRepresentations(signature); + ZoneGrowableArray* arg_locations = + ArgumentLocations(*arg_representations); + + intptr_t num_dart_arguments = signature.num_fixed_parameters(); + intptr_t num_arguments = num_dart_arguments - 1; // Ignore closure. + + // TODO(dacoharkes): Implement this. + // https://github.com/dart-lang/sdk/issues/35761 + // Look at StubCode::GenerateInvokeDartCodeStub. + + __ int3(); + + for (intptr_t i = 0; i < num_arguments; i++) { + Register reg = arg_locations->At(i).reg(); + __ SmiTag(reg); + } + + __ movq(RBX, Immediate(reinterpret_cast(dart_entry_point))); + + __ int3(); + + __ call(RBX); + + __ int3(); +} + +} // namespace dart + +#endif // defined(TARGET_ARCH_X64) && !defined(DART_PRECOMPILED_RUNTIME) diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h index a98669f4112..edd0226a1cb 100644 --- a/runtime/vm/flag_list.h +++ b/runtime/vm/flag_list.h @@ -92,6 +92,7 @@ constexpr bool kDartPrecompiledRuntime = false; "Compile expressions with the Kernel front-end.") \ P(enable_mirrors, bool, true, \ "Disable to make importing dart:mirrors an error.") \ + P(enable_ffi, bool, true, "Disable to make importing dart:ffi an error.") \ P(fields_may_be_reset, bool, false, \ "Don't optimize away static field initialization") \ C(force_clone_compiler_objects, false, false, bool, false, \ diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc index 690c63203d0..981783221ab 100644 --- a/runtime/vm/kernel_loader.cc +++ b/runtime/vm/kernel_loader.cc @@ -1165,6 +1165,10 @@ void KernelLoader::LoadLibraryImportsAndExports(Library* library, target_library.url() == Symbols::DartMirrors().raw()) { H.ReportError("import of dart:mirrors with --enable-mirrors=false"); } + if (!Api::ffiEnabled() && + target_library.url() == Symbols::DartFfi().raw()) { + H.ReportError("import of dart:ffi with --enable-ffi=false"); + } String& prefix = H.DartSymbolPlain(dependency_helper.name_index_); ns = Namespace::New(target_library, show_names, hide_names); if (dependency_helper.flags_ & LibraryDependencyHelper::Export) { diff --git a/runtime/vm/native_arguments.h b/runtime/vm/native_arguments.h index 251740e08ad..86d7be566e9 100644 --- a/runtime/vm/native_arguments.h +++ b/runtime/vm/native_arguments.h @@ -155,7 +155,7 @@ class NativeArguments { // null vector represents infinite list of dynamics return Type::dynamic_type().raw(); } - return TypeArguments::Handle(NativeTypeArgs()).TypeAt(index); + return type_args.TypeAt(index); } void SetReturn(const Object& value) const { *retval_ = value.raw(); } diff --git a/runtime/vm/native_entry.cc b/runtime/vm/native_entry.cc index ff77dedaec3..569b4028de7 100644 --- a/runtime/vm/native_entry.cc +++ b/runtime/vm/native_entry.cc @@ -20,6 +20,14 @@ namespace dart { +void DartNativeThrowTypeArgumentCountException(int num_type_args, + int num_type_args_expected) { + const String& error = String::Handle(String::NewFormatted( + "Wrong number of type arguments (%i), expected %i type arguments", + num_type_args, num_type_args_expected)); + Exceptions::ThrowArgumentError(error); +} + void DartNativeThrowArgumentException(const Instance& instance) { const Array& __args__ = Array::Handle(Array::New(1)); __args__.SetAt(0, instance); diff --git a/runtime/vm/native_entry.h b/runtime/vm/native_entry.h index 0892258b48c..a4a5ee5bed1 100644 --- a/runtime/vm/native_entry.h +++ b/runtime/vm/native_entry.h @@ -73,9 +73,22 @@ class String; static RawObject* DN_Helper##name(Isolate* isolate, Thread* thread, \ Zone* zone, NativeArguments* arguments) -// Helper that throws an argument exception. +// Helpers that throw an argument exception. +void DartNativeThrowTypeArgumentCountException(int num_type_args, + int num_type_args_expected); void DartNativeThrowArgumentException(const Instance& instance); +// Native should throw an exception if the wrong number of type arguments is +// passed. +#define NATIVE_TYPE_ARGUMENT_COUNT(expected) \ + int __num_type_arguments = arguments->NativeTypeArgCount(); \ + if (__num_type_arguments != expected) { \ + DartNativeThrowTypeArgumentCountException(__num_type_arguments, expected); \ + } + +#define GET_NATIVE_TYPE_ARGUMENT(name, value) \ + AbstractType& name = AbstractType::Handle(value); + // Natives should throw an exception if an illegal argument or null is passed. // type name = value. #define GET_NON_NULL_NATIVE_ARGUMENT(type, name, value) \ diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 91a57297f0c..758a006ee63 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -128,6 +128,8 @@ RawClass* Object::closure_data_class_ = reinterpret_cast(RAW_NULL); RawClass* Object::signature_data_class_ = reinterpret_cast(RAW_NULL); RawClass* Object::redirection_data_class_ = reinterpret_cast(RAW_NULL); +RawClass* Object::ffi_trampoline_data_class_ = + reinterpret_cast(RAW_NULL); RawClass* Object::field_class_ = reinterpret_cast(RAW_NULL); RawClass* Object::script_class_ = reinterpret_cast(RAW_NULL); RawClass* Object::library_class_ = reinterpret_cast(RAW_NULL); @@ -582,6 +584,9 @@ void Object::Init(Isolate* isolate) { cls = Class::New(); redirection_data_class_ = cls.raw(); + cls = Class::New(); + ffi_trampoline_data_class_ = cls.raw(); + cls = Class::New(); field_class_ = cls.raw(); @@ -957,6 +962,7 @@ void Object::Cleanup() { closure_data_class_ = reinterpret_cast(RAW_NULL); signature_data_class_ = reinterpret_cast(RAW_NULL); redirection_data_class_ = reinterpret_cast(RAW_NULL); + ffi_trampoline_data_class_ = reinterpret_cast(RAW_NULL); field_class_ = reinterpret_cast(RAW_NULL); script_class_ = reinterpret_cast(RAW_NULL); library_class_ = reinterpret_cast(RAW_NULL); @@ -1056,6 +1062,7 @@ void Object::FinalizeVMIsolate(Isolate* isolate) { SET_CLASS_NAME(closure_data, ClosureData); SET_CLASS_NAME(signature_data, SignatureData); SET_CLASS_NAME(redirection_data, RedirectionData); + SET_CLASS_NAME(ffi_trampoline_data, FfiTrampolineData); SET_CLASS_NAME(field, Field); SET_CLASS_NAME(script, Script); SET_CLASS_NAME(library, LibraryClass); @@ -1814,6 +1821,51 @@ RawError* Object::Init(Isolate* isolate, type_args = type_args.Canonicalize(); object_store->set_type_argument_string_string(type_args); + lib = Library::LookupLibrary(thread, Symbols::DartFfi()); + if (lib.IsNull()) { + lib = Library::NewLibraryHelper(Symbols::DartFfi(), true); + lib.SetLoadRequested(); + lib.Register(thread); + } + object_store->set_bootstrap_library(ObjectStore::kFfi, lib); + + cls = Class::New(kFfiNativeTypeCid); + cls.set_num_type_arguments(0); + cls.set_num_own_type_arguments(0); + cls.set_is_prefinalized(); + pending_classes.Add(cls); + object_store->set_ffi_native_type_class(cls); + RegisterClass(cls, Symbols::FfiNativeType(), lib); + +#define REGISTER_FFI_TYPE_MARKER(clazz) \ + cls = Class::New(kFfi##clazz##Cid); \ + cls.set_num_type_arguments(0); \ + cls.set_num_own_type_arguments(0); \ + cls.set_is_prefinalized(); \ + pending_classes.Add(cls); \ + RegisterClass(cls, Symbols::Ffi##clazz(), lib); + CLASS_LIST_FFI_TYPE_MARKER(REGISTER_FFI_TYPE_MARKER); +#undef REGISTER_FFI_TYPE_MARKER + + cls = Class::New(kFfiNativeFunctionCid); + cls.set_type_arguments_field_offset(Pointer::type_arguments_offset()); + cls.set_num_type_arguments(1); + cls.set_num_own_type_arguments(1); + cls.set_is_prefinalized(); + pending_classes.Add(cls); + RegisterClass(cls, Symbols::FfiNativeFunction(), lib); + + cls = Class::NewPointerClass(kFfiPointerCid); + object_store->set_ffi_pointer_class(cls); + pending_classes.Add(cls); + RegisterClass(cls, Symbols::FfiPointer(), lib); + + cls = Class::New(kFfiDynamicLibraryCid); + cls.set_instance_size(DynamicLibrary::InstanceSize()); + cls.set_is_prefinalized(); + pending_classes.Add(cls); + RegisterClass(cls, Symbols::FfiDynamicLibrary(), lib); + // Finish the initialization by compiling the bootstrap scripts containing // the base interfaces and the implementation of the internal classes. const Error& error = Error::Handle( @@ -1898,6 +1950,20 @@ RawError* Object::Init(Isolate* isolate, CLASS_LIST_TYPED_DATA(REGISTER_EXT_TYPED_DATA_CLASS); #undef REGISTER_EXT_TYPED_DATA_CLASS + cls = Class::New(kFfiNativeTypeCid); + object_store->set_ffi_native_type_class(cls); + +#define REGISTER_FFI_CLASS(clazz) cls = Class::New(kFfi##clazz##Cid); + CLASS_LIST_FFI_TYPE_MARKER(REGISTER_FFI_CLASS); +#undef REGISTER_FFI_CLASS + + cls = Class::New(kFfiNativeFunctionCid); + + cls = Class::NewPointerClass(kFfiPointerCid); + object_store->set_ffi_pointer_class(cls); + + cls = Class::New(kFfiDynamicLibraryCid); + cls = Class::New(kByteBufferCid); cls = Class::New(); @@ -3660,6 +3726,17 @@ RawClass* Class::NewExternalTypedDataClass(intptr_t class_id) { return result.raw(); } +RawClass* Class::NewPointerClass(intptr_t class_id) { + ASSERT(RawObject::IsFfiPointerClassId(class_id)); + intptr_t instance_size = Pointer::InstanceSize(); + Class& result = Class::Handle(New(class_id)); + result.set_instance_size(instance_size); + result.set_type_arguments_field_offset(Pointer::type_arguments_offset()); + result.set_next_field_offset(Pointer::NextFieldOffset()); + result.set_is_prefinalized(); + return result.raw(); +} + void Class::set_name(const String& value) const { ASSERT(raw_ptr()->name_ == String::null()); ASSERT(value.IsSymbol()); @@ -3733,6 +3810,11 @@ RawString* Class::GenerateUserVisibleName() const { case kExternalTypedDataFloat64ArrayCid: return Symbols::Float64List().raw(); + case kFfiPointerCid: + return Symbols::FfiPointer().raw(); + case kFfiDynamicLibraryCid: + return Symbols::FfiDynamicLibrary().raw(); + #if !defined(PRODUCT) case kNullCid: return Symbols::Null().raw(); @@ -3754,6 +3836,8 @@ RawString* Class::GenerateUserVisibleName() const { return Symbols::SignatureData().raw(); case kRedirectionDataCid: return Symbols::RedirectionData().raw(); + case kFfiTrampolineDataCid: + return Symbols::FfiTrampolineData().raw(); case kFieldCid: return Symbols::Field().raw(); case kScriptCid: @@ -5014,9 +5098,19 @@ intptr_t TypeArguments::Length() const { } RawAbstractType* TypeArguments::TypeAt(intptr_t index) const { + ASSERT(!IsNull()); return *TypeAddr(index); } +RawAbstractType* TypeArguments::TypeAtNullSafe(intptr_t index) const { + if (IsNull()) { + // null vector represents infinite list of dynamics + return Type::dynamic_type().raw(); + } + ASSERT((index >= 0) && (index < Length())); + return TypeAt(index); +} + void TypeArguments::SetTypeAt(intptr_t index, const AbstractType& value) const { ASSERT(!IsCanonical()); StorePointer(TypeAddr(index), value.raw()); @@ -5884,9 +5978,11 @@ RawType* Function::ExistingSignatureType() const { ASSERT(!obj.IsNull()); if (IsSignatureFunction()) { return SignatureData::Cast(obj).signature_type(); - } else { - ASSERT(IsClosureFunction()); + } else if (IsClosureFunction()) { return ClosureData::Cast(obj).signature_type(); + } else { + ASSERT(IsFfiTrampoline()); + return FfiTrampolineData::Cast(obj).signature_type(); } } @@ -5937,9 +6033,11 @@ void Function::SetSignatureType(const Type& value) const { if (IsSignatureFunction()) { SignatureData::Cast(obj).set_signature_type(value); ASSERT(!value.IsCanonical() || (value.signature() == this->raw())); - } else { - ASSERT(IsClosureFunction()); + } else if (IsClosureFunction()) { ClosureData::Cast(obj).set_signature_type(value); + } else { + ASSERT(IsFfiTrampoline()); + FfiTrampolineData::Cast(obj).set_signature_type(value); } } @@ -6074,6 +6172,7 @@ void Function::SetRedirectionTarget(const Function& target) const { // native function: Array[0] = String native name // Array[1] = Function implicit closure function // regular function: Function for implicit closure function +// ffi trampoline function: FfiTrampolineData (Dart->C) void Function::set_data(const Object& value) const { StorePointer(&raw_ptr()->data_, value.raw()); } @@ -6387,7 +6486,8 @@ intptr_t Function::NumImplicitParameters() const { } if ((k == RawFunction::kClosureFunction) || (k == RawFunction::kImplicitClosureFunction) || - (k == RawFunction::kSignatureFunction)) { + (k == RawFunction::kSignatureFunction) || + (k == RawFunction::kFfiTrampoline)) { return 1; // Closure object. } if (!is_static()) { @@ -7076,6 +7176,10 @@ RawFunction* Function::New(const String& name, const SignatureData& data = SignatureData::Handle(SignatureData::New(space)); result.set_data(data); + } else if (kind == RawFunction::kFfiTrampoline) { + const FfiTrampolineData& data = + FfiTrampolineData::Handle(FfiTrampolineData::New()); + result.set_data(data); } else { // Functions other than signature functions have no reason to be allocated // in new space. @@ -8015,6 +8119,27 @@ const char* RedirectionData::ToCString() const { target_fun.IsNull() ? "null" : target_fun.ToCString()); } +void FfiTrampolineData::set_signature_type(const Type& value) const { + StorePointer(&raw_ptr()->signature_type_, value.raw()); +} + +RawFfiTrampolineData* FfiTrampolineData::New() { + ASSERT(Object::ffi_trampoline_data_class() != Class::null()); + RawObject* raw = + Object::Allocate(FfiTrampolineData::kClassId, + FfiTrampolineData::InstanceSize(), Heap::kOld); + return reinterpret_cast(raw); +} + +const char* FfiTrampolineData::ToCString() const { + Type& signature_type = Type::Handle(this->signature_type()); + String& signature_type_name = + String::Handle(signature_type.UserVisibleName()); + return OS::SCreate( + Thread::Current()->zone(), "TrampolineData: signature=%s", + signature_type_name.IsNull() ? "null" : signature_type_name.ToCString()); +} + RawField* Field::CloneFromOriginal() const { return this->Clone(*this); } @@ -11181,6 +11306,10 @@ RawLibrary* Library::DeveloperLibrary() { return Isolate::Current()->object_store()->developer_library(); } +RawLibrary* Library::FfiLibrary() { + return Isolate::Current()->object_store()->ffi_library(); +} + RawLibrary* Library::InternalLibrary() { return Isolate::Current()->object_store()->_internal_library(); } @@ -20688,6 +20817,75 @@ const char* ExternalTypedData::ToCString() const { return "ExternalTypedData"; } +RawPointer* Pointer::New(const AbstractType& type_arg, + uint8_t* c_memory_address, + intptr_t cid, + Heap::Space space) { + Thread* thread = Thread::Current(); + Zone* zone = thread->zone(); + TypeArguments& type_args = TypeArguments::Handle(zone); + type_args = TypeArguments::New(1); + type_args.SetTypeAt(Pointer::kNativeTypeArgPos, type_arg); + type_args ^= type_args.Canonicalize(); + + const Class& cls = Class::Handle(Isolate::Current()->class_table()->At(cid)); + cls.EnsureIsFinalized(Thread::Current()); + + Pointer& result = Pointer::Handle(zone); + result ^= Object::Allocate(cid, Pointer::InstanceSize(), space); + NoSafepointScope no_safepoint; + result.SetTypeArguments(type_args); + result.SetCMemoryAddress(c_memory_address); + + return result.raw(); +} + +const char* Pointer::ToCString() const { + TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments()); + String& type_args_name = String::Handle(type_args.UserVisibleName()); + return OS::SCreate(Thread::Current()->zone(), "Pointer%s: address=%p", + type_args_name.ToCString(), GetCMemoryAddress()); +} + +RawDynamicLibrary* DynamicLibrary::New(void* handle, Heap::Space space) { + DynamicLibrary& result = DynamicLibrary::Handle(); + result ^= Object::Allocate(kFfiDynamicLibraryCid, + DynamicLibrary::InstanceSize(), space); + NoSafepointScope no_safepoint; + result.SetHandle(handle); + return result.raw(); +} + +bool Pointer::IsPointer(const Instance& obj) { + ASSERT(!obj.IsNull()); + + // fast path for predefined classes + intptr_t cid = obj.raw()->GetClassId(); + if (RawObject::IsFfiPointerClassId(cid)) { + return true; + } + + // slow check for subtyping + const Class& pointer_class = Class::ZoneHandle( + Isolate::Current()->object_store()->ffi_pointer_class()); + AbstractType& pointer_type = + AbstractType::Handle(pointer_class.DeclarationType()); + pointer_type ^= pointer_type.InstantiateFrom(Object::null_type_arguments(), + Object::null_type_arguments(), + kNoneFree, NULL, Heap::kNew); + AbstractType& type = AbstractType::Handle(obj.GetType(Heap::kNew)); + return type.IsSubtypeOf(pointer_type, Heap::kNew); +} + +bool Instance::IsPointer() const { + return Pointer::IsPointer(*this); +} + +const char* DynamicLibrary::ToCString() const { + return OS::SCreate(Thread::Current()->zone(), "DynamicLibrary: handle=%p", + GetHandle()); +} + RawCapability* Capability::New(uint64_t id, Heap::Space space) { Capability& result = Capability::Handle(); { diff --git a/runtime/vm/object.h b/runtime/vm/object.h index f54967534a8..80f5e1cd5ae 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -431,6 +431,9 @@ class Object { static RawClass* closure_data_class() { return closure_data_class_; } static RawClass* signature_data_class() { return signature_data_class_; } static RawClass* redirection_data_class() { return redirection_data_class_; } + static RawClass* ffi_trampoline_data_class() { + return ffi_trampoline_data_class_; + } static RawClass* field_class() { return field_class_; } static RawClass* script_class() { return script_class_; } static RawClass* library_class() { return library_class_; } @@ -678,6 +681,8 @@ class Object { static RawClass* closure_data_class_; // Class of ClosureData vm obj. static RawClass* signature_data_class_; // Class of SignatureData vm obj. static RawClass* redirection_data_class_; // Class of RedirectionData vm obj. + static RawClass* ffi_trampoline_data_class_; // Class of FfiTrampolineData + // vm obj. static RawClass* field_class_; // Class of the Field vm object. static RawClass* script_class_; // Class of the Script vm object. static RawClass* library_class_; // Class of the Library vm object. @@ -1257,6 +1262,9 @@ class Class : public Object { // Allocate the raw ExternalTypedData classes. static RawClass* NewExternalTypedDataClass(intptr_t class_id); + // Allocate the raw Pointer classes. + static RawClass* NewPointerClass(intptr_t class_id); + // Register code that has used CHA for optimization. // TODO(srdjan): Also register kind of CHA optimization (e.g.: leaf class, // leaf method, ...). @@ -2125,6 +2133,10 @@ class Function : public Object { static intptr_t code_offset() { return OFFSET_OF(RawFunction, code_); } + static intptr_t result_type_offset() { + return OFFSET_OF(RawFunction, result_type_); + } + static intptr_t entry_point_offset() { return OFFSET_OF(RawFunction, entry_point_); } @@ -2551,6 +2563,14 @@ class Function : public Object { RawFunction::kSignatureFunction; } + // Returns true if this function represents an ffi trampoline. + bool IsFfiTrampoline() const { return kind() == RawFunction::kFfiTrampoline; } + static bool IsFfiTrampoline(RawFunction* function) { + NoSafepointScope no_safepoint; + return KindBits::decode(function->ptr()->kind_tag_) == + RawFunction::kFfiTrampoline; + } + bool IsAsyncFunction() const { return modifier() == RawFunction::kAsync; } bool IsAsyncClosure() const { @@ -2952,6 +2972,25 @@ class RedirectionData : public Object { enum class EntryPointPragma { kAlways, kNever, kGetterOnly, kSetterOnly }; +class FfiTrampolineData : public Object { + public: + static intptr_t InstanceSize() { + return RoundedAllocationSize(sizeof(RawFfiTrampolineData)); + } + + private: + // Signature type of this closure function. + RawType* signature_type() const { return raw_ptr()->signature_type_; } + void set_signature_type(const Type& value) const; + + static RawFfiTrampolineData* New(); + + FINAL_HEAP_OBJECT_IMPLEMENTATION(FfiTrampolineData, Object); + friend class Class; + friend class Function; + friend class HeapProfiler; +}; + class Field : public Object { public: RawField* Original() const; @@ -3702,6 +3741,7 @@ class Library : public Object { static RawLibrary* CoreLibrary(); static RawLibrary* CollectionLibrary(); static RawLibrary* DeveloperLibrary(); + static RawLibrary* FfiLibrary(); static RawLibrary* InternalLibrary(); static RawLibrary* IsolateLibrary(); static RawLibrary* MathLibrary(); @@ -5735,6 +5775,12 @@ class Instance : public Object { static intptr_t DataOffsetFor(intptr_t cid); static intptr_t ElementSizeFor(intptr_t cid); + // Pointers may be subtyped, but their subtypes may not get extra fields. + // The subtype runtime representation has exactly the same object layout, + // only the class_id is different. So, it is safe to use subtype instances in + // Pointer handles. + virtual bool IsPointer() const; + static intptr_t NextFieldOffset() { return sizeof(RawInstance); } protected: @@ -5775,6 +5821,7 @@ class Instance : public Object { friend class ByteBuffer; friend class Class; friend class Closure; + friend class Pointer; friend class DeferredObject; friend class RegExp; friend class SnapshotWriter; @@ -5853,6 +5900,7 @@ class TypeArguments : public Instance { intptr_t Length() const; RawAbstractType* TypeAt(intptr_t index) const; + RawAbstractType* TypeAtNullSafe(intptr_t index) const; static intptr_t type_at_offset(intptr_t index) { return OFFSET_OF_RETURNED_VALUE(RawTypeArguments, types) + index * kWordSize; @@ -8407,6 +8455,81 @@ class ByteBuffer : public AllStatic { }; }; +class Pointer : public Instance { + public: + static RawPointer* New(const AbstractType& type_arg, + uint8_t* c_memory_address, + intptr_t class_id = kFfiPointerCid, + Heap::Space space = Heap::kNew); + + static intptr_t InstanceSize() { + return RoundedAllocationSize(sizeof(RawPointer)); + } + + static bool IsPointer(const Instance& obj); + + uint8_t* GetCMemoryAddress() const { + ASSERT(!IsNull()); + return raw_ptr()->c_memory_address_; + } + + void SetCMemoryAddress(uint8_t* value) const { + StoreNonPointer(&raw_ptr()->c_memory_address_, value); + } + + static intptr_t type_arguments_offset() { + return OFFSET_OF(RawPointer, type_arguments_); + } + + static intptr_t address_offset() { + return OFFSET_OF(RawPointer, c_memory_address_); + } + + static intptr_t NextFieldOffset() { return sizeof(RawPointer); } + + static const intptr_t kNativeTypeArgPos = 0; + + // Fetches the NativeType type argument. + RawAbstractType* type_argument() const { + TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments()); + return type_args.TypeAtNullSafe(Pointer::kNativeTypeArgPos); + } + + private: + HEAP_OBJECT_IMPLEMENTATION(Pointer, Instance); + + friend class Class; +}; + +class DynamicLibrary : public Instance { + public: + static RawDynamicLibrary* New(void* handle, Heap::Space space = Heap::kNew); + + static intptr_t InstanceSize() { + return RoundedAllocationSize(sizeof(RawDynamicLibrary)); + } + + static bool IsDynamicLibrary(const Instance& obj) { + ASSERT(!obj.IsNull()); + intptr_t cid = obj.raw()->GetClassId(); + return RawObject::IsFfiDynamicLibraryClassId(cid); + } + + void* GetHandle() const { + ASSERT(!IsNull()); + return raw_ptr()->handle_; + } + + void SetHandle(void* value) const { + StoreNonPointer(&raw_ptr()->handle_, value); + } + + private: + FINAL_HEAP_OBJECT_IMPLEMENTATION(DynamicLibrary, Instance); + + friend class Class; +}; + // Corresponds to // - "new Map()", // - non-const map literals, and diff --git a/runtime/vm/object_service.cc b/runtime/vm/object_service.cc index fda5543f18d..1562714927d 100644 --- a/runtime/vm/object_service.cc +++ b/runtime/vm/object_service.cc @@ -349,6 +349,10 @@ void RedirectionData::PrintJSONImpl(JSONStream* stream, bool ref) const { Object::PrintJSONImpl(stream, ref); } +void FfiTrampolineData::PrintJSONImpl(JSONStream* stream, bool ref) const { + Object::PrintJSONImpl(stream, ref); +} + void Field::PrintJSONImpl(JSONStream* stream, bool ref) const { JSONObject jsobj(stream); Class& cls = Class::Handle(Owner()); @@ -1416,6 +1420,20 @@ void ExternalTypedData::PrintJSONImpl(JSONStream* stream, bool ref) const { } } +void Pointer::PrintJSONImpl(JSONStream* stream, bool ref) const { + // TODO(dacoharkes): what is the JSONStream used for? + // should it fail because it's not supported? + // or should it print something reasonable as default? + Instance::PrintJSONImpl(stream, ref); +} + +void DynamicLibrary::PrintJSONImpl(JSONStream* stream, bool ref) const { + // TODO(dacoharkes): what is the JSONStream used for? + // should it fail because it's not supported? + // or should it print something reasonable as default? + Instance::PrintJSONImpl(stream, ref); +} + void Capability::PrintJSONImpl(JSONStream* stream, bool ref) const { Instance::PrintJSONImpl(stream, ref); } diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h index 9f2cf97057b..a4f02279897 100644 --- a/runtime/vm/object_store.h +++ b/runtime/vm/object_store.h @@ -22,6 +22,7 @@ class ObjectPointerVisitor; M(Collection, collection) \ M(Convert, convert) \ M(Developer, developer) \ + M(Ffi, ffi) \ M(Internal, _internal) \ M(Isolate, isolate) \ M(Math, math) \ @@ -89,6 +90,7 @@ class ObjectPointerVisitor; RW(Library, collection_library) \ RW(Library, convert_library) \ RW(Library, developer_library) \ + RW(Library, ffi_library) \ RW(Library, _internal_library) \ RW(Library, isolate_library) \ RW(Library, math_library) \ @@ -137,6 +139,8 @@ class ObjectPointerVisitor; RW(Array, code_order_table) \ RW(Array, obfuscation_map) \ RW(GrowableObjectArray, changed_in_last_reload) \ + RW(Class, ffi_pointer_class) \ + RW(Class, ffi_native_type_class) \ // Please remember the last entry must be referred in the 'to' function below. // The object store is a per isolate instance which stores references to @@ -229,7 +233,7 @@ class ObjectStore { DECLARE_OBJECT_STORE_FIELD) #undef DECLARE_OBJECT_STORE_FIELD RawObject** to() { - return reinterpret_cast(&changed_in_last_reload_); + return reinterpret_cast(&ffi_pointer_class_); } RawObject** to_snapshot(Snapshot::Kind kind) { switch (kind) { diff --git a/runtime/vm/raw_object.cc b/runtime/vm/raw_object.cc index 76a82859b4a..7735f4d10a2 100644 --- a/runtime/vm/raw_object.cc +++ b/runtime/vm/raw_object.cc @@ -147,6 +147,9 @@ intptr_t RawObject::HeapSizeFromClass() const { break; } #undef SIZE_FROM_CLASS + case kFfiPointerCid: + instance_size = Pointer::InstanceSize(); + break; case kTypeArgumentsCid: { const RawTypeArguments* raw_array = reinterpret_cast(this); @@ -282,6 +285,16 @@ intptr_t RawObject::VisitPointersPredefined(ObjectPointerVisitor* visitor, break; } #undef RAW_VISITPOINTERS + case kFfiPointerCid: { + RawPointer* raw_obj = reinterpret_cast(this); + size = RawPointer::VisitPointerPointers(raw_obj, visitor); + break; + } + case kFfiDynamicLibraryCid: { + RawDynamicLibrary* raw_obj = reinterpret_cast(this); + size = RawDynamicLibrary::VisitDynamicLibraryPointers(raw_obj, visitor); + break; + } case kFreeListElement: { uword addr = RawObject::ToAddr(this); FreeListElement* element = reinterpret_cast(addr); @@ -395,6 +408,7 @@ COMPRESSED_VISITOR(Closure) REGULAR_VISITOR(ClosureData) REGULAR_VISITOR(SignatureData) REGULAR_VISITOR(RedirectionData) +REGULAR_VISITOR(FfiTrampolineData) REGULAR_VISITOR(Field) REGULAR_VISITOR(Script) REGULAR_VISITOR(Library) @@ -439,6 +453,8 @@ NULL_VISITOR(Float64x2) NULL_VISITOR(Bool) NULL_VISITOR(Capability) NULL_VISITOR(SendPort) +REGULAR_VISITOR(Pointer) +NULL_VISITOR(DynamicLibrary) VARIABLE_NULL_VISITOR(Instructions, Instructions::Size(raw_obj)) VARIABLE_NULL_VISITOR(PcDescriptors, raw_obj->ptr()->length_) VARIABLE_NULL_VISITOR(CodeSourceMap, raw_obj->ptr()->length_) diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h index 69ddef8e1fc..3fecf1c82cf 100644 --- a/runtime/vm/raw_object.h +++ b/runtime/vm/raw_object.h @@ -344,6 +344,11 @@ class RawObject { CLASS_LIST_TYPED_DATA(DEFINE_IS_CID) #undef DEFINE_IS_CID +#define DEFINE_IS_CID(clazz) \ + bool IsFfi##clazz() const { return ((GetClassId() == kFfi##clazz##Cid)); } + CLASS_LIST_FFI(DEFINE_IS_CID) +#undef DEFINE_IS_CID + bool IsStringInstance() const { return IsStringClassId(GetClassId()); } bool IsRawNull() const { return GetClassId() == kNullCid; } bool IsDartInstance() const { @@ -471,6 +476,15 @@ class RawObject { static bool IsTypedDataClassId(intptr_t index); static bool IsTypedDataViewClassId(intptr_t index); static bool IsExternalTypedDataClassId(intptr_t index); + static bool IsFfiNativeTypeTypeClassId(intptr_t index); + static bool IsFfiPointerClassId(intptr_t index); + static bool IsFfiTypeClassId(intptr_t index); + static bool IsFfiTypeIntClassId(intptr_t index); + static bool IsFfiTypeDoubleClassId(intptr_t index); + static bool IsFfiTypeVoidClassId(intptr_t index); + static bool IsFfiTypeNativeFunctionClassId(intptr_t index); + static bool IsFfiDynamicLibraryClassId(intptr_t index); + static bool IsFfiClassId(intptr_t index); static bool IsInternalVMdefinedClassId(intptr_t index); static bool IsVariableSizeClassId(intptr_t index); static bool IsImplicitFieldClassId(intptr_t index); @@ -664,7 +678,9 @@ class RawObject { friend class CidRewriteVisitor; friend class Closure; friend class Code; + friend class Pointer; friend class Double; + friend class DynamicLibrary; friend class ForwardPointersVisitor; // StorePointer friend class FreeListElement; friend class Function; @@ -855,6 +871,7 @@ class RawFunction : public RawObject { kDynamicInvocationForwarder, // represents forwarder which performs type // checks for arguments of a dynamic // invocation. + kFfiTrampoline, }; enum AsyncModifier { @@ -1002,6 +1019,15 @@ class RawRedirectionData : public RawObject { RawObject** to_snapshot(Snapshot::Kind kind) { return to(); } }; +class RawFfiTrampolineData : public RawObject { + private: + RAW_HEAP_OBJECT_IMPLEMENTATION(FfiTrampolineData); + + VISIT_FROM(RawObject*, signature_type_); + RawType* signature_type_; + VISIT_TO(RawObject*, signature_type_); +}; + class RawField : public RawObject { RAW_HEAP_OBJECT_IMPLEMENTATION(Field); @@ -2213,6 +2239,24 @@ class RawExternalTypedData : public RawInstance { friend class RawBytecode; }; +class RawPointer : public RawInstance { + RAW_HEAP_OBJECT_IMPLEMENTATION(Pointer); + VISIT_FROM(RawCompressed, type_arguments_) + RawTypeArguments* type_arguments_; + VISIT_TO(RawCompressed, type_arguments_) + uint8_t* c_memory_address_; + + friend class Pointer; +}; + +class RawDynamicLibrary : public RawInstance { + RAW_HEAP_OBJECT_IMPLEMENTATION(DynamicLibrary); + VISIT_NOTHING(); + void* handle_; + + friend class DynamicLibrary; +}; + // VM implementations of the basic types in the isolate. class RawCapability : public RawInstance { RAW_HEAP_OBJECT_IMPLEMENTATION(Capability); @@ -2483,6 +2527,56 @@ inline bool RawObject::IsExternalTypedDataClassId(intptr_t index) { index <= kExternalTypedDataFloat64x2ArrayCid); } +inline bool RawObject::IsFfiNativeTypeTypeClassId(intptr_t index) { + return index == kFfiNativeTypeCid; +} + +inline bool RawObject::IsFfiTypeClassId(intptr_t index) { + // Make sure this is updated when new Ffi types are added. + COMPILE_ASSERT(kFfiNativeFunctionCid == kFfiPointerCid + 1 && + kFfiInt8Cid == kFfiPointerCid + 2 && + kFfiInt16Cid == kFfiPointerCid + 3 && + kFfiInt32Cid == kFfiPointerCid + 4 && + kFfiInt64Cid == kFfiPointerCid + 5 && + kFfiUint8Cid == kFfiPointerCid + 6 && + kFfiUint16Cid == kFfiPointerCid + 7 && + kFfiUint32Cid == kFfiPointerCid + 8 && + kFfiUint64Cid == kFfiPointerCid + 9 && + kFfiIntPtrCid == kFfiPointerCid + 10 && + kFfiFloatCid == kFfiPointerCid + 11 && + kFfiDoubleCid == kFfiPointerCid + 12 && + kFfiVoidCid == kFfiPointerCid + 13); + return (index >= kFfiPointerCid && index <= kFfiVoidCid); +} + +inline bool RawObject::IsFfiTypeIntClassId(intptr_t index) { + return (index >= kFfiInt8Cid && index <= kFfiIntPtrCid); +} + +inline bool RawObject::IsFfiTypeDoubleClassId(intptr_t index) { + return (index >= kFfiFloatCid && index <= kFfiDoubleCid); +} + +inline bool RawObject::IsFfiPointerClassId(intptr_t index) { + return index == kFfiPointerCid; +} + +inline bool RawObject::IsFfiTypeVoidClassId(intptr_t index) { + return index == kFfiVoidCid; +} + +inline bool RawObject::IsFfiTypeNativeFunctionClassId(intptr_t index) { + return index == kFfiNativeFunctionCid; +} + +inline bool RawObject::IsFfiClassId(intptr_t index) { + return (index >= kFfiPointerCid && index <= kFfiVoidCid); +} + +inline bool RawObject::IsFfiDynamicLibraryClassId(intptr_t index) { + return index == kFfiDynamicLibraryCid; +} + inline bool RawObject::IsInternalVMdefinedClassId(intptr_t index) { return ((index < kNumPredefinedCids) && !RawObject::IsImplicitFieldClassId(index)); diff --git a/runtime/vm/raw_object_fields.cc b/runtime/vm/raw_object_fields.cc index e5df2511448..7332edb6931 100644 --- a/runtime/vm/raw_object_fields.cc +++ b/runtime/vm/raw_object_fields.cc @@ -186,7 +186,11 @@ namespace dart { F(WeakProperty, key_) \ F(WeakProperty, value_) \ F(MirrorReference, referent_) \ - F(UserTag, label_) + F(UserTag, label_) \ + F(Pointer, type_arguments_) \ + F(Pointer, c_memory_address_) \ + F(DynamicLibrary, handle_) \ + F(FfiTrampolineData, signature_type_) OffsetsTable::OffsetsTable(Zone* zone) : cached_offsets_(zone) { for (intptr_t i = 0; offsets_table[i].class_id != -1; ++i) { diff --git a/runtime/vm/raw_object_snapshot.cc b/runtime/vm/raw_object_snapshot.cc index 0424d5c7e73..2222f8d67f6 100644 --- a/runtime/vm/raw_object_snapshot.cc +++ b/runtime/vm/raw_object_snapshot.cc @@ -467,6 +467,22 @@ void RawRedirectionData::WriteTo(SnapshotWriter* writer, UNREACHABLE(); } +RawFfiTrampolineData* FfiTrampolineData::ReadFrom(SnapshotReader* reader, + intptr_t object_id, + intptr_t tags, + Snapshot::Kind kind, + bool as_reference) { + UNREACHABLE(); + return FfiTrampolineData::null(); +} + +void RawFfiTrampolineData::WriteTo(SnapshotWriter* writer, + intptr_t object_id, + Snapshot::Kind kind, + bool as_reference) { + UNREACHABLE(); +} + RawFunction* Function::ReadFrom(SnapshotReader* reader, intptr_t object_id, intptr_t tags, @@ -1967,6 +1983,38 @@ void RawExternalTypedData::WriteTo(SnapshotWriter* writer, IsolateMessageTypedDataFinalizer); } +RawPointer* Pointer::ReadFrom(SnapshotReader* reader, + intptr_t object_id, + intptr_t tags, + Snapshot::Kind kind, + bool as_reference) { + FATAL("Snapshotting Pointers is not supported"); + UNREACHABLE(); +} + +void RawPointer::WriteTo(SnapshotWriter* writer, + intptr_t object_id, + Snapshot::Kind kind, + bool as_reference) { + FATAL("Snapshotting Pointers is not supported"); +} + +RawDynamicLibrary* DynamicLibrary::ReadFrom(SnapshotReader* reader, + intptr_t object_id, + intptr_t tags, + Snapshot::Kind kind, + bool as_reference) { + FATAL("Snapshotting DynamicLibraries is not supported"); + UNREACHABLE(); +} + +void RawDynamicLibrary::WriteTo(SnapshotWriter* writer, + intptr_t object_id, + Snapshot::Kind kind, + bool as_reference) { + FATAL("Snapshotting DynamicLibraries is not supported"); +} + RawCapability* Capability::ReadFrom(SnapshotReader* reader, intptr_t object_id, intptr_t tags, diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc index c6cee035d86..e707f0637c0 100644 --- a/runtime/vm/runtime_entry.cc +++ b/runtime/vm/runtime_entry.cc @@ -195,6 +195,11 @@ DEFINE_RUNTIME_ENTRY(NullErrorWithSelector, 1) { NullErrorHelper(zone, selector); } +DEFINE_RUNTIME_ENTRY(ArgumentNullError, 0) { + const String& error = String::Handle(String::New("argument value is null")); + Exceptions::ThrowArgumentError(error); +} + DEFINE_RUNTIME_ENTRY(ArgumentError, 1) { const Instance& value = Instance::CheckedHandle(zone, arguments.ArgAt(0)); Exceptions::ThrowArgumentError(value); diff --git a/runtime/vm/runtime_entry_list.h b/runtime/vm/runtime_entry_list.h index fa97c63fb53..4833b0415ac 100644 --- a/runtime/vm/runtime_entry_list.h +++ b/runtime/vm/runtime_entry_list.h @@ -41,6 +41,7 @@ namespace dart { V(RangeError) \ V(NullError) \ V(NullErrorWithSelector) \ + V(ArgumentNullError) \ V(ArgumentError) \ V(ArgumentErrorUnboxedInt64) \ V(IntegerDivisionByZeroException) \ diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index 63aa9b99817..93fa9c60423 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -4752,6 +4752,13 @@ static bool GetDefaultClassesAliases(Thread* thread, JSONStream* js) { } CLASS_LIST_TYPED_DATA(DEFINE_ADD_MAP_KEY) #undef DEFINE_ADD_MAP_KEY +#define DEFINE_ADD_MAP_KEY(clazz) \ + { \ + JSONArray internals(&map, #clazz); \ + DEFINE_ADD_VALUE_F_CID(Ffi##clazz) \ + } + CLASS_LIST_FFI(DEFINE_ADD_MAP_KEY) +#undef DEFINE_ADD_MAP_KEY #undef DEFINE_ADD_VALUE_F_CID #undef DEFINE_ADD_VALUE_F diff --git a/runtime/vm/snapshot.cc b/runtime/vm/snapshot.cc index ea839f34ed1..c3d7c8e8fe9 100644 --- a/runtime/vm/snapshot.cc +++ b/runtime/vm/snapshot.cc @@ -482,6 +482,10 @@ RawObject* SnapshotReader::ReadObjectImpl(intptr_t header_value, pobj_ = ExternalTypedData::ReadFrom(this, object_id, tags, kind_, true); break; } +#undef SNAPSHOT_READ +#define SNAPSHOT_READ(clazz) case kFfi##clazz##Cid: + + CLASS_LIST_FFI(SNAPSHOT_READ) { UNREACHABLE(); } #undef SNAPSHOT_READ default: UNREACHABLE(); @@ -1174,6 +1178,10 @@ void SnapshotWriter::WriteMarkedObjectImpl(RawObject* raw, raw_obj->WriteTo(this, object_id, kind_, as_reference); return; } +#undef SNAPSHOT_WRITE +#define SNAPSHOT_WRITE(clazz) case kFfi##clazz##Cid: + + CLASS_LIST_FFI(SNAPSHOT_WRITE) { UNREACHABLE(); } #undef SNAPSHOT_WRITE default: break; diff --git a/runtime/vm/snapshot.h b/runtime/vm/snapshot.h index 14322597c55..736329cae4f 100644 --- a/runtime/vm/snapshot.h +++ b/runtime/vm/snapshot.h @@ -48,6 +48,7 @@ class RawContext; class RawContextScope; class RawDouble; class RawExceptionHandlers; +class RawFfiTrampolineData; class RawField; class RawFloat32x4; class RawFloat64x2; diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index c45ab967fe5..68c03715222 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -179,6 +179,7 @@ class ObjectPointerVisitor; V(ClosureData, "ClosureData") \ V(SignatureData, "SignatureData") \ V(RedirectionData, "RedirectionData") \ + V(FfiTrampolineData, "FfiTrampolineData") \ V(Field, "Field") \ V(Script, "Script") \ V(LibraryClass, "Library") \ @@ -373,6 +374,8 @@ class ObjectPointerVisitor; V(DartCore, "dart:core") \ V(DartCollection, "dart:collection") \ V(DartDeveloper, "dart:developer") \ + V(DartFfi, "dart:ffi") \ + V(DartFfiLibName, "ffi") \ V(DartInternal, "dart:_internal") \ V(DartIsolate, "dart:isolate") \ V(DartMirrors, "dart:mirrors") \ @@ -441,6 +444,7 @@ class ObjectPointerVisitor; V(_ensureScheduleImmediate, "_ensureScheduleImmediate") \ V(DartLibrary, "dart.library.") \ V(DartLibraryMirrors, "dart.library.mirrors") \ + V(DartLibraryFfi, "dart.library.ffi") \ V(_name, "_name") \ V(name, "name") \ V(options, "options") \ @@ -460,7 +464,23 @@ class ObjectPointerVisitor; V(Get, "get") \ V(Set, "set") \ V(vm_trace_entrypoints, "vm:testing.unsafe.trace-entrypoints-fn") \ - V(BoundsCheckForPartialInstantiation, "_boundsCheckForPartialInstantiation") + V(BoundsCheckForPartialInstantiation, "_boundsCheckForPartialInstantiation") \ + V(FfiPointer, "Pointer") \ + V(FfiNativeFunction, "NativeFunction") \ + V(FfiInt8, "Int8") \ + V(FfiInt16, "Int16") \ + V(FfiInt32, "Int32") \ + V(FfiInt64, "Int64") \ + V(FfiUint8, "Uint8") \ + V(FfiUint16, "Uint16") \ + V(FfiUint32, "Uint32") \ + V(FfiUint64, "Uint64") \ + V(FfiIntPtr, "IntPtr") \ + V(FfiFloat, "Float") \ + V(FfiDouble, "Double") \ + V(FfiVoid, "Void") \ + V(FfiNativeType, "NativeType") \ + V(FfiDynamicLibrary, "DynamicLibrary") // Contains a list of frequently used strings in a canonicalized form. This // list is kept in the vm_isolate in order to share the copy across isolates diff --git a/runtime/vm/vm_sources.gni b/runtime/vm/vm_sources.gni index 961a8e07f67..198d04e854c 100644 --- a/runtime/vm/vm_sources.gni +++ b/runtime/vm/vm_sources.gni @@ -47,6 +47,7 @@ vm_sources = [ "constants_dbc.h", "constants_ia32.h", "constants_kbc.h", + "constants_x64.cc", "constants_x64.h", "cpu.h", "cpu_arm.cc", @@ -91,6 +92,7 @@ vm_sources = [ "dwarf.h", "exceptions.cc", "exceptions.h", + "ffi_trampoline_stubs_x64.cc", "finalizable_data.h", "fixed_cache.h", "flag_list.h", diff --git a/samples/ffi/coordinate.dart b/samples/ffi/coordinate.dart new file mode 100644 index 00000000000..f8ac7d48fd1 --- /dev/null +++ b/samples/ffi/coordinate.dart @@ -0,0 +1,40 @@ +// 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. + +library FfiTest; + +import 'dart:ffi' as ffi; + +/// Sample struct for dart:ffi library. +@ffi.struct +class Coordinate extends ffi.Pointer { + @ffi.Double() + double x; + + @ffi.Double() + double y; + + @ffi.Pointer() + Coordinate next; + + // Implementation generated by @ffi.struct annotation. + external static int sizeOf(); + + Coordinate offsetBy(int offsetInBytes) => + super.offsetBy(offsetInBytes).cast(); + + Coordinate elementAt(int index) => offsetBy(sizeOf() * index); + + static Coordinate allocate({int count: 1}) => + ffi.allocate(count: count * sizeOf()).cast(); + + /// Allocate a new [Coordinate] in C memory and populate its fields. + factory Coordinate(double x, double y, Coordinate next) { + Coordinate result = Coordinate.allocate() + ..x = x + ..y = y + ..next = next; + return result; + } +} diff --git a/samples/ffi/sample_ffi_data.dart b/samples/ffi/sample_ffi_data.dart new file mode 100644 index 00000000000..ba05601b5d5 --- /dev/null +++ b/samples/ffi/sample_ffi_data.dart @@ -0,0 +1,274 @@ +// 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. + +import 'dart:ffi' as ffi; + +main(List arguments) { + print('start main'); + + { + // basic operation: allocate, get, set, and free + ffi.Pointer p = ffi.allocate(); + p.store(42); + int pValue = p.load(); + print('${p.runtimeType} value: ${pValue}'); + p.free(); + } + + { + // undefined behavior before set + ffi.Pointer p = ffi.allocate(); + int pValue = p.load(); + print('If not set, returns garbage: ${pValue}'); + p.free(); + } + + { + // pointers can be created from an address + ffi.Pointer pHelper = ffi.allocate(); + pHelper.store(1337); + + int address = pHelper.address; + print('Address: ${address}'); + + ffi.Pointer p = ffi.fromAddress(address); + print('${p.runtimeType} value: ${p.load()}'); + + pHelper.free(); + } + + { + // address is zeroed out after free + ffi.Pointer p = ffi.allocate(); + p.free(); + print('After free, address is zero: ${p.address}'); + } + + { + // pointer arithmetic can be done with element offsets or bytes + ffi.Pointer p1 = ffi.allocate(count: 2); + print('p1 address: ${p1.address}'); + + ffi.Pointer p2 = p1.elementAt(1); + print('p1.elementAt(1) address: ${p2.address}'); + p2.store(100); + + ffi.Pointer p3 = p1.offsetBy(8); + print('p1.offsetBy(8) address: ${p3.address}'); + print('p1.offsetBy(8) value: ${p3.load()}'); + p1.free(); + } + + { + // allocating too much throws an exception + try { + int maxMint = 9223372036854775807; // 2^63 - 1 + ffi.allocate(count: maxMint); + } on RangeError { + print('Expected exception on allocating too much'); + } + try { + int maxInt1_8 = 1152921504606846975; // 2^60 -1 + ffi.allocate(count: maxInt1_8); + } on ArgumentError { + print('Expected exception on allocating too much'); + } + } + + { + // pointers can be cast into another type + // resulting in the corresponding bits read + ffi.Pointer p1 = ffi.allocate(); + p1.store(9223372036854775807); // 2^63 - 1 + + ffi.Pointer p2 = p1.cast(); + print('${p2.runtimeType} value: ${p2.load()}'); // -1 + + ffi.Pointer p3 = p2.elementAt(1); + print('${p3.runtimeType} value: ${p3.load()}'); // 2^31 - 1 + + p1.free(); + } + + { + // data can be tightly packed in memory + ffi.Pointer p = ffi.allocate(count: 8); + for (var i in [0, 1, 2, 3, 4, 5, 6, 7]) { + p.elementAt(i).store(i * 3); + } + for (var i in [0, 1, 2, 3, 4, 5, 6, 7]) { + print('p.elementAt($i) value: ${p.elementAt(i).load()}'); + } + p.free(); + } + + { + // exception on storing a value that does not fit + ffi.Pointer p11 = ffi.allocate(); + + try { + p11.store(9223372036854775807); + } on ArgumentError { + print('Expected exception on calling set with a value that does not fit'); + } + + p11.free(); + } + + { + // doubles + ffi.Pointer p = ffi.allocate(); + p.store(3.14159265359); + print('${p.runtimeType} value: ${p.load()}'); + p.store(3.14); + print('${p.runtimeType} value: ${p.load()}'); + p.free(); + } + + { + // floats + ffi.Pointer p = ffi.allocate(); + p.store(3.14159265359); + print('${p.runtimeType} value: ${p.load()}'); + p.store(3.14); + print('${p.runtimeType} value: ${p.load()}'); + p.free(); + } + + { + // ffi.IntPtr varies in size based on whether the platform is 32 or 64 bit + // addresses of pointers fit in this size + ffi.Pointer p = ffi.allocate(); + int p14addr = p.address; + p.store(p14addr); + int pValue = p.load(); + print('${p.runtimeType} value: ${pValue}'); + p.free(); + } + + { + // void pointers are unsized + // the size of the element it is pointing to is undefined + // this means they cannot be ffi.allocated, read, or written + // this would would fail to compile: + // ffi.allocate(); + + ffi.Pointer p1 = ffi.allocate(); + ffi.Pointer p2 = p1.cast(); + print('${p2.runtimeType} address: ${p2.address}'); + + // this fails to compile, we cannot read something unsized + // p2.load(); + + // this fails to compile, we cannot write something unsized + // p2.store(1234); + + p1.free(); + } + + { + // pointer to a pointer to something + ffi.Pointer pHelper = ffi.allocate(); + pHelper.store(17); + + ffi.Pointer> p = ffi.allocate(); + + // storing into a pointer pointer automatically unboxes + p.store(pHelper); + + // reading from a pointer pointer automatically boxes + ffi.Pointer pHelper2 = p.load(); + print('${pHelper2.runtimeType} value: ${pHelper2.load()}'); + + int pValue = p.load>().load(); + print('${p.runtimeType} value\'s value: ${pValue}'); + + p.free(); + pHelper.free(); + } + + { + // the pointer to pointer types must match up + ffi.Pointer pHelper = ffi.allocate(); + pHelper.store(123); + + ffi.Pointer> p = ffi.allocate(); + + // this fails to compile due to type mismatch + // p.store(pHelper); + + pHelper.free(); + p.free(); + } + + { + // null pointer in Dart points to address 0 in c++ + ffi.Pointer> pointerToPointer = ffi.allocate(); + ffi.Pointer value = null; + pointerToPointer.store(value); + value = pointerToPointer.load(); + print("Loading a pointer to the 0 address is null: ${value}"); + pointerToPointer.free(); + } + + { + // sizeof returns element size in bytes + print('sizeOf(): ${ffi.sizeOf()}'); + print('sizeOf(): ${ffi.sizeOf()}'); + print('sizeOf(): ${ffi.sizeOf()}'); + } + + { + // only concrete sub types of NativeType can be ffi.allocated + // this would fail to compile: + // ffi.allocate(); + } + + { + // only concrete sub types of NativeType can be asked for size + // this would fail to compile: + // ffi.sizeOf(); + } + + { + // with ffi.IntPtr pointers, one can manually setup aribtrary data + // structres in C memory. + + void createChain(ffi.Pointer head, int length, int value) { + if (length == 0) { + head.store(value); + return; + } + ffi.Pointer next = ffi.allocate(); + head.store(next.address); + createChain(next, length - 1, value); + } + + int getChainValue(ffi.Pointer head, int length) { + if (length == 0) { + return head.load(); + } + ffi.Pointer next = ffi.fromAddress(head.load()); + return getChainValue(next, length - 1); + } + + void freeChain(ffi.Pointer head, int length) { + ffi.Pointer next = ffi.fromAddress(head.load()); + head.free(); + if (length == 0) { + return; + } + freeChain(next, length - 1); + } + + int length = 10; + ffi.Pointer head = ffi.allocate(); + createChain(head, length, 512); + int tailValue = getChainValue(head, length); + print('tailValue: ${tailValue}'); + freeChain(head, length); + } + + print("end main"); +} diff --git a/samples/ffi/sample_ffi_dynamic_library.dart b/samples/ffi/sample_ffi_dynamic_library.dart new file mode 100644 index 00000000000..cf834005a2d --- /dev/null +++ b/samples/ffi/sample_ffi_dynamic_library.dart @@ -0,0 +1,21 @@ +// 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. + +import 'dart:ffi' as ffi; + +typedef NativeDoubleUnOp = ffi.Double Function(ffi.Double); + +typedef DoubleUnOp = double Function(double); + +main(List arguments) { + ffi.DynamicLibrary l = ffi.DynamicLibrary.open("ffi_test_dynamic_library"); + print(l); + print(l.runtimeType); + + var timesFour = l.lookupFunction("timesFour"); + print(timesFour); + print(timesFour.runtimeType); + + print(timesFour(3.0)); +} diff --git a/samples/ffi/sample_ffi_functions.dart b/samples/ffi/sample_ffi_functions.dart new file mode 100644 index 00000000000..62b5718520d --- /dev/null +++ b/samples/ffi/sample_ffi_functions.dart @@ -0,0 +1,267 @@ +// 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. + +import 'dart:ffi' as ffi; + +typedef NativeUnaryOp = ffi.Int32 Function(ffi.Int32); +typedef NativeBinaryOp = ffi.Int32 Function(ffi.Int32, ffi.Int32); +typedef UnaryOp = int Function(int); +typedef BinaryOp = int Function(int, int); +typedef GenericBinaryOp = int Function(int, T); +typedef NativeQuadOpSigned = ffi.Int64 Function( + ffi.Int64, ffi.Int32, ffi.Int16, ffi.Int8); +typedef NativeQuadOpUnsigned = ffi.Uint64 Function( + ffi.Uint64, ffi.Uint32, ffi.Uint16, ffi.Uint8); +typedef NativeFunc4 = ffi.IntPtr Function(ffi.IntPtr); +typedef NativeDoubleUnaryOp = ffi.Double Function(ffi.Double); +typedef NativeFloatUnaryOp = ffi.Float Function(ffi.Float); +typedef NativeOctenaryOp = ffi.IntPtr Function( + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr); +typedef NativeDoubleOctenaryOp = ffi.Double Function( + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double); +typedef NativeVigesimalOp = ffi.Double Function( + ffi.IntPtr, + ffi.Float, + ffi.IntPtr, + ffi.Double, + ffi.IntPtr, + ffi.Float, + ffi.IntPtr, + ffi.Double, + ffi.IntPtr, + ffi.Float, + ffi.IntPtr, + ffi.Double, + ffi.IntPtr, + ffi.Float, + ffi.IntPtr, + ffi.Double, + ffi.IntPtr, + ffi.Float, + ffi.IntPtr, + ffi.Double); +typedef Int64PointerUnOp = ffi.Pointer Function( + ffi.Pointer); +typedef QuadOp = int Function(int, int, int, int); +typedef DoubleUnaryOp = double Function(double); +typedef OctenaryOp = int Function( + int, int, int, int, int, int, int, int, int, int); +typedef DoubleOctenaryOp = double Function(double, double, double, double, + double, double, double, double, double, double); +typedef VigesimalOp = double Function( + int, + double, + int, + double, + int, + double, + int, + double, + int, + double, + int, + double, + int, + double, + int, + double, + int, + double, + int, + double); + +main(List arguments) { + print('start main'); + + ffi.DynamicLibrary ffiTestFunctions = + ffi.DynamicLibrary.open("ffi_test_functions"); + + { + // int32 bin op + BinaryOp sumPlus42 = + ffiTestFunctions.lookupFunction("SumPlus42"); + + var result = sumPlus42(3, 17); + print(result); + print(result.runtimeType); + } + + { + // various size arguments + QuadOp intComputation = ffiTestFunctions + .lookupFunction("IntComputation"); + var result = intComputation(125, 250, 500, 1000); + print(result); + print(result.runtimeType); + + var mint = 0x7FFFFFFFFFFFFFFF; // 2 ^ 63 - 1 + result = intComputation(1, 1, 0, mint); + print(result); + print(result.runtimeType); + } + + { + // unsigned int parameters + QuadOp uintComputation = ffiTestFunctions + .lookupFunction("UintComputation"); + var result = uintComputation(0xFF, 0xFFFF, 0xFFFFFFFF, -1); + result = uintComputation(1, 1, 0, -1); + print(result); + print(result.runtimeType); + print(-0xFF + 0xFFFF - 0xFFFFFFFF); + } + + { + // architecture size argument + ffi.Pointer> p = + ffiTestFunctions.lookup("Times3"); + UnaryOp f6 = p.asFunction(); + var result = f6(1337); + print(result); + print(result.runtimeType); + } + + { + // function with double + DoubleUnaryOp times1_337Double = ffiTestFunctions + .lookupFunction("Times1_337Double"); + var result = times1_337Double(2.0); + print(result); + print(result.runtimeType); + } + + { + // function with float + DoubleUnaryOp times1_337Float = ffiTestFunctions + .lookupFunction("Times1_337Float"); + var result = times1_337Float(1000.0); + print(result); + print(result.runtimeType); + } + + { + // function with many arguments: arguments get passed in registers and stack + OctenaryOp sumManyInts = ffiTestFunctions + .lookupFunction("SumManyInts"); + var result = sumManyInts(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + print(result); + print(result.runtimeType); + } + + { + // function with many double arguments + DoubleOctenaryOp sumManyDoubles = ffiTestFunctions.lookupFunction< + NativeDoubleOctenaryOp, DoubleOctenaryOp>("SumManyDoubles"); + var result = + sumManyDoubles(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0); + print(result); + print(result.runtimeType); + } + + { + // function with many arguments, ints and doubles mixed + VigesimalOp sumManyNumbers = ffiTestFunctions + .lookupFunction("SumManyNumbers"); + var result = sumManyNumbers(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9, 10.0, 11, + 12.0, 13, 14.0, 15, 16.0, 17, 18.0, 19, 20.0); + print(result); + print(result.runtimeType); + } + + { + // pass an array / pointer as argument + Int64PointerUnOp assign1337Index1 = ffiTestFunctions + .lookupFunction("Assign1337Index1"); + ffi.Pointer p2 = ffi.allocate(count: 2); + p2.store(42); + p2.elementAt(1).store(1000); + print(p2.elementAt(1).address.toRadixString(16)); + print(p2.elementAt(1).load()); + ffi.Pointer result = assign1337Index1(p2); + print(p2.elementAt(1).load()); + print(assign1337Index1); + print(assign1337Index1.runtimeType); + print(result); + print(result.runtimeType); + print(result.address.toRadixString(16)); + print(result.load()); + } + + { + // passing in null for an int argument throws a null pointer exception + BinaryOp sumPlus42 = + ffiTestFunctions.lookupFunction("SumPlus42"); + + int x = null; + try { + sumPlus42(43, x); + } on ArgumentError { + print('Expected exception on passing null for int'); + } + } + + { + // passing in null for a double argument throws a null pointer exception + DoubleUnaryOp times1_337Double = ffiTestFunctions + .lookupFunction("Times1_337Double"); + + double x = null; + try { + times1_337Double(x); + } on ArgumentError { + print('Expected exception on passing null for double'); + } + } + + { + // passing in null for an int argument throws a null pointer exception + VigesimalOp sumManyNumbers = ffiTestFunctions + .lookupFunction("SumManyNumbers"); + + int x = null; + try { + sumManyNumbers(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9, 10.0, 11, 12.0, 13, + 14.0, 15, 16.0, 17, 18.0, x, 20.0); + } on ArgumentError { + print('Expected exception on passing null for int'); + } + } + + { + // passing in null for a pointer argument results in a nullptr in c + Int64PointerUnOp nullableInt64ElemAt1 = + ffiTestFunctions.lookupFunction( + "NullableInt64ElemAt1"); + + ffi.Pointer result = nullableInt64ElemAt1(null); + print(result); + print(result.runtimeType); + + ffi.Pointer p2 = ffi.allocate(count: 2); + result = nullableInt64ElemAt1(p2); + print(result); + print(result.runtimeType); + p2.free(); + } + + print("end main"); +} diff --git a/samples/ffi/sample_ffi_functions_callbacks.dart b/samples/ffi/sample_ffi_functions_callbacks.dart new file mode 100644 index 00000000000..ddd68405f5e --- /dev/null +++ b/samples/ffi/sample_ffi_functions_callbacks.dart @@ -0,0 +1,80 @@ +// 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. + +import 'dart:ffi' as ffi; + +import 'coordinate.dart'; + +typedef NativeCoordinateOp = Coordinate Function(Coordinate); + +typedef CoordinateTrice = Coordinate Function( + ffi.Pointer>, Coordinate); + +typedef BinaryOp = int Function(int, int); +typedef NativeIntptrBinOp = ffi.IntPtr Function(ffi.IntPtr, ffi.IntPtr); +typedef NativeIntptrBinOpLookup + = ffi.Pointer> Function(); + +typedef NativeApplyTo42And74Type = ffi.IntPtr Function( + ffi.Pointer>); + +typedef ApplyTo42And74Type = int Function( + ffi.Pointer>); + +int myPlus(int a, int b) { + print("myPlus"); + print(a); + print(b); + return a + b; +} + +main(List arguments) { + print('start main'); + + ffi.DynamicLibrary ffiTestFunctions = + ffi.DynamicLibrary.open("ffi_test_functions"); + + { + // pass a c pointer to a c function as an argument to a c function + ffi.Pointer> + transposeCoordinatePointer = + ffiTestFunctions.lookup("TransposeCoordinate"); + ffi.Pointer> p2 = + ffiTestFunctions.lookup("CoordinateUnOpTrice"); + CoordinateTrice coordinateUnOpTrice = p2.asFunction(); + Coordinate c1 = Coordinate(10.0, 20.0, null); + c1.next = c1; + Coordinate result = coordinateUnOpTrice(transposeCoordinatePointer, c1); + print(result.runtimeType); + print(result.x); + print(result.y); + } + + { + // return a c pointer to a c function from a c function + ffi.Pointer> p14 = + ffiTestFunctions.lookup("IntptrAdditionClosure"); + NativeIntptrBinOpLookup intptrAdditionClosure = p14.asFunction(); + + ffi.Pointer> intptrAdditionPointer = + intptrAdditionClosure(); + BinaryOp intptrAddition = intptrAdditionPointer.asFunction(); + print(intptrAddition(10, 27)); + } + + { + ffi.Pointer> pointer = + ffi.fromFunction(myPlus); + print(pointer); + + ffi.Pointer> p17 = + ffiTestFunctions.lookup("ApplyTo42And74"); + ApplyTo42And74Type applyTo42And74 = p17.asFunction(); + + // int result = applyTo42And74(pointer); + // print(result); + } + + print("end main"); +} diff --git a/samples/ffi/sample_ffi_functions_structs.dart b/samples/ffi/sample_ffi_functions_structs.dart new file mode 100644 index 00000000000..ba076e007f2 --- /dev/null +++ b/samples/ffi/sample_ffi_functions_structs.dart @@ -0,0 +1,64 @@ +// 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. + +import 'dart:ffi' as ffi; + +import 'coordinate.dart'; + +typedef NativeCoordinateOp = Coordinate Function(Coordinate); + +main(List arguments) { + print('start main'); + + ffi.DynamicLibrary ffiTestFunctions = + ffi.DynamicLibrary.open("ffi_test_functions"); + + { + // pass a struct to a c function and get a struct as return value + ffi.Pointer> p1 = + ffiTestFunctions.lookup("TransposeCoordinate"); + NativeCoordinateOp f1 = p1.asFunction(); + + Coordinate c1 = Coordinate(10.0, 20.0, null); + Coordinate c2 = Coordinate(42.0, 84.0, c1); + c1.next = c2; + + Coordinate result = f1(c1); + + print(c1.x); + print(c1.y); + + print(result.runtimeType); + + print(result.x); + print(result.y); + } + + { + // pass an array of structs to a c funtion + ffi.Pointer> p1 = + ffiTestFunctions.lookup("CoordinateElemAt1"); + NativeCoordinateOp f1 = p1.asFunction(); + + Coordinate c1 = Coordinate.allocate(count: 3); + Coordinate c2 = c1.elementAt(1); + Coordinate c3 = c1.elementAt(2); + c1.x = 10.0; + c1.y = 10.0; + c1.next = c3; + c2.x = 20.0; + c2.y = 20.0; + c2.next = c1; + c3.x = 30.0; + c3.y = 30.0; + c3.next = c2; + + Coordinate result = f1(c1); + + print(result.x); + print(result.y); + } + + print("end main"); +} diff --git a/samples/ffi/sample_ffi_structs.dart b/samples/ffi/sample_ffi_structs.dart new file mode 100644 index 00000000000..fd757e5e89d --- /dev/null +++ b/samples/ffi/sample_ffi_structs.dart @@ -0,0 +1,63 @@ +// 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. + +import 'dart:ffi' as ffi; + +import 'coordinate.dart'; + +main(List arguments) { + print('start main'); + + { + // allocates each coordinate separately in c memory + Coordinate c1 = Coordinate(10.0, 10.0, null); + Coordinate c2 = Coordinate(20.0, 20.0, c1); + Coordinate c3 = Coordinate(30.0, 30.0, c2); + c1.next = c3; + + Coordinate currentCoordinate = c1; + for (var i in [0, 1, 2, 3, 4]) { + currentCoordinate = currentCoordinate.next; + print("${currentCoordinate.x}; ${currentCoordinate.y}"); + } + + c1.free(); + c2.free(); + c3.free(); + } + + { + // allocates coordinates consecutively in c memory + Coordinate c1 = Coordinate.allocate(count: 3); + Coordinate c2 = c1.elementAt(1); + Coordinate c3 = c1.elementAt(2); + c1.x = 10.0; + c1.y = 10.0; + c1.next = c3; + c2.x = 20.0; + c2.y = 20.0; + c2.next = c1; + c3.x = 30.0; + c3.y = 30.0; + c3.next = c2; + + Coordinate currentCoordinate = c1; + for (var i in [0, 1, 2, 3, 4]) { + currentCoordinate = currentCoordinate.next; + print("${currentCoordinate.x}; ${currentCoordinate.y}"); + } + + c1.free(); + } + + { + Coordinate c = Coordinate(10, 10, null); + print(c is Coordinate); + print(c is ffi.Pointer); + print(c is ffi.Pointer); + c.free(); + } + + print("end main"); +} diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index df1237f86fe..3f48b0aefa2 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -183,6 +183,7 @@ _full_sdk_libraries = [ "convert", "core", "developer", + "ffi", "html", "_http", "indexed_db", diff --git a/sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart b/sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart index 5e1c046246c..58a9e90f22f 100644 --- a/sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart +++ b/sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart @@ -63,6 +63,11 @@ const Map libraries = const { categories: "Client,Server,Embedded", maturity: Maturity.UNSTABLE, dart2jsPatchPath: "_internal/js_runtime/lib/developer_patch.dart"), + "ffi": const LibraryInfo("ffi/ffi.dart", + categories: "Server", + // TODO(dacoharkes): Update maturity when we release dart:ffi. + // https://github.com/dart-lang/sdk/issues/34452 + maturity: Maturity.EXPERIMENTAL), "html": const LibraryInfo("html/dart2js/html_dart2js.dart", categories: "Client", maturity: Maturity.WEB_STABLE, diff --git a/sdk/lib/ffi/annotations.dart b/sdk/lib/ffi/annotations.dart new file mode 100644 index 00000000000..24401e6dfcb --- /dev/null +++ b/sdk/lib/ffi/annotations.dart @@ -0,0 +1,50 @@ +// 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. + +part of dart.ffi; + +class Struct { + const Struct(); +} + +/// This Dart class represents a C struct. +/// +/// Fields in this struct, annotated with a subtype of [NativeType], are +/// automatically transformed into wrappers to access the fields of the struct +/// in C memory. +/// +/// Fields without a [NativeType] annotation are not supported. +const struct = const Struct(); + +class DartRepresentationOf { + /// Represents the Dart type corresponding to a [NativeType]. + /// + /// [Int8] -> [int] + /// [Int16] -> [int] + /// [Int32] -> [int] + /// [Int64] -> [int] + /// [Uint8] -> [int] + /// [Uint16] -> [int] + /// [Uint32] -> [int] + /// [Uint64] -> [int] + /// [IntPtr] -> [int] + /// [Double] -> [double] + /// [Float] -> [double] + /// [Pointer] -> [Pointer] + /// T extends [Pointer] -> T + /// [NativeFunction] S1 Function(S2, S3) + /// where DartRepresentationOf(Tn) -> Sn + const DartRepresentationOf(String nativeType); +} + +class Unsized { + const Unsized(); +} + +/// This [NativeType] does not have predefined size. +/// +/// Unsized NativeTypes do not support [sizeOf] because their size is unknown. +/// Consequently, [allocate], [Pointer.load], [Pointer.store], and +/// [Pointer.elementAt] are not available. +const unsized = const Unsized(); diff --git a/sdk/lib/ffi/dynamic_library.dart b/sdk/lib/ffi/dynamic_library.dart new file mode 100644 index 00000000000..98aa64a78a0 --- /dev/null +++ b/sdk/lib/ffi/dynamic_library.dart @@ -0,0 +1,32 @@ +// 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. + +part of dart.ffi; + +/// Represents a dynamically loaded C library. +class DynamicLibrary { + /// Loads a dynamic library file. This is the equivalent of dlopen. + /// + /// Throws an [ArgumentError] if loading the dynamic library fails. + /// + /// Note that it loads the functions in the library lazily (RTLD_LAZY). + external factory DynamicLibrary.open(String name); + + /// Looks up a symbol in the [DynamicLibrary] and returns its address in + /// memory. Equivalent of dlsym. + /// + /// Throws an [ArgumentError] if it fails to lookup the symbol. + external Pointer lookup(String symbolName); + + /// Helper that combines lookup and cast to a Dart function. + F lookupFunction(String symbolName) { + return lookup>(symbolName)?.asFunction(); + } + + /// Dynamic libraries are equal if they load the same library. + external bool operator ==(other); + + /// The hash code for a DynamicLibrary only depends on the loaded library + external int get hashCode; +} diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart new file mode 100644 index 00000000000..407cc122b55 --- /dev/null +++ b/sdk/lib/ffi/ffi.dart @@ -0,0 +1,102 @@ +// 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 + +library dart.ffi; + +part "native_type.dart"; +part "annotations.dart"; +part "dynamic_library.dart"; + +/// Allocate [count] elements of type [T] on the C heap with malloc() and return +/// a pointer to the newly allocated memory. +/// +/// Note that the memory are uninitialized. +/// +/// TODO(dacoharkes): change signature to T allocate() ? +/// This would enable us to allocate structs. However how do we know the size of +/// structs? https://github.com/dart-lang/sdk/issues/35782 +external Pointer allocate({int count: 1}); + +/// Construction from raw value +external T fromAddress(int ptr); + +/// number of bytes used by native type T +external int sizeOf(); + +/// Convert Dart function to a C function pointer, automatically marshalling +/// the arguments and return value +/// +/// Note: this is not implemented, always returns Pointer with address 0. +/// +/// TODO(dacoharkes): Implement this feature. +/// https://github.com/dart-lang/sdk/issues/35761 +external Pointer> fromFunction( + @DartRepresentationOf("T") Function f); + +/* +/// TODO(dacoharkes): Implement this feature. +/// https://github.com/dart-lang/sdk/issues/35770 +/// Return a pointer object that has a finalizer attached to it. When this +/// pointer object is collected by GC the given finalizer is invoked. +/// +/// Note: the pointer object passed to the finalizer is not the same as +/// the pointer object that is returned from [finalizable] - it points +/// to the same memory region but has different identity. +external Pointer finalizable( + Pointer p, void finalizer(Pointer ptr)); +*/ + +/// Represents a pointer into the native C memory. +class Pointer extends NativeType { + const Pointer(); + + /// Store a Dart value into this location. + /// + /// The [value] is automatically marshalled into its C representation. + /// Note that ints which do not fit in [T] are truncated and sign extended, + /// and doubles stored into Pointer<[Float]> lose precision. + external void store(@DartRepresentationOf("T") Object value); + + /// Load a Dart value from this location. + /// + /// The value is automatically unmarshalled from its C representation. + external R load<@DartRepresentationOf("T") R>(); + + /// Access to the raw pointer value. + external int get address; + + /// Pointer arithmetic (takes element size into account). + external Pointer elementAt(int index); + + /// Pointer arithmetic (byte offset). + /// + /// TODO(dacoharkes): remove this? + /// https://github.com/dart-lang/sdk/issues/35883 + external Pointer offsetBy(int offsetInBytes); + + /// Cast Pointer to a (subtype of) Pointer. + external U cast(); + + /// Convert to Dart function, automatically marshalling the arguments + /// and return value. + /// + /// Can only be called on [Pointer]<[NativeFunction]>. + external R asFunction<@DartRepresentationOf("T") R extends Function>(); + + /// Free memory on the C heap pointed to by this pointer with free(). + /// + /// Note that this zeros out the address. + external void free(); + + /// Equality for Pointers only depends on their address. + bool operator ==(other) { + if (other == null) return false; + return address == other.address; + } + + /// The hash code for a Pointer only depends on its address. + int get hashCode { + return address.hashCode; + } +} diff --git a/sdk/lib/ffi/ffi_sources.gni b/sdk/lib/ffi/ffi_sources.gni new file mode 100644 index 00000000000..d67b2673067 --- /dev/null +++ b/sdk/lib/ffi/ffi_sources.gni @@ -0,0 +1,12 @@ +# 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. + +ffi_sdk_sources = [ + "ffi.dart", + + # The above file needs to be first as it lists the parts below. + "annotations.dart", + "dynamic_library.dart", + "native_type.dart" +] diff --git a/sdk/lib/ffi/native_type.dart b/sdk/lib/ffi/native_type.dart new file mode 100644 index 00000000000..61f9cdeb29a --- /dev/null +++ b/sdk/lib/ffi/native_type.dart @@ -0,0 +1,133 @@ +// 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. + +part of dart.ffi; + +/// [NativeType]'s subtypes represent a native type in C. +/// +/// [NativeType]'s subtypes are not constructible in the Dart code and serve +/// purely as markers in type signatures. +class NativeType { + const NativeType(); +} + +/// [_NativeInteger]'s subtypes represent a native integer in C. +/// +/// [_NativeInteger]'s subtypes are not constructible in the Dart code and serve +/// purely as markers in type signatures. +class _NativeInteger extends NativeType { + const _NativeInteger(); +} + +/// [_NativeDouble]'s subtypes represent a native float or double in C. +/// +/// [_NativeDouble]'s subtypes are not constructible in the Dart code and serve +/// purely as markers in type signatures. +class _NativeDouble extends NativeType { + const _NativeDouble(); +} + +/// Represents a native signed 8 bit integer in C. +/// +/// [Int8] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +class Int8 extends _NativeInteger { + const Int8(); +} + +/// Represents a native signed 16 bit integer in C. +/// +/// [Int16] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +class Int16 extends _NativeInteger { + const Int16(); +} + +/// Represents a native signed 32 bit integer in C. +/// +/// [Int32] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +class Int32 extends _NativeInteger { + const Int32(); +} + +/// Represents a native signed 64 bit integer in C. +/// +/// [Int64] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +class Int64 extends _NativeInteger { + const Int64(); +} + +/// Represents a native unsigned 8 bit integer in C. +/// +/// [Uint8] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +class Uint8 extends _NativeInteger { + const Uint8(); +} + +/// Represents a native unsigned 16 bit integer in C. +/// +/// [Uint16] is not constructible in the Dart code and serves purely as marker +/// in type signatures. +class Uint16 extends _NativeInteger { + const Uint16(); +} + +/// Represents a native unsigned 32 bit integer in C. +/// +/// [Uint32] is not constructible in the Dart code and serves purely as marker +/// in type signatures. +class Uint32 extends _NativeInteger { + const Uint32(); +} + +/// Represents a native unsigned 64 bit integer in C. +/// +/// [Uint64] is not constructible in the Dart code and serves purely as marker +/// in type signatures. +class Uint64 extends _NativeInteger { + const Uint64(); +} + +/// Represents a native pointer-sized integer in C. +/// +/// [IntPtr] is not constructible in the Dart code and serves purely as marker +/// in type signatures. +class IntPtr extends _NativeInteger { + const IntPtr(); +} + +/// Represents a native 32 bit float in C. +/// +/// [Float] is not constructible in the Dart code and serves purely as marker +/// in type signatures. +class Float extends _NativeDouble { + const Float(); +} + +/// Represents a native 64 bit double in C. +/// +/// [Double] is not constructible in the Dart code and serves purely as marker +/// in type signatures. +class Double extends _NativeDouble { + const Double(); +} + +/// Represents a void type in C. +/// +/// [Void] is not constructible in the Dart code and serves purely as marker in +/// type signatures. +@unsized +class Void extends NativeType { + const Void(); +} + +/// Represents a function type in C. +/// +/// [NativeFunction] is not constructible in the Dart code and serves purely as +/// marker in type signatures. +@unsized +class NativeFunction extends NativeType {} diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json index db0bf0c4cba..9b239ffc89e 100644 --- a/sdk/lib/libraries.json +++ b/sdk/lib/libraries.json @@ -63,6 +63,14 @@ ], "uri": "collection/collection.dart" }, + "ffi": { + "patches": [ + "../../runtime/lib/ffi_dynamic_library_patch.dart", + "../../runtime/lib/ffi_native_type_patch.dart", + "../../runtime/lib/ffi_patch.dart" + ], + "uri": "ffi/ffi.dart" + }, "typed_data": { "patches": "../../runtime/lib/typed_data_patch.dart", "uri": "typed_data/typed_data.dart" diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml index e2d0f7c2a36..a53b69ad634 100644 --- a/sdk/lib/libraries.yaml +++ b/sdk/lib/libraries.yaml @@ -87,6 +87,13 @@ vm: - "../../runtime/lib/profiler.dart" - "../../runtime/lib/timeline.dart" + ffi: + uri: "ffi/ffi.dart" + patches: + - "../../runtime/lib/ffi_dynamic_library_patch.dart" + - "../../runtime/lib/ffi_native_type_patch.dart" + - "../../runtime/lib/ffi_patch.dart" + _http: uri: "_http/http.dart" diff --git a/tests/standalone_2/ffi/coordinate.dart b/tests/standalone_2/ffi/coordinate.dart new file mode 100644 index 00000000000..fbca95276ed --- /dev/null +++ b/tests/standalone_2/ffi/coordinate.dart @@ -0,0 +1,40 @@ +// 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. + +library FfiTest; + +import 'dart:ffi' as ffi; + +/// Sample struct for dart:ffi library. +@ffi.struct +class Coordinate extends ffi.Pointer { + @ffi.Double() + double x; + + @ffi.Double() + double y; + + @ffi.Pointer() + Coordinate next; + + /// generated by @ffi.struct annotation + external static int sizeOf(); + + Coordinate offsetBy(int offsetInBytes) => + super.offsetBy(offsetInBytes).cast(); + + Coordinate elementAt(int index) => offsetBy(sizeOf() * index); + + static Coordinate allocate({int count: 1}) => + ffi.allocate(count: count * sizeOf()).cast(); + + /// Allocate a new [Coordinate] in C memory and populate its fields. + factory Coordinate(double x, double y, Coordinate next) { + Coordinate result = Coordinate.allocate() + ..x = x + ..y = y + ..next = next; + return result; + } +} diff --git a/tests/standalone_2/ffi/coordinate_bare.dart b/tests/standalone_2/ffi/coordinate_bare.dart new file mode 100644 index 00000000000..764cb9fb40c --- /dev/null +++ b/tests/standalone_2/ffi/coordinate_bare.dart @@ -0,0 +1,20 @@ +// 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. + +library FfiTestCoordinateBare; + +import 'dart:ffi' as ffi; + +/// Stripped down sample struct for dart:ffi library. +@ffi.struct +class Coordinate extends ffi.Pointer { + @ffi.Double() + double x; + + @ffi.Double() + double y; + + @ffi.Pointer() + Coordinate next; +} diff --git a/tests/standalone_2/ffi/coordinate_manual.dart b/tests/standalone_2/ffi/coordinate_manual.dart new file mode 100644 index 00000000000..d4d1aca2aed --- /dev/null +++ b/tests/standalone_2/ffi/coordinate_manual.dart @@ -0,0 +1,44 @@ +// 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. + +library FfiTestCoordinateManual; + +import 'dart:ffi' as ffi; + +/// Sample struct for dart:ffi library without use of ffi annotations. +class Coordinate extends ffi.Pointer { + ffi.Pointer get _xPtr => cast(); + set x(double v) => _xPtr.store(v); + double get x => _xPtr.load(); + + ffi.Pointer get _yPtr => + offsetBy(ffi.sizeOf() * 1).cast(); + set y(double v) => _yPtr.store(v); + double get y => _yPtr.load(); + + ffi.Pointer get _nextPtr => + offsetBy(ffi.sizeOf() * 2).cast(); + set next(Coordinate v) => _nextPtr.store(v); + Coordinate get next => _nextPtr.load(); + + static int sizeOf() => + ffi.sizeOf() * 2 + ffi.sizeOf(); + + Coordinate offsetBy(int offsetInBytes) => + super.offsetBy(offsetInBytes).cast(); + + Coordinate elementAt(int index) => offsetBy(sizeOf() * index); + + static Coordinate allocate({int count: 1}) => + ffi.allocate(count: count * sizeOf()).cast(); + + /// Allocate a new [Coordinate] in C memory and populate its fields. + factory Coordinate(double x, double y, Coordinate next) { + Coordinate result = Coordinate.allocate() + ..x = x + ..y = y + ..next = next; + return result; + } +} diff --git a/tests/standalone_2/ffi/cstring.dart b/tests/standalone_2/ffi/cstring.dart new file mode 100644 index 00000000000..b9760072215 --- /dev/null +++ b/tests/standalone_2/ffi/cstring.dart @@ -0,0 +1,32 @@ +// 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. + +library FfiTest; + +import 'dart:convert'; +import 'dart:ffi' as ffi; + +/// Sample non-struct subtype of Pointer for dart:ffi library. +class CString extends ffi.Pointer { + CString elementAt(int index) => super.elementAt(index).cast(); + + String fromUtf8() { + List units = []; + int len = 0; + while (true) { + int char = elementAt(len++).load(); + if (char == 0) break; + units.add(char); + } + return Utf8Decoder().convert(units); + } + + factory CString.toUtf8(String s) { + CString result = ffi.allocate(count: s.length + 1).cast(); + List units = Utf8Encoder().convert(s); + for (int i = 0; i < s.length; i++) result.elementAt(i).store(units[i]); + result.elementAt(s.length).store(0); + return result; + } +} diff --git a/tests/standalone_2/ffi/data_not_asan_test.dart b/tests/standalone_2/ffi/data_not_asan_test.dart new file mode 100644 index 00000000000..2be85982e13 --- /dev/null +++ b/tests/standalone_2/ffi/data_not_asan_test.dart @@ -0,0 +1,29 @@ +// 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. +// +// Dart test program for testing dart:ffi primitive data pointers. +// This test tries to allocate too much memory on purpose to test the Exception +// thrown on malloc failing. +// This malloc also triggers an asan alarm, so this test is in a separate file +// which is excluded in asan mode. + +library FfiTest; + +import 'dart:ffi' as ffi; + +import "package:expect/expect.dart"; + +void main() { + testPointerAllocateTooLarge(); +} + +/// This test is skipped in asan mode. +void testPointerAllocateTooLarge() { + int maxInt = 9223372036854775807; // 2^63 - 1 + Expect.throws( + () => ffi.allocate(count: maxInt)); // does not fit in range + int maxInt1_8 = 1152921504606846975; // 2^60 -1 + Expect.throws( + () => ffi.allocate(count: maxInt1_8)); // not enough memory +} diff --git a/tests/standalone_2/ffi/data_test.dart b/tests/standalone_2/ffi/data_test.dart new file mode 100644 index 00000000000..84719360c58 --- /dev/null +++ b/tests/standalone_2/ffi/data_test.dart @@ -0,0 +1,492 @@ +// 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. +// +// Dart test program for testing dart:ffi primitive data pointers. + +library FfiTest; + +import 'dart:ffi' as ffi; + +import "package:expect/expect.dart"; + +void main() { + testPointerBasic(); + testPointerFromPointer(); + testPointerPointerArithmetic(); + testPointerPointerArithmeticSizes(); + testPointerAllocateNonPositive(); + testPointerCast(); + testCastGeneric(); + testCastGeneric2(); + testCastNativeType(); + testCondensedNumbersInt8(); + testCondensedNumbersFloat(); + testRangeInt8(); + testRangeUint8(); + testRangeInt16(); + testRangeUint16(); + testRangeInt32(); + testRangeUint32(); + testRangeInt64(); + testRangeUint64(); + testRangeIntPtr(); + testFloat(); + testDouble(); + testVoid(); + testPointerPointer(); + testPointerPointerNull(); + testPointerStoreNull(); + testSizeOf(); + testPointerChain(1000); + testTypeTest(); + testToString(); + testEquality(); + testAllocateGeneric(); + testAllocateVoid(); + testAllocateNativeFunction(); + testAllocateNativeType(); + testSizeOfGeneric(); + testSizeOfVoid(); + testSizeOfNativeFunction(); + testSizeOfNativeType(); + testFreeZeroOut(); +} + +void testPointerBasic() { + ffi.Pointer p = ffi.allocate(); + p.store(42); + Expect.equals(42, p.load()); + p.free(); +} + +void testPointerFromPointer() { + ffi.Pointer p = ffi.allocate(); + p.store(1337); + int ptr = p.address; + ffi.Pointer p2 = ffi.fromAddress(ptr); + Expect.equals(1337, p2.load()); + p.free(); +} + +void testPointerPointerArithmetic() { + ffi.Pointer p = ffi.allocate(count: 2); + ffi.Pointer p2 = p.elementAt(1); + p2.store(100); + ffi.Pointer p3 = p.offsetBy(8); + Expect.equals(100, p3.load()); + p.free(); +} + +void testPointerPointerArithmeticSizes() { + ffi.Pointer p = ffi.allocate(count: 2); + ffi.Pointer p2 = p.elementAt(1); + int addr = p.address; + Expect.equals(addr + 8, p2.address); + p.free(); + + ffi.Pointer p3 = ffi.allocate(count: 2); + ffi.Pointer p4 = p3.elementAt(1); + addr = p3.address; + Expect.equals(addr + 4, p4.address); + p3.free(); +} + +void testPointerAllocateNonPositive() { + Expect.throws(() => ffi.allocate(count: 0)); + Expect.throws(() => ffi.allocate(count: -1)); +} + +void testPointerCast() { + ffi.Pointer p = ffi.allocate(); + ffi.Pointer p2 = p.cast(); // gets the correct type args back + p.free(); +} + +void testCastGeneric() { + ffi.Pointer generic(ffi.Pointer p) { + return p.cast(); + } + + ffi.Pointer p = ffi.allocate(); + ffi.Pointer p2 = generic(p); + p.free(); +} + +void testCastGeneric2() { + ffi.Pointer generic(ffi.Pointer p) { + return p.cast(); + } + + ffi.Pointer p = ffi.allocate(); + ffi.Pointer p2 = generic(p); + p.free(); +} + +void testCastNativeType() { + ffi.Pointer p = ffi.allocate(); + Expect.throws(() { + p.cast(); + }); + p.free(); +} + +void testCondensedNumbersInt8() { + ffi.Pointer p = ffi.allocate(count: 8); + for (var i in [0, 1, 2, 3, 4, 5, 6, 7]) { + p.elementAt(i).store(i * 3); + } + for (var i in [0, 1, 2, 3, 4, 5, 6, 7]) { + Expect.equals(i * 3, p.elementAt(i).load()); + } + p.free(); +} + +void testCondensedNumbersFloat() { + ffi.Pointer p = ffi.allocate(count: 8); + for (var i in [0, 1, 2, 3, 4, 5, 6, 7]) { + p.elementAt(i).store(1.511366173271439e-13); + } + for (var i in [0, 1, 2, 3, 4, 5, 6, 7]) { + Expect.equals(1.511366173271439e-13, p.elementAt(i).load()); + } + p.free(); +} + +void testRangeInt8() { + ffi.Pointer p = ffi.allocate(); + p.store(127); + Expect.equals(127, p.load()); + p.store(-128); + Expect.equals(-128, p.load()); + + Expect.equals(0x0000000000000080, 128); + Expect.equals(0xFFFFFFFFFFFFFF80, -128); + p.store(128); + Expect.equals(-128, p.load()); // truncated and sign extended + + Expect.equals(0xFFFFFFFFFFFFFF7F, -129); + Expect.equals(0x000000000000007F, 127); + p.store(-129); + Expect.equals(127, p.load()); // truncated + p.free(); +} + +void testRangeUint8() { + ffi.Pointer p = ffi.allocate(); + p.store(255); + Expect.equals(255, p.load()); + p.store(0); + Expect.equals(0, p.load()); + + Expect.equals(0x0000000000000000, 0); + Expect.equals(0x0000000000000100, 256); + p.store(256); + Expect.equals(0, p.load()); // truncated + + Expect.equals(0xFFFFFFFFFFFFFFFF, -1); + Expect.equals(0x00000000000000FF, 255); + p.store(-1); + Expect.equals(255, p.load()); // truncated + p.free(); +} + +void testRangeInt16() { + ffi.Pointer p = ffi.allocate(); + p.store(0x7FFF); + Expect.equals(0x7FFF, p.load()); + p.store(-0x8000); + Expect.equals(-0x8000, p.load()); + p.store(0x8000); + Expect.equals( + 0xFFFFFFFFFFFF8000, p.load()); // truncated and sign extended + p.store(-0x8001); + Expect.equals(0x7FFF, p.load()); // truncated + p.free(); +} + +void testRangeUint16() { + ffi.Pointer p = ffi.allocate(); + p.store(0xFFFF); + Expect.equals(0xFFFF, p.load()); + p.store(0); + Expect.equals(0, p.load()); + p.store(0x10000); + Expect.equals(0, p.load()); // truncated + p.store(-1); + Expect.equals(0xFFFF, p.load()); // truncated + p.free(); +} + +void testRangeInt32() { + ffi.Pointer p = ffi.allocate(); + p.store(0x7FFFFFFF); + Expect.equals(0x7FFFFFFF, p.load()); + p.store(-0x80000000); + Expect.equals(-0x80000000, p.load()); + p.store(0x80000000); + Expect.equals( + 0xFFFFFFFF80000000, p.load()); // truncated and sign extended + p.store(-0x80000001); + Expect.equals(0x7FFFFFFF, p.load()); // truncated + p.free(); +} + +void testRangeUint32() { + ffi.Pointer p = ffi.allocate(); + p.store(0xFFFFFFFF); + Expect.equals(0xFFFFFFFF, p.load()); + p.store(0); + Expect.equals(0, p.load()); + p.store(0x100000000); + Expect.equals(0, p.load()); // truncated + p.store(-1); + Expect.equals(0xFFFFFFFF, p.load()); // truncated + p.free(); +} + +void testRangeInt64() { + ffi.Pointer p = ffi.allocate(); + p.store(0x7FFFFFFFFFFFFFFF); // 2 ^ 63 - 1 + Expect.equals(0x7FFFFFFFFFFFFFFF, p.load()); + p.store(-0x8000000000000000); // -2 ^ 63 + Expect.equals(-0x8000000000000000, p.load()); + p.free(); +} + +void testRangeUint64() { + ffi.Pointer p = ffi.allocate(); + p.store(0x7FFFFFFFFFFFFFFF); // 2 ^ 63 - 1 + Expect.equals(0x7FFFFFFFFFFFFFFF, p.load()); + p.store(-0x8000000000000000); // -2 ^ 63 interpreted as 2 ^ 63 + Expect.equals(-0x8000000000000000, p.load()); + + // Dart allows interpreting bits both signed and unsigned + Expect.equals(0xFFFFFFFFFFFFFFFF, -1); + p.store(-1); // -1 interpreted as 2 ^ 64 - 1 + Expect.equals(-1, p.load()); + Expect.equals(0xFFFFFFFFFFFFFFFF, p.load()); + p.free(); +} + +void testRangeIntPtr() { + ffi.Pointer p = ffi.allocate(); + int pAddr = p.address; + p.store(pAddr); // its own address should fit + p.store(0x7FFFFFFF); // and 32 bit addresses should fit + Expect.equals(0x7FFFFFFF, p.load()); + p.store(-0x80000000); + Expect.equals(-0x80000000, p.load()); + p.free(); +} + +void testFloat() { + ffi.Pointer p = ffi.allocate(); + p.store(1.511366173271439e-13); + Expect.equals(1.511366173271439e-13, p.load()); + p.store(1.4260258159703532e-105); // float does not have enough precision + Expect.notEquals(1.4260258159703532e-105, p.load()); + p.free(); +} + +void testDouble() { + ffi.Pointer p = ffi.allocate(); + p.store(1.4260258159703532e-105); + Expect.equals(1.4260258159703532e-105, p.load()); + p.free(); +} + +void testVoid() { + ffi.Pointer p1 = ffi.allocate(); + ffi.Pointer p2 = p1.cast(); // make this dart pointer opaque + p2.address; // we can print the address + p2.free(); +} + +void testPointerPointer() { + ffi.Pointer p = ffi.allocate(); + p.store(17); + ffi.Pointer> p2 = ffi.allocate(); + p2.store(p); + Expect.equals(17, p2.load>().load()); + p2.free(); + p.free(); +} + +void testPointerPointerNull() { + ffi.Pointer> pointerToPointer = ffi.allocate(); + ffi.Pointer value = null; + pointerToPointer.store(value); + value = pointerToPointer.load(); + Expect.isNull(value); + value = ffi.allocate(); + pointerToPointer.store(value); + value = pointerToPointer.load(); + Expect.isNotNull(value); + value.free(); + value = null; + pointerToPointer.store(value); + value = pointerToPointer.load(); + Expect.isNull(value); + pointerToPointer.free(); +} + +void testPointerStoreNull() { + int i = null; + ffi.Pointer p = ffi.allocate(); + Expect.throws(() => p.store(i)); + p.free(); + double d = null; + ffi.Pointer p2 = ffi.allocate(); + Expect.throws(() => p2.store(d)); + p2.free(); +} + +void testSizeOf() { + Expect.equals(1, ffi.sizeOf()); + Expect.equals(2, ffi.sizeOf()); + Expect.equals(4, ffi.sizeOf()); + Expect.equals(8, ffi.sizeOf()); + Expect.equals(1, ffi.sizeOf()); + Expect.equals(2, ffi.sizeOf()); + Expect.equals(4, ffi.sizeOf()); + Expect.equals(8, ffi.sizeOf()); + Expect.equals( + true, 4 == ffi.sizeOf() || 8 == ffi.sizeOf()); + Expect.equals(4, ffi.sizeOf()); + Expect.equals(8, ffi.sizeOf()); +} + +// note: stack overflows at around 15k calls +void testPointerChain(int length) { + void createChain(ffi.Pointer head, int length, int value) { + if (length == 0) { + head.store(value); + return; + } + ffi.Pointer next = ffi.allocate(); + head.store(next.address); + createChain(next, length - 1, value); + } + + int getChainValue(ffi.Pointer head, int length) { + if (length == 0) { + return head.load(); + } + ffi.Pointer next = ffi.fromAddress(head.load()); + return getChainValue(next, length - 1); + } + + void freeChain(ffi.Pointer head, int length) { + ffi.Pointer next = ffi.fromAddress(head.load()); + head.free(); + if (length == 0) { + return; + } + freeChain(next, length - 1); + } + + ffi.Pointer head = ffi.allocate(); + createChain(head, length, 512); + int tailValue = getChainValue(head, length); + Expect.equals(512, tailValue); + freeChain(head, length); +} + +void testTypeTest() { + ffi.Pointer p = ffi.allocate(); + Expect.isTrue(p is ffi.Pointer); + p.free(); +} + +void testToString() { + ffi.Pointer p = ffi.allocate(); + Expect.stringEquals( + "Pointer: address=0x", p.toString().substring(0, 26)); + p.free(); +} + +void testEquality() { + ffi.Pointer p = ffi.fromAddress(12345678); + ffi.Pointer p2 = ffi.fromAddress(12345678); + Expect.equals(p, p2); + Expect.equals(p.hashCode, p2.hashCode); + ffi.Pointer p3 = p.cast(); + Expect.equals(p, p3); + Expect.equals(p.hashCode, p3.hashCode); + Expect.notEquals(p, null); + Expect.notEquals(null, p); + ffi.Pointer p4 = p.offsetBy(1337); + Expect.notEquals(p, p4); +} + +typedef Int8UnOp = ffi.Int8 Function(ffi.Int8); + +void testAllocateGeneric() { + ffi.Pointer generic() { + ffi.Pointer pointer; + pointer = ffi.allocate(); + return pointer; + } + + ffi.Pointer p = generic(); + p.free(); +} + +void testAllocateVoid() { + Expect.throws(() { + ffi.Pointer p = ffi.allocate(); + }); +} + +void testAllocateNativeFunction() { + Expect.throws(() { + ffi.Pointer> p = ffi.allocate(); + }); +} + +void testAllocateNativeType() { + Expect.throws(() { + ffi.allocate(); + }); +} + +void testSizeOfGeneric() { + int generic() { + int size; + size = ffi.sizeOf(); + return size; + } + + int size = generic>(); + Expect.equals(8, size); +} + +void testSizeOfVoid() { + Expect.throws(() { + ffi.sizeOf(); + }); +} + +void testSizeOfNativeFunction() { + Expect.throws(() { + ffi.sizeOf>(); + }); +} + +void testSizeOfNativeType() { + Expect.throws(() { + ffi.sizeOf(); + }); +} + +void testFreeZeroOut() { + // at least one of these pointers should have address != 0 on all platforms + ffi.Pointer p1 = ffi.allocate(); + ffi.Pointer p2 = ffi.allocate(); + Expect.notEquals(0, p1.address & p2.address); + p1.free(); + p2.free(); + Expect.equals(0, p1.address); + Expect.equals(0, p2.address); +} diff --git a/tests/standalone_2/ffi/dynamic_library_test.dart b/tests/standalone_2/ffi/dynamic_library_test.dart new file mode 100644 index 00000000000..1c3c45dd578 --- /dev/null +++ b/tests/standalone_2/ffi/dynamic_library_test.dart @@ -0,0 +1,63 @@ +// 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. +// +// Dart test program for testing dart:ffi dynamic library loading. + +library FfiTest; + +import 'dart:ffi' as ffi; + +import 'package:expect/expect.dart'; + +void main() { + testOpen(); + testOpenError(); + testLookup(); + testLookupError(); + testToString(); + testEquality(); +} + +void testOpen() { + ffi.DynamicLibrary l = ffi.DynamicLibrary.open("ffi_test_dynamic_library"); + Expect.notEquals(null, l); +} + +void testOpenError() { + Expect.throws( + () => ffi.DynamicLibrary.open("doesnotexistforsurelibrary123409876")); +} + +typedef NativeDoubleUnOp = ffi.Double Function(ffi.Double); + +typedef DoubleUnOp = double Function(double); + +void testLookup() { + ffi.DynamicLibrary l = ffi.DynamicLibrary.open("ffi_test_dynamic_library"); + var timesFour = l.lookupFunction("timesFour"); + Expect.approxEquals(12.0, timesFour(3)); +} + +void testLookupError() { + ffi.DynamicLibrary l = ffi.DynamicLibrary.open("ffi_test_dynamic_library"); + Expect.throws(() => l.lookupFunction( + "functionnamethatdoesnotexistforsure749237593845")); +} + +void testToString() { + ffi.DynamicLibrary l = ffi.DynamicLibrary.open("ffi_test_dynamic_library"); + Expect.stringEquals( + "DynamicLibrary: handle=0x", l.toString().substring(0, 25)); +} + +void testEquality() { + ffi.DynamicLibrary l = ffi.DynamicLibrary.open("ffi_test_dynamic_library"); + ffi.DynamicLibrary l2 = ffi.DynamicLibrary.open("ffi_test_dynamic_library"); + Expect.equals(l, l2); + Expect.equals(l.hashCode, l2.hashCode); + Expect.notEquals(l, null); + Expect.notEquals(null, l); + ffi.DynamicLibrary l3 = ffi.DynamicLibrary.open("ffi_test_functions"); + Expect.notEquals(l, l3); +} diff --git a/tests/standalone_2/ffi/enable_ffi_test.dart b/tests/standalone_2/ffi/enable_ffi_test.dart new file mode 100644 index 00000000000..53963126979 --- /dev/null +++ b/tests/standalone_2/ffi/enable_ffi_test.dart @@ -0,0 +1,20 @@ +// 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. +// +// Dart test program for testing the --enable-ffi=false flag. +// +// VMOptions=--enable-ffi=false + +library FfiTest; + +import 'dart:ffi' as ffi; + +import "package:expect/expect.dart"; + +void main() { + ffi.Pointer p = ffi.allocate(); + p.store(42); + Expect.equals(42, p.load()); + p.free(); +} diff --git a/tests/standalone_2/ffi/function_callbacks_test.dart b/tests/standalone_2/ffi/function_callbacks_test.dart new file mode 100644 index 00000000000..2f3212b8da1 --- /dev/null +++ b/tests/standalone_2/ffi/function_callbacks_test.dart @@ -0,0 +1,90 @@ +// 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. +// +// Dart test program for testing dart:ffi function pointers with callbacks. + +library FfiTest; + +import 'dart:ffi' as ffi; + +import "package:expect/expect.dart"; + +import 'coordinate.dart'; + +typedef NativeCoordinateOp = Coordinate Function(Coordinate); + +typedef CoordinateTrice = Coordinate Function( + ffi.Pointer>, Coordinate); + +void main() { + testFunctionWithFunctionPointer(); + testNativeFunctionWithFunctionPointer(); + testFromFunction(); +} + +ffi.DynamicLibrary ffiTestFunctions = + ffi.DynamicLibrary.open("ffi_test_functions"); + +/// pass a pointer to a c function as an argument to a c function +void testFunctionWithFunctionPointer() { + ffi.Pointer> + transposeCoordinatePointer = + ffiTestFunctions.lookup("TransposeCoordinate"); + + ffi.Pointer> p2 = + ffiTestFunctions.lookup("CoordinateUnOpTrice"); + CoordinateTrice coordinateUnOpTrice = p2.asFunction(); + + Coordinate c1 = Coordinate(10.0, 20.0, null); + c1.next = c1; + + Coordinate result = coordinateUnOpTrice(transposeCoordinatePointer, c1); + + print(result.runtimeType); + print(result.x); + print(result.y); + + c1.free(); +} + +typedef BinaryOp = int Function(int, int); + +typedef NativeIntptrBinOp = ffi.IntPtr Function(ffi.IntPtr, ffi.IntPtr); + +typedef NativeIntptrBinOpLookup + = ffi.Pointer> Function(); + +void testNativeFunctionWithFunctionPointer() { + ffi.Pointer> p1 = + ffiTestFunctions.lookup("IntptrAdditionClosure"); + NativeIntptrBinOpLookup intptrAdditionClosure = p1.asFunction(); + + ffi.Pointer> intptrAdditionPointer = + intptrAdditionClosure(); + BinaryOp intptrAddition = intptrAdditionPointer.asFunction(); + Expect.equals(37, intptrAddition(10, 27)); +} + +int myPlus(int a, int b) => a + b; + +typedef NativeApplyTo42And74Type = ffi.IntPtr Function( + ffi.Pointer>); + +typedef ApplyTo42And74Type = int Function( + ffi.Pointer>); + +void testFromFunction() { + ffi.Pointer> pointer = + ffi.fromFunction(myPlus); + Expect.isNotNull(pointer); + + ffi.Pointer> p17 = + ffiTestFunctions.lookup("ApplyTo42And74"); + ApplyTo42And74Type applyTo42And74 = p17.asFunction(); + + // TODO(dacoharkes): implement this + + // int result = applyTo42And74(pointer); + // print(result); +} diff --git a/tests/standalone_2/ffi/function_structs_test.dart b/tests/standalone_2/ffi/function_structs_test.dart new file mode 100644 index 00000000000..6f74a90bbd4 --- /dev/null +++ b/tests/standalone_2/ffi/function_structs_test.dart @@ -0,0 +1,116 @@ +// 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. +// +// Dart test program for testing dart:ffi function pointers with struct +// arguments. + +library FfiTest; + +import 'dart:ffi' as ffi; + +import "package:expect/expect.dart"; + +import 'coordinate.dart'; +import 'very_large_struct.dart'; + +typedef NativeCoordinateOp = Coordinate Function(Coordinate); + +void main() { + testFunctionWithStruct(); + testFunctionWithStructArray(); + testFunctionWithVeryLargeStruct(); +} + +ffi.DynamicLibrary ffiTestFunctions = + ffi.DynamicLibrary.open("ffi_test_functions"); + +/// pass a struct to a c function and get a struct as return value +void testFunctionWithStruct() { + ffi.Pointer> p1 = + ffiTestFunctions.lookup("TransposeCoordinate"); + NativeCoordinateOp f1 = p1.asFunction(); + + Coordinate c1 = Coordinate(10.0, 20.0, null); + Coordinate c2 = Coordinate(42.0, 84.0, c1); + c1.next = c2; + + Coordinate result = f1(c1); + + Expect.approxEquals(20.0, c1.x); + Expect.approxEquals(30.0, c1.y); + + Expect.approxEquals(42.0, result.x); + Expect.approxEquals(84.0, result.y); + + c1.free(); + c2.free(); +} + +/// pass an array of structs to a c funtion +void testFunctionWithStructArray() { + ffi.Pointer> p1 = + ffiTestFunctions.lookup("CoordinateElemAt1"); + NativeCoordinateOp f1 = p1.asFunction(); + + Coordinate c1 = Coordinate.allocate(count: 3); + Coordinate c2 = c1.elementAt(1); + Coordinate c3 = c1.elementAt(2); + c1.x = 10.0; + c1.y = 10.0; + c1.next = c3; + c2.x = 20.0; + c2.y = 20.0; + c2.next = c1; + c3.x = 30.0; + c3.y = 30.0; + c3.next = c2; + + Coordinate result = f1(c1); + Expect.approxEquals(20.0, result.x); + Expect.approxEquals(20.0, result.y); + + c1.free(); +} + +typedef VeryLargeStructSum = int Function(VeryLargeStruct); +typedef NativeVeryLargeStructSum = ffi.Int64 Function(VeryLargeStruct); + +void testFunctionWithVeryLargeStruct() { + ffi.Pointer> p1 = + ffiTestFunctions.lookup("SumVeryLargeStruct"); + VeryLargeStructSum f = p1.asFunction(); + + VeryLargeStruct vls1 = VeryLargeStruct.allocate(count: 2); + VeryLargeStruct vls2 = vls1.elementAt(1); + List structs = [vls1, vls2]; + for (VeryLargeStruct struct in structs) { + struct.a = 1; + struct.b = 2; + struct.c = 4; + struct.d = 8; + struct.e = 16; + struct.f = 32; + struct.g = 64; + struct.h = 128; + struct.i = 256; + struct.j = 512; + struct.k = 1024; + struct.smallLastField = 1; + } + vls1.parent = vls2; + vls1.numChidlren = 2; + vls1.children = vls1; + vls2.parent = vls2; + vls2.parent = null; + vls2.numChidlren = 0; + vls2.children = null; + + int result = f(vls1); + Expect.equals(2051, result); + + result = f(vls2); + Expect.equals(2048, result); + + vls1.free(); +} diff --git a/tests/standalone_2/ffi/function_test.dart b/tests/standalone_2/ffi/function_test.dart new file mode 100644 index 00000000000..bed0264b025 --- /dev/null +++ b/tests/standalone_2/ffi/function_test.dart @@ -0,0 +1,284 @@ +// 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. +// +// Dart test program for testing dart:ffi function pointers. + +library FfiTest; + +import 'dart:ffi' as ffi; + +import "package:expect/expect.dart"; + +void main() { + testNativeFunctionFromCast(); + testNativeFunctionFromLookup(); + test64bitInterpretations(); + testTruncation(); + testNativeFunctionDoubles(); + testNativeFunctionFloats(); + testNativeFunctionManyArguments1(); + testNativeFunctionManyArguments2(); + testNativeFunctionManyArguments3(); + testNativeFunctionPointer(); + testNullInt(); + testNullDouble(); + testNullManyArgs(); + testNullPointers(); + testFloatRounding(); +} + +ffi.DynamicLibrary ffiTestFunctions = + ffi.DynamicLibrary.open("ffi_test_functions"); + +typedef NativeBinaryOp = ffi.Int32 Function(ffi.Int32, ffi.Int32); +typedef UnaryOp = int Function(int); +typedef BinaryOp = int Function(int, int); +typedef GenericBinaryOp = int Function(int, T); + +void testNativeFunctionFromCast() { + ffi.Pointer p1 = ffi.allocate(); + ffi.Pointer> p2 = p1.cast(); + BinaryOp f = p2.asFunction(); + BinaryOp f2 = p2.asFunction>(); + p1.free(); +} + +typedef NativeQuadOpSigned = ffi.Int64 Function( + ffi.Int64, ffi.Int32, ffi.Int16, ffi.Int8); +typedef QuadOp = int Function(int, int, int, int); +typedef NativeQuadOpUnsigned = ffi.Uint64 Function( + ffi.Uint64, ffi.Uint32, ffi.Uint16, ffi.Uint8); + +void testNativeFunctionFromLookup() { + BinaryOp sumPlus42 = + ffiTestFunctions.lookupFunction("SumPlus42"); + Expect.equals(49, sumPlus42(3, 4)); + + QuadOp intComputation = ffiTestFunctions + .lookupFunction("IntComputation"); + Expect.equals(625, intComputation(125, 250, 500, 1000)); + + Expect.equals( + 0x7FFFFFFFFFFFFFFF, intComputation(0, 0, 0, 0x7FFFFFFFFFFFFFFF)); + Expect.equals( + -0x8000000000000000, intComputation(0, 0, 0, -0x8000000000000000)); +} + +void test64bitInterpretations() { + QuadOp uintComputation = ffiTestFunctions + .lookupFunction("UintComputation"); + + // 2 ^ 63 - 1 + Expect.equals( + 0x7FFFFFFFFFFFFFFF, uintComputation(0, 0, 0, 0x7FFFFFFFFFFFFFFF)); + // -2 ^ 63 interpreted as 2 ^ 63 + Expect.equals( + -0x8000000000000000, uintComputation(0, 0, 0, -0x8000000000000000)); + // -1 interpreted as 2 ^ 64 - 1 + Expect.equals(-1, uintComputation(0, 0, 0, -1)); +} + +typedef NativeSenaryOp = ffi.Int64 Function( + ffi.Int8, ffi.Int16, ffi.Int32, ffi.Uint8, ffi.Uint16, ffi.Uint32); +typedef SenaryOp = int Function(int, int, int, int, int, int); + +void testTruncation() { + SenaryOp sumSmallNumbers = ffiTestFunctions + .lookupFunction("SumSmallNumbers"); + + // TODO(dacoharkes): implement truncation and sign extension in trampolines + // for values smaller than 32 bits. + sumSmallNumbers(128, 0, 0, 0, 0, 0); + sumSmallNumbers(-129, 0, 0, 0, 0, 0); + sumSmallNumbers(0, 0, 0, 256, 0, 0); + sumSmallNumbers(0, 0, 0, -1, 0, 0); + + sumSmallNumbers(0, 0x8000, 0, 0, 0, 0); + sumSmallNumbers(0, 0xFFFFFFFFFFFF7FFF, 0, 0, 0, 0); + sumSmallNumbers(0, 0, 0, 0, 0x10000, 0); + sumSmallNumbers(0, 0, 0, 0, -1, 0); + + Expect.equals(0xFFFFFFFF80000000, sumSmallNumbers(0, 0, 0x80000000, 0, 0, 0)); + Expect.equals( + 0x000000007FFFFFFF, sumSmallNumbers(0, 0, 0xFFFFFFFF7FFFFFFF, 0, 0, 0)); + Expect.equals(0, sumSmallNumbers(0, 0, 0, 0, 0, 0x100000000)); + Expect.equals(0xFFFFFFFF, sumSmallNumbers(0, 0, 0, 0, 0, -1)); +} + +typedef NativeDoubleUnaryOp = ffi.Double Function(ffi.Double); +typedef DoubleUnaryOp = double Function(double); + +void testNativeFunctionDoubles() { + DoubleUnaryOp times1_337Double = ffiTestFunctions + .lookupFunction("Times1_337Double"); + Expect.approxEquals(2.0 * 1.337, times1_337Double(2.0)); +} + +typedef NativeFloatUnaryOp = ffi.Float Function(ffi.Float); + +void testNativeFunctionFloats() { + DoubleUnaryOp times1_337Float = ffiTestFunctions + .lookupFunction("Times1_337Float"); + Expect.approxEquals(1337.0, times1_337Float(1000.0)); +} + +typedef NativeOctenaryOp = ffi.IntPtr Function( + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr, + ffi.IntPtr); +typedef OctenaryOp = int Function( + int, int, int, int, int, int, int, int, int, int); + +void testNativeFunctionManyArguments1() { + OctenaryOp sumManyInts = ffiTestFunctions + .lookupFunction("SumManyInts"); + Expect.equals(55, sumManyInts(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); +} + +typedef NativeDoubleOctenaryOp = ffi.Double Function( + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double, + ffi.Double); +typedef DoubleOctenaryOp = double Function(double, double, double, double, + double, double, double, double, double, double); + +void testNativeFunctionManyArguments2() { + DoubleOctenaryOp sumManyDoubles = + ffiTestFunctions.lookupFunction( + "SumManyDoubles"); + Expect.approxEquals( + 55.0, sumManyDoubles(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)); +} + +typedef NativeVigesimalOp = ffi.Double Function( + ffi.IntPtr, + ffi.Float, + ffi.IntPtr, + ffi.Double, + ffi.IntPtr, + ffi.Float, + ffi.IntPtr, + ffi.Double, + ffi.IntPtr, + ffi.Float, + ffi.IntPtr, + ffi.Double, + ffi.IntPtr, + ffi.Float, + ffi.IntPtr, + ffi.Double, + ffi.IntPtr, + ffi.Float, + ffi.IntPtr, + ffi.Double); +typedef VigesimalOp = double Function( + int, + double, + int, + double, + int, + double, + int, + double, + int, + double, + int, + double, + int, + double, + int, + double, + int, + double, + int, + double); + +void testNativeFunctionManyArguments3() { + VigesimalOp sumManyNumbers = ffiTestFunctions + .lookupFunction("SumManyNumbers"); + Expect.approxEquals( + 210.0, + sumManyNumbers(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9, 10.0, 11, 12.0, 13, + 14.0, 15, 16.0, 17, 18.0, 19, 20.0)); +} + +typedef Int64PointerUnOp = ffi.Pointer Function( + ffi.Pointer); + +void testNativeFunctionPointer() { + Int64PointerUnOp assign1337Index1 = ffiTestFunctions + .lookupFunction("Assign1337Index1"); + ffi.Pointer p2 = ffi.allocate(count: 2); + p2.store(42); + p2.elementAt(1).store(1000); + ffi.Pointer result = assign1337Index1(p2); + Expect.equals(1337, result.load()); + Expect.equals(1337, p2.elementAt(1).load()); + Expect.equals(p2.elementAt(1).address, result.address); + p2.free(); +} + +void testNullInt() { + BinaryOp sumPlus42 = + ffiTestFunctions.lookupFunction("SumPlus42"); + + Expect.throws(() => sumPlus42(43, null)); +} + +void testNullDouble() { + DoubleUnaryOp times1_337Double = ffiTestFunctions + .lookupFunction("Times1_337Double"); + Expect.throws(() => times1_337Double(null)); +} + +void testNullManyArgs() { + VigesimalOp sumManyNumbers = ffiTestFunctions + .lookupFunction("SumManyNumbers"); + Expect.throws(() => sumManyNumbers(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9, 10.0, + 11, 12.0, 13, 14.0, 15, 16.0, 17, 18.0, null, 20.0)); +} + +void testNullPointers() { + Int64PointerUnOp nullableInt64ElemAt1 = + ffiTestFunctions.lookupFunction( + "NullableInt64ElemAt1"); + + ffi.Pointer result = nullableInt64ElemAt1(null); + Expect.isNull(result); + + ffi.Pointer p2 = ffi.allocate(count: 2); + result = nullableInt64ElemAt1(p2); + Expect.isNotNull(result); + p2.free(); +} + +typedef NativeFloatPointerToBool = ffi.Uint8 Function(ffi.Pointer); +typedef FloatPointerToBool = int Function(ffi.Pointer); + +void testFloatRounding() { + FloatPointerToBool isRoughly1337 = ffiTestFunctions.lookupFunction< + NativeFloatPointerToBool, FloatPointerToBool>("IsRoughly1337"); + + ffi.Pointer p2 = ffi.allocate(); + p2.store(1337.0); + + int result = isRoughly1337(p2); + Expect.equals(1, result); + + p2.free(); +} diff --git a/tests/standalone_2/ffi/static_checks_test.dart b/tests/standalone_2/ffi/static_checks_test.dart new file mode 100644 index 00000000000..422fc6f97a6 --- /dev/null +++ b/tests/standalone_2/ffi/static_checks_test.dart @@ -0,0 +1,365 @@ +// 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. +// +// Dart test program for testing dart:ffi extra checks + +library FfiTest; + +import 'dart:ffi' as ffi; + +void main() { + testGetGeneric(); + testGetGeneric2(); + testGetVoid(); + testGetNativeFunction(); + testGetNativeType(); + testGetTypeMismatch(); + testSetGeneric(); + testSetGeneric2(); + testSetVoid(); + testSetNativeFunction(); + testSetNativeType(); + testSetTypeMismatch(); + testAsFunctionGeneric(); + testAsFunctionGeneric2(); + testAsFunctionWrongNativeFunctionSignature(); + testAsFunctionTypeMismatch(); + testFromFunctionGeneric(); + testFromFunctionGeneric2(); + testFromFunctionWrongNativeFunctionSignature(); + testFromFunctionTypeMismatch(); + testFromFunctionClosure(); + testFromFunctionTearOff(); + testLookupFunctionGeneric(); + testLookupFunctionGeneric2(); + testLookupFunctionWrongNativeFunctionSignature(); + testLookupFunctionTypeMismatch(); + testNativeFunctionSignatureInvalidReturn(); + testNativeFunctionSignatureInvalidParam(); + testNativeFunctionSignatureInvalidOptionalNamed(); + testNativeFunctionSignatureInvalidOptionalPositional(); +} + +typedef Int8UnOp = ffi.Int8 Function(ffi.Int8); +typedef IntUnOp = int Function(int); + +void testGetGeneric() { + int generic(ffi.Pointer p) { + int result; + result = p.load(); //# 20: compile-time error + return result; + } + + ffi.Pointer p = ffi.allocate(); + p.store(123); + ffi.Pointer loseType = p; + generic(loseType); + p.free(); +} + +void testGetGeneric2() { + T generic() { + ffi.Pointer p = ffi.allocate(); + p.store(123); + T result; + result = p.load(); //# 21: compile-time error + p.free(); + return result; + } + + generic(); +} + +void testGetVoid() { + ffi.Pointer p1 = ffi.allocate(); + ffi.Pointer p2 = p1.cast(); + + p2.load(); //# 22: compile-time error + + p1.free(); +} + +void testGetNativeFunction() { + ffi.Pointer> p = ffi.fromAddress(1337); + IntUnOp f = p.load(); //# 23: compile-time error +} + +void testGetNativeType() { + // Is it possible to obtain a ffi.Pointer at all? +} + +void testGetTypeMismatch() { + ffi.Pointer> p = ffi.allocate(); + ffi.Pointer typedNull = null; + p.store(typedNull); + + // this fails to compile due to type mismatch + ffi.Pointer p2 = p.load(); //# 25: compile-time error + + p.free(); +} + +void testSetGeneric() { + void generic(ffi.Pointer p) { + p.store(123); //# 26: compile-time error + } + + ffi.Pointer p = ffi.allocate(); + p.store(123); + ffi.Pointer loseType = p; + generic(loseType); + p.free(); +} + +void testSetGeneric2() { + void generic(T arg) { + ffi.Pointer p = ffi.allocate(); + p.store(arg); //# 27: compile-time error + p.free(); + } + + generic(123); +} + +void testSetVoid() { + ffi.Pointer p1 = ffi.allocate(); + ffi.Pointer p2 = p1.cast(); + + p2.store(1234); //# 28: compile-time error + + p1.free(); +} + +void testSetNativeFunction() { + ffi.Pointer> p = ffi.fromAddress(1337); + IntUnOp f = (a) => a + 1; + p.store(f); //# 29: compile-time error +} + +void testSetNativeType() { + // Is it possible to obtain a ffi.Pointer at all? +} + +void testSetTypeMismatch() { + // the pointer to pointer types must match up + ffi.Pointer pHelper = ffi.allocate(); + pHelper.store(123); + + ffi.Pointer> p = ffi.allocate(); + + // this fails to compile due to type mismatch + p.store(pHelper); //# 40: compile-time error + + pHelper.free(); + p.free(); +} + +void testAsFunctionGeneric() { + T generic() { + ffi.Pointer> p = ffi.fromAddress(1337); + Function f; + f = p.asFunction(); //# 11: compile-time error + return f; + } + + generic(); +} + +void testAsFunctionGeneric2() { + generic(ffi.Pointer p) { + Function f; + f = p.asFunction(); //# 12: compile-time error + return f; + } + + ffi.Pointer> p = ffi.fromAddress(1337); + generic(p); +} + +void testAsFunctionWrongNativeFunctionSignature() { + ffi.Pointer> p; + Function f = p.asFunction(); //# 13: compile-time error +} + +typedef IntBinOp = int Function(int, int); + +void testAsFunctionTypeMismatch() { + ffi.Pointer> p = ffi.fromAddress(1337); + IntBinOp f = p.asFunction(); //# 14: compile-time error +} + +typedef NativeDoubleUnOp = ffi.Double Function(ffi.Double); +typedef DoubleUnOp = double Function(double); + +double myTimesThree(double d) => d * 3; + +int myTimesFour(int i) => i * 4; + +void testFromFunctionGeneric() { + ffi.Pointer generic(T f) { + ffi.Pointer> result; + result = ffi.fromFunction(f); //# 70: compile-time error + return result; + } + + generic(myTimesThree); +} + +void testFromFunctionGeneric2() { + ffi.Pointer> generic() { + ffi.Pointer> result; + result = ffi.fromFunction(myTimesThree); //# 71: compile-time error + return result; + } + + generic(); +} + +void testFromFunctionWrongNativeFunctionSignature() { + ffi.fromFunction(myTimesFour); //# 72: compile-time error +} + +void testFromFunctionTypeMismatch() { + ffi.Pointer> p; + p = ffi.fromFunction(myTimesFour); //# 73: compile-time error +} + +void testFromFunctionClosure() { + DoubleUnOp someClosure = (double z) => z / 27.0; + ffi.Pointer> p; + p = ffi.fromFunction(someClosure); //# 74: compile-time error +} + +class X { + double tearoff(double d) => d / 27.0; +} + +DoubleUnOp fld = null; + +void testFromFunctionTearOff() { + fld = X().tearoff; + ffi.Pointer> p; + p = ffi.fromFunction(fld); //# 75: compile-time error +} + +void testLookupFunctionGeneric() { + Function generic() { + ffi.DynamicLibrary l = ffi.DynamicLibrary.open("ffi_test_dynamic_library"); + Function result; + result = l.lookupFunction("cos"); //# 15: compile-time error + return result; + } + + generic(); +} + +void testLookupFunctionGeneric2() { + Function generic() { + ffi.DynamicLibrary l = ffi.DynamicLibrary.open("ffi_test_dynamic_library"); + Function result; + result = //# 16: compile-time error + l.lookupFunction("cos"); //# 16: compile-time error + return result; + } + + generic(); +} + +void testLookupFunctionWrongNativeFunctionSignature() { + ffi.DynamicLibrary l = ffi.DynamicLibrary.open("ffi_test_dynamic_library"); + l.lookupFunction("cos"); //# 17: compile-time error +} + +void testLookupFunctionTypeMismatch() { + ffi.DynamicLibrary l = ffi.DynamicLibrary.open("ffi_test_dynamic_library"); + l.lookupFunction("cos"); //# 18: compile-time error +} + +// TODO(dacoharkes): make the next 4 test compile errors +typedef Invalid1 = int Function(ffi.Int8); +typedef Invalid2 = ffi.Int8 Function(int); +typedef Invalid3 = ffi.Int8 Function({ffi.Int8 named}); +typedef Invalid4 = ffi.Int8 Function([ffi.Int8 positional]); + +void testNativeFunctionSignatureInvalidReturn() { + // ffi.Pointer> p = ffi.fromAddress(999); +} + +void testNativeFunctionSignatureInvalidParam() { + // ffi.Pointer> p = ffi.fromAddress(999); +} + +void testNativeFunctionSignatureInvalidOptionalNamed() { + // ffi.Pointer> p = ffi.fromAddress(999); +} + +void testNativeFunctionSignatureInvalidOptionalPositional() { + // ffi.Pointer> p = ffi.fromAddress(999); +} + +// error on missing field annotation +@ffi.struct +class TestStruct extends ffi.Pointer { + @ffi.Double() + double x; + + double y; //# 50: compile-time error +} + +// error on missing struct annotation +class TestStruct2 extends ffi.Pointer { + @ffi.Double() //# 51: compile-time error + double x; //# 51: compile-time error +} + +// error on missing annotation on subtype +@ffi.struct +class TestStruct3 extends TestStruct { + double z; //# 52: compile-time error +} + +// error on double annotation +@ffi.struct +class TestStruct4 extends ffi.Pointer { + @ffi.Double() + @ffi.Double() //# 53: compile-time error + double z; +} + +// error on annotation not matching up +@ffi.struct +class TestStruct5 extends ffi.Pointer { + @ffi.Int64() //# 54: compile-time error + double z; //# 54: compile-time error +} + +// error on annotation not matching up +@ffi.struct +class TestStruct6 extends ffi.Pointer { + @ffi.Void() //# 55: compile-time error + double z; //# 55: compile-time error +} + +// error on annotation not matching up +@ffi.struct +class TestStruct7 extends ffi.Pointer { + @ffi.NativeType() //# 56: compile-time error + double z; //# 56: compile-time error +} + +// error on field initializer on field +@ffi.struct +class TestStruct8 extends ffi.Pointer { + @ffi.Double() //# 57: compile-time error + double z = 10.0; //# 57: compile-time error +} + +// error on field initializer in constructor +@ffi.struct +class TestStruct9 extends ffi.Pointer { + @ffi.Double() + double z; + + TestStruct9() : z = 0.0 {} //# 58: compile-time error +} diff --git a/tests/standalone_2/ffi/structs_test.dart b/tests/standalone_2/ffi/structs_test.dart new file mode 100644 index 00000000000..cbb2eab8027 --- /dev/null +++ b/tests/standalone_2/ffi/structs_test.dart @@ -0,0 +1,136 @@ +// 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. +// +// Dart test program for testing dart:ffi struct pointers. + +library FfiTest; + +import 'dart:ffi' as ffi; + +import "package:expect/expect.dart"; + +import 'coordinate_bare.dart' as bare; +import 'coordinate_manual.dart' as manual; +import 'coordinate.dart'; + +void main() { + testStructAllocate(); + testStructFromAddress(); + testStructWithNulls(); + testBareStruct(); + testManualStruct(); + testTypeTest(); +} + +/// allocates each coordinate separately in c memory +void testStructAllocate() { + Coordinate c1 = Coordinate(10.0, 10.0, null); + Coordinate c2 = Coordinate(20.0, 20.0, c1); + Coordinate c3 = Coordinate(30.0, 30.0, c2); + c1.next = c3; + + Coordinate currentCoordinate = c1; + Expect.equals(10.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(30.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(20.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(10.0, currentCoordinate.x); + + c1.free(); + c2.free(); + c3.free(); +} + +/// allocates coordinates consecutively in c memory +void testStructFromAddress() { + Coordinate c1 = Coordinate.allocate(count: 3); + Coordinate c2 = c1.elementAt(1); + Coordinate c3 = c1.elementAt(2); + c1.x = 10.0; + c1.y = 10.0; + c1.next = c3; + c2.x = 20.0; + c2.y = 20.0; + c2.next = c1; + c3.x = 30.0; + c3.y = 30.0; + c3.next = c2; + + Coordinate currentCoordinate = c1; + Expect.equals(10.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(30.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(20.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(10.0, currentCoordinate.x); + + c1.free(); +} + +void testStructWithNulls() { + Coordinate coordinate = Coordinate(10.0, 10.0, null); + Expect.isNull(coordinate.next); + coordinate.next = coordinate; + Expect.isNotNull(coordinate.next); + coordinate.next = null; + Expect.isNull(coordinate.next); + coordinate.free(); +} + +void testBareStruct() { + int structSize = ffi.sizeOf() * 2 + ffi.sizeOf(); + bare.Coordinate c1 = ffi.allocate(count: structSize * 3).cast(); + bare.Coordinate c2 = c1.offsetBy(structSize).cast(); + bare.Coordinate c3 = c1.offsetBy(structSize * 2).cast(); + c1.x = 10.0; + c1.y = 10.0; + c1.next = c3; + c2.x = 20.0; + c2.y = 20.0; + c2.next = c1; + c3.x = 30.0; + c3.y = 30.0; + c3.next = c2; + + bare.Coordinate currentCoordinate = c1; + Expect.equals(10.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(30.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(20.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(10.0, currentCoordinate.x); + + c1.free(); +} + +void testManualStruct() { + manual.Coordinate c1 = manual.Coordinate(10.0, 10.0, null); + manual.Coordinate c2 = manual.Coordinate(20.0, 20.0, c1); + manual.Coordinate c3 = manual.Coordinate(30.0, 30.0, c2); + c1.next = c3; + + manual.Coordinate currentCoordinate = c1; + Expect.equals(10.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(30.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(20.0, currentCoordinate.x); + currentCoordinate = currentCoordinate.next; + Expect.equals(10.0, currentCoordinate.x); + + c1.free(); + c2.free(); + c3.free(); +} + +void testTypeTest() { + Coordinate c = Coordinate(10, 10, null); + Expect.isTrue(c is ffi.Pointer); + Expect.isTrue(c is ffi.Pointer); + c.free(); +} diff --git a/tests/standalone_2/ffi/subtype_test.dart b/tests/standalone_2/ffi/subtype_test.dart new file mode 100644 index 00000000000..fd04b501465 --- /dev/null +++ b/tests/standalone_2/ffi/subtype_test.dart @@ -0,0 +1,19 @@ +// 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. +// +// Dart test program for testing dart:ffi Pointer subtypes. + +library FfiTest; + +import "package:expect/expect.dart"; + +import 'cstring.dart'; + +void main() { + CString cs = CString.toUtf8("hello world!"); + + Expect.equals("hello world!", cs.fromUtf8()); + + cs.free(); +} diff --git a/tests/standalone_2/ffi/very_large_struct.dart b/tests/standalone_2/ffi/very_large_struct.dart new file mode 100644 index 00000000000..283cb57190f --- /dev/null +++ b/tests/standalone_2/ffi/very_large_struct.dart @@ -0,0 +1,67 @@ +// 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. + +library FfiTestCoordinateBare; + +import 'dart:ffi' as ffi; + +/// Large sample struct for dart:ffi library. +@ffi.struct +class VeryLargeStruct extends ffi.Pointer { + @ffi.Int8() + int a; + + @ffi.Int16() + int b; + + @ffi.Int32() + int c; + + @ffi.Int64() + int d; + + @ffi.Uint8() + int e; + + @ffi.Uint16() + int f; + + @ffi.Uint32() + int g; + + @ffi.Uint64() + int h; + + @ffi.IntPtr() + int i; + + @ffi.Float() + double j; + + @ffi.Double() + double k; + + @ffi.Pointer() + VeryLargeStruct parent; + + @ffi.IntPtr() + int numChidlren; + + @ffi.Pointer() + VeryLargeStruct children; + + @ffi.Int8() + int smallLastField; + + // generated by @ffi.struct annotation + external static int sizeOf(); + + VeryLargeStruct offsetBy(int offsetInBytes) => + super.offsetBy(offsetInBytes).cast(); + + VeryLargeStruct elementAt(int index) => offsetBy(sizeOf() * index); + + static VeryLargeStruct allocate({int count: 1}) => + ffi.allocate(count: count * sizeOf()).cast(); +} diff --git a/tests/standalone_2/standalone_2_vm.status b/tests/standalone_2/standalone_2_vm.status index bc23a305b09..becf2370b30 100644 --- a/tests/standalone_2/standalone_2_vm.status +++ b/tests/standalone_2/standalone_2_vm.status @@ -6,6 +6,9 @@ link_natives_lazily_test: SkipByDesign # Not supported. no_allow_absolute_addresses_test: SkipByDesign # Not supported. +[ $builder_tag == asan ] +ffi/data_not_asan_test: Skip # this test tries to allocate too much memory on purpose + [ $compiler == app_jit ] full_coverage_test: Skip # Platform.executable io/code_collection_test: Skip # Platform.executable @@ -22,7 +25,12 @@ io/test_extension_test: Skip # Platform.executable io/test_runner_test: RuntimeError # Issue 33168 regress_26031_test: Skip # Platform.resolvedExecutable +[ $runtime == dart_precompiled ] +ffi: RuntimeError # https://github.com/dart-lang/sdk/issues/35765 +ffi/static_checks_test: Skip # https://github.com/dart-lang/sdk/issues/35765 + [ $runtime == vm ] +ffi/enable_ffi_test: Fail # test designed to fail: --enable-ffi=false with import dart:ffi io/test_runner_test: Skip # Spawns a process which runs in Dart2 mode. [ $system == android ] @@ -89,6 +97,9 @@ io/http_server_close_response_after_error_test: Pass, Timeout # Issue 28370: tim [ $runtime == dart_precompiled && $system == linux && ($arch == simarm || $arch == simarm64 || $arch == x64) ] io/stdout_stderr_non_blocking_test: Pass, Timeout # Issue 35192 +[ $runtime != dart_precompiled && $runtime != vm ] +ffi: SkipByDesign # ffi is only supported on vm + [ $runtime == vm && !$checked && !$strong ] io/file_constructor_test: RuntimeError @@ -111,3 +122,6 @@ io/skipping_dart2js_compilations_test: Skip # Spawns process in Dart2 mode. full_coverage_test: Skip # TODO(vegorov) SIMDBC interpreter doesn't support coverage yet. link_natives_lazily_test: SkipByDesign # SIMDBC interpreter doesn't support lazy linking of natives. no_lazy_dispatchers_test: SkipByDesign # SIMDBC interpreter doesn't support --no_lazy_dispatchers + +[ $arch != x64 || $system != linux && $system != macos ] +ffi: Skip # ffi not yet supported on other systems than linux/macos 64