slidenumber: true autoscale: true
ABI Stability and More
https://swift.org/blog/abi-stability-and-more/
- ABI Stability
- Module Stability
- Library Evolution
コンパイラのバージョンによらず、 Swiftのインターフェースが同じなら バイナリのインターフェースも同じ
コンパイラのバージョンが変わると、 Swiftのインターフェースが同じでも、 バイナリのインターフェースが変わってた
- みんな大好きMangling
- Calling Convention
- (frozen)structのレイアウト
- Value Witness Tableのレイアウト
- ...
https://github.com/apple/swift/blob/master/docs/ABIStabilityManifesto.md
-
アプリをビルドした後でライブラリを差し替え可能
-
標準ライブラリのOSへの同梱
-
Swift 5.0で達成
ビルド後のライブラリの差し替えであって、 ビルド前ではない
アプリのリビルドはできない
コンパイル後の話
コンパイラのバージョンによらず、 ビルドしたライブラリを配布して、 アプリに組み込んでビルドできる
Swift 5.1の目玉機能(primary goal)
https://swift.org/blog/5-1-release-process/
新しいライブラリとともに、 アプリをリビルドする話
コンパイルする時の話
ビルドされたライブラリを組み込むためには、 ライブラリのインターフェースの情報を、 コンパイラが抽出できる必要がある
XcodeのCmd + Ctl + ↑
とかで見えるやつ(雑)
-
.swiftmodule
-
バイナリ形式のライブラリのヘッダ情報
-
LLVM bitstream format http://llvm.org/docs/BitCodeFormat.html
-
互換性をもたせにくい
-
コンパイラのバージョンが変わるとフォーマットも変わってた
const uint16_t SWIFTMODULE_VERSION_MINOR = 491; // mangled class names as vtable keys
-
.swiftinterface
-
テキスト形式のライブラリのヘッダ情報
https://forums.swift.org/t/plan-for-module-stability/14551
// a.swift
public class Cat {
public func nya1() { print(1) }
internal func nya2() { print(2) }
@inlinable
public func nya3() { print(3) }
@usableFromInline
internal func nya4() { print(4) }
}
$ swiftc -enable-library-evolution -emit-module-interface a.swift
// a.swiftinterface
// swift-interface-format-version: 1.0
// swift-tools-version:
// Swift version 5.1-dev (LLVM 082dec2e22, Swift 60ee9527bb)
// swift-module-flags:
// -target x86_64-apple-darwin18.5.0 -enable-objc-interop
// -enable-library-evolution -module-name a
import Swift
public class Cat {
public func nya1()
@inlinable public func nya3() { print(3) }
@usableFromInline
internal func nya4()
@objc deinit
}
新しいバージョンのライブラリを、 ビルド済みのアプリに対して、 リビルドする事なく差し替えることができる
ABI Stabilityでは Swiftのインターフェースは同一という前提
Library Evolutionでは一定の制約の元で、 Swiftのインターフェースを変更できる
標準ライブラリがアップデートできるのは、 この機能を先行して利用しているから
これを一般開放するのがLibrary Evolution
SE-0260 Library Evolution for Stable ABIs https://github.com/apple/swift-evolution/blob/master/proposals/0260-library-evolution.md
審議スレッド https://forums.swift.org/t/se-0260-library-evolution-for-stable-abis/24260
5/21に審議終了したばかり Swift5.1への導入を予定
ライブラリをバージョンアップ可能にするということは、 コンパイル時と実行時で中身が変わるということ
実行時に変更に対応できる柔軟性が必要になる
コードもそれに配慮した対応が必要
-
従来の通常モードに加え、 Library Evolutionモードでのビルドが導入される
-
標準ライブラリは以前からこのモードだった
-
-enable-library-evolution
スイッチ
以下、LEモードでの仕様の話
バージョンアップでcase
が増やせる
ユーザはswitch-caseに必ずdefault:
が必要になる
ユーザは@unknown
の使い所
バージョンアップでstored propertyが増やせる
@frozen
を指定すると、そのstructだけ通常モードになる
つまり、resiliencyを失う代わりに、実行速度が上がる
メモリ領域の予約
struct Record {
int id;
const char *name;
void *reserved[2];
};
- 必要になるまでは無駄
- はみ出すと苦しい
ポインタに退避
struct Record {
struct RecordImpl * impl
};
// private
struct RecordImpl {
int id;
const char *name;
};
操作用メソッドがたくさん必要
void Record_init(Record * this);
void Record_deinit(Record * this);
int Record_id(const Record * this);
const char * Record_name(const Record * this);
void Record_copy(Record & dest, const Record & src);
ヒープ利用コストがかかる メモリ確保/解放処理の必要や、 キャッシュヒット率の低下が起きる
特に配列にしたときに、 コンテンツがストレージメモリ上で連続しなくなってしまう
実行時に型のサイズを動的に取得する
スタックにメモリを置いたまま実行時可変にできる
// b.swift
public struct Record {
public var id: Int = 0
public var name: String = ""
public init() {}
}
// a.swift
import b
func main() {
var x = Record()
}
$ swiftc -enable-library-evolution -emit-module-interface b.swift
$ swiftc -emit-ir -I . -O a.swift
define hidden swiftcc void @"$s1a4mainyyF"() local_unnamed_addr #1 {
entry:
%0 = tail call swiftcc %swift.metadata_response @"$s1b6RecordVMa"(i64 0) #3
%1 = extractvalue %swift.metadata_response %0, 0
%2 = getelementptr inbounds %swift.type, %swift.type* %1, i64 -1
%3 = bitcast %swift.type* %2 to i8***
%.valueWitnesses = load i8**, i8*** %3, align 8, !invariant.load !13, !dereferenceable !14
%4 = getelementptr inbounds i8*, i8** %.valueWitnesses, i64 8
%5 = bitcast i8** %4 to i64*
%size = load i64, i64* %5, align 8, !invariant.load !13
%x = alloca i8, i64 %size, align 16
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %x)
%6 = bitcast i8* %x to %swift.opaque*
call swiftcc void @"$s1b6RecordVACycfC"(%swift.opaque* noalias nocapture nonnull sret %6)
%7 = getelementptr inbounds i8*, i8** %.valueWitnesses, i64 1
%8 = bitcast i8** %7 to void (%swift.opaque*, %swift.type*)**
%9 = load void (%swift.opaque*, %swift.type*)*,
void (%swift.opaque*, %swift.type*)** %8, align 8, !invariant.load !13
call void %9(%swift.opaque* noalias nonnull %6, %swift.type* %1) #4
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %x)
ret void
}
- metadata accessor
$s1b6RecordVMa
を呼び出してMetatypeを取得 getelementptr
の-1
でValue Witness Tableを取得- VWTの8番から型のサイズを取得
alloca
でそのサイズでメモリ確保
Record.init($s1b6RecordVACycfC)
を呼び出す- VWTの1番から
destroy
を取得 - それを呼び出してオブジェクトを破棄