Skip to content

Commit

Permalink
[ffigen] Dedupe ObjC listener block trampolines (#1541)
Browse files Browse the repository at this point in the history
  • Loading branch information
liamappelbe authored Sep 12, 2024
1 parent 0d45f90 commit 69dd6d1
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 28 deletions.
4 changes: 4 additions & 0 deletions pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 14.1.0-wip

- Dedupe `ObjCBlock` trampolines to reduce generated ObjC code.

## 14.0.1

- Fix bug with nullable types in `ObjCBlock`'s type arguments:
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_${id.hashCode.toRadixString(16)}',
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,
);
}
2 changes: 1 addition & 1 deletion pkgs/ffigen/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# BSD-style license that can be found in the LICENSE file.

name: ffigen
version: 14.0.1
version: 14.1.0-wip
description: >
Generator for FFI bindings, using LibClang to parse C, Objective-C, and Swift
files.
Expand Down
17 changes: 17 additions & 0 deletions pkgs/ffigen/test/native_objc_test/block_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,23 @@ void main() {
expect(descPtr.ref.dispose_helper, isNot(nullptr));
expect(descPtr.ref.signature, nullptr);
});

test('Block trampoline args converted to id', () {
final objCBindings =
File('test/native_objc_test/block_bindings.m').readAsStringSync();

// Objects are converted to id.
expect(objCBindings, isNot(contains('NSObject')));
expect(objCBindings, isNot(contains('NSString')));
expect(objCBindings, contains('id'));

// Blocks are also converted to id. Note: (^) is part of a block type.
expect(objCBindings, isNot(contains('(^)')));

// Other types, like structs, are still there.
expect(objCBindings, contains('Vec2'));
expect(objCBindings, contains('Vec4'));
});
});
}

Expand Down

0 comments on commit 69dd6d1

Please sign in to comment.