Skip to content

Commit d37e833

Browse files
dcharkescommit-bot@chromium.org
authored andcommitted
[vm/ffi] Migrate samples(_2)/ffi to CallocAllocator
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 <[email protected]> Reviewed-by: Aske Simon Christensen <[email protected]>
1 parent 978b838 commit d37e833

36 files changed

+822
-396
lines changed

samples/ffi/calloc.dart

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// TODO(https://dartbug.com/44621): Remove this copy when package:ffi can be
6+
// rolled. We need to wait until the `Allocator` interface has rolled into
7+
// Flutter.
8+
9+
import 'dart:ffi';
10+
import 'dart:io';
11+
12+
final DynamicLibrary stdlib = Platform.isWindows
13+
? DynamicLibrary.open('kernel32.dll')
14+
: DynamicLibrary.process();
15+
16+
typedef PosixCallocNative = Pointer Function(IntPtr num, IntPtr size);
17+
typedef PosixCalloc = Pointer Function(int num, int size);
18+
final PosixCalloc posixCalloc =
19+
stdlib.lookupFunction<PosixCallocNative, PosixCalloc>('calloc');
20+
21+
typedef PosixFreeNative = Void Function(Pointer);
22+
typedef PosixFree = void Function(Pointer);
23+
final PosixFree posixFree =
24+
stdlib.lookupFunction<PosixFreeNative, PosixFree>('free');
25+
26+
typedef WinGetProcessHeapFn = Pointer Function();
27+
final WinGetProcessHeapFn winGetProcessHeap = stdlib
28+
.lookupFunction<WinGetProcessHeapFn, WinGetProcessHeapFn>('GetProcessHeap');
29+
final Pointer processHeap = winGetProcessHeap();
30+
31+
typedef WinHeapAllocNative = Pointer Function(Pointer, Uint32, IntPtr);
32+
typedef WinHeapAlloc = Pointer Function(Pointer, int, int);
33+
final WinHeapAlloc winHeapAlloc =
34+
stdlib.lookupFunction<WinHeapAllocNative, WinHeapAlloc>('HeapAlloc');
35+
36+
typedef WinHeapFreeNative = Int32 Function(
37+
Pointer heap, Uint32 flags, Pointer memory);
38+
typedef WinHeapFree = int Function(Pointer heap, int flags, Pointer memory);
39+
final WinHeapFree winHeapFree =
40+
stdlib.lookupFunction<WinHeapFreeNative, WinHeapFree>('HeapFree');
41+
42+
const int HEAP_ZERO_MEMORY = 8;
43+
44+
/// Manages memory on the native heap.
45+
///
46+
/// Initializes newly allocated memory to zero.
47+
///
48+
/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses
49+
/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default
50+
/// public heap.
51+
class _CallocAllocator implements Allocator {
52+
const _CallocAllocator();
53+
54+
/// Allocates [byteCount] bytes of zero-initialized of memory on the native
55+
/// heap.
56+
///
57+
/// For POSIX-based systems, this uses `calloc`. On Windows, it uses
58+
/// `HeapAlloc` against the default public heap.
59+
///
60+
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
61+
/// satisfied.
62+
// TODO: Stop ignoring alignment if it's large, for example for SSE data.
63+
@override
64+
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
65+
Pointer<T> result;
66+
if (Platform.isWindows) {
67+
result = winHeapAlloc(processHeap, /*flags=*/ HEAP_ZERO_MEMORY, byteCount)
68+
.cast();
69+
} else {
70+
result = posixCalloc(byteCount, 1).cast();
71+
}
72+
if (result.address == 0) {
73+
throw ArgumentError('Could not allocate $byteCount bytes.');
74+
}
75+
return result;
76+
}
77+
78+
/// Releases memory allocated on the native heap.
79+
///
80+
/// For POSIX-based systems, this uses `free`. On Windows, it uses `HeapFree`
81+
/// against the default public heap. It may only be used against pointers
82+
/// allocated in a manner equivalent to [allocate].
83+
///
84+
/// Throws an [ArgumentError] if the memory pointed to by [pointer] cannot be
85+
/// freed.
86+
///
87+
// TODO(dartbug.com/36855): Once we have a ffi.Bool type we can use it instead
88+
// of testing the return integer to be non-zero.
89+
@override
90+
void free(Pointer pointer) {
91+
if (Platform.isWindows) {
92+
if (winHeapFree(processHeap, /*flags=*/ 0, pointer) == 0) {
93+
throw ArgumentError('Could not free $pointer.');
94+
}
95+
} else {
96+
posixFree(pointer);
97+
}
98+
}
99+
}
100+
101+
/// Manages memory on the native heap.
102+
///
103+
/// Initializes newly allocated memory to zero. Use [malloc] for unintialized
104+
/// memory allocation.
105+
///
106+
/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses
107+
/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default
108+
/// public heap.
109+
const Allocator calloc = _CallocAllocator();

samples/ffi/coordinate.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ class Coordinate extends Struct {
1616

1717
external Pointer<Coordinate> next;
1818

19-
factory Coordinate.allocate(double x, double y, Pointer<Coordinate> next) {
20-
return allocate<Coordinate>().ref
19+
factory Coordinate.allocate(
20+
Allocator allocator, double x, double y, Pointer<Coordinate> next) {
21+
return allocator<Coordinate>().ref
2122
..x = x
2223
..y = y
2324
..next = next;

samples/ffi/resource_management/pool.dart

Lines changed: 27 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,36 @@
55
// Explicit pool used for managing resources.
66

77
import "dart:async";
8-
import 'dart:convert';
98
import 'dart:ffi';
10-
import 'dart:typed_data';
119

12-
import 'package:ffi/ffi.dart' as packageFfi;
13-
import 'package:ffi/ffi.dart' show Utf8;
10+
import 'package:ffi/ffi.dart';
1411

15-
/// Manages native resources.
12+
import '../calloc.dart';
13+
14+
/// Keeps track of all allocated memory and frees all allocated memory on
15+
/// [releaseAll].
1616
///
17-
/// Primary implementations are [Pool] and [Unmanaged].
18-
abstract class ResourceManager {
19-
/// Allocates memory on the native heap.
20-
///
21-
/// The native memory is under management by this [ResourceManager].
22-
///
23-
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
24-
/// against the default public heap. Allocation of either element size or count
25-
/// of 0 is undefined.
26-
///
27-
/// Throws an ArgumentError on failure to allocate.
28-
Pointer<T> allocate<T extends NativeType>({int count: 1});
29-
}
17+
/// Wraps an [Allocator] to do the actual allocation and freeing.
18+
class Pool implements Allocator {
19+
/// The [Allocator] used for allocation and freeing.
20+
final Allocator _wrappedAllocator;
21+
22+
Pool(this._wrappedAllocator);
3023

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

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

39-
/// Allocates memory on the native heap.
40-
///
41-
/// The native memory is under management by this [Pool].
42-
///
43-
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
44-
/// against the default public heap. Allocation of either element size or count
45-
/// of 0 is undefined.
30+
/// Allocates memory on the native heap by using the allocator supplied to
31+
/// the constructor.
4632
///
47-
/// Throws an ArgumentError on failure to allocate.
48-
Pointer<T> allocate<T extends NativeType>({int count: 1}) {
49-
final p = Unmanaged().allocate<T>(count: count);
33+
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
34+
/// satisfied.
35+
@override
36+
Pointer<T> allocate<T extends NativeType>(int numBytes, {int? alignment}) {
37+
final p = _wrappedAllocator.allocate<T>(numBytes, alignment: alignment);
5038
_managedMemoryPointers.add(p);
5139
return p;
5240
}
@@ -71,17 +59,21 @@ class Pool implements ResourceManager {
7159
}
7260
_managedResourceReleaseCallbacks.clear();
7361
for (final p in _managedMemoryPointers) {
74-
Unmanaged().free(p);
62+
_wrappedAllocator.free(p);
7563
}
7664
_managedMemoryPointers.clear();
7765
}
66+
67+
@override
68+
void free(Pointer<NativeType> pointer) => throw UnsupportedError(
69+
"Individually freeing Pool allocated memory is not allowed");
7870
}
7971

8072
/// Creates a [Pool] to manage native resources.
8173
///
8274
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
83-
R using<R>(R Function(Pool) f) {
84-
final p = Pool();
75+
R using<R>(R Function(Pool) f, [Allocator wrappedAllocator = calloc]) {
76+
final p = Pool(wrappedAllocator);
8577
try {
8678
return f(p);
8779
} finally {
@@ -96,8 +88,8 @@ R using<R>(R Function(Pool) f) {
9688
/// Please note that all throws are caught and packaged in [RethrownError].
9789
///
9890
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
99-
R usePool<R>(R Function() f) {
100-
final p = Pool();
91+
R usePool<R>(R Function() f, [Allocator wrappedAllocator = calloc]) {
92+
final p = Pool(wrappedAllocator);
10193
try {
10294
return runZoned(() => f(),
10395
zoneValues: {#_pool: p},
@@ -117,78 +109,3 @@ class RethrownError {
117109
toString() => """RethrownError(${original})
118110
${originalStackTrace}""";
119111
}
120-
121-
/// Does not manage it's resources.
122-
class Unmanaged implements ResourceManager {
123-
/// Allocates memory on the native heap.
124-
///
125-
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
126-
/// against the default public heap. Allocation of either element size or count
127-
/// of 0 is undefined.
128-
///
129-
/// Throws an ArgumentError on failure to allocate.
130-
Pointer<T> allocate<T extends NativeType>({int count = 1}) =>
131-
packageFfi.allocate(count: count);
132-
133-
/// Releases memory on the native heap.
134-
///
135-
/// For POSIX-based systems, this uses free. On Windows, it uses HeapFree
136-
/// against the default public heap. It may only be used against pointers
137-
/// allocated in a manner equivalent to [allocate].
138-
///
139-
/// Throws an ArgumentError on failure to free.
140-
///
141-
void free(Pointer pointer) => packageFfi.free(pointer);
142-
}
143-
144-
/// Does not manage it's resources.
145-
final Unmanaged unmanaged = Unmanaged();
146-
147-
extension Utf8InPool on String {
148-
/// Convert a [String] to a Utf8-encoded null-terminated C string.
149-
///
150-
/// If 'string' contains NULL bytes, the converted string will be truncated
151-
/// prematurely. Unpaired surrogate code points in [string] will be preserved
152-
/// in the UTF-8 encoded result. See [Utf8Encoder] for details on encoding.
153-
///
154-
/// Returns a malloc-allocated pointer to the result.
155-
///
156-
/// The memory is managed by the [Pool] passed in as [pool].
157-
Pointer<Utf8> toUtf8(ResourceManager pool) {
158-
final units = utf8.encode(this);
159-
final Pointer<Uint8> result = pool.allocate<Uint8>(count: units.length + 1);
160-
final Uint8List nativeString = result.asTypedList(units.length + 1);
161-
nativeString.setAll(0, units);
162-
nativeString[units.length] = 0;
163-
return result.cast();
164-
}
165-
}
166-
167-
extension Utf8Helpers on Pointer<Utf8> {
168-
/// Returns the length of a null-terminated string -- the number of (one-byte)
169-
/// characters before the first null byte.
170-
int strlen() {
171-
final Pointer<Uint8> array = this.cast<Uint8>();
172-
final Uint8List nativeString = array.asTypedList(_maxSize);
173-
return nativeString.indexWhere((char) => char == 0);
174-
}
175-
176-
/// Creates a [String] containing the characters UTF-8 encoded in [this].
177-
///
178-
/// [this] must be a zero-terminated byte sequence of valid UTF-8
179-
/// encodings of Unicode code points. It may also contain UTF-8 encodings of
180-
/// unpaired surrogate code points, which is not otherwise valid UTF-8, but
181-
/// which may be created when encoding a Dart string containing an unpaired
182-
/// surrogate. See [Utf8Decoder] for details on decoding.
183-
///
184-
/// Returns a Dart string containing the decoded code points.
185-
String contents() {
186-
final int length = strlen();
187-
return utf8.decode(Uint8List.view(
188-
this.cast<Uint8>().asTypedList(length).buffer, 0, length));
189-
}
190-
}
191-
192-
const int _kMaxSmi64 = (1 << 62) - 1;
193-
const int _kMaxSmi32 = (1 << 30) - 1;
194-
final int _maxSize = sizeOf<IntPtr>() == 8 ? _kMaxSmi64 : _kMaxSmi32;

samples/ffi/resource_management/pool_sample.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'dart:ffi';
99
import 'package:expect/expect.dart';
1010

1111
import 'pool.dart';
12+
import 'utf8_helpers.dart';
1213
import '../dylib_utils.dart';
1314

1415
main() {
@@ -21,7 +22,7 @@ main() {
2122

2223
// To ensure resources are freed, wrap them in a [using] call.
2324
using((Pool pool) {
24-
final p = pool.allocate<Int64>(count: 2);
25+
final p = pool<Int64>(2);
2526
p[0] = 24;
2627
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
2728
print(p[1]);
@@ -31,23 +32,23 @@ main() {
3132
// Resources are freed also when abnormal control flow occurs.
3233
try {
3334
using((Pool pool) {
34-
final p = pool.allocate<Int64>(count: 2);
35+
final p = pool<Int64>(2);
3536
p[0] = 25;
3637
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
3738
print(p[1]);
3839
Expect.equals(25, p[1]);
3940
throw Exception("Some random exception");
4041
});
41-
// `free(p)` has been called.
42+
// `calloc.free(p)` has been called.
4243
} on Exception catch (e) {
4344
print("Caught exception: $e");
4445
}
4546

4647
// In a pool multiple resources can be allocated, which will all be freed
4748
// at the end of the scope.
4849
using((Pool pool) {
49-
final p = pool.allocate<Int64>(count: 2);
50-
final p2 = pool.allocate<Int64>(count: 2);
50+
final p = pool<Int64>(2);
51+
final p2 = pool<Int64>(2);
5152
p[0] = 1;
5253
p[1] = 2;
5354
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
@@ -58,7 +59,7 @@ main() {
5859
// If the resource allocation happens in a different scope, then one either
5960
// needs to pass the pool to that scope.
6061
f1(Pool pool) {
61-
return pool.allocate<Int64>(count: 2);
62+
return pool<Int64>(2);
6263
}
6364

6465
using((Pool pool) {

0 commit comments

Comments
 (0)