slidenumbers: true autoscale: true
- わいわいswiftc 番外編ワークショップ #3 - 福岡 の復習
- 「副作用がありそう」なコードをコンパイラが見つけ方を知る
- ご参加ありがとうございました
- みんな一生懸命解いていて嬉しいです
- C++ の部分が難しかったので解けなくても当然です
- 資料は全て先週公開しました
- ただしワークショップのレポジトリは消しています
- https://qiita.com/freddi_/items/aa604dd68697f823a41d
- Swiftの中間表現言語 (Swift Intermediate Language)
- Swiftのコードをコンパイルしている途中で現れる
- 最適化はSILのコードで行われる
- 最適化前のSILがraw SIL、後がcanonical SIL
var a = 10
sil_stage canonical
import Builtin
import Swift
import SwiftShims
@_hasStorage @_hasInitialValue var a: Int { get set }
// a
sil_global hidden @$s5swift1aSivp : $Int
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s5swift1aSivp // id: %2
%3 = global_addr @$s5swift1aSivp : $*Int // user: %6
%4 = integer_literal $Builtin.Int64, 10 // user: %5
%5 = struct $Int (%4 : $Builtin.Int64) // user: %6
store %5 to %3 : $*Int // id: %6
%7 = integer_literal $Builtin.Int32, 0 // user: %8
%8 = struct $Int32 (%7 : $Builtin.Int32) // user: %9
return %8 : $Int32 // id: %9
} // end sil function 'main'
// Int.init(_builtinIntegerLiteral:)
sil public_external [transparent] [serialized] @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int {
// %0 // user: %2
bb0(%0 : $Builtin.IntLiteral, %1 : $@thin Int.Type):
%2 = builtin "s_to_s_checked_trunc_IntLiteral_Int64"(%0 : $Builtin.IntLiteral) : $(Builtin.Int64, Builtin.Int1) // user: %3
%3 = tuple_extract %2 : $(Builtin.Int64, Builtin.Int1), 0 // user: %4
%4 = struct $Int (%3 : $Builtin.Int64) // user: %5
return %4 : $Int // id: %5
} // end sil function '$sSi22_builtinIntegerLiteralSiBI_tcfC'
- SILの構造は以下のざっと以下の通り(テーブルとか一部省略)
- SIL Module (ソースファイル全体)
- の中に、SIL Function (Swiftで作った関数から生成)
- の中に、SIL Basic Block (条件分岐のスコープとか)
- の中に、SIL Instruction (コードの一行一行)
// ファイル全体がSIL Module
sil_stage canonical
import Builtin
...
// SIL Function
// main
sil @main : $@convention(c) (Int32, ...) -> Int32 {
// bb から始まる SIL Basic Block
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional ..
// ここらから SIL Instruction
alloc_global @$s5valueAASivp // id: %2
...
return %8 : $Int32 // id: %9
} // end sil function 'main'
以降、各単語の冒頭のSILは省略できそうなときは省略
- SIL を最適化するコンパイラのモジュール
- それぞれの最適化の役割を持つ Pass というモジュールからなる
- Module 全体を見る Pass と Function を見る Pass がある
- 使われていないコードを削除する最適化Pass
- 使われていないコード == "死んでるコード" == Dead Code
- 大体の場面で DCE と略されている
- コンパイラのコードとか
- ワークショップの課題で作らされた Pass
- ワークショップのは削除の条件が厳しくないminDCE
- 削除しない条件に当てはまる Instruction を Live Insturction としてMark
- Markされていない Insturction を Function から取り除く
- Function からいらないコードが消えている
- (゚д゚)ウマー
- 本家での Live Insturction のだいたいの条件
- ⭕
return
やnoreturn
といった Insturction。- ワークショップでは return のみ
- ❌ 副作用のある可能性がある Insturction
- ⭕ Live Instruction に依存している Instruction
- ❌ Live Insturction が
制御依存
している条件分岐 - 複数の Basic Block の間の走査
- ⭕
- ワークショップで実装した minDCE は⭕がついた条件だけを利用
- ❌ Live Insturction が制御依存している条件分岐
- 複数の Basic Block の間の走査
- https://blog.waft.me/2018/02/19/swift-sil-5/ を読んでね
- Basic Block のルール
- Terminator と呼ばれる Instruction で終わらないといけない
return
noreturn
unreachable
(FatalError
のあとに付いたりする)throw
...
- Terminator と呼ばれる Instruction で終わらないといけない
- 例えば下のSILコード
store %5 to %3 : $*Int // id: %6
%7 = integer_literal $Builtin.Int32, 0 // user: %8
%8 = struct $Int32 (%7 : $Builtin.Int32) // user: %9
return %8 : $Int32 // id: %9
- このコードは 最後に
return %8
してる(id: %9
)id: %9
はreturn (Terminator)
=> Live
%8
の値を生成している Instruction は 依存 している => Live%8
を返している Instruction はid: %8
の Inscructionと呼ぶ
id: %8
が利用している%7
(id: %7
)も依存 => Live%7
からも 依存を芋づる式に走査して Live としてMarkといけない
- 例えば下のSILコードは
id: %6
が消えるかもしれないreturn
に依存していないInstruction%7, %8, %9
に依存していないから
store %5 to %3 : $*Int // id: %6
%7 = integer_literal $Builtin.Int32, 0 // user: %8
%8 = struct $Int32 (%7 : $Builtin.Int32) // user: %9
return %8 : $Int32 // id: %9
- minDCE には問題点
noreturn
の時とか無条件ですべてのコードを消すのでは- その時は Instructionに 副作用があるかどうか を調べる
- ワークショップでは時間が足りんので省略
- 今日の話題はコレ
- 「Instructionに 副作用があるかどうか を調べる」関数の話
- 以下は、本家 DCE にある
seemsUseful
という関数の一部- 名前の通り「UsefulなInstruction」かを
true
/false
で返す- Live と Useful は同じ意味と思って良い
- 先ほど紹介した
Terminator
のチェックもここで判断してる - Instruction の依存の走査はまた別の場所
- 名前の通り「UsefulなInstruction」かを
static bool seemsUseful(SILInstruction *I) {
if (I->mayHaveSideEffects())
return true;
...
- Function 中の Instruction を Mark する
markLive
関数で利用seemUseful
の結果がtrue
ならばInstruction
をmarkValueLive
する- 厳密には
Insctruction
中のSIL Value
をMark
seemsUseful
でUsefulかどうかの判断に使われている関数- これで「副作用があるかどうか」を判断してるらしい
- 条件によっては副作用を発生しないこともあるので
may
- 条件によっては副作用を発生しないこともあるので
mayHaveSideEffects
は、 何を「副作用」としているのか?- 内部実装を見て確かめてみたい
- こんなかんじ
bool SILInstruction::mayHaveSideEffects() const {
// If this instruction traps then it must have side effects.
if (mayTrap())
return true;
MemoryBehavior B = getMemoryBehavior();
return B == MemoryBehavior::MayWrite ||
B == MemoryBehavior::MayReadWrite ||
B == MemoryBehavior::MayHaveSideEffects;
}
- この2つをフォーカスすれば、謎を解明できそう!
mayTrap
MemoryBehavior
- // If this instruction traps then it must have side effects.
- 直訳: この命令がトラップする場合、副作用があるはず
- trap == プログラムの異常終了
- 割とシンプルでやさしい
bool SILInstruction::mayTrap() const {
switch(getKind()) {
case SILInstructionKind::CondFailInst:
case SILInstructionKind::UnconditionalCheckedCastInst:
case SILInstructionKind::UnconditionalCheckedCastAddrInst:
return true;
default:
return false;
}
}
- Passでは、コード中で見るInsctructionは
SILInstruction
型 - 具体的なInstructionの種類を知るには?
dyn_cast<I>
やisa<T>
(キャスト)を使って確認できる
-
今見ているInstructionがInterger Literal Instructionか見ている
// Instが Interger Literal Instructionかしらべる // $0 = integer_literal $Builtin.Int64, 100 if (auto *BI = dyn_cast<IntegerLiteralInst>(Inst)) {
- それぞれのInstructionは、
SILInstruction
型を継承した型で表されるSILInstruction
型は、コンパイラ上で SIL Instruction を表現している
- 目的のInstruction型へのダウンキャストが成功すれば良い
getKind
はキャストを使わない方法enum
を返していて、switch
とか使えて良い
SILInstructionKind
は、enum
の値でInstruction
がどの種類のものかを表す
switch(I->getKind()) {
case SILInstructionKind::IntegerLiteralInst: ...
case SILInstructionKind:: ReturnLiteralInst: ...
bool SILInstruction::mayTrap() const {
switch(getKind()) {
case SILInstructionKind::CondFailInst:
case SILInstructionKind::UnconditionalCheckedCastInst:
case SILInstructionKind::UnconditionalCheckedCastAddrInst:
return true;
default:
return false;
}
}
- この3つのInstructionは
trap
なのでUseful
になっているCondFailInst
UnconditionalCheckedCastInst
UnconditionalCheckedCastAddrInst
- SIL.rstにはだいたいのInstructionの情報が乗っています
- キャムルケースになってるのをスネークケースに分解してCtrl+F
cond_fail
unconditional_checked_cast
unconditional_checked_cast_addr
- 全部ヒットします
// BNF
sil-instruction ::= 'cond_fail' sil-operand, string-literal
- もしオペランドが 1 のときRuntime Failure
- オペランド == 引数と考えるとわかりやすい
sil-operand
がオペランド
cond_fail %0 : $Builtin.Int1, "failure reason"
// %0 must be of type $Builtin.Int1
// BNF
sil-instruction ::= 'unconditional_checked_cast' sil-operand 'to' sil-type
- キャストできるかを確かめる
- オペランドのオブジェクトがキャスト不可の場合Runtime Failure
- as! のようなもの
%1 = unconditional_checked_cast %0 : $A to $B
%1 = unconditional_checked_cast %0 : $*A to $*B
// $A and $B must be both objects or both addresses
// %1 will be of type $B or $*B
// BNF
sil-instruction ::= 'unconditional_checked_cast_addr'
sil-type 'in' sil-operand 'to'
sil-type 'in' sil-operand
- キャスト
- 最初のオペランドが、最後のオペランドの型にがキャスト不可の場合Runtime Failure
unconditional_checked_cast
と同じように見えるけど- どうやらキャスト元のオブジェクトは破棄されるらしい
unconditional_checked_cast_addr $A in %0 : $*@thick A to $B in $*@thick B
// $A and $B must be both addresses
// %1 will be of type $*B
// $A is destroyed during the conversion. There is no implicit copy.
- https://github.com/apple/swift/blob/master/docs/SIL.rst#unconditional_checked_cast
- https://it1.jp/?p=1231 がわかりやすいかも
mayTrap
に入ってないunconditional_checked
系統のInstruction- 入れ忘れか、そうでないか不明 🤔 🤔 🤔 🤔 🤔
- Runtime Failure するのに ....
bool SILInstruction::mayHaveSideEffects() const {
// If this instruction traps then it must have side effects.
if (mayTrap())
return true;
MemoryBehavior B = getMemoryBehavior();
return B == MemoryBehavior::MayWrite ||
B == MemoryBehavior::MayReadWrite ||
B == MemoryBehavior::MayHaveSideEffects;
}
- つまり、プログラムが**条件によっては異常終了するなら「副作用あり」**と捉える
- つぎは Memory Behavior!
- その SIL Instruction がメモリ上でどう振る舞うかを調べる
- 読み解くには、わりとLLVMの知識が必要
SILInstruction::MemoryBehavior SILInstruction::getMemoryBehavior() const {
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
// Handle Swift builtin functions.
const BuiltinInfo &BInfo = BI->getBuiltinInfo();
if (BInfo.ID != BuiltinValueKind::None)
return BInfo.isReadNone() ? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
// Handle LLVM intrinsic functions.
const IntrinsicInfo &IInfo = BI->getIntrinsicInfo();
if (IInfo.ID != llvm::Intrinsic::not_intrinsic) {
// Read-only.
if (IInfo.hasAttribute(llvm::Attribute::ReadOnly) &&
IInfo.hasAttribute(llvm::Attribute::NoUnwind))
return MemoryBehavior::MayRead;
// Read-none?
return IInfo.hasAttribute(llvm::Attribute::ReadNone) &&
IInfo.hasAttribute(llvm::Attribute::NoUnwind)
? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
}
}
// Handle full apply sites that have a resolvable callee function with an
// effects attribute.
if (isa<FullApplySite>(this)) {
FullApplySite Site(const_cast<SILInstruction *>(this));
if (auto *F = Site.getCalleeFunction()) {
return F->getEffectsKind() == EffectsKind::ReadNone
? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
}
}
switch (getKind()) {
#define FULL_INST(CLASS, TEXTUALNAME, PARENT, MEMBEHAVIOR, RELEASINGBEHAVIOR) \
case SILInstructionKind::CLASS: \
return MemoryBehavior::MEMBEHAVIOR;
#include "swift/SIL/SILNodes.def"
}
llvm_unreachable("We've just exhausted the switch.");
}
- 長い;;
- まずは戻り値の
enum
から見ていく
- メモリ上の振る舞いを定義する
enum
- 以下の5つが定義されている
None
MayRead
MayWrite
MayReadWrite
MayHaveSideEffects
- 以下の5つが定義されている
- 何もしないやつ
- The instruction may read memory.
- 直訳) メモリを読むかもしれない
- The instruction may write to memory.
- 直訳) メモリに書き込むかもしれない
- The instruction may read or write memory.
- 直訳) 読むか書き込むかもしれない(またはその両方?)
- The instruction may have side effects not captured solely by its users. Specifically, it can return, release memory, or store. Note, alloc is not considered to have side effects because its result/users represent its effect.
- 直訳) Instructionは、その利用者だけでは捉えられない副作用を持つことがあります。具体的には、リターン、メモリ解放、ストアなどです。注意:allocは、その結果/利用者がその作用を表すので、副作用があるとは考えられません。
- 意訳)なにかしら、のちのち副作用がある
- つまりは、そのInstructionが、どのようにメモリ上で振る舞うか を表す
- 見た感じだと、次のメモリ上の振る舞いが「副作用」として該当するようにしている
MayWrite
(書き)MayReadWrite
(読み書き)MayHaveSideEffects
(副作用あり)
seemUseful
は、今見ているInscructionに副作用があるか調べている
最初の if
にフォーカスを当てる
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
// Handle Swift builtin functions.
const BuiltinInfo &BInfo = BI->getBuiltinInfo();
if (BInfo.ID != BuiltinValueKind::None)
return BInfo.isReadNone() ? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
// Handle LLVM intrinsic functions.
const IntrinsicInfo &IInfo = BI->getIntrinsicInfo();
if (IInfo.ID != llvm::Intrinsic::not_intrinsic) {
// Read-only.
if (IInfo.hasAttribute(llvm::Attribute::ReadOnly) &&
IInfo.hasAttribute(llvm::Attribute::NoUnwind))
return MemoryBehavior::MayRead;
// Read-none?
return IInfo.hasAttribute(llvm::Attribute::ReadNone) &&
IInfo.hasAttribute(llvm::Attribute::NoUnwind)
? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
}
}
- キャストでInstructionが
BuiltinInst
なのかを調べてる
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
...
- Represents an invocation of builtin functionality provided by the code generator.
- 直訳) コード・ジェネレータが提供する組み込み機能の呼び出しを表します。
- 🤔 🤔 🤔 🤔 🤔
- SIL.rst にあった
- https://github.com/apple/swift/blob/master/docs/SIL.rst#builtin
- Invokes functionality built into the backend code generator, such as LLVM- level instructions and intrinsics.
- SIL Instruction の冒頭に
builtin
とついているのがポイント - 直訳) LLVMレベルの命令や組み込み関数など、バックエンドコードジェネレーターに組み込まれた機能を呼び出します。
// Example
%1 = builtin "foo"(%1 : $T, %2 : $U) : $V
BuiltinInst
には以下の2つの種類がある- Swift builtin function
- LLVM intrinsic function
-
Swift(SIL)のコードレベルで表現できない処理 の呼び出し
- LLVMの処理に書き換わるべき場所
-
例) Int(64)同士の比較演算
// SIL %5 = builtin "cmp_eq_Int64"(%3 : $Builtin.Int64, %4 : $Builtin.Int64) ...
↓ LLVM IRだとこうなる
// LLVM IR %10 = icmp eq i64 %8, 10
- Builtins.def にbuiltin functionの一覧あり
- ちなみに先程の
cmp_eq_Int64
は、Builtins.def ではcmp_eq
としてリストされている
- ちなみに先程の
- LLVMに、既にある関数を読み込む コード
- 見た感じ、その2つで条件を分けている
- Swift builtin functions へのアプローチをまずは見てみる
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
// Handle Swift builtin functions.
const BuiltinInfo &BInfo = BI->getBuiltinInfo();
...
// Handle LLVM intrinsic functions.
const IntrinsicInfo &IInfo = BI->getIntrinsicInfo();
...
}
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
// Handle Swift builtin functions.
const BuiltinInfo &BInfo = BI->getBuiltinInfo();
if (BInfo.ID != BuiltinValueKind::None)
return BInfo.isReadNone() ? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
- どうやらこのbuiltinのfunctionの情報をとって、以下の情報をもとにMemory Behaviorを判断
BInfo.ID
BInfo.isReadNone()
BInfo.ID
とは?- 戻り値になってるのは、
BuiltinValueKind
型 - 型の詳細を見てみる
- 戻り値になってるのは、
- 見た感じ
BuiltinValueKind
はenum
/// BuiltinValueKind - The set of (possibly overloaded) builtin functions.
enum class BuiltinValueKind {
None = 0,
#define BUILTIN(Id, Name, Attrs) Id,
#include "swift/AST/Builtins.def"
};
- どうやら先程言及した Builtins.def から、なんか取っている?
- Builtins.def を覗く
- さっき言及した
cmp_eq_Int64
周辺の部分を見てみる
...
#ifndef BUILTIN_BINARY_PREDICATE
#define BUILTIN_BINARY_PREDICATE(Id, Name, Attrs, Overload) \
BUILTIN(Id, Name, Attrs)
#endif
BUILTIN_BINARY_PREDICATE(ICMP_EQ, "cmp_eq", "n", IntegerOrRawPointerOrVector)
BUILTIN_BINARY_PREDICATE(ICMP_NE, "cmp_ne", "n", IntegerOrRawPointerOrVector)
BUILTIN_BINARY_PREDICATE(ICMP_SLE, "cmp_sle", "n", IntegerOrVector)
...
BUILTIN_BINARY_PREDICATE
というものが定義されている
// マクロ
BUILTIN_BINARY_PREDICATE(ICMP_EQ, "cmp_eq", "n", IntegerOrRawPointerOrVector)
ICMP_EQ
がさっき例で載っていた cmp_eq
のId
以下でBUILTIN
に変換できるようになっている
// マクロ
#define BUILTIN_BINARY_PREDICATE(Id, Name, Attrs, Overload) \
BUILTIN(Id, Name, Attrs)
/// BuiltinValueKind - The set of (possibly overloaded) builtin functions.
enum class BuiltinValueKind {
None = 0,
#define BUILTIN(Id, Name, Attrs) Id,
#include "swift/AST/Builtins.def"
};
- つまり、
BInfo.ID
は�Builtns.defで定義された、builtin functionのID- Builtins.defの内部のマクロで
BUILTIN(Id, Name, Attrs)
にかえて、 - その
BUILTIN - Id
をenum
の値にしている
- Builtins.defの内部のマクロで
if (BInfo.ID != BuiltinValueKind::None)
false
ならLLVM intrinsic function として次のパートでハンドリング
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
// Handle Swift builtin functions.
const BuiltinInfo &BInfo = BI->getBuiltinInfo();
if (BInfo.ID != BuiltinValueKind::None)
return BInfo.isReadNone() ? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
- 次は
isReadNone
。だけどだいたい同じメカニズムなのでサッと紹介
- builtin functionが メモリアクセスを許可されているかを調べる
- LLVM IRでは関数に 属性(Attribute) がつけられる
- どういう振る舞いを行うか?が属性から読み取れる
readnone
属性がついていると、メモリアクセスが許可されない- 逆に言えば、
readnone
がついてなければ、なにかメモリでやるかもしれないということになる
- LLVM IRでは関数に 属性(Attribute) がつけられる
// Builtins.def からまた抜粋
#ifndef BUILTIN_BINARY_PREDICATE
#define BUILTIN_BINARY_PREDICATE(Id, Name, Attrs, Overload) \
BUILTIN(Id, Name, Attrs)
#endif
BUILTIN_BINARY_PREDICATE(ICMP_EQ, "cmp_eq", "n", IntegerOrRawPointerOrVector)
BUILTIN(Id, Name, Attrs)
のなかでAttrs="n"
だったらreadnone
- メモリアクセスができない処理に変換されるということ
- bultin functions は Bultins.def で 属性を一つ一つ ラベリングしてる
- 変換先は、属性がつけれない Instruction 単体だから?
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
// Handle Swift builtin functions.
const BuiltinInfo &BInfo = BI->getBuiltinInfo();
if (BInfo.ID != BuiltinValueKind::None)
return BInfo.isReadNone() ? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
- つまり↑は 以下のように読み取れる
- Insructionの情報から、builtin function の属性を見る
- もし、
readnone
(メモリアクセスが許可されてない)なら副作用は当然なし - そうでないなら、副作用は何らかの形であるかもしれない
- このまでのノリがわかると後半もわかる
- 次は、LLVM intrinsic functionsのハンドリング
- こっちも属性を見てるのがノリでわかる
// Handle LLVM intrinsic functions.
const IntrinsicInfo &IInfo = BI->getIntrinsicInfo();
if (IInfo.ID != llvm::Intrinsic::not_intrinsic) {
// Read-only.
if (IInfo.hasAttribute(llvm::Attribute::ReadOnly) &&
IInfo.hasAttribute(llvm::Attribute::NoUnwind))
return MemoryBehavior::MayRead;
// Read-none?
return IInfo.hasAttribute(llvm::Attribute::ReadNone) &&
IInfo.hasAttribute(llvm::Attribute::NoUnwind)
? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
}
- 属性
NoUnwind
- 例外を投げないという属性
if (IInfo.hasAttribute(llvm::Attribute::ReadOnly) &&
IInfo.hasAttribute(llvm::Attribute::NoUnwind))
return MemoryBehavior::MayRead;
// Read-none?
return IInfo.hasAttribute(llvm::Attribute::ReadNone) &&
IInfo.hasAttribute(llvm::Attribute::NoUnwind)
? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
- つまり↑のコードは
- 例外を外に投げないで、かつ読み込みオンリーの関数は
MayRead
- 例外を外に投げる、または、メモリアクセスできる関数は
MayHaveSideEffects
- それ以外は
None
- 例外を外に投げないで、かつ読み込みオンリーの関数は
- まずは、そのInstructionがbultin instructionか見る
- もしそうであれば、LLVM IR になった後の属性を見て、メモリ上の振る舞いを判断
- bultin instruction ではない場合はどうか
- 次のパートを見てみる
// Handle full apply sites that have a resolvable callee function with an
// effects attribute.
if (isa<FullApplySite>(this)) {
FullApplySite Site(const_cast<SILInstruction *>(this));
if (auto *F = Site.getCalleeFunction()) {
return F->getEffectsKind() == EffectsKind::ReadNone
? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
}
}
-
apply
,begin_apply
といった関数呼び出しのためのInsructionがある -
Passのコードでは、
FullApplySite
という型でまとめられている
- SIL Function にも属性はある
- https://github.com/apple/swift/blob/master/docs/SIL.rst#functions
// BNF
sil-function-attribute ::= '[' sil-function-effects ']'
sil-function-effects ::= 'readonly'
sil-function-effects ::= 'readnone'
sil-function-effects ::= 'readwrite'
sil-function-effects ::= 'releasenone'
sil-function-attribute
はSIL Functionの宣言時に必須
// BNF
sil-function ::= 'sil' sil-linkage? sil-function-attribute+
sil-function-name ':' sil-type
'{' sil-basic-block+ '}'
- みたかんじ
- 関数呼び出しのInstructionかどうか調べている
- 関数呼び出しだったら callee(呼びたす関数)の属性を調べる
ReadNone
じゃないならMayHaveSideEffects
if (isa<FullApplySite>(this)) {
FullApplySite Site(const_cast<SILInstruction *>(this));
if (auto *F = Site.getCalleeFunction()) {
return F->getEffectsKind() == EffectsKind::ReadNone
? MemoryBehavior::None
: MemoryBehavior::MayHaveSideEffects;
}
}
- SIL Instructionのメモリの振る舞いを調べるときは、LLVM の属性を利用している
- もしくは SIL Function の属性
- 最後のパート
- どう見てもBuiltin.def(Swift builtin funcion)のときと同じパターン
switch (getKind()) {
#define FULL_INST(CLASS, TEXTUALNAME, PARENT, MEMBEHAVIOR, RELEASINGBEHAVIOR) \
case SILInstructionKind::CLASS: \
return MemoryBehavior::MEMBEHAVIOR;
#include "swift/SIL/SILNodes.def"
}
llvm_unreachable("We've just exhausted the switch.");
- SILNodes.defに各Instructionがどのようにメモリ上の振る舞うか書いてある
FULL_INST
という情報でまとめられているCLASS
には、さっきのSILInstructionKind
と同じ値が割り振られている (getKind
の戻り値のenum
型)MEMBEHAVIOR
には、MemoryBehavior
と同じ値が割り振られている(enum
型)
- 先程の Builtins.def のときと同じ
- ということは、
- SILNodes.def にある、「Inscrutctionがメモリ上でどのように振る舞うか(
MEMBEHAVIOR
)」の定義をマクロでswitch-case
に展開している
- SILNodes.def にある、「Inscrutctionがメモリ上でどのように振る舞うか(
switch (getKind()) {
#define FULL_INST(CLASS, TEXTUALNAME, PARENT, MEMBEHAVIOR, RELEASINGBEHAVIOR) \
case SILInstructionKind::CLASS: \
return MemoryBehavior::MEMBEHAVIOR;
#include "swift/SIL/SILNodes.def"
}
llvm_unreachable("We've just exhausted the switch.");
getMemoryBehavior
は以下の観点でInstructionのメモリの振る舞いを見ている- 変換先の LLVM IR の Instrucion は、どんな属性になるか?
Attrs
in Builtins.def
- 呼び出す LLVM IR の Function は、どんな属性か?
- 呼び出している
SIL Function
は、どんな属性か? - ↑ でもわかんないなら、既に Instruction に定義されている属性を見に行く
MEMBEHAVIOR
in SILNode.def
- 変換先の LLVM IR の Instrucion は、どんな属性になるか?
全部読めた!
- 次の場合、SIL Instruction に副作用があるとコンパイラは判断する
- 条件によっては、Runtime Failure するか?
- 条件によっては、メモリ上で書き込みなどといったことを行うか?
bool SILInstruction::mayHaveSideEffects() const {
// If this instruction traps then it must have side effects.
if (mayTrap())
return true;
MemoryBehavior B = getMemoryBehavior();
return B == MemoryBehavior::MayWrite ||
B == MemoryBehavior::MayReadWrite ||
B == MemoryBehavior::MayHaveSideEffects;
}
- Runtime Errorが起こりそうなコード
- メモリ書き込みなどが起こりそうなコード
- LLVM の属性などを利用して判断している
- Builtins.def や LLVM の関数から読み取って判断
- SILNodes.def で SIL Instruction のメモリの振る舞いは定義されている
- LLVM、SIL の 属性(Attribute)
- SILNodes.def
- Builtins.def
mayHaveSideEffects
getMemoryBehavior