sdk
sdk copied to clipboard
[vm/ffi] Support `.address.cast()` in leaf calls
Many functions with buffers take a void*, for example https://en.cppreference.com/w/c/io/fread.
However, using a Uint8List and .address yields a Pointer<Uint8>.
It would be useful to be able to pass uint8list.address.cast(). That would prevent having to modify the FFIgen generated function signature.
Thanks for the suggestion @brianquinlan!
For anyone willing to contribute, the implementation is in:
https://github.com/dart-lang/sdk/blob/05d9e5bf37a68ec75f8ffc56a65a89e0e86b5a1d/pkg/vm/lib/modular/transformations/ffi/use_sites.dart#L1489-L1504
The implementation should find the cast() invocation and use its receiver instead (effectively ignoring the cast expression as a no-op).
Hey @dcharkes, so we have to change the data type of address from int to Pointer<Void>, so that address always yields a Pointer<Void ? But this would introduce expression such as address.cast().address.cast().address Wdy? am not sure whether am making sense , Please feel free to correct me
Hey @dcharkes, so we have to change the data type of
addressfrominttoPointer<Void>, so thataddressalways yields aPointer<Void? But this would introduce expression such asaddress.cast().address.cast().addressWdy? am not sure whether am making sense , Please feel free to correct me
Hi @codesculpture!
No, the .address here refers to the various extension methods on non-Pointers which return a Pointer such as:
- https://api.dart.dev/dev/3.5.0-202.0.dev/dart-ffi/Uint8ListAddress/address.html
Hi @dcharkes , i think i don't understand the problem
It would be useful to be able to pass uint8list.address.cast(). That would prevent having to modify the FFIgen generated function signature.
can you please elaborate above with an example :)
The following function in C
size_t fread( void *buffer, size_t size, size_t count,
FILE *stream );
yields the following bindings with package:ffigen:
@Native<Size Function(Pointer<Void>, Size, Size, Pointer<File>)
external int fread(Pointer<Void> buffer, int size, int count, Pointer<File> stream);
But if you want to use for buffer a someUint8List.address the type of that expression will be Pointer<Uint8> and not the required Pointer<Void>.
Sorry again @dcharkes , thanks for explaining the problem, i understood the problem, but what is the solution you are suggesting? Thanks
The following code should work:
@Native<Size Function(Pointer<Void>, Size, Size, Pointer<Void>)>()
external int fread(
Pointer<Void> buffer, int size, int count, Pointer<Void> stream);
void main() {
final buffer = Uint8List.fromList([1, 2, 3]);
fread(buffer.address.cast(), 3, 3, nullptr);
}
Currently it is rejected because .address is followed by .cast(). We should change the compiler such that it is allowed.
@dcharkes am trying to debug the https://github.com/dart-lang/sdk/blob/05d9e5bf37a68ec75f8ffc56a65a89e0e86b5a1d/pkg%2Fvm%2Flib%2Fmodular%2Ftransformations%2Fffi%2Fuse_sites.dart, but i cannot find a way to do so, i already asked a related question in dart discord server long ago, what would be ideal solution to debug the internal dart codes (now am adding print statements, that would require re building dart, for every changes)
If you're in VSCode, you can run the CFE like this in the debugger:
{
"name": "dart gen_kernel.dart",
"type": "dart",
"request": "launch",
"program": "pkg/vm/bin/gen_kernel.dart",
"args": [
"--platform=${workspaceFolder}/xcodebuild/ReleaseARM64/vm_platform_strong.dill",
"pkg/vm/testcases/transformations/ffi/address_of_struct_element.dart",
],
"toolArgs": [],
"enableAsserts": true,
"cwd": "${workspaceFolder}",
},
Hey @dcharkes , are we going to allow .address.cast() only on direct typed data (Uint8List) or we gonna allow expression such access members which are from struct and unions myStruct.myList.address.cast() ?
Also @dcharkes, one small doubt is that _replaceNativeCallParameterAndArgument replacing the address with the actual instance in the AST (internally), for ex: if i fed native(buffer.address) this would transformed into native(buffer) ?
Am i correct ?
Also @dcharkes, one small doubt is that
_replaceNativeCallParameterAndArgumentreplacing the address with the actual instance in the AST (internally), for ex: if i fednative(buffer.address)this would transformed intonative(buffer)? Am i correct ?
Yes, this is already what is happening:
https://github.com/dart-lang/sdk/blob/e9c7b2b905e0167cdc3402f086f3fa53872e8b04/pkg/vm/lib/modular/transformations/ffi/use_sites.dart#L1521-L1530
The TypedData is converted to the address that points into into the the typed data in the FFI call in the VM:
https://github.com/dart-lang/sdk/blob/e9c7b2b905e0167cdc3402f086f3fa53872e8b04/runtime/vm/compiler/backend/il.cc#L7589-L7599
Hey @dcharkes , are we going to allow
.address.cast()only on direct typed data (Uint8List) or we gonna allow expression such access members which are from struct and unionsmyStruct.myList.address.cast()?
This should be allowable as well. ๐
https://github.com/dart-lang/sdk/pull/56357 @dcharkes , consider this as a WIP and please review if this is correct approach.
And also i have no idea about whether to change fileOffset while making the recursive call for subexpression of cast.
does fileOffset is line number or character's position in the file?
Yes, that's the right approach!
fileOffset can probably just stay the same.
@dcharkes , where can i write test for this?
@dcharkes , where can i write test for this?
You can take a look at the original PR: https://dart-review.googlesource.com/c/sdk/+/360882
- pkg/vm/testcases/transformations/ffi/* for a test that checks that the transform does what you expect.
- tests/ffi/address_of_*.dart for tests that run the Dart code
- tests/ffi/static_checks/vmspecific_static_checks_address_of_test.dart for tests that check that the analyzer and cfe return errors on the expected situations (doing a cast to the wrong type argument for example).
You'll probably also need to change the analyzer to allow the cast:
- pkg/analyzer/lib/src/generated/ffi_verifier.dart
Hey @dcharkes , can you explain how "--platform" argument is making possible debugging, the reason why am asking is, i need to debug the ffi_verifier.dart but i am not sure how to do it. Also if possible please elaborate how i can debug the dart files generally, if there is any references please send. It would be super helpful.
Thanks
If you're using vscode:
{
"name": "dart analyzer.dart",
"type": "dart",
"request": "launch",
"program": "pkg/analyzer_cli/bin/analyzer.dart",
"args": [
"tests/ffi/static_checks/vmspecific_static_checks_array_test.dart",
],
"toolArgs": [],
"enableAsserts": true,
"cwd": "${workspaceFolder}",
},
For the transformation tests:
{
"name": "dart pkg/vm/test/transformations/ffi_test.dart",
"type": "dart",
"request": "launch",
"program": "pkg/vm/test/transformations/ffi_test.dart",
"args": [
"compound_copies",
],
"toolArgs": [
"-DupdateExpectations=true",
],
"enableAsserts": true,
"cwd": "${workspaceFolder}",
},
https://discord.com/channels/608014603317936148/608022273152122881/1268987825081028638
Please help if u can @dcharkes
Thanks
Edit: mraleph helped me.
void main() {
final myStruct = Struct.create<MyStruct>();
myNative(
myStruct.a.address,
myStruct.b.address, // It expected to return Pointer<Int>, but it returning Pointer<Never>
);
}
@Native<
Void Function(
Pointer<Int8>,
Pointer<Void>,
)>(isLeaf: true)
external void myNative(
Pointer<Int8> pointer,
Pointer<Void> pointer2,
);
final class MyStruct extends Struct {
@Int8()
external int a;
@Int8()
external int b;
}
Does address of any members from Struct is always Pointer<Never> regardless of their type ? @dcharkes
I expected analyzer throw error for above program, but it dint thrown any error. That made me to ask this question
Seems members of Union and Struct always yields a Pointer<Never>
@dcharkes , Here tests/ffi does this(allowing cast on address) change require to generate tests from generators or can be written manually ?
Also is the tests in tests/ffi/static_checks are generated or manually ? It seems generated but i cannot find generators for those @dcharkes
void main() { final myStruct = Struct.create<MyStruct>(); myNative( myStruct.a.address, myStruct.b.address, // It expected to return Pointer<Int>, but it returning Pointer<Never> ); } @Native< Void Function( Pointer<Int8>, Pointer<Void>, )>(isLeaf: true) external void myNative( Pointer<Int8> pointer, Pointer<Void> pointer2, ); final class MyStruct extends Struct { @Int8() external int a; @Int8() external int b; }Does address of any members from
Structis alwaysPointer<Never>regardless of their type ? @dcharkesI expected analyzer throw error for above program, but it dint thrown any error. That made me to ask this question
It's a Pointer<Never> at runtime (dynamic type), which is a valid subtype of the static type: Pointer<Int8>. (Never is a valid subtype of everything.)
The .cast<T>() returns an object with with a static type Pointer<T> but at runtime also Pointer<Never>.
@dcharkes , Here
tests/ffidoes this(allowing cast on address) change require to generate tests from generators or can be written manually ?
You can probably write the handful of tests manually. The test generators are set up to produce correct types. ๐ Just make sure to cover some obvious different cases:
- A cast of a struct field
- A cast of a typed data
- A cast of a Pointer
Also is the tests in
tests/ffi/static_checksare generated or manually ? It seems generated but i cannot find generators for those @dcharkes
No, those are written by hand. The old fashioned way!
In general, I only write a test generator if I have many tests in exactly the same structure but with one thing varying. (E.g. the FFI call tests all pass complex parameters and sum all the individual ints and then return it. And the one thing varying is what the parameter list is.)
But the tests in tests/ffi/static_checks contains the error annotations in code as comments, i would be surprised if those errors were written manually. And does those tests actually evaluated against those errors which are in form of comments ? @dcharkes
But the tests in
tests/ffi/static_checkscontains the error annotations in code as comments, i would be surprised if those errors were written manually. And does those tests actually evaluated against those errors which are in form of comments ? @dcharkes
You run the test, and then it complains it expected a certain error string, and you paste the string in. ๐
To run the test for the cfe and analyzer, you can use the following (replace the test name).
$ tools/build.py -mrelease runtime create_platform_sdk && tools/test.py -mrelease -cfasta tests/ffi/static_checks/vmspecific_static_checks_array_test.dart
$ tools/build.py -mrelease runtime create_platform_sdk && tools/test.py -mrelease -cdart2analyzer tests/ffi/static_checks/vmspecific_static_checks_array_test.dart
You run the test, and then it complains it expected a certain error string, and you paste the string in. ๐
I thought this and imagined this could only be possible in my dreams๐
Fine then, will copy/paste.
Thanks @dcharkes for all of the help.
I had a comment (which is a question) here
You run the test, and then it complains it expected a certain error string, and you paste the string in. ๐
I thought this and imagined this could only be possible in my dreams๐
Fine then, will copy/paste.
Thanks @dcharkes for all of the help.
Thanks for contributing! โค๏ธ
I had a comment (which is a question) here
That was next in my inbox, I've left a reply!
@dcharkes, i asked a question https://dart-review.googlesource.com/c/sdk/+/378221 here, pls help.