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

Memory management for Blocks #429

Merged
merged 8 commits into from
Jul 28, 2022
Merged
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
40 changes: 19 additions & 21 deletions lib/src/code_generator/objc_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class ObjCBlock extends BindingType {
BindingString toBindingString(Writer w) {
final s = StringBuffer();

builtInFunctions.ensureBlockUtilsExist(w, s);

final params = <Parameter>[];
for (int i = 0; i < argTypes.length; ++i) {
params.add(Parameter(name: 'arg$i', type: argTypes[i]));
Expand Down Expand Up @@ -97,25 +99,26 @@ $voidPtr $registerClosure(Function fn) {
s.write('}\n');

// Write the wrapper class.
s.write('class $name {\n');
s.write(' final ${blockPtr.getCType(w)} _impl;\n');
s.write(' final ${w.className} _lib;\n');
s.write(' $name._(this._impl, this._lib);\n');

// Constructor from a function pointer.
final defaultValue = returnType.getDefaultValue(w, '_lib');
final exceptionalReturn = defaultValue == null ? '' : ', $defaultValue';
s.write('''
$name.fromFunctionPointer(this._lib, $natFnPtr ptr)
: _impl = _lib.${builtInFunctions.newBlock.name}(
class $name extends _ObjCBlockBase {
$name._(${blockPtr.getCType(w)} id, ${w.className} lib) :
super._(id, lib, retain: false, release: true);

/// Creates a block from a C function pointer.
$name.fromFunctionPointer(${w.className} lib, $natFnPtr ptr) :
this._(lib.${builtInFunctions.newBlock.name}(
${w.ffiLibraryPrefix}.Pointer.fromFunction<
${trampFuncType.getCType(w)}>($funcPtrTrampoline
$exceptionalReturn).cast(), ptr.cast());
$name.fromFunction(this._lib, ${funcType.getDartType(w)} fn)
: _impl = _lib.${builtInFunctions.newBlock.name}(
$exceptionalReturn).cast(), ptr.cast()), lib);

/// Creates a block from a Dart function.
$name.fromFunction(${w.className} lib, ${funcType.getDartType(w)} fn) :
this._(lib.${builtInFunctions.newBlock.name}(
${w.ffiLibraryPrefix}.Pointer.fromFunction<
${trampFuncType.getCType(w)}>($closureTrampoline
$exceptionalReturn).cast(), $registerClosure(fn));
$exceptionalReturn).cast(), $registerClosure(fn)), lib);
''');

// Call method.
Expand All @@ -125,17 +128,17 @@ $voidPtr $registerClosure(Function fn) {
s.write(' ${params[i].name}');
}
s.write(''') {
${isVoid ? '' : 'return '}_impl.ref.invoke.cast<
${isVoid ? '' : 'return '}_id.ref.invoke.cast<
${natTrampFnType.getCType(w)}>().asFunction<
${trampFuncType.getDartType(w)}>()(_impl''');
${trampFuncType.getDartType(w)}>()(_id''');
for (int i = 0; i < params.length; ++i) {
s.write(', ${params[i].name}');
}
s.write(''');
}''');

// Get the pointer to the underlying block.
s.write(' ${blockPtr.getCType(w)} get pointer => _impl;\n');
s.write(' ${blockPtr.getCType(w)} get pointer => _id;\n');

s.write('}\n');
return BindingString(
Expand All @@ -151,12 +154,7 @@ $voidPtr $registerClosure(Function fn) {
for (final t in argTypes) {
t.addDependencies(dependencies);
}

builtInFunctions.newBlockDesc.addDependencies(dependencies);
builtInFunctions.blockDescSingleton.addDependencies(dependencies);
builtInFunctions.blockStruct.addDependencies(dependencies);
builtInFunctions.concreteGlobalBlock.addDependencies(dependencies);
builtInFunctions.newBlock.addDependencies(dependencies);
builtInFunctions.addBlockDependencies(dependencies);
}

@override
Expand Down
197 changes: 132 additions & 65 deletions lib/src/code_generator/objc_built_in_functions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ class ObjCBuiltInFunctions {
);
late final registerName = ObjCInternalFunction(
'_registerName', _registerNameFunc, (Writer w, String name) {
final s = StringBuffer();
final selType = _registerNameFunc.functionType.returnType.getCType(w);
s.write('\n$selType $name(String name) {\n');
s.write(' final cstr = name.toNativeUtf8();\n');
s.write(' final sel = ${_registerNameFunc.name}(cstr.cast());\n');
s.write(' ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);\n');
s.write(' return sel;\n');
s.write('}\n');
return s.toString();
return '''
$selType $name(String name) {
final cstr = name.toNativeUtf8();
final sel = ${_registerNameFunc.name}(cstr.cast());
${w.ffiPkgLibraryPrefix}.calloc.free(cstr);
return sel;
}
''';
});

late final _getClassFunc = Func(
Expand All @@ -38,9 +38,8 @@ class ObjCBuiltInFunctions {
);
late final getClass =
ObjCInternalFunction('_getClass', _getClassFunc, (Writer w, String name) {
final s = StringBuffer();
final objType = _getClassFunc.functionType.returnType.getCType(w);
s.write('''
return '''
$objType $name(String name) {
final cstr = name.toNativeUtf8();
final clazz = ${_getClassFunc.name}(cstr.cast());
Expand All @@ -50,8 +49,7 @@ $objType $name(String name) {
}
return clazz;
}
''');
return s.toString();
''';
});

late final _retainFunc = Func(
Expand All @@ -75,6 +73,27 @@ $objType $name(String name) {
_releaseFunc,
);

late final _blockCopyFunc = Func(
name: '_Block_copy',
originalName: '_Block_copy',
returnType: PointerType(voidType),
parameters: [Parameter(name: 'value', type: PointerType(voidType))],
isInternal: true,
);
late final _blockReleaseFunc = Func(
name: '_Block_release',
originalName: '_Block_release',
returnType: voidType,
parameters: [Parameter(name: 'value', type: PointerType(voidType))],
isInternal: true,
);
late final _blockReleaseFinalizer = ObjCInternalGlobal(
'_objc_releaseFinalizer',
(Writer w) => '${w.ffiLibraryPrefix}.NativeFinalizer('
'${_blockReleaseFunc.funcPointerName}.cast())',
_blockReleaseFunc,
);

// We need to load a separate instance of objc_msgSend for each signature.
final _msgSendFuncs = <String, Func>{};
Func getMsgSendFunc(Type returnType, List<ObjCMethodParam> params) {
Expand Down Expand Up @@ -130,17 +149,17 @@ $objType $name(String name) {
);
late final newBlockDesc =
ObjCInternalFunction('_newBlockDesc', null, (Writer w, String name) {
final s = StringBuffer();
final blockType = blockStruct.getCType(w);
final descType = blockDescStruct.getCType(w);
final descPtr = PointerType(blockDescStruct).getCType(w);
s.write('\n$descPtr $name() {\n');
s.write(' final d = ${w.ffiPkgLibraryPrefix}.calloc.allocate<$descType>('
'${w.ffiLibraryPrefix}.sizeOf<$descType>());\n');
s.write(' d.ref.size = ${w.ffiLibraryPrefix}.sizeOf<$blockType>();\n');
s.write(' return d;\n');
s.write('}\n');
return s.toString();
return '''
$descPtr $name() {
final d = ${w.ffiPkgLibraryPrefix}.calloc.allocate<$descType>(
${w.ffiLibraryPrefix}.sizeOf<$descType>());
d.ref.size = ${w.ffiLibraryPrefix}.sizeOf<$blockType>();
return d;
}
''';
});
late final blockDescSingleton = ObjCInternalGlobal(
'_objc_block_desc',
Expand All @@ -152,62 +171,67 @@ $objType $name(String name) {
(Writer w) => '${w.lookupFuncIdentifier}<${voidType.getCType(w)}>('
"'_NSConcreteGlobalBlock')",
);
late final newBlock =
ObjCInternalFunction('_newBlock', null, (Writer w, String name) {
final s = StringBuffer();
late final newBlock = ObjCInternalFunction('_newBlock', _blockCopyFunc,
(Writer w, String name) {
final blockType = blockStruct.getCType(w);
final blockPtr = PointerType(blockStruct).getCType(w);
final voidPtr = PointerType(voidType).getCType(w);
s.write('\n$blockPtr $name($voidPtr invoke, $voidPtr target) {\n');
s.write(' final b = ${w.ffiPkgLibraryPrefix}.calloc.allocate<$blockType>('
'${w.ffiLibraryPrefix}.sizeOf<$blockType>());\n');
s.write(' b.ref.isa = ${concreteGlobalBlock.name};\n');
s.write(' b.ref.invoke = invoke;\n');
s.write(' b.ref.target = target;\n');
s.write(' b.ref.descriptor = ${blockDescSingleton.name};\n');
s.write(' return b;\n');
s.write('}\n');
return s.toString();
return '''
$blockPtr $name($voidPtr invoke, $voidPtr target) {
final b = ${w.ffiPkgLibraryPrefix}.calloc.allocate<$blockType>(
${w.ffiLibraryPrefix}.sizeOf<$blockType>());
b.ref.isa = ${concreteGlobalBlock.name};
b.ref.invoke = invoke;
b.ref.target = target;
b.ref.descriptor = ${blockDescSingleton.name};
final copy = ${_blockCopyFunc.name}(b.cast()).cast<$blockType>();
${w.ffiPkgLibraryPrefix}.calloc.free(b);
return copy;
}
''';
});

bool utilsExist = false;
void ensureUtilsExist(Writer w, StringBuffer s) {
if (utilsExist) return;
utilsExist = true;

final objType = PointerType(objCObjectType).getCType(w);
void _writeFinalizableClass(
Writer w,
StringBuffer s,
String name,
String kind,
String idType,
String retain,
String release,
String finalizer) {
s.write('''
class _ObjCWrapper implements ${w.ffiLibraryPrefix}.Finalizable {
final $objType _id;
class $name implements ${w.ffiLibraryPrefix}.Finalizable {
final $idType _id;
final ${w.className} _lib;
bool _pendingRelease;

_ObjCWrapper._(this._id, this._lib,
$name._(this._id, this._lib,
{bool retain = false, bool release = false}) : _pendingRelease = release {
if (retain) {
_lib.${_retainFunc.name}(_id);
_lib.$retain(_id.cast());
}
if (release) {
_lib.${_releaseFinalizer.name}.attach(this, _id.cast(), detach: this);
_lib.$finalizer.attach(this, _id.cast(), detach: this);
}
}

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

@override
bool operator ==(Object other) {
return other is _ObjCWrapper && _id == other._id;
return other is $name && _id == other._id;
}

@override
Expand All @@ -216,6 +240,36 @@ class _ObjCWrapper implements ${w.ffiLibraryPrefix}.Finalizable {
''');
}

bool utilsExist = false;
void ensureUtilsExist(Writer w, StringBuffer s) {
if (utilsExist) return;
utilsExist = true;
_writeFinalizableClass(
w,
s,
'_ObjCWrapper',
'object',
PointerType(objCObjectType).getCType(w),
_retainFunc.name,
_releaseFunc.name,
_releaseFinalizer.name);
}

bool blockUtilsExist = false;
void ensureBlockUtilsExist(Writer w, StringBuffer s) {
if (blockUtilsExist) return;
blockUtilsExist = true;
_writeFinalizableClass(
w,
s,
'_ObjCBlockBase',
'block',
PointerType(blockStruct).getCType(w),
_blockCopyFunc.name,
_blockReleaseFunc.name,
_blockReleaseFinalizer.name);
}

void addDependencies(Set<Binding> dependencies) {
registerName.addDependencies(dependencies);
getClass.addDependencies(dependencies);
Expand All @@ -230,33 +284,46 @@ class _ObjCWrapper implements ${w.ffiLibraryPrefix}.Finalizable {
}
}

void addBlockDependencies(Set<Binding> dependencies) {
newBlockDesc.addDependencies(dependencies);
blockDescSingleton.addDependencies(dependencies);
blockStruct.addDependencies(dependencies);
concreteGlobalBlock.addDependencies(dependencies);
newBlock.addDependencies(dependencies);
_blockCopyFunc.addDependencies(dependencies);
_blockReleaseFunc.addDependencies(dependencies);
_blockReleaseFinalizer.addDependencies(dependencies);
}

final _interfaceRegistry = <String, ObjCInterface>{};
void registerInterface(ObjCInterface interface) {
_interfaceRegistry[interface.originalName] = interface;
}

void generateNSStringUtils(Writer w, StringBuffer s) {
// Generate a constructor that wraps stringWithCString.
s.write(' factory NSString(${w.className} _lib, String str) {\n');
s.write(' final cstr = str.toNativeUtf8();\n');
s.write(' final nsstr = stringWithCString_encoding_('
'_lib, cstr.cast(), 4 /* UTF8 */);\n');
s.write(' ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);\n');
s.write(' return nsstr;\n');
s.write(' }\n\n');
// Generate a constructor that wraps stringWithCString, and a toString
// method that wraps UTF8String.
s.write('''
factory NSString(${w.className} _lib, String str) {
final cstr = str.toNativeUtf8();
final nsstr = stringWithCString_encoding_(_lib, cstr.cast(), 4 /* UTF8 */);
${w.ffiPkgLibraryPrefix}.calloc.free(cstr);
return nsstr;
}

// Generate a toString method that wraps UTF8String.
s.write(' @override\n');
s.write(' String toString() => (UTF8String).cast<'
'${w.ffiPkgLibraryPrefix}.Utf8>().toDartString();\n\n');
@override
String toString() =>
(UTF8String).cast<${w.ffiPkgLibraryPrefix}.Utf8>().toDartString();
''');
}

void generateStringUtils(Writer w, StringBuffer s) {
// Generate an extension on String to convert to NSString
s.write('extension StringToNSString on String {\n');
s.write(' NSString toNSString(${w.className} lib) => '
'NSString(lib, this);\n');
s.write('}\n\n');
s.write('''
extension StringToNSString on String {
NSString toNSString(${w.className} lib) => NSString(lib, this);
}
''');
}
}

Expand Down
Loading