Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-13888] Dictionary iteration by keys forces copy-on-write for struct elements #56286

Open
troughton opened this issue Nov 23, 2020 · 4 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. standard library Area: Standard library umbrella

Comments

@troughton
Copy link
Contributor

Previous ID SR-13888
Radar rdar://problem/71677776
Original Reporter @troughton
Type Bug
Environment

Reproduces in Swift 5.3 in Xcode 12.2 (12B45b).

Additional Detail from JIRA
Votes 0
Component/s Standard Library
Labels Bug
Assignee None
Priority Medium

md5: 09b091ceed16227924bc5ab27342d84d

relates to:

  • SR-13879 Dictionary.Indices retains dictionary, causes CoW on mutation during loop

Issue Description:

For the following example, I would expect testDict to complete successfully; however, isKnownUniquelyReferenced fails for the dictionary values, forcing unnecessary copies if the values use copy on write. The optimiser should be able to avoid retains on the dictionary values in the `.keys` loop since they are not referenced or used there.

class TestClass {}

struct CopyOnWriteWrapper {
    var instance = TestClass()

    @inline(never)
    mutating func checkUniqueness() {
        precondition(isKnownUniquelyReferenced(&self.instance))
    }
}

func testDict() {
    var elements = ["a": CopyOnWriteWrapper(), "b": CopyOnWriteWrapper(), "c": CopyOnWriteWrapper()]
    for key in elements.keys {
        elements[key]!.checkUniqueness() // preconditionFailure
    }
}
@typesanitizer
Copy link

This seems like a variation on https://bugs.swift.org/browse/SR-13879 but with the keys property instead of the indices property.

@swift-ci create

@lorentey
Copy link
Member

This is because the unwrap operation for an optional value does not support in-place mutations – it works by copying the wrapped value out (making it non-unique), then writing it back after the mutation is done.

Dictionary implements two workarounds for this problem. First, you can get rid of the optional result by indexing into the values view:

    for index in elements.indices {
        elements.values[i].checkUniqueness()
    }

Second, if all you have is a key, then you can use the defaulted subscript operation to get a non-optional value.

    for key in elements.keys {
        elements[key, default: CopyOnWriteWrapper()].checkUniqueness()
    }

Both .values and subscript(_:default🙂 support in-place mutations.

@lorentey
Copy link
Member

Until the force-unwrap construct is changed to support in-place mutations, this can also be worked around more universally by adding an Optional extension:

extension Optional {
  mutating func withForceUnwrappedValue<R>(_ body: (inout Wrapped) throws-> R) rethrows -> R {
    var value = self!
    self = nil
    defer { self = value }
    return try body(value)
  }
}

    for key in elements.keys {
        elements[key].withForceUnwrappedValue { $0.checkUniqueness() }
    }

@lorentey
Copy link
Member

An alternate, less unwieldy, formulation is to use a property with a _modify accessor:

extension Optional {
  var unwrapped: Wrapped {
    get { self! }
    set { self = newValue }
    _modify {
      var value = self!
      self = nil
      defer { self = value }
      yield &value
    }
  }
}

    for key in elements.keys {
        elements[key].unwrapped.checkUniqueness()
    }

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. standard library Area: Standard library umbrella
Projects
None yet
Development

No branches or pull requests

3 participants