> On Mar 14, 2016, at 3:08 PM, Arnold Schwaighofer via swift-evolution 
> <[email protected]> wrote:
> 
>> 
>> On Mar 14, 2016, at 1:29 PM, John McCall <[email protected]> wrote:
>> 
>>> On Mar 14, 2016, at 12:51 PM, Arnold Schwaighofer <[email protected]> 
>>> wrote:
>>>> On Mar 14, 2016, at 11:53 AM, John McCall <[email protected]> wrote:
>>>> 
>>>>> On Mar 14, 2016, at 8:00 AM, Arnold Schwaighofer 
>>>>> <[email protected]> wrote:
>>>>> I would like to propose adding an additional method to the standard 
>>>>> library’s ‘Unmanaged’ wrapper to mark values as having a guaranteed 
>>>>> lifetime by another reference to it.
>>>>> 
>>>>> Currently, there is no way to opt out of ARC even if we know the lifetime 
>>>>> of a value is guaranteed by another reference to it. We can pass around 
>>>>> values in the wrapper without incurring reference counting but as soon as 
>>>>> we take the instance out of the wrapper we will incur reference counting.
>>>>> 
>>>>> func doSomething(u: Unmanaged<Owned>
>>>>> ) {
>>>>> 
>>>>> // Incurs refcount increment before the call and decrement after for self.
>>>>> 
>>>>>  u
>>>>> .takeUnretainedValue().doSomeWork()
>>>>> 
>>>>> The addition of this API will allow selectively disabling ARC for the 
>>>>> scope of the lifetime of the value returned by this API call.
>>>> 
>>>> Calling this method makes a semantic guarantee that the returned reference 
>>>> is being kept alive somehow for a certain period of time.  So, the obvious 
>>>> questions here are:
>>>> 
>>>> 1. What are the bounds of that period of time?
>>> 
>>> I mention in the API doc that this is for the duration of lifetime the 
>>> returned value.
>>> 
>>> var x = takeUnretainedValue()
>>> var y = x
>>> 
>>> In this example this would be for the lifetime for the value in x which 
>>> extends to the lifetime of y.
>> 
>> What if I return it or assign it to non-local memory?
> 
> See my comment about the l-value.
> 
>> 
>> I feel like you’re trying to define this by optimizer behavior.  That’s not 
>> a workable language rule; programmers are not going to reason about SSA 
>> values.
> 
> I did mentioned that this applies to storing the value in an l-value. 
> 
> But I think you are saying to define this in terms of a “precise lifetime 
> language”. I will try to talk in person to flesh out the language.
> 
> 
>> 
>>> I will try to address your comments on needing more precise language here 
>>> (precise lifetime), i.e for locals we need to fix the lifetime, classes, 
>>> globals.
>>> 
>>> 
>>>> 2. How can the user actually satisfy this guarantee?
>>> 
>>> By “fixing the lifetime” of the value is one way or by relying on lifetime 
>>> relation ships, see the class example I give. The fact that we are calling 
>>> a method on "self: Owner" guarantees that the reference in “ref: Ownee” was 
>>> not ultimately released (Admittedly, this is relying on self being passed 
>>> @guaranteed). The user must know that nobody will store to “ref” in-between.
>>> 
>>> Your point is that this needs clear language.
>>> 
>>> "
>>> // Get the value of the unmanaged referenced as a managed reference without
>>> // consuming an unbalanced retain of it. Asserts that some other owner 
>>> // guarantees the lifetime of the value for the lifetime of the return 
>>> managed
>>> // reference. You are responsible for making sure that this assertion holds
>>> // true.
>>> //
> 
> 
> Comment about the l-value:
> 
> 
>>> // NOTE:
>>> // Be aware that storing the returned managed reference in an L-Value 
>>> extends
>>> // the lifetime this assertion must hold true for by the lifetime of the  
>>> // L-Value.
>>> //   var owningReference = Instance()  
>>> //   var lValue : Instance
>>> //   withFixedLifetime(owningReference) { 
>>> //     lValue = 
>>> Unmanaged.passUnretained(owningReference).takeGuaranteedValue()
>>> //   }
>>> //   lValue.doSomething() // Bug: owningReference lifetime has ended 
>>> earlier.
>>> "
>>> 
>>>> 3. What happens if they’re wrong?
>>> 
>>> Undefined behavior. This is an unsafe extension. Users can shoot themselves 
>>> in the foot today using Unmanaged by calling release() on it.
>>> 
>>>> 
>>>> #1 is totally unclear in your proposal.  Usually we do this sort of 
>>>> scoping thing with a callback, rather than a property, for exactly the 
>>>> reason that it lets us naturally bound the extent of the guarantee.  The 
>>>> library’s implementation of that function would then use some builtin 
>>>> functions to bound how long the guarantee lasts.  If we decide that that’s 
>>>> too awkward to actually use, we could burn this into the language as a 
>>>> custom result convention and invent some language rules for the guaranteed 
>>>> extents of such a result based on how the expression is used; that would 
>>>> permit more APIs to opt in to a +0 guaranteed result, but the language 
>>>> rules would probably be pretty ad hoc.  
>>> 
>>> 
>>> I am not sure you can bound it this that way. I think we want this 
>>> assertion to be propagated through l-values.
>>> 
>>> 
>>> var outlive: Ownee
>>> var owningReference: Ownee
>>> 
>>> withFixedLifetime(owningReference) {
>>> Unamanged.passUnretained(owningReference).withGuaranteedValue {
>>>   escape($0, to: &outlive)
>>> }
>>> 
>>> I think we should just define this as undefined behavior.
>> 
>> Why?  It doesn’t seem harmful.  It’s just that the guarantee doesn’t apply.
> 
> Yes the guarantee does not apply, the user has escaped “the returned value to 
> an l-value that outlives the “owningReference”.
>> 
>> …to be clear, I certainly hope that the intended optimization strategy here 
>> isn’t just to unconditionally remove any retains and releases visible within 
>> the guaranteed block.  You can still at best remove them in pairs.  The 
>> effect on the optimizer here is just that uses that are known to occur 
>> within a guaranteed block can be ignored for the purposes of eliminating 
>> imprecise retain/release pairs.
> 
> 
> Yes, only paired removal. Just like we can do with @guaranteed parameters 
> today.
> 
> 
> But imagine the “escape” function has been inlined. The optimizer will see:
> 
> Unamanged.passUnretained(owningReference).withGuaranteedValue {
>   outlive = $0
> }
> 
> This is not different to
> 
> Unamanged.passUnretained(owningReference).withGuaranteedValue {
>   let x = $0
>   x.doSomething()
> }
> 
> And we want to optimize the latter.
> 
> The closure syntax does not add any guarantee.

Let me retract this, I think I understand how you see the semantics of 
withGuaranteedValue:


  tmp = Builtin.unsafeGuaranteed(_unmanaged_ref) returns a managed reference
  closure(tmp) // consumes a managed reference
  Builtin.endUnsafeGuaranteed(tmp) // ends the lifetime for which the guarantee 
must hold.

I agree that would make the awkward language of transitively having to 
guarantee the lifetime through l-values unnecessary.
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to