sdk icon indicating copy to clipboard operation
sdk copied to clipboard

[vm/ffi] Support `.address.cast()` in leaf calls

Open dcharkes opened this issue 1 year ago โ€ข 4 comments
trafficstars

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).

dcharkes avatar Jun 10 '24 17:06 dcharkes

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

codesculpture avatar Jun 22 '24 20:06 codesculpture

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

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

dcharkes avatar Jun 24 '24 15:06 dcharkes

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 :)

codesculpture avatar Jun 25 '24 20:06 codesculpture

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>.

dcharkes avatar Jun 26 '24 06:06 dcharkes

Sorry again @dcharkes , thanks for explaining the problem, i understood the problem, but what is the solution you are suggesting? Thanks

codesculpture avatar Jul 06 '24 18:07 codesculpture

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 avatar Jul 08 '24 10:07 dcharkes

@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)

codesculpture avatar Jul 17 '24 08:07 codesculpture

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}",
			},

dcharkes avatar Jul 17 '24 10:07 dcharkes

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() ?

codesculpture avatar Jul 29 '24 18:07 codesculpture

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 ?

codesculpture avatar Jul 29 '24 18:07 codesculpture

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 ?

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

dcharkes avatar Jul 30 '24 07:07 dcharkes

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() ?

This should be allowable as well. ๐Ÿ‘

dcharkes avatar Jul 30 '24 07:07 dcharkes

https://github.com/dart-lang/sdk/pull/56357 @dcharkes , consider this as a WIP and please review if this is correct approach.

codesculpture avatar Jul 31 '24 17:07 codesculpture

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?

codesculpture avatar Jul 31 '24 17:07 codesculpture

Yes, that's the right approach!

fileOffset can probably just stay the same.

dcharkes avatar Aug 02 '24 07:08 dcharkes

@dcharkes , where can i write test for this?

codesculpture avatar Aug 02 '24 07:08 codesculpture

@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

dcharkes avatar Aug 02 '24 08:08 dcharkes

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

codesculpture avatar Aug 02 '24 15:08 codesculpture

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}",
			},

dcharkes avatar Aug 02 '24 16:08 dcharkes

https://discord.com/channels/608014603317936148/608022273152122881/1268987825081028638

Please help if u can @dcharkes

Thanks

Edit: mraleph helped me.

codesculpture avatar Aug 02 '24 17:08 codesculpture

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

codesculpture avatar Aug 03 '24 17:08 codesculpture

Seems members of Union and Struct always yields a Pointer<Never>

codesculpture avatar Aug 03 '24 17:08 codesculpture

@dcharkes , Here tests/ffi does this(allowing cast on address) change require to generate tests from generators or can be written manually ?

codesculpture avatar Aug 04 '24 13:08 codesculpture

Also is the tests in tests/ffi/static_checks are generated or manually ? It seems generated but i cannot find generators for those @dcharkes

codesculpture avatar Aug 04 '24 13:08 codesculpture

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

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/ffi does 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_checks are 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.)

dcharkes avatar Aug 05 '24 07:08 dcharkes

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

codesculpture avatar Aug 05 '24 07:08 codesculpture

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

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

dcharkes avatar Aug 05 '24 07:08 dcharkes

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

codesculpture avatar Aug 05 '24 07:08 codesculpture

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 avatar Aug 05 '24 07:08 dcharkes

@dcharkes, i asked a question https://dart-review.googlesource.com/c/sdk/+/378221 here, pls help.

codesculpture avatar Aug 08 '24 04:08 codesculpture