The issue here is that we have turned off the optimization that would trigger here since in the general case in the context of potentially mismatched retain/release pairs, this is unsafe. Making such a transformation safe is part of the work of Semantic SIL.
Now that being said, I wonder if in such small cases like this, we could peephole. I would have to think about it. You would have to be able to guarantee that there aren't any retains/releases in between the retain/release that either of the instructions could be paired with.
Would it be difficult to change SILGen to not emit the unnecessary retain/release in the first place? It knows the parameter is guaranteed, and I suspect it converts it to a +1 value needlessly.
The retain is necessary because fire() could end up modifying self.next. There's not really a better answer here without a much smarter effects system.
My hope is that when semantic sil is done, you will not need such support in the general case. That being said, I don't remember all of the use cases for this and the restrictions. I would ask Arnold. But that being said I am not 100% sure what his forum name is (@Arnold)?
I don't think this is going to see the light of day in a supported form (without the underscore).
Michael is right that the need for this should mostly be subsumed by optimizing semantic sil.
The constraints are are outlined in the proposal. The user of _withUnsafeGuaranteedRef needs to ensure that there is another reference keeping next alive for the duration of the _withUnsafeGuaranteedRef call. The code you outlined is unsafe without further knowledge. self: Test lifetime is not guaranteed across the call. There is nothing that prevents the optimizer from moving the potentially final release of self before the withUnsafeGuaranteed call. Something like _fixlifetime has to ensure safety.
public func fire() {
self.next._withUnsafeGuaranteedRef {
$0.fire()
}
_fixLifetime(self)
}
Thanks @Arnold, I'm aware that I will need to guarantee that there's another reference keeping the object _withUnsafeGuaranteedRef is called on alive (sorry, my example was quite incomplete). In my actual application that is definitely ensured so I believe that should be safe.
@Michael_Gottesman what I can't quite follow yet is how with semantic SIL done we could proof this automatically. Let's assume I have
public final class Node {
internal var next: Node?
internal var prev: Node?
public func fire() {
[...]
self.next?.fire()
}
}
So essentially I have a doubly linked list of Nodes. Now in my application I know, that the linked list never gets modified in the fire calls. Therefore I know that ref-counting self.next isn't necessary. That's exactly what I wanted to express with the _withUnsafeGuaranteedRef. Would semantic SIL done really be able to elide this ref-counting by itself, ie. without using _withUnsafeGuaranteedRef?
Yes. Given that you are dealing with a final in module thing, our analysis will know that the value is not being written to. In such a case, we would be able to change the load [copy] to a load borrow. I think you would need some extra constraints (I am hand waving a little bit), but that is the general gist.