Skip to content
This repository has been archived by the owner on Jan 28, 2024. It is now read-only.

Use getDartType rather than getFfiDartType in ObjC block codegen #632

Merged
merged 9 commits into from
Oct 27, 2023

Conversation

liamappelbe
Copy link
Contributor

@liamappelbe liamappelbe commented Oct 25, 2023

Improves the codegen for blocks with arguments or return types that are objects, nullables, or blocks. Users can now pass or receive the Dart wrapper object, rather than dealing with pointers.

Ref counting notes:

  • Arguments to a Dart block might outlive the scope of the block, so must be retained when they're wrapped
  • Returns from a Dart block might outlive the wrapper object, so must be retained as the raw pointer is being returned
  • To balance those 2 points, the call method of the Block wrapper shouldn't retain arguments it passes to the block, or the result it gets back.
  • Due to Delete Dart Functions associated with ObjC Blocks dart-lang/native#204, some of the ref counting tests have leaks, because a wrapper object is being captured in a lambda context that is stored in the block trampoline table.

Fixes dart-lang/native#473

@liamappelbe liamappelbe requested a review from dcharkes October 25, 2023 02:20
@liamappelbe liamappelbe marked this pull request as ready for review October 25, 2023 02:20
@liamappelbe liamappelbe marked this pull request as draft October 25, 2023 04:13
@liamappelbe liamappelbe changed the title Use getDartType rather than getFfiDartType in ObjC block codegen WIP: Use getDartType rather than getFfiDartType in ObjC block codegen Oct 25, 2023
@liamappelbe liamappelbe removed the request for review from dcharkes October 25, 2023 04:13
@liamappelbe
Copy link
Contributor Author

Marking not ready for review. I can't repro the mac CI failure locally. Need to investigate more.

@liamappelbe liamappelbe changed the title WIP: Use getDartType rather than getFfiDartType in ObjC block codegen Use getDartType rather than getFfiDartType in ObjC block codegen Oct 26, 2023
@liamappelbe liamappelbe requested a review from dcharkes October 26, 2023 04:25
@liamappelbe liamappelbe marked this pull request as ready for review October 26, 2023 04:25
@liamappelbe
Copy link
Contributor Author

The failures were to do with ref counting mismatches. They're all fixed now.

Copy link
Contributor

@dcharkes dcharkes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm w. comments

Thanks @liamappelbe!

(If you want to release a version, don't forget changelog entry and pubspec version bump.)

@@ -34,6 +70,10 @@ + (NSThread*)callOnNewThread:(VoidBlock)block;
+ (float)callFloatBlock:(FloatBlock)block;
+ (double)callDoubleBlock:(DoubleBlock)block;
+ (Vec4)callVec4Block:(Vec4Block)block;
+ (DummyObject*)callObjectBlock:(ObjectBlock)block NS_RETURNS_RETAINED;
+ (DummyObject* _Nullable)callNullableBlock:(NullableBlock)block;
+ (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
+ (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult;
+ (nullable DummyObject*)callNullableBlock:(NullableBlock)block;

Ditto in other places.

Also, does Objective-C default to nonnull if nullable is omitted? Or is that Objective-C version specific?
It might be worth adding at least one test with nonnull to make sure we parse it correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, does Objective-C default to nonnull if nullable is omitted?

Yep. From the parser's perspective, we don't even see the annotations. We just see the clang_Type_getNullability flag, which combines both these annotations. I've added a test case to nullable_test anyway.

typedef int32_t (^IntBlock)(int32_t);
typedef float (^FloatBlock)(float);
typedef double (^DoubleBlock)(double);
typedef Vec4 (^Vec4Block)(Vec4);
typedef void (^VoidBlock)();
typedef DummyObject* (^ObjectBlock)(DummyObject*);
typedef DummyObject* _Nullable (^NullableBlock)(DummyObject* _Nullable);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The block itself isn't nullable, but the argument and return type are. I don't have a good suggestion for a better name though. If we split the test in two blocks, one with nullable argument and one with nullable return it could be NullableReturnBlock and NullableArgumentBlock or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about NullableObjectBlock?

@@ -12,11 +12,47 @@
double w;
} Vec4;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit for block_config.yaml

description: 'Tests calling Objective-C blocks.'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

});

final obj = DummyObject.new1(lib);
final result1 = block(obj);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add test which pass null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


test('Block block', () {
final blockBlock = BlockBlock.fromFunction(lib, (IntBlock intBlock) {
return IntBlock.fromFunction(lib, (int x) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! 😃

/// only. This string should not be printed as generated code.
/// Returns generated Dart code that converts the given value from its
/// DartType to its FfiDartType.
String convertDartTypeToFfiDartType(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document the params so that if someone not familiar with objC introduces a new implementation of Type doesn't have to reverse engineer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


/// Returns generated Dart code that converts the given value from its
/// FfiDartType to its DartType.
String convertFfiDartTypeToDartType(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, please document the arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

String convertDartTypeToFfiDartType(
Writer w,
String value, {
bool objCShouldRetain = false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

objCRetain (It's not a should, it's guaranteed, we generate a retain: true.)

Also, let's make it non-optional. I think it would be useful that all call sites explicitly reason about memory management in the generated code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Writer w,
String value,
String library, {
bool objCShouldRetain = true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, objCRetain and required arg.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

String? objCEnclosingClass,
}) =>
ObjCInterface.generateConstructor(
objCEnclosingClass ?? 'NSObject', value, library, objCShouldRetain);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is NSObject ever happening?
If so, is it a sign we are not generating a class because it's not part of the ffigen.yaml?

If it can happen:

  • Please add a test case that exercises this.
    • Leave a comment here to that test case.
  • Emit a warning.

If it cannot (or should not) happen:

  • Add an assert or exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't ever happen (clang should give an error). Added a null assertion instead.

@liamappelbe liamappelbe merged commit ba94da9 into main Oct 27, 2023
6 checks passed
@liamappelbe liamappelbe deleted the blockusertype branch October 27, 2023 00:38
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Objective-C blocks should use the generated Dart class rather than ffi.Pointer<ObjCObject>
2 participants