Skip to content

Instantly share code, notes, and snippets.

@omochi
Created May 24, 2019 10:21
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save omochi/731f84adad9168bd51f268df2caef75f to your computer and use it in GitHub Desktop.
Save omochi/731f84adad9168bd51f268df2caef75f to your computer and use it in GitHub Desktop.

slidenumber: true autoscale: true

ABI安定化とLibrary Evolution

omochimetaru

わいわいswiftc #11


Swift5といえば


ABI安定化


ABI Stability and More

https://swift.org/blog/abi-stability-and-more/


3つの機能

  • ABI Stability
  • Module Stability
  • Library Evolution

ABI Stability

コンパイラのバージョンによらず、 Swiftのインターフェースが同じなら バイナリのインターフェースも同じ


従来

コンパイラのバージョンが変わると、 Swiftのインターフェースが同じでも、 バイナリのインターフェースが変わってた


  • みんな大好きMangling
  • Calling Convention
  • (frozen)structのレイアウト
  • Value Witness Tableのレイアウト
  • ...

https://github.com/apple/swift/blob/master/docs/ABIStabilityManifesto.md


恩恵

  • アプリをビルドした後でライブラリを差し替え可能

  • 標準ライブラリのOSへの同梱

  • Swift 5.0で達成


注意

ビルド後のライブラリの差し替えであって、 ビルド前ではない

アプリのリビルドはできない

コンパイル後の話


Module Stability

コンパイラのバージョンによらず、 ビルドしたライブラリを配布して、 アプリに組み込んでビルドできる


Swift 5.1の目玉機能(primary goal)

https://swift.org/blog/5-1-release-process/


新しいライブラリとともに、 アプリをリビルドする話

コンパイルする時の話


ビルドされたライブラリを組み込むためには、 ライブラリのインターフェースの情報を、 コンパイラが抽出できる必要がある


ライブラリのヘッダ情報

XcodeのCmd + Ctl + ↑とかで見えるやつ(雑)


従来


  • 互換性をもたせにくい

  • コンパイラのバージョンが変わるとフォーマットも変わってた


const uint16_t SWIFTMODULE_VERSION_MINOR = 491; // mangled class names as vtable keys

https://github.com/apple/swift/blob/cfc2dda69a7afe819ae245fbbd8f4501424c0644/include/swift/Serialization/ModuleFormat.h#L55


解決方法

  • .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
}

Library Evolution

新しいバージョンのライブラリを、 ビルド済みのアプリに対して、 リビルドする事なく差し替えることができる


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モード

  • 従来の通常モードに加え、 Library Evolutionモードでのビルドが導入される

  • 標準ライブラリは以前からこのモードだった

  • -enable-library-evolutionスイッチ

以下、LEモードでの仕様の話


enumがnon-frozenになる

バージョンアップでcaseが増やせる

ユーザはswitch-caseに必ずdefault:が必要になる

ユーザは@unknownの使い所


structがresilientになる

バージョンアップでstored propertyが増やせる


structへの@frozen指定

@frozenを指定すると、そのstructだけ通常モードになる

つまり、resiliencyを失う代わりに、実行速度が上がる


Resilientなstruct


従来(C言語)

メモリ領域の予約

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を取得
  • それを呼び出してオブジェクトを破棄
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment