-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[vm/ffi] Struct.addressOf removal #40667
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I think Maybe the finalizer can be associated with a separate object (maybe like a Temporary pointers (like indexing into arrays) shouldn't need to handle the burden of finalizers, but (some) allocations do. |
If we associate the finalizer with a separate object (not the |
An example: class Managed<T extends NativeType> {
Managed._(); // Cannot be constructed by user code
Pointer<T> _pointer; // Internally contains a pointer
Pointer<T> get asPointer => _pointer; // Exposes the pointer to user code to use (temporarily)
void removeFinalizer(); // Removes the finalizer, useful for times where user code manually requests a cleanup
}
// Creates a instance of Managed, which has identity and a finalizer
Managed<T> addFinalizer(Pointer<T> ptr, Pointer<NativeFunction<void Function(Pointer<T>)>> free); Then when you use this new object: final array = addFinalizer(allocate<Uint64>(count: 8), free);
array.asPointer[0] = 123; // With asPointer
array[1] = 456; // With an extension method that uses the underlying pointer The thing that should be made clear is that class Uint64Array extends Struct {
Pointer<Uint64> values;
IntPtr size;
}
Pointer<Uint64Array> newArray(int length) {
final array = addFinalizer(allocate<Uint64Array>(), free);
array.ref.values = addFinalizer(allocate<Uint64>(count: length), free); // Bug here, since object identity is being discarded during the struct write
array.ref.size = length;
return array;
} |
This is a problem. All your code will have to look like: Managed<Int8> m; // has a finalizer
f(m.asPointer);
reachabilityFence(m); Otherwise Managed<Int8> m; // has a finalizer
final value1 = m.asPointer;
// runs garbage collector, sees `m` is unreachable.
f(value1); Here is an older issue which explored a similar idea, but with subtypes of
Any place where you pass a pointer to C need a reachability fence:
For structs we envision people writing allocate/free C functions for the data structure. That way you'd only have one C function to call, and only on the outermost Dart object representing the struct. For example, SQLite has this pattern:
|
Notes from a discussion today (@mkustermann @mraleph): When we add structs by value (#36730), if we use internal TypedData, When we add structs by value (#36730), if we use ExternalTypedData, When we add nested structs (#37271), class MyInnerStruct extends Struct {
@Int8() int;
}
class MyStruct extends Struct {
MyInnerStruct inner;
}
Pointer<MyStruct> p;
Pointer<MyInnerStruct> p2 = p.ref.inner.asPointer; |
Categorically, it seems that there are several different types of pointers here: By-value structs using TypedData, pointer references to TypedData, and pointers with finalizers should be treated as opaque by Dart, since they deal with "external forces" (GC). These all deal with GC-managed resources and therefore not safe to modify as raw pointers from Dart. We could call these managed pointers. Nested structs pointers, elementAt, and pointers returned from FFI (through function calls or struct fields) aren't opaque to Dart, since the underlying data isn't allowed to move. We can also call these unmanaged pointers. Most of the complexity comes from getting unmanaged pointers from managed pointers. Is it possible to pin managed pointers while unmanaged pointers are alive? |
In this design, all Pointer, Struct backed by Pointer, and Struct backed by external typed data:
Struct backed by internal typed data:
I agree it is not desirable that we cannot see the difference between relocatable and un-relocatable objects statically in the types. Let's see if we can come up with something to address that.
Great idea, unfortunately, the Dart GC does not have a feature to pin objects to prevent them from moving. |
My only nit is that The same thing can happen with structs backed by internal typed data, if you count object destruction as "getting moved by the GC". This goes back to the original "non-Pointer object that represents the allocation w/ finalization" idea. |
True, good catch! DesignI think in general I think it'd better to expose to users that it can be either Pointer or TypedData. Current design https://dart-review.googlesource.com/c/sdk/+/137581. We'll probably replace abstract class Struct extends NativeType {
final Pointer<Struct> _backingStore; // Can also be typed data later when we add structs by value.
final int _offsetInBytes;
/// Construct a reference to the [nullptr].
///
/// Use [StructPointer]'s `.ref` to gain references to native memory backed
/// structs.
Struct()
: _backingStore = nullptr,
_offsetInBytes = 0;
Struct._fromPointer(this._backingStore, this._offsetInBytes);
}
/// Extension on [Struct] specialized for it's subtypes.
extension StructAddressOf<T extends Struct> on T {
/// Returns the address backing the reference.
///
/// Deprecated, see https://github.com/dart-lang/sdk/issues/40667.
@deprecated
Pointer<T> get addressOf {
if (_offsetInBytes == 0) {
return _backingStore as Pointer<T>;
}
return Pointer<T>.fromAddress(_backingStore.address + _offsetInBytes);
}
} |
Now that we have nested structs, objects a subtype of `Struct` can be backed by either a `Pointer` or a `TypedData`. Having this accessor is misleading. Instead of passing a struct around (which could be backed by either), the `Pointer<T extends Struct>` should be passed around and `.ref` should be used everywhere when access to the backing pointer is required. Issue: #40667 Related issues: * Optimize .ref #38648 * Support tree shaking of structs #38721 Change-Id: I3c73423480b91c00639df886bf1d6ac2e444beab Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/177581 Reviewed-by: Martin Kustermann <[email protected]> Commit-Queue: Daco Harkes <[email protected]>
Instead, keep the pointers around in various places. Issue: #40667 Change-Id: I0d661dc74fa4d9745baa03da059f2233eb0d08d0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/181382 Reviewed-by: Aske Simon Christensen <[email protected]>
Removes invocations of `.addressOf` in samples. Removes the constructor from the Coordinate testing class which has become useless with no access to the underlying pointer. Issue: #40667 Change-Id: I1e55a4b159662654e1d14b2d51b0b43d734d5010 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/181405 Commit-Queue: Daco Harkes <[email protected]> Reviewed-by: Aske Simon Christensen <[email protected]>
Removes invocations of `.addressOf` in tests. Removes the constructor from the Coordinate testing class which has become useless with no access to the underlying pointer. Removes the bare variant of Coordinate because it is identical to the non-bare variant. Issue: #40667 Change-Id: Ie26fa3a9cac38e794e93f5fadfec2562a814c1ae Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/181403 Reviewed-by: Aske Simon Christensen <[email protected]> Commit-Queue: Daco Harkes <[email protected]>
This reverts commit 65fab23. Reason for revert: These can't be remove until the next dev roll of Flutter. See flutter/flutter#76129. Original change's description: > [vm/ffi] Remove deprecated `Struct.addressOf` getter > > This got deprecated in Dart 2.12 stable. Removing for the next release. > > Closes: #40667 > > Change-Id: Ifdbcf76178be6fca8603d478d629b6b6655b7123 > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/184463 > Reviewed-by: Clement Skau <[email protected]> > Commit-Queue: Daco Harkes <[email protected]> Change-Id: I94c22a206f3668ddb72d284afdc3b048d8b11aaa Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/185500 Commit-Queue: Zach Anderson <[email protected]> Commit-Queue: Daco Harkes <[email protected]> Reviewed-by: Daco Harkes <[email protected]>
CL was reverted so reopening the issue. |
Uh oh!
There was an error while loading. Please reload this page.
Update 2021-01-27: We have deprecated
.addressOf
for Dart 2.12 and will remove it in Dart 2.13.Structs do not expose whether they are backed by c memory (
Pointer
) or Dart memory (TypedData
). If interaction with c memory is required, the pointer to the struct should be kept around.=========
Currently we support doing
.addressOf
onStruct
s.sdk/sdk/lib/ffi/struct.dart
Lines 34 to 38 in a75ffc8
The address returned is a different
Pointer
object, possibly with a different address if an index was passed:sdk/sdk/lib/ffi/ffi.dart
Lines 607 to 612 in a75ffc8
This logic no longer works when adding finalizers, as finalizers depend on object identity.
We have multiple options here:
Pointer
without the byte offset, which would be the wrong address if an index was passed. This would be a breaking change.Pointer
, documenting that users cannot rely on finalizers when doing this. This would definitely be a footgun..addressOf
all together. This is also a breaking change. (There are 70 uses in our code base alone, excluding packages. It would require changing dart code to pass aroundPointer<MyStruct>
instead ofMyStruct
in many places.)Side note, in order for finalizer behavior to work correctly the
Struct
class needs to use the originalPointer
to access the fields:(Currently, there's no
_byteOffset
field, and the_addressOf
contains a derived pointer.)Related issues:
cc @mkustermann
The text was updated successfully, but these errors were encountered: