Updated proposal: use a method that accepts a closure to delineate the required 
guaranteed lifetime for which the assertion by the programmer holds.
And, hopefully, no optimizer language speak.


Add an API to Unmanaged to get the instance it holds @guaranteed

Proposal: SE-0000 Add an API to Unmanaged to get the instance it holds 
guaranteed 
<https://github.com/aschwaighofer/swift-evolution/blob/unmanaged_unsafe_guaranteed_proposal/proposals/0000-unsafe-guaranteed.md>
Author(s): Arnold Schwaighofer <https://github.com/aschwaighofer>
Status: Draft
Review manager: TBD
 
<https://github.com/aschwaighofer/swift-evolution/blob/unmanaged_unsafe_guaranteed_proposal/proposals/0000-unsafe-guaranteed.md#introduction>Introduction

The standard library Unmanged<Instance> struct provides an instance wrapper 
that does not participate in ARC; it allows the user to make manual 
retain/release calls and get at the contained instance at balanced/unbalanced 
retain counts.

This proposal suggests to add another method withUnsafeGuaranteedValue to 
Unmanaged that accepts a closure. Calling this method is akin to making an 
assertion about the guaranteed lifetime of the instance for the delinated scope 
of the method invocation. The closure is passed the unmanaged reference as a 
managed reference.

  func doSomething(u : Unmanged<Owned>) {
    // The programmer asserts that there exists another managed reference of the
    // unmanaged reference stored in 'u' and that the lifetime of the referenced
    // instance is guaranteed to extend beyond the 'withUnsafeGuaranteedValue'
    // invocation.
    u.withUnsafeGuaranteedValue {
      $0.doSomething()
    }
  }
This assertion will help the compiler remove ARC operations.

public struct Unmanaged<Instance : AnyObject> {

  // Get the value of the unmanaged referenced as a managed reference without
  // consuming an unbalanced retain of it and pass it to the closure. Asserts
  // that there is some other reference ('the owning reference') to the
  // unmanaged reference that guarantees the lifetime of the unmanaged reference
  // for the duration of the 'withUnsafeGuaranteedValue' call.
  //
  // NOTE: You are responsible for ensuring this by making the owning 
reference's
  // lifetime fixed for the duration of the 'withUnsafeGuaranteedValue' call.
  //
  // Violation of this will incur undefined behavior.
  //
  // A lifetime of a reference 'the instance' is fixed over a point in the
  // programm if:
  //
  // * There is another managed reference to the same instance 'the instance'
  //   whose life time is fixed over the point in the program by means of
  //  'withExtendedLifetime' closing over this point.
  //
  //   var owningReference = Instance()
  //   ...
  //   withExtendedLifetime(owningReference) {
  //       point($0)
  //   }
  //
  // Or if:
  //
  // * There is a class, or struct instance ('owner') whose lifetime is fixed at
  //   the point and which has a stored property that references 'the instance'
  //   for the duration of the fixed lifetime of the 'owner'.
  //
  //  class Owned {
  //  }
  //
  //  class Owner {
  //    final var owned : Owned
  //
  //    func foo() {
  //        withExtendedLifetime(self) {
  //            doSomething(...)
  //        } // Assuming: No stores to owned occur for the dynamic lifetime of
  //          //           the withExtendedLifetime invocation.
  //    }
  //
  //    func doSomething() {
  //       // both 'self' and 'owned''s lifetime is fixed over this point.
  //       point(self, owned)
  //    }
  //  }
  //
  // The last rule applies transitively through a chains of stored references
  // and nested structs.
  //
  // Examples:
  //
  //   var owningReference = Instance()
  //   ...
  //   withExtendedLifetime(owningReference) {
  //     let u = Unmanaged.passUnretained(owningReference)
  //     for i in 0 ..< 100 {
  //       u.withUnsafeGuaranteedValue {
  //         $0.doSomething()
  //       }
  //     }
  //   }
  //
  //  class Owner {
  //    final var owned : Owned
  //
  //    func foo() {
  //        withExtendedLifetime(self) {
  //            doSomething(Unmanaged.passUnretained(owned))
  //        }
  //    }
  //
  //    func doSomething(u : Unmanged<Owned>) {
  //      u.withUnsafeGuaranteedValue {
  //        $0.doSomething()
  //      }
  //    }
  //  }
  public func withUnsafeGuaranteedValue<Result>(
    @noescape closure: (Instance) throws -> Result
  ) rethrows {
    let instance = _value
    let (guaranteedInstance, token) = Builtin.unsafeGuaranteed(instance)
    try closure(guaranteedInstance)
    Builtin.unsafeGuaranteedEnd(token)
  }
Prototype: link to a prototype implementation 
<https://github.com/aschwaighofer/swift/tree/unsafe_guaranteed_prototype>
 
<https://github.com/aschwaighofer/swift-evolution/blob/unmanaged_unsafe_guaranteed_proposal/proposals/0000-unsafe-guaranteed.md#motivation>Motivation

A user can often make assertions that an instance is kept alive by another 
reference to it for the duration of some scope.

Consider the following example:

class Owned {
  func doSomeWork() {}
}

public class Owner {
  var ref: Owned

  init() { ref = Owned() }

  public func doWork() {
    withExtendedLifetime(self) {
      doSomething(ref)
    }
  }

  func doSomething(o: Owned) {
     o.doSomeWork()
  }
}
In this context the lifetime of Owner always exceeds o: Ownee. However, there 
is currently no way to tell the compiler about such an assertion. We can pass 
reference counted values in an Unmanged container without incurring reference 
count changes.

public class Owner {

  public func doWork() {
    // No reference count for passing the parameter.
    doSomething(Unmanaged.passUnretained(ref))
  }

  func doSomething(u: Unmanaged<Owned>) {
    // ...
  }
}
We can get at the contained instance by means of takeUnretainedValue() which 
will return a balanced retained value: the value is returned at +1 for release 
by the caller. However, when it comes to accessing the contained instance we 
incur reference counting.

  func doSomething(u: Unmanaged<Owned>) {
    // Incurs refcount increment before the call and decrement after for self.
    u.takeUnretainedValue().doSomeWork()
  }
With the proposed API call the user could make the assertion that u's contained 
instance's lifetime is guaranteed by another reference to it.

  func doSomething(u: Unmanaged<Owned>) {
    // Incurs refcount increment before the call and decrement after for self
    // that can be removed by the compiler based on the assertion made.
    u.withUnsafeGuaranteedValue {
      $0.doSomeWork()
    }
  }
The compiler can easily remove the reference counts marked by this method call 
with local reasoning.

 
<https://github.com/aschwaighofer/swift-evolution/blob/unmanaged_unsafe_guaranteed_proposal/proposals/0000-unsafe-guaranteed.md#impact-on-existing-code>Impact
 on existing code

This is a new API that does not replace an existing method. No existing code 
should be affected.

 
<https://github.com/aschwaighofer/swift-evolution/blob/unmanaged_unsafe_guaranteed_proposal/proposals/0000-unsafe-guaranteed.md#alternatives-considered>Alternatives
 considered

A somewhat related proposal would be to allow for class types to completely opt 
out of ARC forcing the programmer to perform manual reference counting. The 
scope of such a proposal would be much bigger making it questionable for Swift 
3 inclusion. I believe that the two approaches are complementary and don't 
completely supplant each other. This proposal's approach allows for selectively 
opting out of ARC while providing a high notational burdon when wide spread 
over the application (wrapping and unwrapping of Unmanaged). Opting completely 
out of ARC is an all-in solution, which on the other hand does not have the 
high notational burden.


> On Mar 15, 2016, at 4:06 PM, Dave Abrahams <[email protected]> wrote:
> 
> 
> on Mon Mar 14 2016, John McCall <rjmccall-AT-apple.com> wrote:
> 
>>> 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?
>> 
>> 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.
> 
> Thank you, John.  That is the problem I usually have with evaluating
> most optimizer proposals.

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to