Skip to content

Commit

Permalink
[vm/ffi] Migrate samples(_2)/ffi to CallocAllocator
Browse files Browse the repository at this point in the history
This CL does not yet roll `package:ffi` to use `Allocator`, because that
breaks the checked in Dart in Flutter in g3. Instead, this copies
`_CallocAllocator` from `package:ffi` into the samples.

New API landed in: https://dart-review.googlesource.com/c/sdk/+/177705

Issue: #44621
Issue: #38721

Change-Id: I83da349c2e52d7f079aa1569b4726318fee24c9d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/177706
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
  • Loading branch information
dcharkes authored and commit-bot@chromium.org committed Jan 14, 2021
1 parent 978b838 commit d37e833
Show file tree
Hide file tree
Showing 36 changed files with 822 additions and 396 deletions.
109 changes: 109 additions & 0 deletions samples/ffi/calloc.dart
Original file line number Diff line number Diff line change
@@ -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 `calloc`. 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();
5 changes: 3 additions & 2 deletions samples/ffi/coordinate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ class Coordinate extends Struct {

external Pointer<Coordinate> next;

factory Coordinate.allocate(double x, double y, Pointer<Coordinate> next) {
return allocate<Coordinate>().ref
factory Coordinate.allocate(
Allocator allocator, double x, double y, Pointer<Coordinate> next) {
return allocator<Coordinate>().ref
..x = x
..y = y
..next = next;
Expand Down
137 changes: 27 additions & 110 deletions samples/ffi/resource_management/pool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,36 @@
// Explicit pool used for managing resources.

import "dart:async";
import 'dart:convert';
import 'dart:ffi';
import 'dart:typed_data';

import 'package:ffi/ffi.dart' as packageFfi;
import 'package:ffi/ffi.dart' show Utf8;
import 'package:ffi/ffi.dart';

/// Manages native resources.
import '../calloc.dart';

/// Keeps track of all allocated memory and frees all allocated memory on
/// [releaseAll].
///
/// Primary implementations are [Pool] and [Unmanaged].
abstract class ResourceManager {
/// Allocates memory on the native heap.
///
/// The native memory is under management by this [ResourceManager].
///
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
/// against the default public heap. Allocation of either element size or count
/// of 0 is undefined.
///
/// Throws an ArgumentError on failure to allocate.
Pointer<T> allocate<T extends NativeType>({int count: 1});
}
/// Wraps an [Allocator] to do the actual allocation and freeing.
class Pool implements Allocator {
/// The [Allocator] used for allocation and freeing.
final Allocator _wrappedAllocator;

Pool(this._wrappedAllocator);

/// Manages native resources.
class Pool implements ResourceManager {
/// Native memory under management by this [Pool].
final List<Pointer<NativeType>> _managedMemoryPointers = [];

/// Callbacks for releasing native resources under management by this [Pool].
final List<Function()> _managedResourceReleaseCallbacks = [];

/// Allocates memory on the native heap.
///
/// The native memory is under management by this [Pool].
///
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
/// against the default public heap. Allocation of either element size or count
/// of 0 is undefined.
/// Allocates memory on the native heap by using the allocator supplied to
/// the constructor.
///
/// Throws an ArgumentError on failure to allocate.
Pointer<T> allocate<T extends NativeType>({int count: 1}) {
final p = Unmanaged().allocate<T>(count: count);
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
/// satisfied.
@override
Pointer<T> allocate<T extends NativeType>(int numBytes, {int? alignment}) {
final p = _wrappedAllocator.allocate<T>(numBytes, alignment: alignment);
_managedMemoryPointers.add(p);
return p;
}
Expand All @@ -71,17 +59,21 @@ class Pool implements ResourceManager {
}
_managedResourceReleaseCallbacks.clear();
for (final p in _managedMemoryPointers) {
Unmanaged().free(p);
_wrappedAllocator.free(p);
}
_managedMemoryPointers.clear();
}

@override
void free(Pointer<NativeType> pointer) => throw UnsupportedError(
"Individually freeing Pool allocated memory is not allowed");
}

/// Creates a [Pool] to manage native resources.
///
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
R using<R>(R Function(Pool) f) {
final p = Pool();
R using<R>(R Function(Pool) f, [Allocator wrappedAllocator = calloc]) {
final p = Pool(wrappedAllocator);
try {
return f(p);
} finally {
Expand All @@ -96,8 +88,8 @@ R using<R>(R Function(Pool) f) {
/// Please note that all throws are caught and packaged in [RethrownError].
///
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
R usePool<R>(R Function() f) {
final p = Pool();
R usePool<R>(R Function() f, [Allocator wrappedAllocator = calloc]) {
final p = Pool(wrappedAllocator);
try {
return runZoned(() => f(),
zoneValues: {#_pool: p},
Expand All @@ -117,78 +109,3 @@ class RethrownError {
toString() => """RethrownError(${original})
${originalStackTrace}""";
}

/// Does not manage it's resources.
class Unmanaged implements ResourceManager {
/// Allocates memory on the native heap.
///
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
/// against the default public heap. Allocation of either element size or count
/// of 0 is undefined.
///
/// Throws an ArgumentError on failure to allocate.
Pointer<T> allocate<T extends NativeType>({int count = 1}) =>
packageFfi.allocate(count: count);

/// Releases memory 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 on failure to free.
///
void free(Pointer pointer) => packageFfi.free(pointer);
}

/// Does not manage it's resources.
final Unmanaged unmanaged = Unmanaged();

extension Utf8InPool on String {
/// Convert a [String] to a Utf8-encoded null-terminated C string.
///
/// If 'string' contains NULL bytes, the converted string will be truncated
/// prematurely. Unpaired surrogate code points in [string] will be preserved
/// in the UTF-8 encoded result. See [Utf8Encoder] for details on encoding.
///
/// Returns a malloc-allocated pointer to the result.
///
/// The memory is managed by the [Pool] passed in as [pool].
Pointer<Utf8> toUtf8(ResourceManager pool) {
final units = utf8.encode(this);
final Pointer<Uint8> result = pool.allocate<Uint8>(count: units.length + 1);
final Uint8List nativeString = result.asTypedList(units.length + 1);
nativeString.setAll(0, units);
nativeString[units.length] = 0;
return result.cast();
}
}

extension Utf8Helpers on Pointer<Utf8> {
/// Returns the length of a null-terminated string -- the number of (one-byte)
/// characters before the first null byte.
int strlen() {
final Pointer<Uint8> array = this.cast<Uint8>();
final Uint8List nativeString = array.asTypedList(_maxSize);
return nativeString.indexWhere((char) => char == 0);
}

/// Creates a [String] containing the characters UTF-8 encoded in [this].
///
/// [this] must be a zero-terminated byte sequence of valid UTF-8
/// encodings of Unicode code points. It may also contain UTF-8 encodings of
/// unpaired surrogate code points, which is not otherwise valid UTF-8, but
/// which may be created when encoding a Dart string containing an unpaired
/// surrogate. See [Utf8Decoder] for details on decoding.
///
/// Returns a Dart string containing the decoded code points.
String contents() {
final int length = strlen();
return utf8.decode(Uint8List.view(
this.cast<Uint8>().asTypedList(length).buffer, 0, length));
}
}

const int _kMaxSmi64 = (1 << 62) - 1;
const int _kMaxSmi32 = (1 << 30) - 1;
final int _maxSize = sizeOf<IntPtr>() == 8 ? _kMaxSmi64 : _kMaxSmi32;
13 changes: 7 additions & 6 deletions samples/ffi/resource_management/pool_sample.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'dart:ffi';
import 'package:expect/expect.dart';

import 'pool.dart';
import 'utf8_helpers.dart';
import '../dylib_utils.dart';

main() {
Expand All @@ -21,7 +22,7 @@ main() {

// To ensure resources are freed, wrap them in a [using] call.
using((Pool pool) {
final p = pool.allocate<Int64>(count: 2);
final p = pool<Int64>(2);
p[0] = 24;
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
print(p[1]);
Expand All @@ -31,23 +32,23 @@ main() {
// Resources are freed also when abnormal control flow occurs.
try {
using((Pool pool) {
final p = pool.allocate<Int64>(count: 2);
final p = pool<Int64>(2);
p[0] = 25;
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
print(p[1]);
Expect.equals(25, p[1]);
throw Exception("Some random exception");
});
// `free(p)` has been called.
// `calloc.free(p)` has been called.
} on Exception catch (e) {
print("Caught exception: $e");
}

// In a pool multiple resources can be allocated, which will all be freed
// at the end of the scope.
using((Pool pool) {
final p = pool.allocate<Int64>(count: 2);
final p2 = pool.allocate<Int64>(count: 2);
final p = pool<Int64>(2);
final p2 = pool<Int64>(2);
p[0] = 1;
p[1] = 2;
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
Expand All @@ -58,7 +59,7 @@ main() {
// If the resource allocation happens in a different scope, then one either
// needs to pass the pool to that scope.
f1(Pool pool) {
return pool.allocate<Int64>(count: 2);
return pool<Int64>(2);
}

using((Pool pool) {
Expand Down
Loading

0 comments on commit d37e833

Please sign in to comment.