ABI incompatibility between Swift 5 & 6 with inherited protocols
Description
Where a library is built with Swift 5, with ABI resilience, and then called from Swift 6, inherited protocols cause a crash at runtime.
Rebuilding the framework with the Swift 6 compiler resolves the problem, but that shouldn't be necessary!
Reproduction
Build this as a dynamic library with ABI resilience and Swift 5,
public class Database {}
public protocol Reader: AnyObject, Sendable {
func read<T>(_ closure: (Database) throws -> T) throws -> T
}
public protocol Writer: Reader {
func write<T>(_ closure: (Database) throws -> T) throws -> T
}
public final class ReaderWriter: Writer {
public init() {}
public func write<T>(_ closure: (Database) throws -> T) throws -> T {
return try closure(Database())
}
public func read<T>(_ closure: (Database) throws -> T) throws -> T {
return try closure(Database())
}
}
Then call it from code compile with Swift 6 (Swift 5 language mode):
let db: any Writer = ReaderWriter()
let result = try db.read { _ in
print("reading")
return [3: "hi"]
}
print("result: \(result)")
You'll crash in the call to read, because it's on the Reader protocol. Calls to write on the child Writer protocol will work fine:
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x8)
* frame #0: 0x0000000104df3bb8 ABIBugTestFramework`dispatch thunk of ABIBugTestFramework.Reader.read<τ_0_0>((ABIBugTestFramework.Database) throws -> τ_1_0) throws -> τ_1_0 + 8
frame #1: 0x0000000105372464 GRDBABIBug2.debug.dylib`closure #2 in ContentView.body.getter at ContentView.swift:33:37
(lldb) disassemble
ABIBugTestFramework`dispatch thunk of ABIBugTestFramework.Reader.read<τ_0_0>((ABIBugTestFramework.Database) throws -> τ_1_0) throws -> τ_1_0:
0x104df3bb0 <+0>: stp x29, x30, [sp, #-0x10]!
0x104df3bb4 <+4>: mov x29, sp
-> 0x104df3bb8 <+8>: ldr x9, [x4, #0x8]
0x104df3bbc <+12>: blr x9
0x104df3bc0 <+16>: ldp x29, x30, [sp], #0x10
0x104df3bc4 <+20>: ret
Both projects to reproduce in case the above isn't sufficient: Archive.zip
Expected behavior
No crash!
Environment
Swift 5 (Xcode 15.4) swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4) Target: arm64-apple-macosx14.0
Swift 6 (Xcode 16.0b3) swift-driver version: 1.111.2 Apple Swift version 6.0 (swiftlang-6.0.0.5.15 clang-1600.0.22.6) Target: arm64-apple-macosx14.0
Additional information
If there's any workaround for this, I'd love to hear it.
The crash appears to occur when Swift 6 built code is responsible for creating the type metadata (even though it gets it from the type metadata accessor in the framework?!). For example, adding this function to the framework:
public func createReaderWriter() -> ReaderWriter {
ReaderWriter()
}
(and calling it instead of ReaderWriter() from Swift 6) will still crash, but adding this function to the framework:
public func createReaderWriter() -> any Writer {
ReaderWriter()
}
(And removing all references to ReaderWriter from the Swift 6-compiled code) will no longer crash.
@hborla not sure why "concurrency"? AFAICT, nothing here is related to concurrency?
Ah sorry, I'm triaging fairly quickly to make progress through the large backlog of untraiged issues. I saw Sendable and "ABI difference between Swift 5 and Swift 6" and assumed it was because of Sendable stripping, but I agree that's not the problem here.
Minimal repro:
// lib.swift
public protocol Reader: Sendable {
func read()
}
public protocol Writer: Reader {}
public struct ReaderWriter: Writer {
public init() {}
public func read() {}
}
// use.swift
import lib
let db: any Writer = ReaderWriter()
db.read()
Looks like an unexpected regression from https://github.com/swiftlang/swift/commit/b333fb1683ef20b6dc965b78a743feccaea9fedd. Odd
Fix is at https://github.com/swiftlang/swift/pull/75769
Haha it was Concurrency after all 😅