In obj-c or swift, ARC would handle that for you, so this makes the memory management aspect of using Metal a bit more of a headache.
Objective-C has a standard rule for when a method is supposed to autorelease its return value: to quote the metal-cpp readme, it's when "you create an object with a method that does not begin with `alloc`, `new`, `copy`, or `mutableCopy`". In many cases, these are convenience methods that also have equivalent "initWith" methods, e.g.
[NSString stringWithFormat:@"%d", 42]
is equivalent to [[[NSString alloc] initWithFormat:@"%d", 42] autorelease]
But I'm not too familiar with Metal, and… it looks like Metal doesn't have very many of these methods in the first place. Instead it has a lot of 'new' methods, which shouldn't use autorelease.When a method does call autorelease, Swift doesn't have any way of getting around it, though if there are autoreleasing and alloc/init variants of the same method, it will prefer alloc/init. Other than that, Swift likes to use the function objc_retainAutoreleasedReturnValue [1], which may prevent the object from going on the autorelease pool (with the 'optimized return' magic), but only as a non-guaranteed optimization.
[1] https://github.com/apple-opensource/objc4/blob/a367941bce42b...
It looks like objc_retainAutoreleasedReturnValue might be exactly what I'm looking for, even if it isn't 100% guaranteed; if I'm understanding, it would be safe, and wouldn't actually leak as long as you had an autorelease pool somewhere in the chain. However, I'm not seeing a binding to it in the objc crate[3]. Sounds like maybe I should file an issue?
Also, given that such a method seems obviously useful, I wonder why it's not being called from these C++ bindings?
[1]: https://developer.apple.com/documentation/metal/mtlcommandbu...
[2]: https://github.com/gfx-rs/metal-rs/blob/master/src/commandbu...
Yeah, it looks like there's no way to avoid autorelease here.
> It looks like objc_retainAutoreleasedReturnValue might be exactly what I'm looking for, even if it isn't 100% guaranteed; if I'm understanding, it would be safe, and wouldn't actually leak as long as you had an autorelease pool somewhere in the chain.
Indeed it would be safe and wouldn't leak, but the optimization is very much not guaranteed. It's based on the autorelease implementation manually reading the instruction at its return address to see if it's about to call objc_retainAutoreleaseReturnValue. See the description here:
https://github.com/apple-opensource/objc4/blob/a367941bce42b...
In fact – I did not know this before just now – on every arch other than x86-64 it requires a magic assembly sequence to be placed between the call to an autoreleasing method and the call to objc_retainAutoreleaseReturnValue.
It looks like swiftc implements this by just emitting LLVM inline asm blocks:
%6 = call %1* bitcast (void ()* @objc_msgSend to %1* (i8*, i8*, %0*)*)(i8* %5, i8* %3, %0* %4) #4
call void asm sideeffect "mov\09fp, fp\09\09// marker for objc_retainAutoreleaseReturnValue", ""()
%7 = bitcast %1* %6 to i8*
%8 = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %7)
This is optimistically assuming that LLVM won't emit any instructions between the call instruction and the magic asm, which is not guaranteed, especially if compiler optimizations are off. But if it does emit extra instructions, then you just don't get the autorelease optimization: the object is added to the autorelease pool, and objc_retainAutoreleaseReturnValue simply calls objc_retain.(…Though, on second look, it seems that swiftc and clang sometimes use a different, more robust approach to emitting the same magic instruction… but only sometimes.)
Regardless, enough stars have to align for the optimization to work that you shouldn't rely on it to avoid a (temporary) memory leak; you should only treat it as an optional micro-optimization.
That said, the C++ buildings could have implemented the same scheme using inline assembly. And so could the Rust crate (edit: well, I guess inline asm is not stable in Rust yet). It's not the like magic instructions are ABI unstable or anything, given that clang and swiftc happily stick them in when compiling any old Objective-C or Swift code. But I'm guessing the authors of the C++ bindings either didn't want to bother with inline assembly, or considered it an unnecessary micro-optimization. Or perhaps didn't even know about it. /shrug/
Swift and obj-c have the same ARC semantics, so I'm not sure what you mean by swift-style refcounting. It should be identical to the obj-c ARC semantics.
https://clang.llvm.org/docs/AutomaticReferenceCounting.html outlines the ARC semantics, including the autorelease behavior.
Swift and ObjC implementations have levers which discourage objects being sent to the pool in common cases. It is possible to pull them from other languages but not easy.
It doesn't break ARC right, it just won't do the automatic reference counting in C++ source. You can send it back and forth over the wall with CFBridgingRetain and CFBridgingRelease no?
Having an autorelease pool is pretty standard practice, and ARC works the same way in ObjC (and I assume Swift) apps - stuff that was autoreleased accumulates in the pool until it is drained, which tends to happen every turn of the run loop.
I guess I should say that clang's ARC doesn't apply to these c++ wrappers.
The c++ code is loading symbols using the objc runtime API's to load symbols and call objc runtime APIs to dispatch the metal calls. So, you could pass these references to other objc code and it should have the correct refcounts afterwards.
Having to manually retain/release objects is a bit of a pain, but its workable.
Using a c++ RAII type to retain/release is also somewhat do-able, but I worked in a codebase that had that kind of code and it can be frustrating to get the refcounts to be correct synchronized. Although that was back with c++11, so I'm sure things have changed that would make this easier today.