You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[ffigen] Use objc_retainAutoreleasedReturnValue #3653
From the documentation, I think we can just replace all our calls to objc_retain with this function. If the reference was not recently autoreleased, this is the same as an objc_retain call. But if it was autoreleased, then this essentially promotes the autoreleased reference to a strong reference.
This would reduce the number of autoreleased references we hold, helping to mitigate dart-lang/sdk#61129 / #3652 (though it's still not a complete solution, because the ObjC APIs the user is calling could autorelease their own internal references).
I actually did an experiment with this and I don't think that it always works (#3652).
The problem is that some APIs calls (e.g. NSURLSession.dataTaskWithRequest) seem to add things to the autorelease pool other than their return values. After you call NSURLSession.dataTaskWithRequest, you'll see that the 7 objects in the autorelease pool (instead of the expected one). Those objects include __NSCFLocalDataTask and __CFN_TaskMetrics (whatever those are).
Right, we'd still need the VM message loop autorelease pool for references the APIs creates internally. But I think this still worth doing, as long as objc_retainAutoreleasedReturnValue is actually a drop-in replacement for objc_retain.
From the documentation, I think we can just replace all our calls to objc_retain with this function.
...
But I think this still worth doing, as long as objc_retainAutoreleasedReturnValue is actually a drop-in replacement for objc_retain.
It's not so simple as just calling objc_retainAutoreleasedReturnValue: There is a special protocol between caller and callee.
The called ObjC function that returns the object will get the return address from the stack and look at the following machine instructions to see if it fits a certain pattern (see here). This is also described e.g. here
So we cannot just use a FFI calls to objc_msgSend and then another FFI call to objc_retainAutoreleasedReturnValue with the returned value -- it will not take it out of autorelease pool.
The called ObjC function that returns the object will get the return address from the stack and look at the following machine instructions to see if it fits a certain pattern (see here). This is also described e.g. here
FWIW, when I was experimenting with this, I created one Objective-C trampoline per bound method so clang would be responsible for generating the correct code. But I thought that this wasn't worth it because:
it's a lot of extra native code generation
it doesn't actually fix the problem (because some methods add to the autorelease pool as an implementation detail)
The landed PR does not actually do anything because there is a protocol between objc_autoreleaseReturnValue and objc_retainAutoreleasedReturnValue which you need to follow for hand-off to succeed.
TLDR is that on ARM64 objc_autoreleaseReturnValue will put a tuple of (obj, return-address) into the TLS and objc_retainAutoreleasedReturnValue will check whether return-address corresponds to its call-site. There are also some corner-cases (e.g. classes which intercept retain / release messages are handled specially) where these methods will look at instructions at the call-site to detect a special marking instructions (e.g. mov fp, fp on ARM64).
I don't think we can fully satisfy the protocol without some low-level surgery in the FFI implementation in the VM.
PS. Additional data point. If you take the PR and revert all the changes except changes in the tests - those tests will still pass. Which seems to indicate that the PR does not do anything.
Ok, that's a much bigger change then. We'd need to add a new flag similar to isLeaf to @Native etc, and then change the trampoline based on that flag. We would only really need to enable that flag for objc_msgSend and co (and only if they return an object pointer with a +0 reference).
As for how we change the trampoline:
The straightforward option would be to call objc_retainAutoreleasedReturnValue inside the trampoline, which is fine as long as the caller knows that this means the reference is now +1 instead of +0, and must be caught in a Dart wrapper object. As long as we're careful about version skew between VM/FFIgen/package:objective_c, this should be ok. A bit risky tho.
A fancier approach would be to try to make the objc_retainAutoreleasedReturnValue work by making the trampoline transparent, so that from Dart code it looks like we are directly calling objc_msgSend. This seems like it would be extremely difficult.
Overall I'm not sure it's worth it. Better to just wrap every objc_msgSend call in an autorelease pool. Seems like a lot of effort to go to to save a few ns.
Activity
brianquinlan commentedon Sep 16, 2026
I actually did an experiment with this and I don't think that it always works (#3652).
The problem is that some APIs calls (e.g.
NSURLSession.dataTaskWithRequest) seem to add things to the autorelease pool other than their return values. After you callNSURLSession.dataTaskWithRequest, you'll see that the 7 objects in the autorelease pool (instead of the expected one). Those objects include__NSCFLocalDataTaskand__CFN_TaskMetrics(whatever those are).liamappelbe commentedon Sep 16, 2026
Right, we'd still need the VM message loop autorelease pool for references the APIs creates internally. But I think this still worth doing, as long as
objc_retainAutoreleasedReturnValueis actually a drop-in replacement forobjc_retain.mkustermann commentedon Sep 18, 2026
It's not so simple as just calling
objc_retainAutoreleasedReturnValue: There is a special protocol between caller and callee.The called ObjC function that returns the object will get the return address from the stack and look at the following machine instructions to see if it fits a certain pattern (see here). This is also described e.g. here
So we cannot just use a FFI calls to
objc_msgSendand then another FFI call toobjc_retainAutoreleasedReturnValuewith the returned value -- it will not take it out of autorelease pool.brianquinlan commentedon Sep 18, 2026
FWIW, when I was experimenting with this, I created one Objective-C trampoline per bound method so clang would be responsible for generating the correct code. But I thought that this wasn't worth it because:
The trampoline looks like:
The generated code ends with:
You can then change the generated binding like:
mraleph commentedon Sep 21, 2026
The landed PR does not actually do anything because there is a protocol between
objc_autoreleaseReturnValueandobjc_retainAutoreleasedReturnValuewhich you need to follow for hand-off to succeed.See the code, more specifically
prepareOptimizedReturnandacceptOptimizedReturn.TLDR is that on ARM64
objc_autoreleaseReturnValuewill put a tuple of(obj, return-address)into the TLS andobjc_retainAutoreleasedReturnValuewill check whetherreturn-addresscorresponds to its call-site. There are also some corner-cases (e.g. classes which interceptretain/releasemessages are handled specially) where these methods will look at instructions at the call-site to detect a special marking instructions (e.g.mov fp, fpon ARM64).I don't think we can fully satisfy the protocol without some low-level surgery in the FFI implementation in the VM.
PS. Additional data point. If you take the PR and revert all the changes except changes in the tests - those tests will still pass. Which seems to indicate that the PR does not do anything.
liamappelbe commentedon Sep 22, 2026
Ok, that's a much bigger change then. We'd need to add a new flag similar to
isLeafto@Nativeetc, and then change the trampoline based on that flag. We would only really need to enable that flag forobjc_msgSendand co (and only if they return an object pointer with a +0 reference).As for how we change the trampoline:
objc_retainAutoreleasedReturnValueinside the trampoline, which is fine as long as the caller knows that this means the reference is now +1 instead of +0, and must be caught in a Dart wrapper object. As long as we're careful about version skew between VM/FFIgen/package:objective_c, this should be ok. A bit risky tho.objc_retainAutoreleasedReturnValuework by making the trampoline transparent, so that from Dart code it looks like we are directly callingobjc_msgSend. This seems like it would be extremely difficult.Overall I'm not sure it's worth it. Better to just wrap every
objc_msgSendcall in an autorelease pool. Seems like a lot of effort to go to to save a few ns.