> On Dec 6, 2016, at 2:23 PM, John McCall via swift-dev <swift-dev@swift.org>
> wrote:
>
>> On Dec 6, 2016, at 11:35 AM, Joe Groff <jgr...@apple.com
>> <mailto:jgr...@apple.com>> wrote:
>>> On Dec 6, 2016, at 11:29 AM, John McCall <rjmcc...@apple.com
>>> <mailto:rjmcc...@apple.com>> wrote:
>>>
>>>> On Dec 6, 2016, at 10:17 AM, Joe Groff via swift-dev <swift-dev@swift.org
>>>> <mailto:swift-dev@swift.org>> wrote:
>>>>> On Dec 5, 2016, at 4:24 PM, Michael Gottesman via swift-dev
>>>>> <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:
>>>>>
>>>>> Hello everyone!
>>>>>
>>>>> This is a proposal for 2 instructions needed to express borrowing via SSA
>>>>> at the SIL level. The need for these were discovered while I was
>>>>> prototyping a SIL ownership verifier.
>>>>>
>>>>> A html version of the proposal:
>>>>>
>>>>> https://gottesmm.github.io/proposals/sil-ownership-value-ssa-operations.html
>>>>>
>>>>> <https://gottesmm.github.io/proposals/sil-ownership-value-ssa-operations.html>
>>>>>
>>>>> And inline:
>>>>>
>>>>> ----
>>>>>
>>>>> # Summary
>>>>>
>>>>> This document proposes the addition of the following new SIL instructions:
>>>>>
>>>>> 1. `store_borrow`
>>>>> 2. `begin_borrow`
>>>>>
>>>>> These enable the expression of the following operations in Semantic SIL:
>>>>>
>>>>> 1. Passing an `@guaranteed` value to an `@in_guaranteed` argument without
>>>>> performing a copy. (`store_borrow`)
>>>>> 2. Copying a field from an `@owned` aggregate without consuming or
>>>>> copying the entire
>>>>> aggregate. (`begin_borrow`)
>>>>> 3. Passing an `@owned` value as an `@guaranteed` argument parameter.
>>>>>
>>>>> # Definitions
>>>>>
>>>>> ## store_borrow
>>>>>
>>>>> Define `store_borrow` as:
>>>>>
>>>>> store_borrow %x to %y : $*T
>>>>> ...
>>>>> end_borrow %y from %x : $*T, $T
>>>>>
>>>>> =>
>>>>>
>>>>> store %x to %y
>>>>>
>>>>> `store_borrow` is needed to convert `@guaranteed` values to
>>>>> `@in_guaranteed`
>>>>> arguments. Without a `store_borrow`, this can only be expressed via an
>>>>> inefficient `copy_value` + `store` + `load` + `destroy_value` sequence:
>>>>>
>>>>> sil @g : $@convention(thin) (@in_guaranteed Foo) -> ()
>>>>>
>>>>> sil @f : $@convention(thin) (@guaranteed Foo) -> () {
>>>>> bb0(%0 : $Foo):
>>>>> %1 = function_ref @g : $@convention(thin) (@in_guaranteed Foo) -> ()
>>>>> %2 = alloc_stack $Foo
>>>>> %3 = copy_value %0 : $Foo
>>>>> store %3 to [init] %2 : $Foo
>>>>> apply %1(%2) : $@convention(thin) (@in_guaranteed Foo) -> ()
>>>>> %4 = load [take] %2 : $*Foo
>>>>> destroy_value %4 : $Foo
>>>>> dealloc_stack %2 : $Foo
>>>>> ...
>>>>> }
>>>>>
>>>>> `store_borrow` allows us to express this in a more efficient and
>>>>> expressive SIL:
>>>>>
>>>>> sil @f : $@convention(thin) (@guaranteed Foo) -> () {
>>>>> bb0(%0 : $Foo):
>>>>> %1 = function_ref @g : $@convention(thin) (@in_guaranteed Foo) -> ()
>>>>> %2 = alloc_stack $Foo
>>>>> store_borrow %0 to %2 : $*T
>>>>> apply %1(%2) : $@convention(thin) (@in_guaranteed Foo) -> ()
>>>>> end_borrow %2 from %0 : $*T, $T
>>>>> dealloc_stack %2 : $Foo
>>>>> ...
>>>>> }
>>>>>
>>>>> **NOTE** Once `@in_guaranteed` arguments become passed as values,
>>>>> `store_borrow`
>>>>> will no longer be necessary.
>>>>>
>>>>> ## begin_borrow
>>>>>
>>>>> Define a `begin_borrow` instruction as:
>>>>>
>>>>> %borrowed_x = begin_borrow %x : $T
>>>>> %borrow_x_field = struct_extract %borrowed_x : $T, #T.field
>>>>> apply %f(%borrowed_x) : $@convention(thin) (@guaranteed T) -> ()
>>>>> end_borrow %borrowed_x from %x : $T, $T
>>>>>
>>>>> =>
>>>>>
>>>>> %x_field = struct_extract %x : $T, #T.field
>>>>> apply %f(%x_field) : $@convention(thin) (@guaranteed T) -> ()
>>>>>
>>>>> A `begin_borrow` instruction explicitly converts an `@owned` value to a
>>>>> `@guaranteed` value. The result of the `begin_borrow` is paired with an
>>>>> `end_borrow` instruction that explicitly represents the end scope of the
>>>>> `begin_borrow`.
>>>>>
>>>>> `begin_borrow` also allows for the explicit borrowing of an `@owned`
>>>>> value for
>>>>> the purpose of passing the value off to an `@guaranteed` parameter.
>>>>>
>>>>> *NOTE* Alternatively, we could make it so that *_extract operations
>>>>> started
>>>>> borrow scopes, but this would make SIL less explicit from an ownership
>>>>> perspective since one wouldn't be able to visually identify the first
>>>>> `struct_extract` in a chain of `struct_extract`. In the case of
>>>>> `begin_borrow`,
>>>>> there is no question and it is completely explicit.
>>>>
>>>> begin_borrow SGTM. Does end_borrow need to be explicit, or could we leave
>>>> it implicit and rely on dataflow diagnostics to ensure the borrowed
>>>> value's lifetime is dominated by the owner's? It seems to me like, even if
>>>> end_borrow is explicit, we'd want a lifetime-shortening pass to shrinkwrap
>>>> end_borrows to the precise lifetime of the borrowed value's uses.
>>>
>>> I definitely think it should be explicit, as Michael has it.
>>
>> Would you be able to elaborate why? I suppose explicit is a more
>> conservative starting point. It feels to me like making it explicit isn't
>> doing much more than imposing more verification and optimization burden on
>> us, but I'm probably missing something.
>
> Well, for one, that verification burden isn't unimportant. Under ownership,
> DI has to enforce things about borrowed values during the lifetime of the
> borrow. I expect that to apply to values and not just variables. Having
> lifetimes marked out explicitly should make that much saner.
>
> It's also quite a bit easier to verify things when there's a simple nesting
> property, e.g.
> %1 = load_borrow %0
> %2 = struct_element borrow %1, $foo
> %3 = blah
> end_borrow %2
> end_borrow %1
> as opposed to tracking that uses of %2 implicitly require both %2 and %1 to
> have remained borrowed.
>
> For another, it's not obvious that borrowing is a trivial operation. If
> borrowing can change representations, as it does in Rust and as we might have
> to do in Swift (for tuples at least, maybe for arrays/strings/whatever), then
> something needs to represent the lifetime of that representation, and
> creating it for an opaque T may be non-trivial.
>
> And even if we don't need to generate code normally at begin_borrow /
> end_borrow points, I can pretty easily imagine that being interesting for
> extra, sanitizer-style instrumentation.
>
> John.
Yes, we also need explicit markers for code motion barriers so we don’t need to
consider any “use” a potential code barrier.
However, in the most recent proposal I’ve seen, I think we plan to have this
instead:
%1 = load_borrow %0 (alternatively begin_borrow)
%2 = struct_extract %1, #field (implied subobject borrow)
%3 = blah %2
end_borrow %1
Note:
- struct_extract only works on a borrowed parent object, so there’s no need for
another scope.
- %2 is a dependent value on %1
- You can’t simultaneously shared-borrow one subobject of a value while
unique-borrowing another because unique-borrowing requires an address.
Andy
_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev