isUniquelyReferenced issues


(Patrick Pijnappel) #1

In trying to implement a COW type, but I'm running into problems with
isUniqueReferenced breaking in even fairly simple cases. For example (with
-O) the code below prints "bar: false", commenting out the print in
test() makes
it print "bar: true", and removing the var parameter var foo: Foo and using
var foo = foo instead breaks it again. Am I doing something wrong here?

class FooStorage { var x: Int = 0 }

struct Foo { var storage = FooStorage() }

func bar(var foo: Foo) {

print("bar: \(isUniquelyReferencedNonObjC(&foo.storage))")

}

func test() {

var foo = Foo()

print("test: \(isUniquelyReferencedNonObjC(&foo.storage))")

bar(foo)

}

test()


(Joe Groff) #2

You're not doing anything wrong, this is just the ARC optimizer at work. `foo` inside `test` is dead after the call to `bar`, so ownership is transferred directly to `bar`'s parameter.

-Joe

···

On Mar 31, 2016, at 3:58 PM, Patrick Pijnappel via swift-dev <swift-dev@swift.org> wrote:

In trying to implement a COW type, but I'm running into problems with isUniqueReferenced breaking in even fairly simple cases. For example (with -O) the code below prints "bar: false", commenting out the print in test() makes it print "bar: true", and removing the var parameter var foo: Foo and using var foo = foo instead breaks it again. Am I doing something wrong here?

class FooStorage { var x: Int = 0 }

struct Foo { var storage = FooStorage() }

func bar(var foo: Foo) {
  print("bar: \(isUniquelyReferencedNonObjC(&foo.storage))")
}

func test() {
  var foo = Foo()
  print("test: \(isUniquelyReferencedNonObjC(&foo.storage))")
  bar(foo)
}

test()


(Joe Groff) #3

If you want to ensure that `foo` remains alive despite this, you can use `withExtendedLifetime`:

class FooStorage { var x: Int = 0 }

struct Foo { var storage = FooStorage() }

func bar(var foo: Foo) {
  print("bar: \(isUniquelyReferencedNonObjC(&foo.storage))")
}

func test() {
  var foo = Foo()
  print("test: \(isUniquelyReferencedNonObjC(&foo.storage))")
  withExtendedLifetime(foo) {
    bar(foo)
  }
}

-Joe

···

On Mar 31, 2016, at 4:21 PM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

On Mar 31, 2016, at 3:58 PM, Patrick Pijnappel via swift-dev <swift-dev@swift.org> wrote:

In trying to implement a COW type, but I'm running into problems with isUniqueReferenced breaking in even fairly simple cases. For example (with -O) the code below prints "bar: false", commenting out the print in test() makes it print "bar: true", and removing the var parameter var foo: Foo and using var foo = foo instead breaks it again. Am I doing something wrong here?

class FooStorage { var x: Int = 0 }

struct Foo { var storage = FooStorage() }

func bar(var foo: Foo) {
  print("bar: \(isUniquelyReferencedNonObjC(&foo.storage))")
}

func test() {
  var foo = Foo()
  print("test: \(isUniquelyReferencedNonObjC(&foo.storage))")
  bar(foo)
}

test()

You're not doing anything wrong, this is just the ARC optimizer at work. `foo` inside `test` is dead after the call to `bar`, so ownership is transferred directly to `bar`'s parameter.


(Patrick Pijnappel) #4

The modified version doesn't seem to change any of the results (on -O or
-Onone). Note that the problem is that it's *not* uniquely referenced
inside bar where it actually should be – that would mean that ownership is
currently not directly transferred right?

···

On Fri, Apr 1, 2016 at 10:27 AM, Joe Groff <jgroff@apple.com> wrote:

> On Mar 31, 2016, at 4:21 PM, Joe Groff via swift-dev < > swift-dev@swift.org> wrote:
>
>>
>> On Mar 31, 2016, at 3:58 PM, Patrick Pijnappel via swift-dev < > swift-dev@swift.org> wrote:
>>
>> In trying to implement a COW type, but I'm running into problems with
isUniqueReferenced breaking in even fairly simple cases. For example (with
-O) the code below prints "bar: false", commenting out the print in test()
makes it print "bar: true", and removing the var parameter var foo: Foo and
using var foo = foo instead breaks it again. Am I doing something wrong
here?
>>
>> class FooStorage { var x: Int = 0 }
>>
>> struct Foo { var storage = FooStorage() }
>>
>> func bar(var foo: Foo) {
>> print("bar: \(isUniquelyReferencedNonObjC(&foo.storage))")
>> }
>>
>> func test() {
>> var foo = Foo()
>> print("test: \(isUniquelyReferencedNonObjC(&foo.storage))")
>> bar(foo)
>> }
>>
>> test()
>
> You're not doing anything wrong, this is just the ARC optimizer at work.
`foo` inside `test` is dead after the call to `bar`, so ownership is
transferred directly to `bar`'s parameter.

If you want to ensure that `foo` remains alive despite this, you can use
`withExtendedLifetime`:

class FooStorage { var x: Int = 0 }

struct Foo { var storage = FooStorage() }

func bar(var foo: Foo) {
        print("bar: \(isUniquelyReferencedNonObjC(&foo.storage))")
}

func test() {
        var foo = Foo()
        print("test: \(isUniquelyReferencedNonObjC(&foo.storage))")
        withExtendedLifetime(foo) {
                bar(foo)
        }
}

-Joe


(Joe Groff) #5

You're right, I'm sorry, I misread your original comment. If the ARC optimizer didn't transfer ownership, then it is correct for `isUniquelyReferenced` to be false inside `bar`, since the `foo` inside of `test` and the `foo` parameter to `bar` are semantically independent values. If this weren't the case, then `bar` could modify a COW value type and have its changes be seen back in `test`'s copy.

-Joe

···

On Mar 31, 2016, at 11:49 PM, Patrick Pijnappel <patrickpijnappel@gmail.com> wrote:

The modified version doesn't seem to change any of the results (on -O or -Onone). Note that the problem is that it's not uniquely referenced inside bar where it actually should be – that would mean that ownership is currently not directly transferred right?


(Andrew Trick) #6

In other words, to avoid a copy, the COW value must be passed ‘inout’. This is normally true anyway for functions that mutate the value.

It would be neat to have a ‘move’ operator that handed ownership of the COW value off to the callee. But the memory safety of that would be problematic in general.

-Andy

···

On Apr 1, 2016, at 8:43 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

On Mar 31, 2016, at 11:49 PM, Patrick Pijnappel <patrickpijnappel@gmail.com> wrote:

The modified version doesn't seem to change any of the results (on -O or -Onone). Note that the problem is that it's not uniquely referenced inside bar where it actually should be – that would mean that ownership is currently not directly transferred right?

You're right, I'm sorry, I misread your original comment. If the ARC optimizer didn't transfer ownership, then it is correct for `isUniquelyReferenced` to be false inside `bar`, since the `foo` inside of `test` and the `foo` parameter to `bar` are semantically independent values. If this weren't the case, then `bar` could modify a COW value type and have its changes be seen back in `test`'s copy.


(John McCall) #7

It could be supported on local variables, which would probably be good enough. DI would just ensure that the value wasn't used after that point. Similar DI work will be necessary in the long run anyway to implement unique-ownership types.

John.

···

On Apr 1, 2016, at 4:10 PM, Andrew Trick via swift-dev <swift-dev@swift.org> wrote:

On Apr 1, 2016, at 8:43 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

On Mar 31, 2016, at 11:49 PM, Patrick Pijnappel <patrickpijnappel@gmail.com> wrote:

The modified version doesn't seem to change any of the results (on -O or -Onone). Note that the problem is that it's not uniquely referenced inside bar where it actually should be – that would mean that ownership is currently not directly transferred right?

You're right, I'm sorry, I misread your original comment. If the ARC optimizer didn't transfer ownership, then it is correct for `isUniquelyReferenced` to be false inside `bar`, since the `foo` inside of `test` and the `foo` parameter to `bar` are semantically independent values. If this weren't the case, then `bar` could modify a COW value type and have its changes be seen back in `test`'s copy.

In other words, to avoid a copy, the COW value must be passed ‘inout’. This is normally true anyway for functions that mutate the value.

It would be neat to have a ‘move’ operator that handed ownership of the COW value off to the callee. But the memory safety of that would be problematic in general.


(Patrick Pijnappel) #8

Ok I checked, in the version from my original email we have:

callq _swift_retain

movq %rbx, %rdi

callq __TF4main3barFVS_3FooT_

movq %rbx, %rdi

callq _swift_release

Which makes foo obviously non-uniquely referenced inside bar, preventing
COW optimization. So it seems the optimizer somehow doesn't notice it's
dead here and doesn't transfer ownership (?).

···

On Fri, Apr 1, 2016 at 5:49 PM, Patrick Pijnappel < patrickpijnappel@gmail.com> wrote:

The modified version doesn't seem to change any of the results (on -O or
-Onone). Note that the problem is that it's *not* uniquely referenced
inside bar where it actually should be – that would mean that ownership is
currently not directly transferred right?

On Fri, Apr 1, 2016 at 10:27 AM, Joe Groff <jgroff@apple.com> wrote:

> On Mar 31, 2016, at 4:21 PM, Joe Groff via swift-dev < >> swift-dev@swift.org> wrote:
>
>>
>> On Mar 31, 2016, at 3:58 PM, Patrick Pijnappel via swift-dev < >> swift-dev@swift.org> wrote:
>>
>> In trying to implement a COW type, but I'm running into problems with
isUniqueReferenced breaking in even fairly simple cases. For example (with
-O) the code below prints "bar: false", commenting out the print in test()
makes it print "bar: true", and removing the var parameter var foo: Foo and
using var foo = foo instead breaks it again. Am I doing something wrong
here?
>>
>> class FooStorage { var x: Int = 0 }
>>
>> struct Foo { var storage = FooStorage() }
>>
>> func bar(var foo: Foo) {
>> print("bar: \(isUniquelyReferencedNonObjC(&foo.storage))")
>> }
>>
>> func test() {
>> var foo = Foo()
>> print("test: \(isUniquelyReferencedNonObjC(&foo.storage))")
>> bar(foo)
>> }
>>
>> test()
>
> You're not doing anything wrong, this is just the ARC optimizer at
work. `foo` inside `test` is dead after the call to `bar`, so ownership is
transferred directly to `bar`'s parameter.

If you want to ensure that `foo` remains alive despite this, you can use
`withExtendedLifetime`:

class FooStorage { var x: Int = 0 }

struct Foo { var storage = FooStorage() }

func bar(var foo: Foo) {
        print("bar: \(isUniquelyReferencedNonObjC(&foo.storage))")
}

func test() {
        var foo = Foo()
        print("test: \(isUniquelyReferencedNonObjC(&foo.storage))")
        withExtendedLifetime(foo) {
                bar(foo)
        }
}

-Joe