briefy
briefy

Reputation: 221

why is there a memory confiicting in single thread swift?

Referring to the official tutorial, there is memory conflict in swift, however, based on the knowledge of my javascript, there is not a memory conflict, the code below will always be right.

func balance(_ x: inout Int, _ y: inout Int) {
  let sum = x + y
  x = sum / 2
  y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore)  // OK
balance(&playerOneScore, &playerOneScore)  // Error: conflicting accesses to playerOneScore

Upvotes: 2

Views: 275

Answers (1)

Martin R
Martin R

Reputation: 539985

From Memory Safety:

Specifically, a conflict occurs if you have two accesses that meet all of the following conditions:

  • At least one is a write access or a nonatomic access.
  • They access the same location in memory.
  • Their durations overlap.

And later:

A function has long-term write access to all of its in-out parameters. The write access for an in-out parameter starts after all of the non-in-out parameters have been evaluated and lasts for the entire duration of that function call.

Therefore, passing the the same variable as in-out parameter to

func balance(_ x: inout Int, _ y: inout Int) 

counts as overlapping write accesses to the same memory location, and therefore as a conflict.

For the rationale and more details, see SE-0176 Enforce Exclusive Access to Memory, which has been implemented in Swift 4. In particular, exclusive memory access is enforced to

  • Prevent undefined behavior and unexpected or confusing results.
  • Allow the compiler to make optimistic assumptions about the loads and stores.

As an example, the following two seemingly equivalent functions are in fact not equivalent if the same variable is passed as an inout argument, so that mutating x can affect y and vice versa:

func balance(_ x: inout Int, _ y: inout Int) {
    let sum = x + y
    x = sum / 2
    y = sum - x
}

func balance(_ x: inout Int, _ y: inout Int) {
    x = (x + y) / 2
    y = (x + y) / 2 - x
}

Another example:

var global = 0
func foo(_ x: inout Int, _ y: inout Int) {
    x += y
    global = y
}

If mutating x might modify y and vice versa, the compiler cannot optimize the code to load the value of y in a register first, i.e. perform the equivalent of

func foo(_ x: inout Int, _ y: inout Int) {
    let savedY = y
    x += savedY
    global = savedY
}

It has also been discussed (see Eliminating non-instantaneous accesses?) to eliminate long-term access by making temporary copies for the duration of the function call which are assigned back when the function returns, i.e. do something like

func balance(_ x: inout Int, _ y: inout Int) {
    var (localX, localY) = (x, y)
    let sum = localX + localY
    localX = sum / 2
    localY = sum - localX
    (x, y) = (localX, localY)
}

This idea was discarded because it is bad for the performance, even for “simple types” but much worse for “container types” like Array.

Upvotes: 5

Related Questions