Skip to content
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

[ffigen] Experiment to remove synth blocks #1869

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 103 additions & 51 deletions pkgs/ffigen/lib/src/code_generator/objc_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ObjCBlock extends BindingType {
final Type returnType;
final List<Parameter> params;
final bool returnsRetained;
ObjCBlockWrapperFuncs? _blockWrappers;
late final ObjCBlockWrapperFuncs _blockWrappers;

factory ObjCBlock({
required Type returnType,
Expand Down Expand Up @@ -59,9 +59,7 @@ class ObjCBlock extends BindingType {
required this.returnsRetained,
required this.builtInFunctions,
}) : super(originalName: name) {
if (hasListener) {
_blockWrappers = builtInFunctions.getBlockTrampolines(this);
}
_blockWrappers = builtInFunctions.getBlockTrampolines(this);
}

// Generates a human readable name for the block based on the args and return
Expand Down Expand Up @@ -138,14 +136,23 @@ class ObjCBlock extends BindingType {
final callExtension =
w.topLevelUniqueNamer.makeUnique('${name}_CallExtension');

final newPointerBlock = ObjCBuiltInFunctions.newPointerBlock.gen(w);
final newClosureBlock = ObjCBuiltInFunctions.newClosureBlock.gen(w);
final newPointerBlock =
'todo'; //ObjCBuiltInFunctions.newPointerBlock.gen(w);
final newClosureBlock = _blockWrappers.newClosureBlock.name;
final invokeBlock = _blockWrappers.invokeBlock.name;
final registerBlockClosure =
ObjCBuiltInFunctions.registerBlockClosure.gen(w);
final getBlockClosure = ObjCBuiltInFunctions.getBlockClosure.gen(w);
final releaseFn = ObjCBuiltInFunctions.objectRelease.gen(w);
final wrapBlockingBlockFn = ObjCBuiltInFunctions.wrapBlockingBlock.gen(w);
final signalWaiterFn = ObjCBuiltInFunctions.signalWaiter.gen(w);
final blockClosureDisposePort =
ObjCBuiltInFunctions.blockClosureDisposePort.gen(w);
final disposeObjCBlockWithClosure =
ObjCBuiltInFunctions.disposeObjCBlockWithClosure.gen(w);
final returnFfiDartType = returnType.getFfiDartType(w);
final voidPtrCType = voidPtr.getCType(w);
final int64Type = NativeType(SupportedNativeType.int64).getCType(w);
final blockCType = blockPtr.getCType(w);
final blockType = _blockType(w);
final defaultValue = returnType.getDefaultValue(w);
Expand All @@ -154,36 +161,36 @@ class ObjCBlock extends BindingType {
// Write the function pointer based trampoline function.
s.write('''
$returnFfiDartType $funcPtrTrampoline(
$blockCType block, ${func.paramsFfiDartType}) =>
block.ref.target.cast<${func.natFnFfiDartType}>()
$blockCType block, int closureId, ${func.paramsFfiDartType}) =>
${w.ffiLibraryPrefix}.Pointer<${func.natFnFfiDartType}>.fromAddress(closureId)
.asFunction<${func.ffiDartType}>()(${func.paramsNameOnly});
$voidPtrCType $funcPtrCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction<
${func.trampCType}>($funcPtrTrampoline $exceptionalReturn).cast();
final $funcPtrCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction<
${func.trampCType}>($funcPtrTrampoline $exceptionalReturn);
''');

// Write the closure based trampoline function.
s.write('''
$returnFfiDartType $closureTrampoline(
$blockCType block, ${func.paramsFfiDartType}) =>
($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly});
$voidPtrCType $closureCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction<
${func.trampCType}>($closureTrampoline $exceptionalReturn).cast();
$blockCType block, int closureId, ${func.paramsFfiDartType}) =>
($getBlockClosure(closureId) as ${func.ffiDartType})(${func.paramsNameOnly});
final $closureCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction<
${func.trampCType}>($closureTrampoline $exceptionalReturn);
''');

if (hasListener) {
// Write the listener trampoline function.
s.write('''
$returnFfiDartType $listenerTrampoline(
$blockCType block, ${func.paramsFfiDartType}) {
($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly});
$blockCType block, int closureId, ${func.paramsFfiDartType}) {
($getBlockClosure(closureId) as ${func.ffiDartType})(${func.paramsNameOnly});
$releaseFn(block.cast());
}
${func.trampNatCallType} $listenerCallable = ${func.trampNatCallType}.listener(
$listenerTrampoline $exceptionalReturn)..keepIsolateAlive = false;
$returnFfiDartType $blockingTrampoline(
$blockCType block, ${blockingFunc.paramsFfiDartType}) {
$blockCType block, int closureId, ${blockingFunc.paramsFfiDartType}) {
try {
($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly});
($getBlockClosure(closureId) as ${func.ffiDartType})(${func.paramsNameOnly});
} catch (e) {
} finally {
$signalWaiterFn(waiter);
Expand Down Expand Up @@ -231,18 +238,20 @@ abstract final class $name {
/// This block must be invoked by native code running on the same thread as
/// the isolate that registered it. Invoking the block on the wrong thread
/// will result in a crash.
static $blockType fromFunctionPointer(${func.natFnPtrCType} ptr) =>
$blockType($newPointerBlock($funcPtrCallable, ptr.cast()),
retain: false, release: true);
// static $blockType fromFunctionPointer(${func.natFnPtrCType} ptr) =>
// $blockType($newPointerBlock($funcPtrCallable, ptr.cast()),
// retain: false, release: true);

/// Creates a block from a Dart function.
///
/// This block must be invoked by native code running on the same thread as
/// the isolate that registered it. Invoking the block on the wrong thread
/// will result in a crash.
static $blockType fromFunction(${func.dartType} fn) =>
$blockType($newClosureBlock($closureCallable, $convFn),
retain: false, release: true);
$blockType($newClosureBlock(
$closureCallable, $registerBlockClosure($convFn),
$blockClosureDisposePort, $disposeObjCBlockWithClosure),
retain: false, release: true);
''');

// Listener block constructor is only available for void blocks.
Expand All @@ -262,8 +271,8 @@ abstract final class $name {
);
final listenerConvFn =
'(${func.paramsFfiDartType}) => $listenerConvFnInvocation';
final wrapListenerFn = _blockWrappers!.listenerWrapper.name;
final wrapBlockingFn = _blockWrappers!.blockingWrapper.name;
final wrapListenerFn = _blockWrappers.listenerWrapper!.name;
final wrapBlockingFn = _blockWrappers.blockingWrapper!.name;

s.write('''

Expand All @@ -278,7 +287,9 @@ abstract final class $name {
/// blocks do not keep the isolate alive.
static $blockType listener(${func.dartType} fn) {
final raw = $newClosureBlock(
$listenerCallable.nativeFunction.cast(), $listenerConvFn);
$listenerCallable.nativeFunction.cast(),
$registerBlockClosure($listenerConvFn),
$blockClosureDisposePort, $disposeObjCBlockWithClosure);
final wrapper = $wrapListenerFn(raw);
$releaseFn(raw.cast());
return $blockType(wrapper, retain: false, release: true);
Expand All @@ -295,9 +306,13 @@ abstract final class $name {
/// indefinitely, or have other undefined behavior.
static $blockType blocking(${func.dartType} fn) {
final raw = $newClosureBlock(
$blockingCallable.nativeFunction.cast(), $listenerConvFn);
$blockingCallable.nativeFunction.cast(),
$registerBlockClosure($listenerConvFn),
$blockClosureDisposePort, $disposeObjCBlockWithClosure);
final rawListener = $newClosureBlock(
$blockingListenerCallable.nativeFunction.cast(), $listenerConvFn);
$blockingListenerCallable.nativeFunction.cast(),
$registerBlockClosure($listenerConvFn),
$blockClosureDisposePort, $disposeObjCBlockWithClosure);
final wrapper = $wrapBlockingBlockFn($wrapBlockingFn, raw, rawListener);
$releaseFn(raw.cast());
$releaseFn(rawListener.cast());
Expand All @@ -308,10 +323,6 @@ abstract final class $name {
s.write('}\n\n');

// Call operator extension method.
s.write('''
Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately, I need to be able to call blocks e.g.

      ncb.NSURLSessionDataDelegate
          .URLSession_dataTask_didReceiveResponse_completionHandler_
          .implementAsListener(protoBuilder,
              (nsSession, nsDataTask, nsResponse, nsCompletionHandler) {
        final exactResponse = URLResponse._exactURLResponseType(nsResponse);
        final disposition = onResponse(
            URLSession._(nsSession,
                isBackground: isBackground, hasDelegate: true),
            URLSessionTask._(nsDataTask),
            exactResponse);
        nsCompletionHandler.call(disposition);
      });

Copy link
Contributor

Choose a reason for hiding this comment

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

The generated code also seems to have some issues:
image

Copy link
Contributor

Choose a reason for hiding this comment

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

There are some other code gen issues like:

Try importing the library that defines 'registerBlockClosure', correcting the name to the name of an existing function, or defining a function named 'registerBlockClosure'.dart[undefined_function](https://dart.dev/diagnostics/undefined_function)```

Copy link
Contributor Author

@liamappelbe liamappelbe Jan 7, 2025

Choose a reason for hiding this comment

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

@brianquinlan Those errors look like you're not using the version of package:objective_c from this branch. Might need a dependency override with a path dep.

I'll work on supporting the call operator today.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added support for the call operator

/// Call operator for `$blockType`.
extension $callExtension on $blockType {
${returnType.getDartType(w)} call(${func.paramsDartType}) =>''');
final callMethodArgs = params
.map((p) => p.type.convertDartTypeToFfiDartType(
w,
Expand All @@ -320,32 +331,35 @@ extension $callExtension on $blockType {
objCAutorelease: false,
))
.join(', ');
final callMethodInvocation = '''
ref.pointer.ref.invoke.cast<${func.trampNatFnCType}>()
.asFunction<${func.trampFfiDartType}>()(
ref.pointer, $callMethodArgs)''';
s.write(returnType.convertFfiDartTypeToDartType(
final callMethod = returnType.convertFfiDartTypeToDartType(
w,
callMethodInvocation,
'$invokeBlock(ref.pointer, $callMethodArgs)',
objCRetain: !returnsRetained,
));
s.write(';\n');
);
s.write('''
/// Call operator for `$blockType`.
extension $callExtension on $blockType {
${returnType.getDartType(w)} call(${func.paramsDartType}) => $callMethod;
}

''');

s.write('}\n\n');
return BindingString(
type: BindingStringType.objcBlock, string: s.toString());
}

@override
BindingString? toObjCBindingString(Writer w) {
if (_blockWrappers?.objCBindingsGenerated ?? true) return null;
_blockWrappers!.objCBindingsGenerated = true;
if (_blockWrappers.objCBindingsGenerated) return null;
_blockWrappers.objCBindingsGenerated = true;

final argsReceived = <String>[];
final retains = <String>[];
final noRetains = <String>[];
for (var i = 0; i < params.length; ++i) {
final param = params[i];
final argName = 'arg$i';
noRetains.add(argName);
argsReceived.add(param.getNativeType(varName: argName));
retains.add(param.type.generateRetain(argName) ?? argName);
}
Expand All @@ -360,28 +374,62 @@ ref.pointer.ref.invoke.cast<${func.trampNatFnCType}>()
...argsReceived,
].join(', ');

final listenerWrapper = _blockWrappers!.listenerWrapper.name;
final blockingWrapper = _blockWrappers!.blockingWrapper.name;
final listenerName =
w.objCLevelUniqueNamer.makeUnique('_ListenerTrampoline');
final returnNativeType = returnType.getNativeType();

final newClosureBlock = _blockWrappers.newClosureBlock.name;
final invokeBlock = _blockWrappers.invokeBlock.name;
final blockTypeName = w.objCLevelUniqueNamer.makeUnique('_BlockType');
final blockingName =
w.objCLevelUniqueNamer.makeUnique('_BlockingTrampoline');
final trampolineArg =
_blockWrappers.trampolineType.getNativeType(varName: 'trampoline');
final retainStr = returnsRetained ? 'NS_RETURNS_RETAINED' : '';
final dtorClass = '_${w.className}_BlockDestroyer';

final s = StringBuffer();
s.write('''

typedef ${returnType.getNativeType()} (^$listenerName)($argStr);
typedef $returnNativeType (^$blockTypeName)($argStr);
__attribute__((visibility("default"))) __attribute__((used))
$listenerName $listenerWrapper($listenerName block) NS_RETURNS_RETAINED {
$returnNativeType $invokeBlock(
${['$blockTypeName block', ...argsReceived].join(', ')}) $retainStr {
return block(${noRetains.join(', ')});
}

__attribute__((visibility("default"))) __attribute__((used))
$blockTypeName $newClosureBlock(
$trampolineArg, int64_t closure_id, int64_t dispose_port,
void (*dtor)(int64_t, int64_t)) NS_RETURNS_RETAINED {
$dtorClass* obj = [[$dtorClass alloc] init];
obj.closure_id = closure_id;
obj.dispose_port = dispose_port;
obj.dtor = dtor;
__weak __block $blockTypeName weakBlk;
$blockTypeName blk = ^$returnNativeType($argStr) {
return trampoline(weakBlk, ${['obj.closure_id', ...noRetains].join(', ')});
};
weakBlk = blk;
return blk;
}
''');

if (hasListener) {
final listenerWrapper = _blockWrappers.listenerWrapper!.name;
final blockingWrapper = _blockWrappers.blockingWrapper!.name;

s.write('''

__attribute__((visibility("default"))) __attribute__((used))
$blockTypeName $listenerWrapper($blockTypeName block) NS_RETURNS_RETAINED {
return ^void($argStr) {
${generateRetain('block')};
block(${retains.join(', ')});
};
}

typedef ${returnType.getNativeType()} (^$blockingName)($blockingArgStr);
typedef $returnNativeType (^$blockingName)($blockingArgStr);
__attribute__((visibility("default"))) __attribute__((used))
$listenerName $blockingWrapper(
$blockTypeName $blockingWrapper(
$blockingName block, $blockingName listenerBlock,
void* (*newWaiter)(), void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED {
NSThread *targetThread = [NSThread currentThread];
Expand All @@ -398,6 +446,7 @@ $listenerName $blockingWrapper(
};
}
''');
}
return BindingString(
type: BindingStringType.objcBlock, string: s.toString());
}
Expand Down Expand Up @@ -503,6 +552,9 @@ class _FnHelper {
type: PointerType(objCBlockType),
name: 'block',
objCConsumed: false),
Parameter(
type: NativeType(SupportedNativeType.int64),
name: 'closure_id', objCConsumed: false),
...params,
],
);
Expand Down
Loading
Loading