dart-archive / wasm

Utilities for loading and running WASM modules from Dart code
https://pub.dev/packages/wasm
BSD 3-Clause "New" or "Revised" License
312 stars 24 forks source link

Migrate finalizers #105

Closed modulovalue closed 1 year ago

modulovalue commented 1 year ago

Context #95 After this change, the build step for wasmer should reduce to building the wasmer library only. Creating bindings for ios, android and so on should therefore become significantly simpler.

@liamappelbe what do you think? I haven't used this API yet personally, but it seems like it was built for this purpose and is probably a wrapper around the same API that you were using natively.

liamappelbe commented 1 year ago

You've got some test failures. Take a look at this test to see how to use finalizers. Another example is how we use them in the ObjC bindings:

class _ObjCWrapper implements ffi.Finalizable {
  final ffi.Pointer<ObjCObject> _id;
  final AVFAudio _lib;
  bool _pendingRelease;

  _ObjCWrapper._(this._id, this._lib,
      {bool retain = false, bool release = false})
      : _pendingRelease = release {
    if (retain) {
      _lib._objc_retain(_id.cast());
    }
    if (release) {
      _lib._objc_releaseFinalizer2.attach(this, _id.cast(), detach: this);
    }
  }

  /// Releases the reference to the underlying ObjC object held by this wrapper.
  /// Throws a StateError if this wrapper doesn't currently hold a reference.
  void release() {
    if (_pendingRelease) {
      _pendingRelease = false;
      _lib._objc_release(_id.cast());
      _lib._objc_releaseFinalizer2.detach(this);
    } else {
      throw StateError(
          'Released an ObjC object that was unowned or already released.');
    }
  }
}

Basically, you need to implement Finalizable in any class that has sole ownership of a native resource. Then in that class's constructor you call finalizer.attach(this, native_pointer, detach: this);, where finalizer is an instance of NativeFinalizer (and is typically a singleton). The native finalizer function you pass to NativeFinalizer must actually be native (ie it can't be implemented in Dart currently), and have this signature void nativeFinalizer(void* native_pointer).

As you can see in the _ObjCWrapper example, it's also standard practice to give users the option of manually releasing the native resource.

modulovalue commented 1 year ago

Thank you. I'll look into it.

modulovalue commented 1 year ago

@liamappelbe I think the tests are failing because Finalizer and NativeFinalizer aren't available in 2.12.0. I'm rather in favor of supporting versions that low for now, because I have a project at 2.16.0. (but if you want to move the lower bound up, don't worry about that.)

I'm going to wait with the migration to dart-provided-finalizers until I have a clearer picture of the native external string APIs (context: https://github.com/dart-lang/wasm/issues/95#issuecomment-1312339899) making them work well with WASM would be huge for me and I guess we could reuse finalizer.c for writing bindings to that if needed.

modulovalue commented 1 year ago

This PR will become very stale, it'll make more sense to just redo it entirely.