Open3

【Swift 5.6】Swift.Slice に独自の Collection 型を食わせて AccelerateBuffer.withUnsafeBuffer するとクラッシュする件【未解決】

kotan.knkotan.kn

概要

独自でCollectionの型を定義し,それを Swift.Slice に食わせて Accelerate FrameworkwithUnsafeBufferPointer で呼ぶと Swift.Slice 中の breakpoint を踏み抜いて死ぬ.
原因がわからなくてこっちまで死にそう.たすけて

OKなコードいくつか

1. Swift.Slice<Swift.Array>

Swift標準の Swift.Array をSwift標準の Swift.Sliceに食わせて withUnsafeBufferPointer する

import Accelerate
let base = ["Book 1", "Book 2", "Book 3"]
let slice = Slice(base: base, bounds: 1..<3)
slice.withUnsafeBufferPointer {
    guard let address = $0.baseAddress else { return }
    print("アドレスは\(address), 中身は\(Array($0))")
}

2. MySlice<Swift.Array>

Swift標準の Swift.Slice と同等のインタフェースを持った MySlice 型を定義して Swift標準の Swift.Array を食わせて同じことをする.

import Accelerate
struct MySlice<Target: Collection> where Target.Index == Int {
    let base: Target
    let offset: Target.Index
    let count: Target.Index
    init(base target: Target, bounds: Range<Target.Index>) {
        base = target
        offset = bounds.lowerBound
        count = bounds.count
    }
}
extension MySlice : RandomAccessCollection {
    var startIndex: Target.Index {
        offset
    }
    var endIndex: Target.Index {
        offset + count
    }
    subscript(position: Target.Index) -> Target.Element {
        base[position]
    }
}
extension MySlice : AccelerateBuffer where Target : AccelerateBuffer, Target.Index == Int {
    func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Target.Element>) throws -> R) rethrows -> R {
        try base.withUnsafeBufferPointer {
            try body(.init(rebasing: $0[offset..<offset+count]))
        }
    }
}
let base = ["Book 1", "Book 2", "Book 3"]
let slice = MySlice(base: base, bounds: 1..<3)
slice.withUnsafeBufferPointer {
    guard let address = $0.baseAddress else { return }
    print("アドレスは\(address), 中身は\(Array($0))")
}

3. MySlice<MyArray>

RandomAccessCollection に準拠した独自の MyArray 型, Swift.Slice と同等のインタフェースを持った MySlice 型をそれぞれ定義して同様のことをする.

import Accelerate
struct MySlice<Target: Collection> where Target.Index == Int {
    let base: Target
    let offset: Target.Index
    let count: Target.Index
    init(base target: Target, bounds: Range<Target.Index>) {
        base = target
        offset = bounds.lowerBound
        count = bounds.count
    }
}
extension MySlice : RandomAccessCollection {
    var startIndex: Target.Index {
        offset
    }
    var endIndex: Target.Index {
        offset + count
    }
    subscript(position: Target.Index) -> Target.Element {
        base[position]
    }
}
extension MySlice : AccelerateBuffer where Target : AccelerateBuffer, Target.Index == Int {
    func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Target.Element>) throws -> R) rethrows -> R {
        try base.withUnsafeBufferPointer {
            try body(.init(rebasing: $0[offset..<offset+count]))
        }
    }
}
struct MyArray<Source> {
    var array: Array<Source>
}
extension MyArray : RandomAccessCollection {
    var startIndex: Array<Source>.Index {
        array.startIndex
    }
    var endIndex: Array<Source>.Index {
        array.endIndex
    }
    subscript(position: Array<Source>.Index) -> Source {
        array[position]
    }
}
extension MyArray : AccelerateBuffer {
    func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Source>) throws -> R) rethrows -> R {
        try array.withUnsafeBufferPointer(body)
    }
}
let base = MyArray(array: ["Book 1", "Book 2", "Book 3"])
let slice = MySlice(base: base, bounds: 1..<3)
slice.withUnsafeBufferPointer {
    guard let address = $0.baseAddress else { return }
    print("アドレスは\(address), 中身は\(Array($0))")
}

NGなコード

Swift.Slice<MyArray>

RandomAccessCollection に準拠した独自の MyArray 型を定義して Swift.Slice に食わせ,同じことをする.

import Accelerate
struct MyArray<Source> {
    var array: Array<Source>
}
extension MyArray : RandomAccessCollection {
    var startIndex: Array<Source>.Index {
        array.startIndex
    }
    var endIndex: Array<Source>.Index {
        array.endIndex
    }
    subscript(position: Array<Source>.Index) -> Source {
        array[position]
    }
}
extension MyArray : AccelerateBuffer {
    func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Source>) throws -> R) rethrows -> R {
        try array.withUnsafeBufferPointer(body)
    }
}
let base = MyArray(array: ["Book 1", "Book 2", "Book 3"])
let slice = Slice(base: base, bounds: 1..<2)
slice.withUnsafeBufferPointer {
    guard let address = $0.baseAddress else { return }
    print("アドレスは\(address), 中身は\(Array($0))")
}

すると Swift.Slice に仕込まれた breakpoint を踏み抜いて死ぬ.

総括

まとめると以下のようになります

Swift.Slice MySlice
Swift.Array ✅OK ✅OK
MyArray 💥クラッシュ ✅OK

Swift.Slice ではなく Swift.ArraySlice を使うとこの問題は回避できるのですが,
Slice に入れたい対象は RandomAccessCollection を実装していることしか保証できず,Arrayではないためそのままでは ArraySlice を使えません.
RandomAccessCollection を実装しているので都度 Swift.Array に変換すれば良いのですが,オブジェクトのコピーが行われるようではスライス型を定義する意味がないと考えます.
今回は中身を文字列で行いましたが数値型でも同様です.

再現環境

MacBook Air (M1, 2020)
swift-driver version: 1.45.2 Apple Swift version 5.6

kotan.knkotan.kn

クラッシュする際も何かしら自明なエラーが出るわけではなく,Swift.Slice に含まれている breakpoint を踏み抜いている以外に手がかりがなくどうしようもない.

上述のとおり,独自に Slice 型を定義すれば回避できる.
ただ,このためだけに独自型を用意するのは無駄どころか互換性を損ねて良くないのでやめたい.

どうしたら良いものか