swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.5k stars 10.35k forks source link

[SR-13321] ARC should end lifetimes of some references earlier #55761

Open dabrahams opened 4 years ago

dabrahams commented 4 years ago
Previous ID SR-13321
Radar None
Original Reporter @dabrahams
Type Bug
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 1 | |Component/s | Compiler | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: 6394984644ea260ec24bc29d52dc014d

Issue Description:

At least when built with optimization, the following test program should print “true” 3 times; instead it prints ”false” 3 times. This demonstrates that some references are not released as early as they could be, blocking a large class of CoW optimizations.

/// A simple boxed integer with value semantics.
struct X {
  class Box {
    var value: Int
    init(value: Int) { self.value = value }
  }
  var box: Box
  var value: Int { box.value }

  init(_ x: Int) { box = .init(value: x) }

  static func +=(lhs: inout X, rhs: X) {
    if isKnownUniquelyReferenced(&lhs.box) {
      lhs.box.value += rhs.value
    }
    else {
      var r = rhs
      if isKnownUniquelyReferenced(&r.box) {
        // Never reached, even if rhs is no longer needed.
        r.box.value += lhs.value
        lhs = r
      }
      else {
        lhs = lhs + r
      }
    }
  }

  static func +(lhs: X, rhs: X) -> X {
    var lhs1 = lhs
    if isKnownUniquelyReferenced(&lhs1.box) {
      // Never reached, even if lhs is no longer needed.
      lhs1 += rhs
      return lhs1
    }
    var rhs1 = rhs
    if isKnownUniquelyReferenced(&rhs1.box) {
      // Never reached, even if rhs is no longer needed.
      rhs1 += lhs1
      return rhs1
    }
    return .init(lhs1.value + rhs1.value)
  }
  var boxID: ObjectIdentifier { .init(box) }
}

func testMe0() {
  var a = X(3)
  let idA = a.boxID

  let b = X(5)
  let idB = b.boxID

  a += b

  assert(a.boxID == idA) // a's storage reused
  assert(b.boxID == idB) // b not mutated, holds same box
}
testMe0()

func testMe1() {
  var a = X(3)
  let idA = a.boxID

  let b = X(5)
  let idB = b.boxID

  let a1 = a            // second referece to a's box
  a += b                // b's lifetime is ending

  assert(a1.boxID == idA) // a1 holds the same box it was given
  print(a.boxID == idB)   // false; should be true <===
}
testMe1()

func testMe2() {
  let a = X(3)
  let idA = a.boxID

  let b = X(5)
  let idB = b.boxID

  let c = a + b           // lifetimes of both a and b are ending

  print(c.boxID == idA || c.boxID == idB) // false; should be true <===
}
testMe2()

func testMe3() {
  var a = X(3)
  let idA = a.boxID

  let b = X(5)
  let idB = b.boxID

  a = a + b

  assert(b.boxID == idB) 
  print(a.boxID == idA)  // false; should be true <===
}
testMe3()

/cc @atrick saeta (JIRA User) because I thought they might be interested.

swift-ci commented 4 years ago

Comment by Brennan Saeta (JIRA)

Definitely interested in seeing this issue resolved! Thanks for flagging @dabrahams. :-)