-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
[vm/ffi] variance of functions in load/store #37385
Comments
Can we just remove the Dart signature from |
I think there are multiple questions to answer here:
RE: 1Yes, it does. We need to add regression tests. RE: 2
typedef Int64PointerParamOp = Void Function(Pointer<Int64>);
typedef NaTyPointerParamOp = Void Function(Pointer<NativeType>);
final fp = ffiTestFunctions.lookup<NativeFunction<NaTyPointerParamOp>>(paramOpName);
final f = fp.asFunction<Int64PointerParamOp>();
For For RE: 3The Dart type system allows one to pass subtypes of the declared types as argument or return value at runtime. We invent the return value ourselves on return, so that should be fine. Edit: this also seems to work. |
|
Re. 2: Following the principle of being consistent with the rest of the language, argument types should be covariant and return types should be contravariant. I don't think the question should be about whether we should "support" something -- we should fit in with the language, so rules which apply to other language constructs need to apply to the FFI as well. |
If we do typedef Int64PointerReturnOp = Pointer<Int64> Function();
typedef NaTyPointerReturnOp = Pointer<NativeType> Function();
final fp = ffiTestFunctions.lookup<NativeFunction<Int64PointerReturnOp>>(name);
final f = fp.asFunction<Int64PointerReturnOp>(); // DartRepresentationOf conversion applies.
final NaTyPointerReturnOp f2 = f1; // Normal Dart assignability rules apply. There is one case in which this would be inconvenient though: final fp = ffiTestFunctions.lookup<NativeFunction<Int64PointerReturnOp>>(name);
final NaTyPointerReturnOp f2 = fp.asFunction<Int64PointerReturnOp>(); If you leave out the type argument on (If we decide to make the return type of If we don't go for invariant, the normal assignability relation applies:
(Note the inversion of the assignment direction between the types.) |
It sounds like you make a good case for why we should remove this arbitrary invariance constraint. Is there any point above which should be interpreted otherwise? |
Actually, we don't always use invariance when treating function types. Here's an example where the current treatment is incorrect. This program is accepted: import 'dart:ffi';
main() {
Pointer<NativeFunction<Void Function(Pointer<NativeType>)>> fn = fromAddress(0);
Pointer<Pointer<NativeFunction<Void Function(Pointer<Double>)>>> ptr = allocate();
ptr.store(fn);
} |
I meant for It looks like that the example that you wrote should actually be accepted. It is analogous with: final void Function(Pointer<NativeType>) fn; // = ...
final List<void Function(Pointer<Double>)> container = [];
container.add(fn); // This is fine.
final fn2 = container.first; // Static type `void Function(Pointer<Double>)`.
final arg = Pointer<Double> p = allocate();
fn2(p); // This is fine. But we should reject the following cases: Pointer<NativeFunction<Void Function(Pointer<Double>)>> fn = fromAddress(0);
Pointer<Pointer<NativeFunction<Void Function(Pointer<NativeType>)>>> ptr = allocate();
ptr.store(fn); Pointer<NativeFunction<Pointer<NativeType> Function()>> fn = fromAddress(0);
Pointer<Pointer<NativeFunction<Pointer<Double> Function()>>> ptr = allocate();
ptr.store(fn); |
RE 2 & 3: I started writing some tests for these, and I realized we have a lot of cases:
For the first two groups (parameters subtyping in calls from Dart to C): Pointer<NativeFunction<Void Function(Pointer<T>)>> fp;
void Function(Pointer<S>) f = fp.asFunction<S>();
Pointer<Q> arg;
f(arg);
This gives us 6 cases for We'll have a similar amount of cases for return values, for parameters in callbacks, and return values (and error return values) in callbacks. |
Maybe we should disallow We would need to constrain trampoline return values (in (edit: I remember that's why I originally disallowed |
Implicit downcasts only apply to values -- not to type parameters, which is the subject of this issue. I don't think we should be re-implementing the type system here. How can we remove the Dart signature and only use the native one? |
Incorrect. List<List<int>> n = [
[1, 2, 3]
];
List<List<Object>> n2 = n;
n2.add([3.4]); Unhandled exception:
type 'List<Object>' is not a subtype of type 'List<int>' of 'value'
#0 List.add (dart:core-patch/growable_array.dart)
...
That would make
I agree, but how? :) |
That is still an example of a value-level downcast -- the parameter to Here's a similar example with function types instead: int foo(int x) => 0;
main() {
int Function(int) fn0 = foo;
fn0(3); // No check
dynamic fn1 = foo;
fn1("abc"); // Check fails
} My point is just that a statically-typed closure call-site does not need to perform any dynamic checks. Therefore, one could write their FFI bindings like this: DynamicLibrary lib = null; // ...
int Function(int) thing = lib.lookupFunction<Int64 Function(Int64)>("thing");
main() {
thing(10); // No checks performed (uses "unchecked" entry-point from MEP).
} I don't understand the problems you're imagining here. There are no subclasses of |
As discussed offline, we will lock out all dynamic invocations of |
Nitpick: What you see here is not an implicit downcast, it's a covariant parameter check. An implicit down-cast happens when the static type of an expression is a proper supertype of the static context type it appears in. We then do an implicit down-cast to the context type. This test can then fail at run-time. A covariant parameter check occurs when a method parameter is covariant, either because it's declared using Completely different, same result :) With NNBD, we will disallow implicit downcasts - we will no longer insert the cast, but just make the code a compile-time error. We will not change covariant parameter checks. |
The scope of this issue is fixing the variance of function types in load and store. Issue # 4 (asFunction and fromFunction API) moves to its own issue: #37464 |
When we solve #35902, this issue will be a non issue. |
This bug can be easily fixed on its own, or else will be addressed in #37464. |
This reverts commit 3712ed2. Reason for revert: Breaks Arm32 precompiled. Issue: #38737 Original change's description: > [vm/ffi] Optimize Pointer operations for statically known types > > This CL optimizes Pointer operations in hot loops for Pointer<NativeInteger/NativeDouble/Pointer> (not for structs). > > Design: go/dart-ffi-pointers-il > > It provides roughly a 100x speedup for the FfiMemory benchmark. The next 5x speedup is to get rid of allocations due to `load` and `store` not being inlined. > > FFI API is changed to enable optimizations: > > * Disable dynamic invocations of Pointer.load / Pointer.store. > * Disallow implicit downcast of argument passed to Pointer.store. > * Stop zeroing out Pointer.address on Pointer.free(). > > Issue: #38172 > > Related issues: > Closes: #35902 (Disallowing dynamic invocations of Pointer ops.) > Closes: #37385 (Function variance checking.) > > Change-Id: I96058d8b5b49052eb6999f084372e6f08b4f6f17 > Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,app-kernel-linux-debug-x64-try,vm-kernel-linux-debug-ia32-try,vm-dartkb-linux-debug-simarm64-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-dartkb-linux-debug-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-dartkb-linux-release-x64-abi-try,vm-kernel-precomp-android-release-arm64-try > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/117547 > Commit-Queue: Daco Harkes <dacoharkes@google.com> > Reviewed-by: Martin Kustermann <kustermann@google.com> TBR=kustermann@google.com,sjindel@google.com,dacoharkes@google.com Change-Id: I3b7923ace45beaa9f99119e9ea20c1e52b429ad8 No-Presubmit: true No-Tree-Checks: true No-Try: true Issue: #38172 Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try, vm-ffi-android-debug-arm64-try, app-kernel-linux-debug-x64-try, vm-kernel-linux-debug-ia32-try, vm-dartkb-linux-debug-simarm64-try, vm-kernel-win-debug-x64-try, vm-kernel-win-debug-ia32-try, vm-dartkb-linux-debug-x64-try, vm-kernel-precomp-linux-debug-x64-try, vm-dartkb-linux-release-x64-abi-try, vm-kernel-precomp-android-release-arm64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/120582 Reviewed-by: Daco Harkes <dacoharkes@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com>
Original CL in patchset 1. Fix for simdbc in patchset 4. Fix for arm32 precompiled in: https://dart-review.googlesource.com/c/sdk/+/120660/ This CL optimizes Pointer operations in hot loops for Pointer<NativeInteger/NativeDouble/Pointer> (not for structs). Design: go/dart-ffi-pointers-il It provides roughly a 100x speedup for the FfiMemory benchmark. The next 5x speedup is to get rid of allocations due to `load` and `store` not being inlined. FFI API is changed to enable optimizations: * Disable dynamic invocations of Pointer.load / Pointer.store. * Disallow implicit downcast of argument passed to Pointer.store. * Stop zeroing out Pointer.address on Pointer.free(). Issue: #38172 Related issues: Closes: #35902 (Disallowing dynamic invocations of Pointer ops.) Closes: #37385 (Function variance checking) Change-Id: I3921a595fd05026d6ca565ace496771d7c1d877b Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,app-kernel-linux-debug-x64-try,vm-kernel-linux-debug-ia32-try,vm-dartkb-linux-debug-simarm64-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-dartkb-linux-debug-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-dartkb-linux-release-x64-abi-try,vm-kernel-precomp-android-release-arm64-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-linux-release-simarm-try,vm-kernel-linux-release-simarm64-try,vm-kernel-mac-debug-simdbc64-try,vm-kernel-precomp-android-release-arm_x64-try,vm-kernel-reload-mac-release-simdbc64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-precomp-mac-release-simarm_x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/120661 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com>
Issue: #37385 Change-Id: I0a0704f3513bf8de802e7481d813f72678837e0b Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,dart-sdk-linux-try,analyzer-win-release-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107511 Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
Issue: #37385 Change-Id: I0a0704f3513bf8de802e7481d813f72678837e0b Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,dart-sdk-linux-try,analyzer-win-release-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107511 Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
/cc @sjindel-google
Currently we enforce that the arguments and return type of functions are invariant on trampolines.
3cce6fc changed the semantics of
Pointer.store
andPointer.load
to be consistent with the Dart language semantics (notably implicit downcasts).We should investigate the use of
Pointer<NativeType>
as argument or return value, document it's semantics, and write tests for it.The text was updated successfully, but these errors were encountered: