From 4e2343c290f8250ddc4b652e06f7ba097178274a Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 13 Jan 2021 17:04:08 +0000 Subject: [PATCH] [vm/ffi] Introduce `Allocator` API Introduces the Allocator API in `dart:ffi`. This CL does not yet roll `package:ffi` to use `Allocator`, because that breaks the checked in Dart in Fluter in g3. Instead, this coppies `_MallocAllocator` from `package:ffi` into the ffi tests for testing. This CL does not yet migrate off `allocate` and `free` in the SDK. That is done in a dependent CL. Issue: https://github.com/dart-lang/sdk/issues/44621 Issue: https://github.com/dart-lang/sdk/issues/38721 TEST=tests/ffi/allocator_test.dart TEST=tests/ffi/calloc_test.dart TEST=tests/ffi/vmspecific_static_checks_test.dart Change-Id: I173e213a750b8b3f594bb8d4fc72575f2b6b91f7 Cq-Include-Trybots: luci.dart.try:vm-precomp-ffi-qemu-linux-release-arm-try,analyzer-analysis-server-linux-try,analyzer-linux-release-try,analyzer-nnbd-linux-release-try,front-end-linux-release-x64-try,front-end-nnbd-linux-release-x64-try,benchmark-linux-try,dart-sdk-linux-try,pkg-linux-release-try,vm-ffi-android-release-arm-try,vm-ffi-android-release-arm64-try,vm-kernel-nnbd-win-debug-x64-try,vm-kernel-win-debug-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/177705 Reviewed-by: Clement Skau --- .../lib/src/generated/ffi_verifier.dart | 75 +++++++++--- pkg/vm/lib/transformations/ffi.dart | 10 ++ pkg/vm/lib/transformations/ffi_use_sites.dart | 11 +- .../vm/lib/ffi_allocation_patch.dart | 19 +++ sdk/lib/ffi/allocation.dart | 37 ++++++ sdk/lib/ffi/ffi.dart | 1 + sdk/lib/ffi/ffi_sources.gni | 1 + sdk/lib/libraries.json | 3 +- sdk/lib/libraries.yaml | 1 + tests/ffi/allocator_test.dart | 24 ++++ tests/ffi/calloc.dart | 109 ++++++++++++++++++ tests/ffi/calloc_test.dart | 36 ++++++ tests/ffi/data_test.dart | 19 --- tests/ffi/vmspecific_static_checks_test.dart | 16 +++ tests/ffi_2/allocator_test.dart | 24 ++++ tests/ffi_2/calloc.dart | 109 ++++++++++++++++++ tests/ffi_2/calloc_test.dart | 36 ++++++ tests/ffi_2/data_test.dart | 19 --- .../ffi_2/vmspecific_static_checks_test.dart | 16 +++ 19 files changed, 511 insertions(+), 55 deletions(-) create mode 100644 sdk/lib/_internal/vm/lib/ffi_allocation_patch.dart create mode 100644 sdk/lib/ffi/allocation.dart create mode 100644 tests/ffi/allocator_test.dart create mode 100644 tests/ffi/calloc.dart create mode 100644 tests/ffi/calloc_test.dart create mode 100644 tests/ffi_2/allocator_test.dart create mode 100644 tests/ffi_2/calloc.dart create mode 100644 tests/ffi_2/calloc_test.dart diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart index 160ed2f980b..30aab7d6467 100644 --- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart +++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart @@ -14,6 +14,11 @@ import 'package:analyzer/src/dart/error/ffi_code.dart'; /// used. See 'pkg/vm/lib/transformations/ffi_checks.md' for the specification /// of the desired hints. class FfiVerifier extends RecursiveAstVisitor { + static const _allocatorClassName = 'Allocator'; + static const _allocateExtensionMethodName = 'call'; + static const _allocatorExtensionName = 'AllocatorAlloc'; + static const _dartFfiLibraryName = 'dart.ffi'; + static const List _primitiveIntegerNativeTypes = [ 'Int8', 'Int16', @@ -31,6 +36,8 @@ class FfiVerifier extends RecursiveAstVisitor { 'Double', ]; + static const _structClassName = 'Struct'; + /// The type system used to check types. final TypeSystemImpl typeSystem; @@ -52,9 +59,10 @@ class FfiVerifier extends RecursiveAstVisitor { if (extendsClause != null) { final TypeName superclass = extendsClause.superclass; if (_isDartFfiClass(superclass)) { - if (superclass.name.staticElement.name == 'Struct') { + final className = superclass.name.staticElement.name; + if (className == _structClassName) { inStruct = true; - } else { + } else if (className != _allocatorClassName) { _errorReporter.reportErrorForNode( FfiCode.SUBTYPE_OF_FFI_CLASS_IN_EXTENDS, superclass.name, @@ -71,6 +79,10 @@ class FfiVerifier extends RecursiveAstVisitor { // No classes from the FFI may be explicitly implemented. void checkSupertype(TypeName typename, FfiCode subtypeOfFfiCode, FfiCode subtypeOfStructCode) { + final superName = typename.name.staticElement?.name; + if (superName == _allocatorClassName) { + return; + } if (_isDartFfiClass(typename)) { _errorReporter.reportErrorForNode( subtypeOfFfiCode, typename, [node.name, typename.name]); @@ -119,6 +131,21 @@ class FfiVerifier extends RecursiveAstVisitor { super.visitFieldDeclaration(node); } + @override + void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { + Element element = node.staticElement; + if (element is MethodElement) { + Element enclosingElement = element.enclosingElement; + if (enclosingElement is ExtensionElement) { + if (_isAllocatorExtension(enclosingElement) && + element.name == _allocateExtensionMethodName) { + _validateAllocate(node); + } + } + } + super.visitFunctionExpressionInvocation(node); + } + @override void visitMethodInvocation(MethodInvocation node) { Element element = node.methodName.staticElement; @@ -145,6 +172,12 @@ class FfiVerifier extends RecursiveAstVisitor { super.visitMethodInvocation(node); } + /// Return `true` if the given [element] represents the extension + /// `AllocatorAlloc`. + bool _isAllocatorExtension(Element element) => + element.name == _allocatorExtensionName && + element.library.name == _dartFfiLibraryName; + /// Return `true` if the [typeName] is the name of a type from `dart:ffi`. bool _isDartFfiClass(TypeName typeName) => _isDartFfiElement(typeName.name.staticElement); @@ -154,14 +187,15 @@ class FfiVerifier extends RecursiveAstVisitor { if (element is ConstructorElement) { element = element.enclosingElement; } - return element is ClassElement && element.library.name == 'dart.ffi'; + return element is ClassElement && + element.library.name == _dartFfiLibraryName; } /// Return `true` if the given [element] represents the extension /// `DynamicLibraryExtension`. bool _isDynamicLibraryExtension(Element element) => element.name == 'DynamicLibraryExtension' && - element.library.name == 'dart.ffi'; + element.library.name == _dartFfiLibraryName; bool _isEmptyStruct(ClassElement classElement) { final fields = classElement.fields; @@ -182,13 +216,13 @@ class FfiVerifier extends RecursiveAstVisitor { } bool _isHandle(Element element) => - element.name == 'Handle' && element.library.name == 'dart.ffi'; + element.name == 'Handle' && element.library.name == _dartFfiLibraryName; /// Returns `true` iff [nativeType] is a `ffi.NativeFunction` type. bool _isNativeFunctionInterfaceType(DartType nativeType) { if (nativeType is InterfaceType) { final element = nativeType.element; - if (element.library.name == 'dart.ffi') { + if (element.library.name == _dartFfiLibraryName) { return element.name == 'NativeFunction' && nativeType.typeArguments?.length == 1; } @@ -198,13 +232,13 @@ class FfiVerifier extends RecursiveAstVisitor { bool _isNativeFunctionPointerExtension(Element element) => element.name == 'NativeFunctionPointer' && - element.library.name == 'dart.ffi'; + element.library.name == _dartFfiLibraryName; /// Returns `true` iff [nativeType] is a `ffi.NativeType` type. bool _isNativeTypeInterfaceType(DartType nativeType) { if (nativeType is InterfaceType) { final element = nativeType.element; - if (element.library.name == 'dart.ffi') { + if (element.library.name == _dartFfiLibraryName) { return element.name == 'NativeType'; } } @@ -213,13 +247,13 @@ class FfiVerifier extends RecursiveAstVisitor { /// Return `true` if the given [element] represents the class `Pointer`. bool _isPointer(Element element) => - element.name == 'Pointer' && element.library.name == 'dart.ffi'; + element.name == 'Pointer' && element.library.name == _dartFfiLibraryName; /// Returns `true` iff [nativeType] is a `ffi.Pointer` type. bool _isPointerInterfaceType(DartType nativeType) { if (nativeType is InterfaceType) { final element = nativeType.element; - if (element.library.name == 'dart.ffi') { + if (element.library.name == _dartFfiLibraryName) { return element.name == 'Pointer' && nativeType.typeArguments?.length == 1; } @@ -235,8 +269,8 @@ class FfiVerifier extends RecursiveAstVisitor { return false; } final superClassElement = superType.element; - if (superClassElement.library.name == 'dart.ffi') { - return superClassElement.name == 'Struct' && + if (superClassElement.library.name == _dartFfiLibraryName) { + return superClassElement.name == _structClassName && nativeType.typeArguments?.isEmpty == true; } } @@ -249,8 +283,8 @@ class FfiVerifier extends RecursiveAstVisitor { if (superType is ClassElement) { bool isStruct(InterfaceType type) { return type != null && - type.element.name == 'Struct' && - type.element.library.name == 'dart.ffi'; + type.element.name == _structClassName && + type.element.library.name == _dartFfiLibraryName; } return isStruct(superType.supertype) || @@ -320,7 +354,7 @@ class FfiVerifier extends RecursiveAstVisitor { _PrimitiveDartType _primitiveNativeType(DartType nativeType) { if (nativeType is InterfaceType) { final element = nativeType.element; - if (element.library.name == 'dart.ffi') { + if (element.library.name == _dartFfiLibraryName) { final String name = element.name; if (_primitiveIntegerNativeTypes.contains(name)) { return _PrimitiveDartType.int; @@ -353,6 +387,17 @@ class FfiVerifier extends RecursiveAstVisitor { return _PrimitiveDartType.none; } + void _validateAllocate(FunctionExpressionInvocation node) { + final DartType dartType = node.typeArgumentTypes[0]; + if (!_isValidFfiNativeType(dartType, true, true)) { + final AstNode errorNode = node; + _errorReporter.reportErrorForNode( + FfiCode.NON_CONSTANT_TYPE_ARGUMENT, + errorNode, + ['$_allocatorExtensionName.$_allocateExtensionMethodName']); + } + } + /// Validate that the [annotations] include exactly one annotation that /// satisfies the [requiredTypes]. If an error is produced that cannot be /// associated with an annotation, associate it with the [errorNode]. diff --git a/pkg/vm/lib/transformations/ffi.dart b/pkg/vm/lib/transformations/ffi.dart index f8b473f4afa..0e05d237121 100644 --- a/pkg/vm/lib/transformations/ffi.dart +++ b/pkg/vm/lib/transformations/ffi.dart @@ -205,9 +205,12 @@ class FfiTransformer extends Transformer { final Procedure numAddition; final Library ffiLibrary; + final Class allocatorClass; final Class nativeFunctionClass; final Class pointerClass; final Class structClass; + final Procedure allocateMethod; + final Procedure allocatorAllocateMethod; final Procedure castMethod; final Procedure offsetByMethod; final Procedure elementAtMethod; @@ -228,6 +231,7 @@ class FfiTransformer extends Transformer { final Map elementAtMethods; final Procedure loadStructMethod; final Procedure memCopy; + final Procedure allocationTearoff; final Procedure asFunctionTearoff; final Procedure lookupFunctionTearoff; @@ -257,9 +261,13 @@ class FfiTransformer extends Transformer { listElementAt = coreTypes.index.getMember('dart:core', 'List', '[]'), numAddition = coreTypes.index.getMember('dart:core', 'num', '+'), ffiLibrary = index.getLibrary('dart:ffi'), + allocatorClass = index.getClass('dart:ffi', 'Allocator'), nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'), pointerClass = index.getClass('dart:ffi', 'Pointer'), structClass = index.getClass('dart:ffi', 'Struct'), + allocateMethod = index.getMember('dart:ffi', 'AllocatorAlloc', 'call'), + allocatorAllocateMethod = + index.getMember('dart:ffi', 'Allocator', 'allocate'), castMethod = index.getMember('dart:ffi', 'Pointer', 'cast'), offsetByMethod = index.getMember('dart:ffi', 'Pointer', '_offsetBy'), elementAtMethod = index.getMember('dart:ffi', 'Pointer', 'elementAt'), @@ -301,6 +309,8 @@ class FfiTransformer extends Transformer { }), loadStructMethod = index.getTopLevelMember('dart:ffi', '_loadStruct'), memCopy = index.getTopLevelMember('dart:ffi', '_memCopy'), + allocationTearoff = index.getMember( + 'dart:ffi', 'AllocatorAlloc', LibraryIndex.tearoffPrefix + 'call'), asFunctionTearoff = index.getMember('dart:ffi', 'NativeFunctionPointer', LibraryIndex.tearoffPrefix + 'asFunction'), lookupFunctionTearoff = index.getMember( diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart index 3260c0a74a7..65041ae2278 100644 --- a/pkg/vm/lib/transformations/ffi_use_sites.dart +++ b/pkg/vm/lib/transformations/ffi_use_sites.dart @@ -128,7 +128,9 @@ class _FfiUseSiteTransformer extends FfiTransformer { @override visitProcedure(Procedure node) { if (isFfiLibrary && node.isExtensionMember) { - if (node == asFunctionTearoff || node == lookupFunctionTearoff) { + if (node == allocationTearoff || + node == asFunctionTearoff || + node == lookupFunctionTearoff) { // Skip static checks and transformation for the tearoffs. return node; } @@ -279,6 +281,13 @@ class _FfiUseSiteTransformer extends FfiTransformer { } } return _replaceFromFunction(node); + } else if (target == allocateMethod) { + final DartType nativeType = node.arguments.types[0]; + + _ensureNativeTypeValid(nativeType, node); + + // TODO(http://dartbug.com/38721): Inline the body to get rid of a + // generic invocation of sizeOf. } } on _FfiStaticTypeError { // It's OK to swallow the exception because the diagnostics issued will diff --git a/sdk/lib/_internal/vm/lib/ffi_allocation_patch.dart b/sdk/lib/_internal/vm/lib/ffi_allocation_patch.dart new file mode 100644 index 00000000000..5b794e651a5 --- /dev/null +++ b/sdk/lib/_internal/vm/lib/ffi_allocation_patch.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// All imports must be in all FFI patch files to not depend on the order +// the patches are applied. +import "dart:_internal" show patch; +import 'dart:typed_data'; +import 'dart:isolate'; + +extension AllocatorAlloc on Allocator { + // TODO(http://dartbug.com/38721): Implement this in the CFE to remove the + // invocation of sizeOf to enable tree shaking. + // TODO(http://dartbug.com/39964): Add `alignmentOf()` call. + @patch + Pointer call([int count = 1]) { + return this.allocate(sizeOf() * count); + } +} diff --git a/sdk/lib/ffi/allocation.dart b/sdk/lib/ffi/allocation.dart new file mode 100644 index 00000000000..fc5fce40278 --- /dev/null +++ b/sdk/lib/ffi/allocation.dart @@ -0,0 +1,37 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of dart.ffi; + +/// Manages memory on the native heap. +abstract class Allocator { + /// This interface is meant to be implemented, not extended or mixed in. + Allocator._() { + throw UnsupportedError("Cannot be instantiated"); + } + + /// Allocates [byteCount] bytes of memory on the native heap. + /// + /// If [alignment] is provided, the allocated memory will be at least aligned + /// to [alignment] bytes. + /// + /// Throws an [ArgumentError] if the number of bytes or alignment cannot be + /// satisfied. + Pointer allocate(int byteCount, {int? alignment}); + + /// Releases memory allocated on the native heap. + /// + /// Throws an [ArgumentError] if the memory pointed to by [pointer] cannot be + /// freed. + void free(Pointer pointer); +} + +/// Extension on [Allocator] to provide allocation with [NativeType]. +extension AllocatorAlloc on Allocator { + /// Allocates `sizeOf() * count` bytes of memory using + /// `allocator.allocate`. + /// + /// This extension method must be invoked with a compile-time constant [T]. + external Pointer call([int count = 1]); +} diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart index 68424f86632..01a407f48cf 100644 --- a/sdk/lib/ffi/ffi.dart +++ b/sdk/lib/ffi/ffi.dart @@ -17,6 +17,7 @@ import 'dart:isolate'; import 'dart:typed_data'; part "native_type.dart"; +part "allocation.dart"; part "annotations.dart"; part "dynamic_library.dart"; part "struct.dart"; diff --git a/sdk/lib/ffi/ffi_sources.gni b/sdk/lib/ffi/ffi_sources.gni index 5b34b9c2dc1..db03071ef09 100644 --- a/sdk/lib/ffi/ffi_sources.gni +++ b/sdk/lib/ffi/ffi_sources.gni @@ -6,6 +6,7 @@ ffi_sdk_sources = [ "ffi.dart", # The above file needs to be first as it lists the parts below. + "allocation.dart", "annotations.dart", "dynamic_library.dart", "native_type.dart", diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json index 0b9d2072864..8f584e215f8 100644 --- a/sdk/lib/libraries.json +++ b/sdk/lib/libraries.json @@ -86,6 +86,7 @@ "uri": "ffi/ffi.dart", "patches": [ "_internal/vm/lib/ffi_patch.dart", + "_internal/vm/lib/ffi_allocation_patch.dart", "_internal/vm/lib/ffi_dynamic_library_patch.dart", "_internal/vm/lib/ffi_native_type_patch.dart", "_internal/vm/lib/ffi_struct_patch.dart" @@ -470,4 +471,4 @@ } } } -} \ No newline at end of file +} diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml index f6cb8c37efd..89e3640539d 100644 --- a/sdk/lib/libraries.yaml +++ b/sdk/lib/libraries.yaml @@ -91,6 +91,7 @@ vm: uri: "ffi/ffi.dart" patches: - "_internal/vm/lib/ffi_patch.dart" + - "_internal/vm/lib/ffi_allocation_patch.dart" - "_internal/vm/lib/ffi_dynamic_library_patch.dart" - "_internal/vm/lib/ffi_native_type_patch.dart" - "_internal/vm/lib/ffi_struct_patch.dart" diff --git a/tests/ffi/allocator_test.dart b/tests/ffi/allocator_test.dart new file mode 100644 index 00000000000..ab2e8dbab00 --- /dev/null +++ b/tests/ffi/allocator_test.dart @@ -0,0 +1,24 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Tests that we can implement the Allocator interface. + +import 'dart:ffi'; + +class MyAllocator implements Allocator { + const MyAllocator(); + + @override + Pointer allocate(int numBytes, {int? alignment}) { + throw "Not implemented"; + } + + void free(Pointer pointer) {} +} + +const myAllocator = MyAllocator(); + +void main() { + print(myAllocator); +} diff --git a/tests/ffi/calloc.dart b/tests/ffi/calloc.dart new file mode 100644 index 00000000000..f76e9cb23e0 --- /dev/null +++ b/tests/ffi/calloc.dart @@ -0,0 +1,109 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// TODO(https://dartbug.com/44621): Remove this copy when package:ffi can be +// rolled. We need to wait until the `Allocator` interface has rolled into +// Flutter. + +import 'dart:ffi'; +import 'dart:io'; + +final DynamicLibrary stdlib = Platform.isWindows + ? DynamicLibrary.open('kernel32.dll') + : DynamicLibrary.process(); + +typedef PosixCallocNative = Pointer Function(IntPtr num, IntPtr size); +typedef PosixCalloc = Pointer Function(int num, int size); +final PosixCalloc posixCalloc = + stdlib.lookupFunction('calloc'); + +typedef PosixFreeNative = Void Function(Pointer); +typedef PosixFree = void Function(Pointer); +final PosixFree posixFree = + stdlib.lookupFunction('free'); + +typedef WinGetProcessHeapFn = Pointer Function(); +final WinGetProcessHeapFn winGetProcessHeap = stdlib + .lookupFunction('GetProcessHeap'); +final Pointer processHeap = winGetProcessHeap(); + +typedef WinHeapAllocNative = Pointer Function(Pointer, Uint32, IntPtr); +typedef WinHeapAlloc = Pointer Function(Pointer, int, int); +final WinHeapAlloc winHeapAlloc = + stdlib.lookupFunction('HeapAlloc'); + +typedef WinHeapFreeNative = Int32 Function( + Pointer heap, Uint32 flags, Pointer memory); +typedef WinHeapFree = int Function(Pointer heap, int flags, Pointer memory); +final WinHeapFree winHeapFree = + stdlib.lookupFunction('HeapFree'); + +const int HEAP_ZERO_MEMORY = 8; + +/// Manages memory on the native heap. +/// +/// Initializes newly allocated memory to zero. +/// +/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses +/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default +/// public heap. +class _CallocAllocator implements Allocator { + const _CallocAllocator(); + + /// Allocates [byteCount] bytes of zero-initialized of memory on the native + /// heap. + /// + /// For POSIX-based systems, this uses `malloc`. On Windows, it uses + /// `HeapAlloc` against the default public heap. + /// + /// Throws an [ArgumentError] if the number of bytes or alignment cannot be + /// satisfied. + // TODO: Stop ignoring alignment if it's large, for example for SSE data. + @override + Pointer allocate(int byteCount, {int? alignment}) { + Pointer result; + if (Platform.isWindows) { + result = winHeapAlloc(processHeap, /*flags=*/ HEAP_ZERO_MEMORY, byteCount) + .cast(); + } else { + result = posixCalloc(byteCount, 1).cast(); + } + if (result.address == 0) { + throw ArgumentError('Could not allocate $byteCount bytes.'); + } + return result; + } + + /// Releases memory allocated on the native heap. + /// + /// For POSIX-based systems, this uses `free`. On Windows, it uses `HeapFree` + /// against the default public heap. It may only be used against pointers + /// allocated in a manner equivalent to [allocate]. + /// + /// Throws an [ArgumentError] if the memory pointed to by [pointer] cannot be + /// freed. + /// + // TODO(dartbug.com/36855): Once we have a ffi.Bool type we can use it instead + // of testing the return integer to be non-zero. + @override + void free(Pointer pointer) { + if (Platform.isWindows) { + if (winHeapFree(processHeap, /*flags=*/ 0, pointer) == 0) { + throw ArgumentError('Could not free $pointer.'); + } + } else { + posixFree(pointer); + } + } +} + +/// Manages memory on the native heap. +/// +/// Initializes newly allocated memory to zero. Use [malloc] for unintialized +/// memory allocation. +/// +/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses +/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default +/// public heap. +const Allocator calloc = _CallocAllocator(); diff --git a/tests/ffi/calloc_test.dart b/tests/ffi/calloc_test.dart new file mode 100644 index 00000000000..b8b9bbff449 --- /dev/null +++ b/tests/ffi/calloc_test.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:ffi'; + +import 'package:expect/expect.dart'; + +import 'calloc.dart'; +import 'coordinate.dart'; + +void main() { + testZeroInt(); + testZeroFloat(); + testZeroStruct(); +} + +void testZeroInt() { + final p = calloc(); + Expect.equals(0, p.value); + calloc.free(p); +} + +void testZeroFloat() { + final p = calloc(); + Expect.approxEquals(0.0, p.value); + calloc.free(p); +} + +void testZeroStruct() { + final p = calloc(); + Expect.approxEquals(0, p.ref.x); + Expect.approxEquals(0, p.ref.y); + Expect.equals(nullptr, p.ref.next); + calloc.free(p); +} diff --git a/tests/ffi/data_test.dart b/tests/ffi/data_test.dart index b2d304d198f..0dd067042bc 100644 --- a/tests/ffi/data_test.dart +++ b/tests/ffi/data_test.dart @@ -44,10 +44,8 @@ void main() { testTypeTest(); testToString(); testEquality(); - testAllocateGeneric(); testAllocateVoid(); testAllocateNativeFunction(); - testAllocateNativeType(); testSizeOfGeneric(); testSizeOfVoid(); testSizeOfNativeFunction(); @@ -423,17 +421,6 @@ void testEquality() { typedef Int8UnOp = Int8 Function(Int8); -void testAllocateGeneric() { - Pointer generic() { - Pointer pointer; - pointer = allocate(); - return pointer; - } - - Pointer p = generic(); - free(p); -} - void testAllocateVoid() { Expect.throws(() { Pointer p = allocate(); @@ -446,12 +433,6 @@ void testAllocateNativeFunction() { }); } -void testAllocateNativeType() { - Expect.throws(() { - allocate(); - }); -} - void testSizeOfGeneric() { int generic() { int size; diff --git a/tests/ffi/vmspecific_static_checks_test.dart b/tests/ffi/vmspecific_static_checks_test.dart index 870de9cffef..9b7940ef957 100644 --- a/tests/ffi/vmspecific_static_checks_test.dart +++ b/tests/ffi/vmspecific_static_checks_test.dart @@ -53,6 +53,8 @@ void main() { testEmptyStructAsFunctionReturn(); testEmptyStructFromFunctionArgument(); testEmptyStructFromFunctionReturn(); + testAllocateGeneric(); + testAllocateNativeType(); } typedef Int8UnOp = Int8 Function(Int8); @@ -550,3 +552,17 @@ void testEmptyStructFromFunctionReturn() { class HasNestedEmptyStruct extends Struct { external EmptyStruct nestedEmptyStruct; //# 1106: compile-time error } + +void testAllocateGeneric() { + Pointer generic() { + Pointer pointer = nullptr; + pointer = malloc(); //# 1320: compile-time error + return pointer; + } + + Pointer p = generic(); +} + +void testAllocateNativeType() { + malloc(); //# 1321: compile-time error +} diff --git a/tests/ffi_2/allocator_test.dart b/tests/ffi_2/allocator_test.dart new file mode 100644 index 00000000000..396f5c9775c --- /dev/null +++ b/tests/ffi_2/allocator_test.dart @@ -0,0 +1,24 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Tests that we can implement the Allocator interface. + +import 'dart:ffi'; + +class MyAllocator implements Allocator { + const MyAllocator(); + + @override + Pointer allocate(int numBytes, {int alignment}) { + throw "Not implemented"; + } + + void free(Pointer pointer) {} +} + +const myAllocator = MyAllocator(); + +void main() { + print(myAllocator); +} diff --git a/tests/ffi_2/calloc.dart b/tests/ffi_2/calloc.dart new file mode 100644 index 00000000000..a0599157c79 --- /dev/null +++ b/tests/ffi_2/calloc.dart @@ -0,0 +1,109 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// TODO(https://dartbug.com/44621): Remove this copy when package:ffi can be +// rolled. We need to wait until the `Allocator` interface has rolled into +// Flutter. + +import 'dart:ffi'; +import 'dart:io'; + +final DynamicLibrary stdlib = Platform.isWindows + ? DynamicLibrary.open('kernel32.dll') + : DynamicLibrary.process(); + +typedef PosixCallocNative = Pointer Function(IntPtr num, IntPtr size); +typedef PosixCalloc = Pointer Function(int num, int size); +final PosixCalloc posixCalloc = + stdlib.lookupFunction('calloc'); + +typedef PosixFreeNative = Void Function(Pointer); +typedef PosixFree = void Function(Pointer); +final PosixFree posixFree = + stdlib.lookupFunction('free'); + +typedef WinGetProcessHeapFn = Pointer Function(); +final WinGetProcessHeapFn winGetProcessHeap = stdlib + .lookupFunction('GetProcessHeap'); +final Pointer processHeap = winGetProcessHeap(); + +typedef WinHeapAllocNative = Pointer Function(Pointer, Uint32, IntPtr); +typedef WinHeapAlloc = Pointer Function(Pointer, int, int); +final WinHeapAlloc winHeapAlloc = + stdlib.lookupFunction('HeapAlloc'); + +typedef WinHeapFreeNative = Int32 Function( + Pointer heap, Uint32 flags, Pointer memory); +typedef WinHeapFree = int Function(Pointer heap, int flags, Pointer memory); +final WinHeapFree winHeapFree = + stdlib.lookupFunction('HeapFree'); + +const int HEAP_ZERO_MEMORY = 8; + +/// Manages memory on the native heap. +/// +/// Initializes newly allocated memory to zero. +/// +/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses +/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default +/// public heap. +class _CallocAllocator implements Allocator { + const _CallocAllocator(); + + /// Allocates [byteCount] bytes of zero-initialized of memory on the native + /// heap. + /// + /// For POSIX-based systems, this uses `malloc`. On Windows, it uses + /// `HeapAlloc` against the default public heap. + /// + /// Throws an [ArgumentError] if the number of bytes or alignment cannot be + /// satisfied. + // TODO: Stop ignoring alignment if it's large, for example for SSE data. + @override + Pointer allocate(int byteCount, {int alignment}) { + Pointer result; + if (Platform.isWindows) { + result = winHeapAlloc(processHeap, /*flags=*/ HEAP_ZERO_MEMORY, byteCount) + .cast(); + } else { + result = posixCalloc(byteCount, 1).cast(); + } + if (result.address == 0) { + throw ArgumentError('Could not allocate $byteCount bytes.'); + } + return result; + } + + /// Releases memory allocated on the native heap. + /// + /// For POSIX-based systems, this uses `free`. On Windows, it uses `HeapFree` + /// against the default public heap. It may only be used against pointers + /// allocated in a manner equivalent to [allocate]. + /// + /// Throws an [ArgumentError] if the memory pointed to by [pointer] cannot be + /// freed. + /// + // TODO(dartbug.com/36855): Once we have a ffi.Bool type we can use it instead + // of testing the return integer to be non-zero. + @override + void free(Pointer pointer) { + if (Platform.isWindows) { + if (winHeapFree(processHeap, /*flags=*/ 0, pointer) == 0) { + throw ArgumentError('Could not free $pointer.'); + } + } else { + posixFree(pointer); + } + } +} + +/// Manages memory on the native heap. +/// +/// Initializes newly allocated memory to zero. Use [malloc] for unintialized +/// memory allocation. +/// +/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses +/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default +/// public heap. +const Allocator calloc = _CallocAllocator(); diff --git a/tests/ffi_2/calloc_test.dart b/tests/ffi_2/calloc_test.dart new file mode 100644 index 00000000000..b8b9bbff449 --- /dev/null +++ b/tests/ffi_2/calloc_test.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:ffi'; + +import 'package:expect/expect.dart'; + +import 'calloc.dart'; +import 'coordinate.dart'; + +void main() { + testZeroInt(); + testZeroFloat(); + testZeroStruct(); +} + +void testZeroInt() { + final p = calloc(); + Expect.equals(0, p.value); + calloc.free(p); +} + +void testZeroFloat() { + final p = calloc(); + Expect.approxEquals(0.0, p.value); + calloc.free(p); +} + +void testZeroStruct() { + final p = calloc(); + Expect.approxEquals(0, p.ref.x); + Expect.approxEquals(0, p.ref.y); + Expect.equals(nullptr, p.ref.next); + calloc.free(p); +} diff --git a/tests/ffi_2/data_test.dart b/tests/ffi_2/data_test.dart index a533ced69b5..35f9c5e0449 100644 --- a/tests/ffi_2/data_test.dart +++ b/tests/ffi_2/data_test.dart @@ -44,10 +44,8 @@ void main() { testTypeTest(); testToString(); testEquality(); - testAllocateGeneric(); testAllocateVoid(); testAllocateNativeFunction(); - testAllocateNativeType(); testSizeOfGeneric(); testSizeOfVoid(); testSizeOfNativeFunction(); @@ -423,17 +421,6 @@ void testEquality() { typedef Int8UnOp = Int8 Function(Int8); -void testAllocateGeneric() { - Pointer generic() { - Pointer pointer; - pointer = allocate(); - return pointer; - } - - Pointer p = generic(); - free(p); -} - void testAllocateVoid() { Expect.throws(() { Pointer p = allocate(); @@ -446,12 +433,6 @@ void testAllocateNativeFunction() { }); } -void testAllocateNativeType() { - Expect.throws(() { - allocate(); - }); -} - void testSizeOfGeneric() { int generic() { int size; diff --git a/tests/ffi_2/vmspecific_static_checks_test.dart b/tests/ffi_2/vmspecific_static_checks_test.dart index ebbe4748119..967e16b3129 100644 --- a/tests/ffi_2/vmspecific_static_checks_test.dart +++ b/tests/ffi_2/vmspecific_static_checks_test.dart @@ -53,6 +53,8 @@ void main() { testEmptyStructAsFunctionReturn(); testEmptyStructFromFunctionArgument(); testEmptyStructFromFunctionReturn(); + testAllocateGeneric(); + testAllocateNativeType(); } typedef Int8UnOp = Int8 Function(Int8); @@ -550,3 +552,17 @@ void testEmptyStructFromFunctionReturn() { class HasNestedEmptyStruct extends Struct { EmptyStruct nestedEmptyStruct; //# 1106: compile-time error } + +void testAllocateGeneric() { + Pointer generic() { + Pointer pointer = nullptr; + pointer = malloc(); //# 1320: compile-time error + return pointer; + } + + Pointer p = generic(); +} + +void testAllocateNativeType() { + malloc(); //# 1321: compile-time error +}