COW for non-mutating methods

I'm implementing a COW big int type but am running into problems with
non-mutating functions (e.g. the + operator). Simplified example code below
shows AFAIK the default way to implement COW, but the non-mutating method
doesn't identify the reference as unique (even with -O), resulting in a
needless copy.

I've tried everything I could think of, but only inout parameters seem to
work. How does the standard library do this, for e.g. String + String?

struct Foo {

  var storage = Storage()

  class Storage { var x = 0 }

  init(_ x: Int) { storage.x = x }

  mutating func negate() {

    if !isKnownUniquelyReferenced(&storage) {

      print("Copy")

    }

    storage.x = -storage.x

  }

  func negated() -> Foo {

    var result = self // This counts as a second reference

    result.negate()

    return result

  }

}

func test() {

  var a = Foo(5)

  a.negate()

  print(a.storage.x)

  let b = Foo(5)

  let c = b.negated()

  print(c.storage.x)

}

test()

*Output*

-5

Copy

-5

Unfortunately, the compiler currently always passes the 'self' parameter of a nonmutating method with a caller-release convention, meaning that any local mutable copy necessarily needs to retain a local copy and can't consume the incoming 'self' value, and there's currently no way to override this. Theoretically, defining `negated` as a free function should enable this optimization, since non-'self' parameters are passed callee-release and can be consumed:

func negate(foo: Foo) -> Foo {
  var result = foo
  result.negate()
  return result
}

though from what I've seen, the ARC optimizer doesn't always successfully shorten the lifetime of parameters enough to turn the "result = foo" into a move, which is a known bug.

-Joe

···

On Dec 1, 2016, at 3:29 AM, Patrick Pijnappel via swift-dev <swift-dev@swift.org> wrote:

I'm implementing a COW big int type but am running into problems with non-mutating functions (e.g. the + operator). Simplified example code below shows AFAIK the default way to implement COW, but the non-mutating method doesn't identify the reference as unique (even with -O), resulting in a needless copy.

I've tried everything I could think of, but only inout parameters seem to work. How does the standard library do this, for e.g. String + String?

struct Foo {
  var storage = Storage()

  class Storage { var x = 0 }

  init(_ x: Int) { storage.x = x }

  mutating func negate() {
    if !isKnownUniquelyReferenced(&storage) {
      print("Copy")
    }
    storage.x = -storage.x
  }

  func negated() -> Foo {
    var result = self // This counts as a second reference
    result.negate()
    return result
  }
}

func test() {
  var a = Foo(5)
  a.negate()
  print(a.storage.x)

  let b = Foo(5)
  let c = b.negated()
  print(c.storage.x)
}

test()