Swiftの中間言語SILを読む その4 - sil_optコマンドの使い方

February 2, 2018

特定の最適化Passのみを有効にする術を見つけたのでメモ。

sil_optコマンド

utils/build-scriptを実行するとswiftコマンドやテスト用のバイナリの他にもいくつかツールがビルドされるのだが、その中の一つにsil-optというコマンドがある。

sil-optは主にSILの最適化のテストに使われるコマンドで、SILを受け取ってオプションで指定されたPassを有効にして最適化を行う。

% ../build/Ninja-ReleaseAssert/swift-macosx-x86_64/bin/sil-opt -help
OVERVIEW: Swift SIL optimizer

USAGE: sil-opt [options] input file

...

-helpでPass一覧がでてくるので、有効にしたいPassを見つけて指定する。例えば前回半分読んだDevirtualizationであれば-devirtualizerを指定する。

% sil-opt -devirtualizer hoge.sil

litと組み合わせて、SILOptimizerのテストケースの中で以下のような感じで使われる。 これはinliningのテストの例。

// RUN: %target-sil-opt -assume-parsing-unqualified-ownership-sil -enable-sil-verify-all %s -inline -sil-inline-generics=true | %FileCheck %s

Optionを指定しなければSILのparseだけ行って特に何もしないため、SILParserのテストとしても使われている。

// RUN: %target-swift-frontend %s -emit-silgen | %target-sil-opt

sil-optコマンドを試してみる

Dead Code Elimination(以下DCE)という不要コードを消す最適化Passを例にsil-optコマンドを試してみる。

まずはswiftファイルを用意する。aが使われていない。

func hogehgoe() -> Int {
  let a = 1
  return 2
}

SILを吐く。

% swift -frontend -emit-sil d.swift > d.sil

SILを確認する。stageはもちろんcanonical

sil_stage canonical

DCEはGeneral Optimizationなので、-Oが付いてない状態では最適化されずSILaが残っている。

// hogehgoe()
sil hidden @_T01d8hogehgoeSiyF : $@convention(thin) () -> Int {
bb0:
  %0 = integer_literal $Builtin.Int64, 1          // user: %1
  %1 = struct $Int (%0 : $Builtin.Int64)          // user: %2
  debug_value %1 : $Int, let, name "a"            // id: %2
  %3 = integer_literal $Builtin.Int64, 2          // user: %4
  %4 = struct $Int (%3 : $Builtin.Int64)          // user: %5
  return %4 : $Int                                // id: %5
} // end sil function '_T01d8hogehgoeSiyF'

SILをsil-optに渡す。-helpで調べると-dceというオプションでDCEが有効にできそう。

$ sil-opt -dce d.sil

出力を見る。aが消えている。

// hogehgoe()
sil hidden @_T01d8hogehgoeSiyF : $@convention(thin) () -> Int {
bb0:
  %0 = integer_literal $Builtin.Int64, 2          // user: %1
  %1 = struct $Int (%0 : $Builtin.Int64)          // user: %2
  return %1 : $Int                                // id: %2
} // end sil function '_T01d8hogehgoeSiyF'

-dceをつけないとaは残ったままなのも確認できた。

使い方は大まかにはこんな感じなのだが、いろいろハマりどころがあって動かすのにかなり時間がかかってしまったので、ググってたどり着いた誰かの為にいくつかここで紹介しておく。

ハマりどころ その1: swiftc -emit-silで出力したSILだと動作しない

テストケースで%target-swift-frontendと書かれているように。swift -frontend -emit-silのように-frontendでSILをemitする必要がある。

// これはNG
$ swiftc -emit-sil hoge.swift | sil-opt

// これはOK
$ swift -frontend -emit-sil hoge.swift | sil-opt

もちろん-emit-silgenでも同様。 そもそもこの2つで出力が違うということを知らなかった。。

ハマりどころ その2: Canonical SILを渡すときはオプションが必要な場合がある

canonicalなものをsil-optに渡すときには-assume-parsing-unqualified-ownership-silというオプションをつける必要がある場合がある。つけないとassertで引っかかる。

$ sil-opt -assume-parsing-unqualified-ownership-sil can.sil

オプションの説明をみるとこんな感じ。きっとownership関連なのだろうけど、いまはよくわからない。

Assume all parsed functions have unqualified ownership

テストケースにもすべてこれがついているのでとりあえず「canonicalならつける」とだけ理解している。。理由は後々きっと分かるだろう。。。

逆にraw SILだとつけると引っかかるので外す必要がある。

ハマりどころ その3: swiftc / swift -frontendのemit silでそもそも吐かれるSILが全然違う時がある

結局原因わからずなのだけれど、Devirtualizeを例にsil-optを試そうとしていてハマった。というのもswift -frontend -emit-silだと-Ononeを指定したとしてもclass_method命令が消えてしまうのだ。

まず-emit-silgenclass_methodが吐かれる様子を見てみる。つまりraw SILの場合にclass_methodがあることを確認する。

$ swiftc -emit-silgen sample1.swift | grep class_method
  %6 = class_method %5 : $Animal, #Animal.hoge!1 : (Animal) -> () -> Int, [email protected](method) (@guaranteed Animal) -> Int // user: %7
% swift -frontend -emit-silgen sample1.swift | grep class_method
  %6 = class_method %5 : $Animal, #Animal.hoge!1 : (Animal) -> () -> Int, [email protected](method) (@guaranteed Animal) -> Int // user: %7

次に-emit-silを試す。するとフロントエンドモードの場合だけclass_methodが消える。

% swiftc -emit-sil sample1.swift | grep class_method
  %5 = class_method %3 : $Animal, #Animal.hoge!1 : (Animal) -> () -> Int, [email protected](method) (@guaranteed Animal) -> Int // user: %6
% swift -frontend -emit-sil sample1.swift | grep class_method

Devirtualizeは-Oのみで動作するので、そもそもDevirtualizeは動いていないはずである。事実Devirtualizerにbreakpointを貼っても止まらなかったのでそもそも実行されていない。swiftcとフロントエンドモードでraw -> canonical にする過程に違いがあるのだろうか。。正直よくわらからない。

おまけ

上のハマりポイントを調査しているときに見つけたオプションをメモ。 とにかくOptimizeをOFFにしたいときに指定する。ただし-disable-diagnostic-passesを指定してしまうと-emit-silでもraw SILが吐かれるので注意。

swift -frontend -emit-sil -disable-sil-perf-optzns -disable-diagnostic-passes -disable-arc-opts hoge.sil 

まとめ

sil-optの使い方を確認したが、どう考えても使いづらく、 以前紹介したswiftc -O -Xllvm -sil-print-all -Xllvm -sil-print-only-functions=hoge test.swift の方が圧倒的に使いやすいので、よっぽどのことがない限り使わないと思う。。

このエントリーをはてなブックマークに追加