mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 02:57:35 +00:00
[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 <cskau@google.com>
This commit is contained in:
parent
10917ffd55
commit
4e2343c290
|
@ -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<void> {
|
||||
static const _allocatorClassName = 'Allocator';
|
||||
static const _allocateExtensionMethodName = 'call';
|
||||
static const _allocatorExtensionName = 'AllocatorAlloc';
|
||||
static const _dartFfiLibraryName = 'dart.ffi';
|
||||
|
||||
static const List<String> _primitiveIntegerNativeTypes = [
|
||||
'Int8',
|
||||
'Int16',
|
||||
|
@ -31,6 +36,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
|
|||
'Double',
|
||||
];
|
||||
|
||||
static const _structClassName = 'Struct';
|
||||
|
||||
/// The type system used to check types.
|
||||
final TypeSystemImpl typeSystem;
|
||||
|
||||
|
@ -52,9 +59,10 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
|
|||
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<void> {
|
|||
// 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<void> {
|
|||
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<void> {
|
|||
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<void> {
|
|||
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<void> {
|
|||
}
|
||||
|
||||
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<void> {
|
|||
|
||||
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<void> {
|
|||
|
||||
/// 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<void> {
|
|||
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<void> {
|
|||
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<void> {
|
|||
_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<void> {
|
|||
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].
|
||||
|
|
|
@ -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<NativeType, Procedure> 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(
|
||||
|
|
|
@ -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
|
||||
|
|
19
sdk/lib/_internal/vm/lib/ffi_allocation_patch.dart
Normal file
19
sdk/lib/_internal/vm/lib/ffi_allocation_patch.dart
Normal file
|
@ -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<T> to enable tree shaking.
|
||||
// TODO(http://dartbug.com/39964): Add `alignmentOf<T>()` call.
|
||||
@patch
|
||||
Pointer<T> call<T extends NativeType>([int count = 1]) {
|
||||
return this.allocate(sizeOf<T>() * count);
|
||||
}
|
||||
}
|
37
sdk/lib/ffi/allocation.dart
Normal file
37
sdk/lib/ffi/allocation.dart
Normal file
|
@ -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<T> allocate<T extends NativeType>(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<T>() * count` bytes of memory using
|
||||
/// `allocator.allocate`.
|
||||
///
|
||||
/// This extension method must be invoked with a compile-time constant [T].
|
||||
external Pointer<T> call<T extends NativeType>([int count = 1]);
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
24
tests/ffi/allocator_test.dart
Normal file
24
tests/ffi/allocator_test.dart
Normal file
|
@ -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<T> allocate<T extends NativeType>(int numBytes, {int? alignment}) {
|
||||
throw "Not implemented";
|
||||
}
|
||||
|
||||
void free(Pointer pointer) {}
|
||||
}
|
||||
|
||||
const myAllocator = MyAllocator();
|
||||
|
||||
void main() {
|
||||
print(myAllocator);
|
||||
}
|
109
tests/ffi/calloc.dart
Normal file
109
tests/ffi/calloc.dart
Normal file
|
@ -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<PosixCallocNative, PosixCalloc>('calloc');
|
||||
|
||||
typedef PosixFreeNative = Void Function(Pointer);
|
||||
typedef PosixFree = void Function(Pointer);
|
||||
final PosixFree posixFree =
|
||||
stdlib.lookupFunction<PosixFreeNative, PosixFree>('free');
|
||||
|
||||
typedef WinGetProcessHeapFn = Pointer Function();
|
||||
final WinGetProcessHeapFn winGetProcessHeap = stdlib
|
||||
.lookupFunction<WinGetProcessHeapFn, WinGetProcessHeapFn>('GetProcessHeap');
|
||||
final Pointer processHeap = winGetProcessHeap();
|
||||
|
||||
typedef WinHeapAllocNative = Pointer Function(Pointer, Uint32, IntPtr);
|
||||
typedef WinHeapAlloc = Pointer Function(Pointer, int, int);
|
||||
final WinHeapAlloc winHeapAlloc =
|
||||
stdlib.lookupFunction<WinHeapAllocNative, WinHeapAlloc>('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<WinHeapFreeNative, WinHeapFree>('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<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
|
||||
Pointer<T> 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();
|
36
tests/ffi/calloc_test.dart
Normal file
36
tests/ffi/calloc_test.dart
Normal file
|
@ -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<Uint8>();
|
||||
Expect.equals(0, p.value);
|
||||
calloc.free(p);
|
||||
}
|
||||
|
||||
void testZeroFloat() {
|
||||
final p = calloc<Float>();
|
||||
Expect.approxEquals(0.0, p.value);
|
||||
calloc.free(p);
|
||||
}
|
||||
|
||||
void testZeroStruct() {
|
||||
final p = calloc<Coordinate>();
|
||||
Expect.approxEquals(0, p.ref.x);
|
||||
Expect.approxEquals(0, p.ref.y);
|
||||
Expect.equals(nullptr, p.ref.next);
|
||||
calloc.free(p);
|
||||
}
|
|
@ -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<T> generic<T extends NativeType>() {
|
||||
Pointer<T> pointer;
|
||||
pointer = allocate();
|
||||
return pointer;
|
||||
}
|
||||
|
||||
Pointer p = generic<Int64>();
|
||||
free(p);
|
||||
}
|
||||
|
||||
void testAllocateVoid() {
|
||||
Expect.throws(() {
|
||||
Pointer<Void> p = allocate();
|
||||
|
@ -446,12 +433,6 @@ void testAllocateNativeFunction() {
|
|||
});
|
||||
}
|
||||
|
||||
void testAllocateNativeType() {
|
||||
Expect.throws(() {
|
||||
allocate();
|
||||
});
|
||||
}
|
||||
|
||||
void testSizeOfGeneric() {
|
||||
int generic<T extends Pointer>() {
|
||||
int size;
|
||||
|
|
|
@ -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<T> generic<T extends NativeType>() {
|
||||
Pointer<T> pointer = nullptr;
|
||||
pointer = malloc(); //# 1320: compile-time error
|
||||
return pointer;
|
||||
}
|
||||
|
||||
Pointer p = generic<Int64>();
|
||||
}
|
||||
|
||||
void testAllocateNativeType() {
|
||||
malloc(); //# 1321: compile-time error
|
||||
}
|
||||
|
|
24
tests/ffi_2/allocator_test.dart
Normal file
24
tests/ffi_2/allocator_test.dart
Normal file
|
@ -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<T> allocate<T extends NativeType>(int numBytes, {int alignment}) {
|
||||
throw "Not implemented";
|
||||
}
|
||||
|
||||
void free(Pointer pointer) {}
|
||||
}
|
||||
|
||||
const myAllocator = MyAllocator();
|
||||
|
||||
void main() {
|
||||
print(myAllocator);
|
||||
}
|
109
tests/ffi_2/calloc.dart
Normal file
109
tests/ffi_2/calloc.dart
Normal file
|
@ -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<PosixCallocNative, PosixCalloc>('calloc');
|
||||
|
||||
typedef PosixFreeNative = Void Function(Pointer);
|
||||
typedef PosixFree = void Function(Pointer);
|
||||
final PosixFree posixFree =
|
||||
stdlib.lookupFunction<PosixFreeNative, PosixFree>('free');
|
||||
|
||||
typedef WinGetProcessHeapFn = Pointer Function();
|
||||
final WinGetProcessHeapFn winGetProcessHeap = stdlib
|
||||
.lookupFunction<WinGetProcessHeapFn, WinGetProcessHeapFn>('GetProcessHeap');
|
||||
final Pointer processHeap = winGetProcessHeap();
|
||||
|
||||
typedef WinHeapAllocNative = Pointer Function(Pointer, Uint32, IntPtr);
|
||||
typedef WinHeapAlloc = Pointer Function(Pointer, int, int);
|
||||
final WinHeapAlloc winHeapAlloc =
|
||||
stdlib.lookupFunction<WinHeapAllocNative, WinHeapAlloc>('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<WinHeapFreeNative, WinHeapFree>('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<T> allocate<T extends NativeType>(int byteCount, {int alignment}) {
|
||||
Pointer<T> 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();
|
36
tests/ffi_2/calloc_test.dart
Normal file
36
tests/ffi_2/calloc_test.dart
Normal file
|
@ -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<Uint8>();
|
||||
Expect.equals(0, p.value);
|
||||
calloc.free(p);
|
||||
}
|
||||
|
||||
void testZeroFloat() {
|
||||
final p = calloc<Float>();
|
||||
Expect.approxEquals(0.0, p.value);
|
||||
calloc.free(p);
|
||||
}
|
||||
|
||||
void testZeroStruct() {
|
||||
final p = calloc<Coordinate>();
|
||||
Expect.approxEquals(0, p.ref.x);
|
||||
Expect.approxEquals(0, p.ref.y);
|
||||
Expect.equals(nullptr, p.ref.next);
|
||||
calloc.free(p);
|
||||
}
|
|
@ -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<T> generic<T extends NativeType>() {
|
||||
Pointer<T> pointer;
|
||||
pointer = allocate();
|
||||
return pointer;
|
||||
}
|
||||
|
||||
Pointer p = generic<Int64>();
|
||||
free(p);
|
||||
}
|
||||
|
||||
void testAllocateVoid() {
|
||||
Expect.throws(() {
|
||||
Pointer<Void> p = allocate();
|
||||
|
@ -446,12 +433,6 @@ void testAllocateNativeFunction() {
|
|||
});
|
||||
}
|
||||
|
||||
void testAllocateNativeType() {
|
||||
Expect.throws(() {
|
||||
allocate();
|
||||
});
|
||||
}
|
||||
|
||||
void testSizeOfGeneric() {
|
||||
int generic<T extends Pointer>() {
|
||||
int size;
|
||||
|
|
|
@ -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<T> generic<T extends NativeType>() {
|
||||
Pointer<T> pointer = nullptr;
|
||||
pointer = malloc(); //# 1320: compile-time error
|
||||
return pointer;
|
||||
}
|
||||
|
||||
Pointer p = generic<Int64>();
|
||||
}
|
||||
|
||||
void testAllocateNativeType() {
|
||||
malloc(); //# 1321: compile-time error
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue