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] Dedupe ObjC listener block trampolines #1541

Merged
merged 9 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 14.1.0-wip

- Fix bug with nullable types in `ObjCBlock`'s type arguments.
- Dedupe `ObjCBlock` trampolines to reduce generated ObjC code.

## 14.0.0

Expand Down
4 changes: 4 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/library.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class Library {
/// List of bindings in this library.
late List<Binding> bindings;

final ObjCBuiltInFunctions? objCBuiltInFunctions;

late Writer _writer;
Writer get writer => _writer;

Expand All @@ -34,6 +36,7 @@ class Library {
List<LibraryImport>? libraryImports,
bool silenceEnumWarning = false,
List<String> nativeEntryPoints = const <String>[],
this.objCBuiltInFunctions,
}) {
_findBindings(bindings, sort);

Expand Down Expand Up @@ -103,6 +106,7 @@ class Library {
for (final b in original) {
b.addDependencies(dependencies);
}
objCBuiltInFunctions?.addDependencies(dependencies);

/// Save bindings.
bindings = dependencies.toList();
Expand Down
42 changes: 18 additions & 24 deletions pkgs/ffigen/lib/src/code_generator/objc_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@
// BSD-style license that can be found in the LICENSE file.

import '../code_generator.dart';
import '../config_provider/config_types.dart';
import '../header_parser/data.dart' show bindingsIndex;

import 'binding_string.dart';
import 'writer.dart';

class ObjCBlock extends BindingType {
final ObjCBuiltInFunctions builtInFunctions;
final Type returnType;
final List<Parameter> params;
final bool returnsRetained;
Func? _wrapListenerBlock;
ObjCListenerBlockTrampoline? _wrapListenerBlock;

factory ObjCBlock({
required Type returnType,
required List<Parameter> params,
required bool returnsRetained,
required ObjCBuiltInFunctions builtInFunctions,
}) {
final usr = _getBlockUsr(returnType, params, returnsRetained);

Expand All @@ -33,6 +34,7 @@ class ObjCBlock extends BindingType {
returnType: returnType,
params: params,
returnsRetained: returnsRetained,
builtInFunctions: builtInFunctions,
);
bindingsIndex.addObjCBlockToSeen(usr, block);

Expand All @@ -45,6 +47,7 @@ class ObjCBlock extends BindingType {
required this.returnType,
required this.params,
required this.returnsRetained,
required this.builtInFunctions,
}) : super(originalName: name);

// Generates a human readable name for the block based on the args and return
Expand Down Expand Up @@ -213,7 +216,7 @@ abstract final class $name {
);
final listenerConvFn =
'($paramsFfiDartType) => $listenerConvFnInvocation';
final wrapFn = _wrapListenerBlock?.name;
final wrapFn = _wrapListenerBlock?.func.name;
final releaseFn = ObjCBuiltInFunctions.objectRelease.gen(w);

s.write('''
Expand Down Expand Up @@ -278,7 +281,8 @@ ref.pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>

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

final argsReceived = <String>[];
final retains = <String>[];
Expand All @@ -288,15 +292,17 @@ ref.pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>
argsReceived.add(param.getNativeType(varName: argName));
retains.add(param.type.generateRetain(argName) ?? argName);
}
final fnName = _wrapListenerBlock!.name;
final blockTypedef = w.objCLevelUniqueNamer.makeUnique('ListenerBlock');
final argStr = argsReceived.join(', ');
final fnName = _wrapListenerBlock!.func.name;
final blockName = w.objCLevelUniqueNamer.makeUnique('_ListenerTrampoline');
final blockTypedef = '${returnType.getNativeType()} (^$blockName)($argStr)';

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

typedef ${getNativeType(varName: blockTypedef)};
$blockTypedef $fnName($blockTypedef block) NS_RETURNS_RETAINED {
return ^void(${argsReceived.join(', ')}) {
typedef $blockTypedef;
$blockName $fnName($blockName block) NS_RETURNS_RETAINED {
return ^void($argStr) {
block(${retains.join(', ')});
};
}
Expand All @@ -315,17 +321,8 @@ $blockTypedef $fnName($blockTypedef block) NS_RETURNS_RETAINED {
p.type.addDependencies(dependencies);
}

if (hasListener && params.any((p) => p.type.generateRetain('') != null)) {
_wrapListenerBlock = Func(
name: 'wrapListenerBlock_$name',
returnType: this,
parameters: [Parameter(name: 'block', type: this, objCConsumed: false)],
objCReturnsRetained: true,
isLeaf: true,
isInternal: true,
useNameForLookup: true,
ffiNativeConfig: const FfiNativeConfig(enabled: true),
)..addDependencies(dependencies);
if (hasListener) {
_wrapListenerBlock = builtInFunctions.getListenerBlockTrampoline(this);
}
}

Expand All @@ -342,10 +339,7 @@ $blockTypedef $fnName($blockTypedef block) NS_RETURNS_RETAINED {
String getObjCBlockSignatureType(Writer w) => getDartType(w);

@override
String getNativeType({String varName = ''}) {
final paramStrs = params.map<String>((p) => p.getNativeType());
return '${returnType.getNativeType()} (^$varName)(${paramStrs.join(', ')})';
}
String getNativeType({String varName = ''}) => 'id $varName';

@override
bool get sameFfiDartAndCType => true;
Expand Down
54 changes: 54 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.

import '../code_generator.dart';
import '../config_provider/config_types.dart';

import 'binding_string.dart';
import 'writer.dart';
Expand All @@ -12,6 +13,7 @@ class ObjCBuiltInFunctions {
ObjCBuiltInFunctions(this.generateForPackageObjectiveC);

final bool generateForPackageObjectiveC;
var _depsAdded = false;

static const registerName = ObjCImport('registerName');
static const getClass = ObjCImport('getClass');
Expand Down Expand Up @@ -116,6 +118,7 @@ class ObjCBuiltInFunctions {
// for float return types we need objc_msgSend_fpret.
final _msgSendFuncs = <String, ObjCMsgSendFunc>{};
ObjCMsgSendFunc getMsgSendFunc(Type returnType, List<Parameter> params) {
assert(!_depsAdded);
var key = returnType.cacheKey();
for (final p in params) {
key += ' ${p.type.cacheKey()}';
Expand All @@ -129,19 +132,63 @@ class ObjCBuiltInFunctions {

final _selObjects = <String, ObjCInternalGlobal>{};
ObjCInternalGlobal getSelObject(String methodName) {
assert(!_depsAdded);
return _selObjects[methodName] ??= ObjCInternalGlobal(
'_sel_${methodName.replaceAll(":", "_")}',
(Writer w) => '${registerName.gen(w)}("$methodName")',
);
}

final _blockTrampolines = <String, ObjCListenerBlockTrampoline>{};
ObjCListenerBlockTrampoline? getListenerBlockTrampoline(ObjCBlock block) {
assert(!_depsAdded);

var needsTrampoline = false;
final paramIds = <String>[];
for (final param in block.params) {
final retainFunc = param.type.generateRetain('');
if (retainFunc != null) {
needsTrampoline = true;
}

// The trampoline ID is based on the getNativeType of the param. Objects
// and blocks both have `id` as their native type, but need separate
// trampolines since they have different retain functions. So add the
// retainFunc (if any) to all the param IDs.
paramIds.add('${param.getNativeType()}-${retainFunc ?? ''}');
}
if (!needsTrampoline) return null;
final id = paramIds.join(',');

return _blockTrampolines[id] ??= ObjCListenerBlockTrampoline(Func(
name: '_wrapListenerBlock',
returnType: PointerType(objCBlockType),
parameters: [
Parameter(
name: 'block',
type: PointerType(objCBlockType),
objCConsumed: false)
],
objCReturnsRetained: true,
isLeaf: true,
isInternal: true,
useNameForLookup: true,
ffiNativeConfig: const FfiNativeConfig(enabled: true),
));
}

void addDependencies(Set<Binding> dependencies) {
if (_depsAdded) return;
_depsAdded = true;
for (final msgSendFunc in _msgSendFuncs.values) {
msgSendFunc.addDependencies(dependencies);
}
for (final sel in _selObjects.values) {
sel.addDependencies(dependencies);
}
for (final tramp in _blockTrampolines.values) {
tramp.func.addDependencies(dependencies);
}
}

static bool isInstanceType(Type type) {
Expand All @@ -151,6 +198,13 @@ class ObjCBuiltInFunctions {
}
}

/// A native trampoline function for a listener block.
class ObjCListenerBlockTrampoline {
final Func func;
bool objCBindingsGenerated = false;
ObjCListenerBlockTrampoline(this.func);
}

/// A function, global variable, or helper type defined in package:objective_c.
class ObjCImport {
final String name;
Expand Down
4 changes: 1 addition & 3 deletions pkgs/ffigen/lib/src/code_generator/objc_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,6 @@ class ObjCInterface extends BindingType with ObjCMethods {
// Add dependencies for any methods that were added.
addMethodDependencies(dependencies, needMsgSend: true);
}

builtInFunctions.addDependencies(dependencies);
}

void _copyMethodsFromSuperType() {
Expand Down Expand Up @@ -318,7 +316,7 @@ class ObjCInterface extends BindingType with ObjCMethods {
_isBuiltIn ? '${w.objcPkgPrefix}.$name' : name;

@override
String getNativeType({String varName = ''}) => '$originalName* $varName';
String getNativeType({String varName = ''}) => 'id $varName';

@override
String getObjCBlockSignatureType(Writer w) => getDartType(w);
Expand Down
1 change: 1 addition & 0 deletions pkgs/ffigen/lib/src/code_generator/objc_methods.dart
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ class ObjCMethod {
...params,
],
returnsRetained: returnsRetained,
builtInFunctions: builtInFunctions,
)..addDependencies(dependencies);
}
}
Expand Down
1 change: 1 addition & 0 deletions pkgs/ffigen/lib/src/header_parser/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Library parse(Config c) {
libraryImports: c.libraryImports.values.toList(),
silenceEnumWarning: c.silenceEnumWarning,
nativeEntryPoints: c.entryPoints.map((uri) => uri.toFilePath()).toList(),
objCBuiltInFunctions: objCBuiltInFunctions,
);

return library;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ ObjCBlock parseObjCBlock(clang_types.CXType cxtype) {
returnType: returnType,
params: params,
returnsRetained: false,
builtInFunctions: objCBuiltInFunctions,
);
}
Loading