|
|
|
|
|
by raphlinus
1664 days ago
|
|
Thanks, this is helpful. The specific method that's causing me trouble right at the moment is "computeCommandEncoder"[1], which is a method off MTLCommandBuffer, and I think not in the "new" class. In the Rust bindings[2], this is just a msg_send! and from what I can tell is getting an autoreleased reference, not a retained one. 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... [3]: https://docs.rs/objc/0.2.7/objc/runtime/index.html |
|
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:
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/