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-8398] Mutating external variable from closure in recursive function #50924

Closed
swift-ci opened this issue Jul 28, 2018 · 10 comments
Closed

[SR-8398] Mutating external variable from closure in recursive function #50924

swift-ci opened this issue Jul 28, 2018 · 10 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@swift-ci
Copy link
Collaborator

Previous ID SR-8398
Radar None
Original Reporter Vermeulen (JIRA User)
Type Bug
Status Resolved
Resolution Done
Environment

Xcode 10.0 beta 4, Swift Development Snapshot 7/23

Additional Detail from JIRA
Votes 1
Component/s Compiler
Labels Bug
Assignee Hudhud (JIRA)
Priority Medium

md5: 33fd4ca0d4dc41aab00e903e672e8e53

Issue Description:

@belkadan
Copy link
Contributor

It looks like each call to recursive is getting its own independent mutable dictionary. Oddly enough, forcing cache to be a computed property fixes the problem as well:

func fibonacci(_ n: Int) -> Int {
    var cache: [Int: Int] = [:] {
      willSet { print(newValue) }
    }
    
    func recursive(_ m: Int) -> Int {
        return cache[m] ?? {
            let output = m < 2 ? m : recursive(m - 1) + recursive(m - 2)
            cache[m] = output
            print("cached \(m)")
            return output
        }()
    }
    return recursive(n)
}
_ = fibonacci(5)

@slavapestov, @jckarter, any idea how the captures are getting messed up?

@swift-ci
Copy link
Collaborator Author

Comment by Tim (JIRA)

@belkadan I think you posted the wrong snippet as it does not contain a computed property (but I can confirm that having a computed property `cache` that forwards to a property `_cache` fixes it).

@jckarter
Copy link
Member

Hm, for some reason we're creating an entirely new box for `cache` in the autoclosure for the right-hand side of ??:

// implicit closure #&#8203;1 in recursive #&#8203;1 (_:) in fibonacci(_:)
sil private [transparent] @$S3bar9fibonacciyS2iF9recursiveL_yS2iFSiyKXKfu_ : $@convention(thin) (Int, @guaranteed { var Dictionary<Int, Int> }, @inout_aliasable Dictionary<Int, Int>) -> (Int, @error Error) {
// %0                                             // users: %13, %4
// %1                                             // user: %5
// %2                                             // users: %13, %10, %7
bb0(%0 : $Int, %1 : ${ var Dictionary<Int, Int> }, %2 : $*Dictionary<Int, Int>):
  debug_value undef : $Error, var, name "$error", argno 1 // id: %3
  debug_value %0 : $Int, let, name "m", argno 2   // id: %4
  %5 = project_box %1 : ${ var Dictionary<Int, Int> }, 0 // user: %6
  debug_value_addr %5 : $*Dictionary<Int, Int>, var, name "cache", argno 3 // id: %6
  debug_value_addr %2 : $*Dictionary<Int, Int>, var, name "cache", argno 4 // id: %7
  %8 = alloc_box ${ var Dictionary<Int, Int> }    // users: %15, %14, %11, %9
  %9 = project_box %8 : ${ var Dictionary<Int, Int> }, 0 // user: %10
  copy_addr %2 to [initialization] %9 : $*Dictionary<Int, Int> // id: %10

@jckarter
Copy link
Member

@swift-ci create

@belkadan
Copy link
Contributor

Ah, I suppose it's not a property, but cache will be treated as computed because of the willSet.

@swift-ci
Copy link
Collaborator Author

Comment by Tim (JIRA)

@belkadan For what it's worth, your code snippet doesn't fix the problem in the 7/30 master snapshot (but it does in whatever Swift version Xcode 10 beta 5 ships with).

@belkadan
Copy link
Contributor

Huh, interesting. Well, either way we need to track down the real problem.

@gregomni
Copy link
Collaborator

gregomni commented Aug 5, 2018

Computing the transitive capture list of the closure it needs `cache` for itself and for its calls to `recursive()` but with different capture flags so it ends up in the lowered capture info twice. Making `cache` a computed property keeps it from being direct, so the capture flags match, avoiding the problem.

@gregomni
Copy link
Collaborator

gregomni commented Aug 5, 2018

#18514

@jckarter
Copy link
Member

The fix is merged into master, so newer snapshots ought to have this fix. I'm going to try to bring it into the 4.2 branch as well: #18701

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
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. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

4 participants