AccessEnforcementOpts leaves a merged access scope open on a loop's continue edge: false exclusivity trap at -O · Issue #93411 · swiftlang/swift · GitHub
Skip to content

AccessEnforcementOpts leaves a merged access scope open on a loop's continue edge: false exclusivity trap at -O #93411

Open

Description

@patp

Description

With -O, AccessEnforcementOpts merges two dynamic [modify] accesses to the same stored property into one scope that spans a guard ... else { continue }. The continue edge leaves the loop body without an end_access. On the next iteration, the next access to that property traps with a false "Simultaneous accesses" exclusivity violation.

The program has no overlapping accesses. It runs correctly at -Onone, and at -O when the pass is disabled with -Xllvm -sil-disable-pass=access-enforcement-opts.

The optional closure call in Clock.tick() matters: it makes the accesses tracked ([dynamic] without [no_nested_conflict]), so the runtime records the open scope. The x counter matters too: without it, the loop runs once to a break and no trap occurs.

Reproduction

// repro.swift
struct Clock {
    var cycles = 0
    var hook: (() -> Void)?

    mutating func tick() {
        cycles += 1
        hook?()
    }
}

final class Machine {
    var clock = Clock()
    var taken = [false, true, false, true, false, false]
    var next = 0
    var ok = false

    @inline(never)
    func pick() {
        clock.tick()
        ok = !taken[next % taken.count]
        next += 1
    }

    func run() {
        var x = 2
        while true {
            pick()
            clock.tick()
            guard ok else { continue }   // taken: try again
            clock.tick()
            x -= 1
            if x == 0 { break }
        }
    }
}

let m = Machine()
m.run()
print("ok", m.clock.cycles)
$ swiftc -Onone repro.swift -o repro && ./repro
ok 8
$ swiftc -O -Xllvm -sil-disable-pass=access-enforcement-opts repro.swift -o repro && ./repro
ok 8
$ swiftc -O repro.swift -o repro && ./repro
Simultaneous accesses to 0x100c7d4a0, but modification requires exclusive access.
Previous access (a modification) started at repro`Machine.run() + 192 (0x100c48be0).
Current access (a modification) started at:
0    libswiftCore.dylib                 0x00000001ae8eb69c _swift_reportExclusivityConflict + 332
1    libswiftCore.dylib                 0x00000001ae882f34 swift_beginAccess + 192
2    repro                              0x0000000100c49058 Machine.pick() + 44
3    repro                              0x0000000100c48b20 Machine.run() + 172
4    repro                              0x0000000100c489d0 main + 76
5    dyld                               0x0000000199f3e460 start + 6688
Fatal access conflict detected.

The optimized SIL of Machine.run() (swiftc -O -emit-sil repro.swift, trimmed) shows the scope %14, which covers both clock.tick() calls. It is ended in bb8, but the guard's failure edge bb4 -> bb11 -> bb1 branches back to the loop header with %14 still open. There, pick() begins another [modify] access to clock:

bb1(%12 : $Builtin.Int64):                        // Preds: bb11 bb10 bb0
  %13 = apply %6(%0) : $@convention(method) (@guaranteed Machine) -> ()   // pick()
  %14 = begin_access [modify] [dynamic] %4        // users: %38, %51, %25, %16, %15
  ...
bb4:                                              // Preds: bb3 bb2
  %33 = begin_access [read] [static] [no_nested_conflict] %5
  end_access %33
  cond_br %35, bb5, bb11
  ...
bb8:                                              // Preds: bb7 bb6
  end_access %14                                  // id: %51
  cond_br %58, bb9, bb10
  ...
bb10:                                             // Preds: bb8
  br bb1(%53)
bb11:                                             // Preds: bb4
  br bb1(%12)                                     // <- %14 not ended on this edge

Expected behavior

The program prints ok 8 at -O, as it does at -Onone. A merged access scope must end on every path that leaves it, or the two accesses must not be merged when the second does not post-dominate the end of the first.

Environment

swift-driver version: 1.168.6 Apple Swift version 6.4 (swiftlang-6.4.0.34.1 clang-2100.3.34.1)
Target: arm64-apple-macosx27.0.0
Xcode 27.1 (27A9275), macOS 27.0.1 (26A434)

Additional information

Found in a release build of a larger app. In a loop of the same shape (retry with continue until a random cell is free, then count down), the app trapped in the first call after the continue. Disabling the pass for that module, with -Xllvm -sil-disable-pass=access-enforcement-opts, is the workaround in use; exclusivity checking stays enabled.

Possibly related, but with a different trigger (fixed by #83999): #83924.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions