[ffi] Add .ref= setter for pointers of structs or unions

Adds the `ref` setter to the `StructPointer` and `UnionPointer`
extensions, copying a compound structure into native memory.
The FFI use-site transformation transforms invocations of this setter
into an appropriate memcopy call.

Closes https://github.com/dart-lang/sdk/issues/44768

TEST=tests/ffi(_2)/extension_methods_test.dart

Change-Id: I3ef06ad08b8e71e39b05d662e8082fc5d0ad876d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/227542
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
Simon Binder 2022-01-12 20:48:28 +00:00 committed by Commit Bot
parent 981c16ea52
commit 0a3d896274
9 changed files with 320 additions and 24 deletions

View file

@ -7,6 +7,11 @@
- Add `Finalizer` and `WeakReference` which can potentially detect when
objects are "garbage collected".
#### `dart:ffi`
- Add `ref=` and `[]=` methods to the `StructPointer` and `UnionPointer`
extensions. They copy a compound instance into a native memory region.
#### `dart:indexed_db`
- `IdbFactory.supportsDatabaseNames` has been deprecated. It will always return

View file

@ -194,10 +194,14 @@ class FfiTransformer extends Transformer {
final Procedure offsetByMethod;
final Procedure elementAtMethod;
final Procedure addressGetter;
final Procedure structPointerRef;
final Procedure structPointerElemAt;
final Procedure unionPointerRef;
final Procedure unionPointerElemAt;
final Procedure structPointerGetRef;
final Procedure structPointerSetRef;
final Procedure structPointerGetElemAt;
final Procedure structPointerSetElemAt;
final Procedure unionPointerGetRef;
final Procedure unionPointerSetRef;
final Procedure unionPointerGetElemAt;
final Procedure unionPointerSetElemAt;
final Procedure structArrayElemAt;
final Procedure unionArrayElemAt;
final Procedure arrayArrayElemAt;
@ -371,14 +375,22 @@ class FfiTransformer extends Transformer {
arrayConstructor = index.getConstructor('dart:ffi', 'Array', '_'),
fromAddressInternal =
index.getTopLevelProcedure('dart:ffi', '_fromAddress'),
structPointerRef =
structPointerGetRef =
index.getProcedure('dart:ffi', 'StructPointer', 'get:ref'),
structPointerElemAt =
structPointerSetRef =
index.getProcedure('dart:ffi', 'StructPointer', 'set:ref'),
structPointerGetElemAt =
index.getProcedure('dart:ffi', 'StructPointer', '[]'),
unionPointerRef =
structPointerSetElemAt =
index.getProcedure('dart:ffi', 'StructPointer', '[]='),
unionPointerGetRef =
index.getProcedure('dart:ffi', 'UnionPointer', 'get:ref'),
unionPointerElemAt =
unionPointerSetRef =
index.getProcedure('dart:ffi', 'UnionPointer', 'set:ref'),
unionPointerGetElemAt =
index.getProcedure('dart:ffi', 'UnionPointer', '[]'),
unionPointerSetElemAt =
index.getProcedure('dart:ffi', 'UnionPointer', '[]='),
structArrayElemAt = index.getProcedure('dart:ffi', 'StructArray', '[]'),
unionArrayElemAt = index.getProcedure('dart:ffi', 'UnionArray', '[]'),
arrayArrayElemAt = index.getProcedure('dart:ffi', 'ArrayArray', '[]'),

View file

@ -169,15 +169,24 @@ class _FfiUseSiteTransformer extends FfiTransformer {
fileOffset: node.fileOffset,
);
}
if (target == structPointerRef ||
target == structPointerElemAt ||
target == unionPointerRef ||
target == unionPointerElemAt) {
if (target == structPointerGetRef ||
target == structPointerGetElemAt ||
target == unionPointerGetRef ||
target == unionPointerGetElemAt) {
final DartType nativeType = node.arguments.types[0];
_ensureNativeTypeValid(nativeType, node, allowCompounds: true);
return _replaceRef(node);
return _replaceGetRef(node);
} else if (target == structPointerSetRef ||
target == structPointerSetElemAt ||
target == unionPointerSetRef ||
target == unionPointerSetElemAt) {
final DartType nativeType = node.arguments.types[0];
_ensureNativeTypeValid(nativeType, node, allowCompounds: true);
return _replaceSetRef(node);
} else if (target == structArrayElemAt || target == unionArrayElemAt) {
final DartType nativeType = node.arguments.types[0];
@ -533,7 +542,7 @@ class _FfiUseSiteTransformer extends FfiTransformer {
return StaticGet(field);
}
Expression _replaceRef(StaticInvocation node) {
Expression _replaceGetRef(StaticInvocation node) {
final dartType = node.arguments.types[0];
final clazz = (dartType as InterfaceType).classNode;
final constructor = clazz.constructors
@ -555,6 +564,38 @@ class _FfiUseSiteTransformer extends FfiTransformer {
return ConstructorInvocation(constructor, Arguments([pointer]));
}
/// Replaces a `.ref=` or `[]=` on a compound pointer extension with a memcopy
/// call.
Expression _replaceSetRef(StaticInvocation node) {
final target = node.arguments.positional[0]; // Receiver of extension
final Expression source, targetOffset;
if (node.arguments.positional.length == 3) {
// []= call, args are (receiver, index, source)
source = getCompoundTypedDataBaseField(
node.arguments.positional[2], node.fileOffset);
targetOffset = multiply(node.arguments.positional[1],
_inlineSizeOf(node.arguments.types[0] as InterfaceType)!);
} else {
// .ref= call, args are (receiver, source)
source = getCompoundTypedDataBaseField(
node.arguments.positional[1], node.fileOffset);
targetOffset = ConstantExpression(IntConstant(0));
}
return StaticInvocation(
memCopy,
Arguments([
target,
targetOffset,
source,
ConstantExpression(IntConstant(0)),
_inlineSizeOf(node.arguments.types[0] as InterfaceType)!,
]),
);
}
Expression _replaceRefArray(StaticInvocation node) {
final dartType = node.arguments.types[0];
final clazz = (dartType as InterfaceType).classNode;

View file

@ -0,0 +1,27 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class Coordinate extends Struct {
@Int64()
external int x;
@Int64()
external int y;
void copyInto(Pointer<Coordinate> ptr) {
ptr.ref = this;
}
}
class SomeUnion extends Union {
external Coordinate coordinate;
@Int64()
external int id;
void copyIntoAtIndex(Pointer<SomeUnion> ptr, int index) {
ptr[index] = this;
}
}
void main() {}

View file

@ -0,0 +1,87 @@
library #lib /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
import "dart:ffi" as ffi;
import "dart:typed_data" as typ;
import "dart:_internal" as _in;
import "dart:ffi";
import "package:ffi/ffi.dart";
@#C6
class Coordinate extends ffi::Struct {
synthetic constructor •() → self::Coordinate
: super ffi::Struct::•()
;
constructor #fromTypedDataBase(core::Object #typedDataBase) → self::Coordinate
: super ffi::Struct::_fromTypedDataBase(#typedDataBase)
;
@#C7
get x() → core::int
return ffi::_loadInt64(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
@#C7
set x(core::int #externalFieldValue) → void
return ffi::_storeInt64(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
@#C7
get y() → core::int
return ffi::_loadInt64(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C11.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
@#C7
set y(core::int #externalFieldValue) → void
return ffi::_storeInt64(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C11.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
method copyInto(ffi::Pointer<self::Coordinate> ptr) → void {
ffi::_memCopy(ptr, #C8, this.{ffi::_Compound::_typedDataBase}{core::Object}, #C8, self::Coordinate::#sizeOf);
}
@#C13
static get #sizeOf() → core::int*
return #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
}
@#C19
class SomeUnion extends ffi::Union {
synthetic constructor •() → self::SomeUnion
: super ffi::Union::•()
;
constructor #fromTypedDataBase(core::Object #typedDataBase) → self::SomeUnion
: super ffi::Union::_fromTypedDataBase(#typedDataBase)
;
get coordinate() → self::Coordinate
return new self::Coordinate::#fromTypedDataBase( block {
core::Object #typedDataBase = this.{ffi::_Compound::_typedDataBase}{core::Object};
core::int #offset = #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
} =>#typedDataBase is ffi::Pointer<dynamic> ?{core::Object} ffi::_fromAddress<self::Coordinate>(#typedDataBase.{ffi::Pointer::address}{core::int}.{core::num::+}(#offset){(core::num) → core::num}) : let typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in #typedData.{typ::TypedData::buffer}{typ::ByteBuffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}{core::int}.{core::num::+}(#offset){(core::num) → core::num}, #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}){([core::int, core::int?]) → typ::Uint8List});
set coordinate(self::Coordinate #externalFieldValue) → void
return ffi::_memCopy(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue.{ffi::_Compound::_typedDataBase}{core::Object}, #C8, #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
@#C7
get id() → core::int
return ffi::_loadInt64(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
@#C7
set id(core::int #externalFieldValue) → void
return ffi::_storeInt64(this.{ffi::_Compound::_typedDataBase}{core::Object}, #C9.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
method copyIntoAtIndex(ffi::Pointer<self::SomeUnion> ptr, core::int index) → void {
ffi::_memCopy(ptr, index.{core::num::*}(self::SomeUnion::#sizeOf){(core::num) → core::num}, this.{ffi::_Compound::_typedDataBase}{core::Object}, #C8, self::SomeUnion::#sizeOf);
}
@#C13
static get #sizeOf() → core::int*
return #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
}
static method main() → void {}
constants {
#C1 = "vm:ffi:struct-fields"
#C2 = TypeLiteralConstant(ffi::Int64)
#C3 = <core::Type>[#C2, #C2]
#C4 = null
#C5 = ffi::_FfiStructLayout {fieldTypes:#C3, packing:#C4}
#C6 = core::pragma {name:#C1, options:#C5}
#C7 = ffi::Int64 {}
#C8 = 0
#C9 = <core::int*>[#C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8, #C8]
#C10 = 8
#C11 = <core::int*>[#C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10, #C10]
#C12 = "vm:prefer-inline"
#C13 = core::pragma {name:#C12, options:#C4}
#C14 = 16
#C15 = <core::int*>[#C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14, #C14]
#C16 = TypeLiteralConstant(self::Coordinate)
#C17 = <core::Type>[#C16, #C2]
#C18 = ffi::_FfiStructLayout {fieldTypes:#C17, packing:#C4}
#C19 = core::pragma {name:#C1, options:#C18}
}

View file

@ -923,9 +923,17 @@ extension StructPointer<T extends Struct> on Pointer<T> {
T get ref =>
throw "UNREACHABLE: This case should have been rewritten in the CFE.";
@patch
set ref(T value) =>
throw "UNREACHABLE: This case should have been rewritten in the CFE";
@patch
T operator [](int index) =>
throw "UNREACHABLE: This case should have been rewritten in the CFE.";
@patch
void operator []=(int index, T value) =>
throw "UNREACHABLE: This case should have been rewritten in the CFE.";
}
extension UnionPointer<T extends Union> on Pointer<T> {
@ -933,9 +941,17 @@ extension UnionPointer<T extends Union> on Pointer<T> {
T get ref =>
throw "UNREACHABLE: This case should have been rewritten in the CFE.";
@patch
set ref(T value) =>
throw "UNREACHABLE: This case should have been rewritten in the CFE";
@patch
T operator [](int index) =>
throw "UNREACHABLE: This case should have been rewritten in the CFE.";
@patch
void operator []=(int index, T value) =>
throw "UNREACHABLE: This case should have been rewritten in the CFE.";
}
extension AbiSpecificIntegerPointer<T extends AbiSpecificInteger>

View file

@ -648,14 +648,20 @@ extension PointerPointer<T extends NativeType> on Pointer<Pointer<T>> {
/// Extension on [Pointer] specialized for the type argument [Struct].
extension StructPointer<T extends Struct> on Pointer<T> {
/// Creates a reference to access the fields of this struct backed by native
/// memory at [address].
/// A Dart view of the struct referenced by this pointer.
///
/// Reading [ref] creates a reference accessing the fields of this struct
/// backed by native memory at [address].
/// The [address] must be aligned according to the struct alignment rules of
/// the platform.
///
/// This extension method must be invoked with a compile-time constant [T].
/// Assigning to [ref] copies contents of the struct into the native memory
/// starting at [address].
///
/// This extension method must be invoked on a receiver of type `Pointer<T>`
/// where `T` is a compile-time constant type.
external T get ref;
external set ref(T value);
/// Creates a reference to access the fields of this struct backed by native
/// memory at `address + sizeOf<T>() * index`.
@ -663,20 +669,34 @@ extension StructPointer<T extends Struct> on Pointer<T> {
/// The [address] must be aligned according to the struct alignment rules of
/// the platform.
///
/// This extension method must be invoked with a compile-time constant [T].
/// This extension method must be invoked on a receiver of type `Pointer<T>`
/// where `T` is a compile-time constant type.
external T operator [](int index);
/// Copies the [value] struct into native memory, starting at
/// `address * sizeOf<T>() * index`.
///
/// This extension method must be invoked on a receiver of type `Pointer<T>`
/// where `T` is a compile-time constant type.
external void operator []=(int index, T value);
}
/// Extension on [Pointer] specialized for the type argument [Union].
extension UnionPointer<T extends Union> on Pointer<T> {
/// Creates a reference to access the fields of this union backed by native
/// memory at [address].
/// A Dart view of the union referenced by this pointer.
///
/// Reading [ref] creates a reference accessing the fields of this union
/// backed by native memory at [address].
/// The [address] must be aligned according to the union alignment rules of
/// the platform.
///
/// This extension method must be invoked with a compile-time constant [T].
/// Assigning to [ref] copies contents of the union into the native memory
/// starting at [address].
///
/// This extension method must be invoked on a receiver of type `Pointer<T>`
/// where `T` is a compile-time constant type.
external T get ref;
external set ref(T value);
/// Creates a reference to access the fields of this union backed by native
/// memory at `address + sizeOf<T>() * index`.
@ -684,8 +704,16 @@ extension UnionPointer<T extends Union> on Pointer<T> {
/// The [address] must be aligned according to the union alignment rules of
/// the platform.
///
/// This extension method must be invoked with a compile-time constant [T].
/// This extension method must be invoked on a receiver of type `Pointer<T>`
/// where `T` is a compile-time constant type.
external T operator [](int index);
/// Copies the [value] union into native memory, starting at
/// `address * sizeOf<T>() * index`.
///
/// This extension method must be invoked on a receiver of type `Pointer<T>`
/// where `T` is a compile-time constant type.
external void operator []=(int index, T value);
}
/// Extension on [Pointer] specialized for the type argument
@ -713,13 +741,15 @@ extension PointerArray<T extends NativeType> on Array<Pointer<T>> {
/// Bounds checking indexing methods on [Array]s of [Struct].
extension StructArray<T extends Struct> on Array<T> {
/// This extension method must be invoked with a compile-time constant [T].
/// This extension method must be invoked on a receiver of type `Pointer<T>`
/// where `T` is a compile-time constant type.
external T operator [](int index);
}
/// Bounds checking indexing methods on [Array]s of [Union].
extension UnionArray<T extends Union> on Array<T> {
/// This extension method must be invoked with a compile-time constant [T].
/// This extension method must be invoked on a receiver of type `Pointer<T>`
/// where `T` is a compile-time constant type.
external T operator [](int index);
}

View file

@ -11,6 +11,7 @@ main(List<String> arguments) {
for (int i = 0; i < 100; i++) {
testStoreLoad();
testReifiedGeneric();
testCompoundLoadAndStore();
}
}
@ -50,6 +51,14 @@ testStoreLoad() {
foo.a = 1;
Expect.equals(1, foo.a);
calloc.free(p3);
final p4 = calloc<Foo>(2);
Foo src = p4[1];
src.a = 2;
p4.ref = src;
Foo dst = p4.ref;
Expect.equals(2, dst.a);
calloc.free(p4);
}
testReifiedGeneric() {
@ -59,7 +68,37 @@ testReifiedGeneric() {
calloc.free(p);
}
testCompoundLoadAndStore() {
final foos = calloc<Foo>(10);
final reference = foos.ref..a = 10;
for (var i = 1; i < 9; i++) {
foos[i] = reference;
Expect.isTrue(foos[i].a == 10);
foos.elementAt(i).ref = reference;
Expect.isTrue(foos.elementAt(i).ref.a == 10);
}
final bars = calloc<Bar>(10);
bars[0].foo = reference;
for (var i = 1; i < 9; i++) {
bars[i] = bars[0];
Expect.isTrue(bars.elementAt(i).ref.foo.a == 10);
}
calloc.free(foos);
calloc.free(bars);
}
class Foo extends Struct {
@Int8()
external int a;
}
class Bar extends Union {
external Foo foo;
@Int32()
external int baz;
}

View file

@ -13,6 +13,7 @@ main(List<String> arguments) {
for (int i = 0; i < 100; i++) {
testStoreLoad();
testReifiedGeneric();
testCompoundLoadAndStore();
}
}
@ -52,6 +53,14 @@ testStoreLoad() {
foo.a = 1;
Expect.equals(1, foo.a);
calloc.free(p3);
final p4 = calloc<Foo>(2);
Foo src = p4[1];
src.a = 2;
p4.ref = src;
Foo dst = p4.ref;
Expect.equals(2, dst.a);
calloc.free(p4);
}
testReifiedGeneric() {
@ -61,7 +70,37 @@ testReifiedGeneric() {
calloc.free(p);
}
testCompoundLoadAndStore() {
final foos = calloc<Foo>(10);
final reference = foos.ref..a = 10;
for (var i = 1; i < 9; i++) {
foos[i] = reference;
Expect.isTrue(foos[i].a == 10);
foos.elementAt(i).ref = reference;
Expect.isTrue(foos.elementAt(i).ref.a == 10);
}
final bars = calloc<Bar>(10);
bars[0].foo = reference;
for (var i = 1; i < 9; i++) {
bars[i] = bars[0];
Expect.isTrue(bars.elementAt(i).ref.foo.a == 10);
}
calloc.free(foos);
calloc.free(bars);
}
class Foo extends Struct {
@Int8()
int a;
}
class Bar extends Union {
Foo foo;
@Int32()
int baz;
}