bdero / cabal

Flutter's biggest open secret.
BSD 3-Clause "New" or "Revised" License
40 stars 2 forks source link

FFI build/runtime issues #5

Open bdero opened 8 months ago

bdero commented 8 months ago

FYI @johnmccutchan

  1. Encountered this over the weekend. This error occurs at runtime while creating a rigid body:
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Unsupported operation: The body is inlined in the frontend.
#0      DynamicLibraryExtension.lookupFunction (dart:ffi-patch/ffi_dynamic_library_patch.dart:67:7)
#1      new Body._ (package:cabal/physics/src/body.dart:76:46)
#2      new RigidBody._ (package:cabal/physics/src/body.dart:185:15)
#3      World.createRigidBody (package:cabal/physics/src/world.dart:52:22)
#4      CabalGame.startECS (package:cabal/game/cabal_game.dart:140:36)
#5      ECSGame.start (package:cabal/base/ecs_game.dart:39:5)
#6      GameRenderBox.attach.<anonymous closure> (package:cabal/base/game_render_box.dart:45:12)
#7      Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:847:45)
#8      Future._propagateToListeners (dart:async/future_impl.dart:876:13)
#9      Future._completeWithValue (dart:async/future_impl.dart:652:5)
#10     Future._asyncCompleteWithValue.<anonymous closure> (dart:async/future_impl.dart:722:7)
#11     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
#12     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)
  1. Also, around half the the time when I try to run cabal, the following error happens. However, rerunning always clears it up:
flutter run --debug --local-engine-src-path ~/projects/flutter/engine/src --local-engine=host_debug_unopt_arm64 --local-engine-host=host_debug_unopt_arm64 -d macos --enable-impeller
Launching lib/main.dart on macOS in debug mode...
lib/physics/src/body.dart:76:46: Error: Expected type 'void Function(Pointer<WorldBody>, Float32List)' to be 'void Function(Pointer<WorldBody>, Pointer<Float>)', which is the Dart type corresponding to 'NativeFunction<Void Function(Pointer<WorldBody>, Pointer<Float>)>'.
 - 'Pointer' is from 'dart:ffi'.
 - 'WorldBody' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
 - 'Float32List' is from 'dart:typed_data'.
 - 'Float' is from 'dart:ffi'.
 - 'NativeFunction' is from 'dart:ffi'.
 - 'Void' is from 'dart:ffi'.
  final unwrappedPositionSetter = jolt.dylib.lookupFunction<
                                             ^
lib/physics/src/body.dart:85:53: Error: Expected type 'void Function(Pointer<WorldBody>, Float32List)' to be 'void Function(Pointer<WorldBody>, Pointer<Float>)', which is the Dart type corresponding to 'NativeFunction<Void Function(Pointer<WorldBody>, Pointer<Float>)>'.
 - 'Pointer' is from 'dart:ffi'.
 - 'WorldBody' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
 - 'Float32List' is from 'dart:typed_data'.
 - 'Float' is from 'dart:ffi'.
 - 'NativeFunction' is from 'dart:ffi'.
 - 'Void' is from 'dart:ffi'.
  static final unwrappedPositionGetter = jolt.dylib.lookupFunction<
                                                    ^
lib/physics/src/body.dart:96:53: Error: Expected type 'void Function(Pointer<WorldBody>, Float32List)' to be 'void Function(Pointer<WorldBody>, Pointer<Float>)', which is the Dart type corresponding to 'NativeFunction<Void Function(Pointer<WorldBody>, Pointer<Float>)>'.
 - 'Pointer' is from 'dart:ffi'.
 - 'WorldBody' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
 - 'Float32List' is from 'dart:typed_data'.
 - 'Float' is from 'dart:ffi'.
 - 'NativeFunction' is from 'dart:ffi'.
 - 'Void' is from 'dart:ffi'.
  static final unwrappedRotationSetter = jolt.dylib.lookupFunction<
                                                    ^
lib/physics/src/body.dart:105:53: Error: Expected type 'void Function(Pointer<WorldBody>, Float32List)' to be 'void Function(Pointer<WorldBody>, Pointer<Float>)', which is the Dart type corresponding to 'NativeFunction<Void Function(Pointer<WorldBody>, Pointer<Float>)>'.
 - 'Pointer' is from 'dart:ffi'.
 - 'WorldBody' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
 - 'Float32List' is from 'dart:typed_data'.
 - 'Float' is from 'dart:ffi'.
 - 'NativeFunction' is from 'dart:ffi'.
 - 'Void' is from 'dart:ffi'.
  static final unwrappedRotationGetter = jolt.dylib.lookupFunction<
                                                    ^
lib/physics/src/body.dart:116:59: Error: Expected type 'void Function(Pointer<WorldBody>, Float32List)' to be 'void Function(Pointer<WorldBody>, Pointer<Float>)', which is the Dart type corresponding to 'NativeFunction<Void Function(Pointer<WorldBody>, Pointer<Float>)>'.
 - 'Pointer' is from 'dart:ffi'.
 - 'WorldBody' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
 - 'Float32List' is from 'dart:typed_data'.
 - 'Float' is from 'dart:ffi'.
 - 'NativeFunction' is from 'dart:ffi'.
 - 'Void' is from 'dart:ffi'.
  static final unwrappedWorldTransformGetter = jolt.dylib.lookupFunction<
                                                          ^
lib/physics/src/body.dart:127:66: Error: Expected type 'void Function(Pointer<WorldBody>, Float32List)' to be 'void Function(Pointer<WorldBody>, Pointer<Float>)', which is the Dart type corresponding to 'NativeFunction<Void Function(Pointer<WorldBody>, Pointer<Float>)>'.
 - 'Pointer' is from 'dart:ffi'.
 - 'WorldBody' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
 - 'Float32List' is from 'dart:typed_data'.
 - 'Float' is from 'dart:ffi'.
 - 'NativeFunction' is from 'dart:ffi'.
 - 'Void' is from 'dart:ffi'.
  static final unwrapperCenterOfMassTransformGetter = jolt.dylib.lookupFunction<
                                                                 ^
lib/physics/src/shape.dart:22:54: Error: Expected type 'void Function(Pointer<CollisionShape>, Float32List)' to be 'void Function(Pointer<CollisionShape>, Pointer<Float>)', which is the Dart type corresponding to 'NativeFunction<Void Function(Pointer<CollisionShape>, Pointer<Float>)>'.
 - 'Pointer' is from 'dart:ffi'.
 - 'CollisionShape' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
 - 'Float32List' is from 'dart:typed_data'.
 - 'Float' is from 'dart:ffi'.
 - 'NativeFunction' is from 'dart:ffi'.
 - 'Void' is from 'dart:ffi'.
  static final unwrappedGetCenterOfMass = jolt.dylib.lookupFunction<
                                                     ^
lib/physics/src/shape.dart:34:53: Error: Expected type 'void Function(Pointer<CollisionShape>, Float32List, Float32List)' to be 'void Function(Pointer<CollisionShape>, Pointer<Float>, Pointer<Float>)', which is the Dart type corresponding to 'NativeFunction<Void Function(Pointer<CollisionShape>, Pointer<Float>, Pointer<Float>)>'.
 - 'Pointer' is from 'dart:ffi'.
 - 'CollisionShape' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
 - 'Float32List' is from 'dart:typed_data'.
 - 'Float' is from 'dart:ffi'.
 - 'NativeFunction' is from 'dart:ffi'.
 - 'Void' is from 'dart:ffi'.
  static final unwrappedGetLocalBounds = jolt.dylib.lookupFunction<
                                                    ^
lib/physics/src/shape.dart:180:56: Error: Expected type 'Pointer<CollisionShape> Function(Pointer<ConvexShapeConfig>, Float32List, int)' to be 'Pointer<CollisionShape> Function(Pointer<ConvexShapeConfig>, Pointer<Float>, int)', which is the Dart type corresponding to 'NativeFunction<Pointer<CollisionShape> Function(Pointer<ConvexShapeConfig>, Pointer<Float>, Int)>'.
 - 'Pointer' is from 'dart:ffi'.
 - 'CollisionShape' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
 - 'ConvexShapeConfig' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
 - 'Float32List' is from 'dart:typed_data'.
 - 'Float' is from 'dart:ffi'.
 - 'NativeFunction' is from 'dart:ffi'.
 - 'Int' is from 'dart:ffi'.
  static final unwrappedCreateConvexShape = jolt.dylib.lookupFunction<
                                                       ^
lib/physics/src/shape.dart:269:54: Error: Expected type 'Pointer<CollisionShape> Function(Float32List, int, Uint32List, int)' to be 'Pointer<CollisionShape> Function(Pointer<Float>, int, Pointer<Uint32>, int)', which is the Dart type corresponding to 'NativeFunction<Pointer<CollisionShape> Function(Pointer<Float>, Int, Pointer<Uint32>, Int)>'.
 - 'Pointer' is from 'dart:ffi'.
 - 'CollisionShape' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
 - 'Float32List' is from 'dart:typed_data'.
 - 'Uint32List' is from 'dart:typed_data'.
 - 'Float' is from 'dart:ffi'.
 - 'Uint32' is from 'dart:ffi'.
 - 'NativeFunction' is from 'dart:ffi'.
 - 'Int' is from 'dart:ffi'.
  static final unwrappedCreateMeshShape = jolt.dylib.lookupFunction<
                                                     ^
Target kernel_snapshot failed: Exception

Command PhaseScriptExecution failed with a nonzero exit code
warning: Run script build phase 'Run Script' will be run during every build because it does not specify any outputs. To address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'Flutter Assemble' from project 'Runner')
** BUILD FAILED **

Building macOS application...
Error: Build process failed
bdero commented 7 months ago

Okay, so I blew out everything and re-setup Cabal against engine ToT. Problem 2 seems to have gone away, but I'm still encountering problem 1.

bdero commented 7 months ago

Ah nvm, it turns out problem 2 is still afoot, the tool is just silently crashing right after launching the app, but --verbose reveals the errors.

bdero commented 7 months ago

FYI @dcharkes -- would you happen to know why the DynamicLibrary.lookupFunction extension no longer accepts mapping Float32List to ffi.Pointer<ffi.Float>?

Here's an example of the invocation:

  final unwrappedPositionSetter = jolt.dylib.lookupFunction<
      ffi.Void Function(ffi.Pointer<jolt.WorldBody>, ffi.Pointer<ffi.Float>),
      void Function(ffi.Pointer<jolt.WorldBody>,
          Float32List)>('body_set_position', isLeaf: true);

And the corresponding error:

[        ] [+2596 ms] lib/physics/src/body.dart:76:46: Error: Expected type 'void Function(Pointer<WorldBody>, Float32List)' to be 'void
Function(Pointer<WorldBody>, Pointer<Float>)', which is the Dart type corresponding to 'NativeFunction<Void Function(Pointer<WorldBody>,
Pointer<Float>)>'.
[        ] [        ]  - 'Pointer' is from 'dart:ffi'.
[        ] [        ]  - 'WorldBody' is from 'package:cabal/physics/src/jolt_ffi_generated.dart' ('lib/physics/src/jolt_ffi_generated.dart').
[        ] [        ]  - 'Float32List' is from 'dart:typed_data'.
[        ] [        ]  - 'Float' is from 'dart:ffi'.
[        ] [        ]  - 'NativeFunction' is from 'dart:ffi'.
[        ] [        ]  - 'Void' is from 'dart:ffi'.
[        ] [        ]   final unwrappedPositionSetter = jolt.dylib.lookupFunction<
[        ] [        ]                                              ^
dcharkes commented 7 months ago

We realized that there were some issues with the way we introduced the API, so we reverted the API in https://dart-review.googlesource.com/c/sdk/+/349340. We're working on relanding it with a new API https://github.com/dart-lang/sdk/issues/44589.

johnmccutchan commented 7 months ago

What API guarantees does dart:ffi provide?

dcharkes commented 7 months ago

What API guarantees does dart:ffi provide?

APIs that made it into Dart stable that are not marked experimental are unlikely to be broken. (To my knowledge this API never was available in Dart stable, only in dev/beta releases. We cherry picked the revert to the stable release to prevent releasing an API that we realized had problems.)

johnmccutchan commented 7 months ago

The API was available for months and we were big users of it. This has completely broken our project and it makes me worried about being broken again in the future.

Where are these breaking changes communicated?

When is the replacement API going to be available?

dcharkes commented 7 months ago

The API was available for months and we were big users of it.

That is rather unfortunate.

I'm not sure how it was available for months for you? It landed December 12 (https://dart-review.googlesource.com/c/sdk/+/338620) and was reverted January 31 (https://dart-review.googlesource.com/c/sdk/+/349340). I'm a bit confused as how this project would have been able to use it after January 31st on tip of tree, or how it would have been using it on stable? Why did it only break now? Or did we somehow release a stable version with this API?

This has completely broken our project and it makes me worried about being broken again in the future.

I can understand that, that's why we have a breaking change policy.

Where are these breaking changes communicated?

Breaking changes to stable releases are communicated as per the Dart breaking change policy.

Breaking changes to dev/beta release are not.

Is this project running on tip of tree always? For that we don't have a good process I think, we generally don't promise anything tip of tree.

When is the replacement API going to be available?

I have a WIP, I can take a look next week to land it.

I believe for now the workaround would be to copy the data into C memory before an FFI call and free it immediately after. (Of course this costs performance, which is why we want to support this feature in the first place.)

Once again, I'm sorry for the inconvenience.

(And I'm confused as of why the problem surfaced in March on tip of tree instead of January 31st. Any insights here?

It has been a tip of tree all the time. So I'm uncertain why this didn't surface immediately after the revert 2 months ago.

environment:
  sdk: '>=3.3.0-120.0.dev <4.0.0'

)

johnmccutchan commented 7 months ago

Breaking changes to stable releases are communicated as per the Dart breaking change policy.

Breaking changes to dev/beta release are not.

This policy for beta releases seems hostile to our developers. If we think something is good enough to be in a beta release a developer should feel confident that they can use it and that if we take it away before the official release we will provide guidance. Ideally we do not remove APIs that were released in betas though.

Is this project running on tip of tree always? For that we don't have a good process I think, we generally don't promise anything tip of tree.

Given the timeline, I think we adopted this API right away. But, then, we stopped working on the project in February to focus on other things. We recently started working on this again and noticed all these strange breakages.

We now have a bunch of "churn" work to do to make our project functional and performance will be worse because we will be unnecessarily allocating/freeing all the time.

I have a WIP, I can take a look next week to land it.

This is great to hear but it doesn't give me confidence that porting cabal to this freshly landed API will be safe in the future. What guarantees are we getting? I do not want to have to rip out a bunch of code again in a month.

bdero commented 7 months ago

Thanks for the clarification @dcharkes!

I believe for now the workaround would be to copy the data into C memory before an FFI call and free it immediately after

Yeah, this is how I ended up working around it in the meantime.