【Swift 5.6】Swift.Slice に独自の Collection 型を食わせて AccelerateBuffer.withUnsafeBuffer するとクラッシュする件【未解決】
概要
独自でCollectionの型を定義し,それを Swift.Slice
に食わせて Accelerate Framework
の withUnsafeBufferPointer
で呼ぶと 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
クラッシュする際も何かしら自明なエラーが出るわけではなく,Swift.Slice に含まれている breakpoint を踏み抜いている以外に手がかりがなくどうしようもない.
上述のとおり,独自に Slice 型を定義すれば回避できる.
ただ,このためだけに独自型を用意するのは無駄どころか互換性を損ねて良くないのでやめたい.
どうしたら良いものか
Swift 5.7でも変わらず