Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Halideの中間表現に対する構文解析器の実装

Akihiro Shoji
September 27, 2019

 Halideの中間表現に対する構文解析器の実装

2019/9/9から2019/9/27までの間、株式会社Fixstarsさんのサマーインターンに「画像処理DSL(Halide)のFPGA向け最適化コンパイラの開発」というテーマで参加しました。そこで、Halideの中間表現に対する構文解析器を実装しました。これは、そのサマーインターンの成果報告会のために作ったもので、外部に一般公開して良いとの許可を快くいただけたので、公開したものです。

Akihiro Shoji

September 27, 2019
Tweet

Other Decks in Programming

Transcript

  1. 自己紹介 • 筑波大学 情報学群 情報科学類 3年 • 趣味: プログラミング ◦

    プログラミング言語処理系の実装が大好き (理論も好きだけど ) • 興味 ◦ プログラミング言語処理系 : 論文を読みながら自作処理系を早くしたりいじるのが趣味。 ◦ Operating System : Linux Kernelをいじったりデバドラを書いたり輪読会を主催してたりする。 ◦ CPU Architecture : はやくCPUとCPUに対するコンパイラ、そしてその上で動作する OSを書きたい。 ◦ ↑興味対象がそれなりに多くて研究室 &研究テーマどうしよう。。。となっているのが最近の悩み • インターンに来た理由 ◦ コンパイラが好きなのでコンパイラに関するインターンがやりたかったので!
  2. 取り組んだことの概要 • Halide入門 ◦ 2次元のConvolutionをHalideで実装し、Halide実装のチューニング。 初日〜2日目 • Halide CompilerのTutorialとして簡単なASTの簡約パスを実装 (2日目後半)

    • Halideの中間表現(Halide IRと呼称)に対する構文解析を実装 (3日目〜3週) ◦ Halide IRの仕様を調べながら構文を定式化する ▪ IR.hでIRの構造を調べ、IRPrinter.cppでIRがどう表示されるのかを調べる。 ◦ 気合でパーサを書く (定式化に基づき、 HalideASTを生成する
  3. Halide入門 • Halideとは、主に画像処理を行うための DSLであり、C++の内部DSLとして実装されている。 • Halideについて日本語で調べると、 FixStarsの資料が多く出てくる(FixStars以外の情報がない) • Halideのキーとなる概念は、アルゴリズムとスケジューリングの分離である。 ◦

    つまり、実際に各ピクセルに対する処理は純粋関数的に定義する。 ◦ ベクトル化や並列化などの処理戦略などのスケジューリングは別途指定する。 ◦ これにより、アルゴリズムを抽象的に記述し、具体的な最適化は独立してチューニングできる。 • ↑ここまでをインターン開始の数日前に調べてきた • とはいえ、書いたことはなかったので、 2次元 Convolution の実装とそのチューニングをチュートリアル として行った。
  4. Halide CompilerのTutorial • Halide CompilerのTutorialとして、HalideのASTを簡約するパスを作成 ◦ IRMutatorというクラスを継承すると VistorパターンでASTをトラバースできる。 ◦ Expr

    visit(const T *op)というインターフェイスの Tをフックしたいノードとして、オーバーライドしてそのノードを変 形する形でパスを書くことができる。 • 0 * xやx * 0を0に変形する、x - 0をxに変形するなどの簡約を行った。
  5. Halideの中間表現に対する構文解析器の実装 • 概要 ◦ HalideのIR(C++のクラス)をostreamに出力すると、文字列化された IRが得られる ▪ → 今回実装したのは、その IRの文字列表現に対する構文解析器である。

    • モチベーション ◦ 文字列化したIRは現在、IRをデバッグ目的にとりあえず可視化する用途でのみ用いられている。 ◦ 一方で、IRを手書きしたいシチュエーションも存在する ▪ 出力されるIRとHalideプログラムの対応が非自明な場合に、 IRを直接いじることができるように ◦ 現状で、その文字列表現から HalideのIRを構築するフロントエンドが存在しない ◦ HalideのCore commiterもHalide IR frontendがあると嬉しいらしい ▪ →作った • 実装言語と規模 ◦ D言語で、6500行程度(開発期間は6日くらい) ◦ Halide IRの文字列表現を受け取り、 S式に変換する ▪ パーサーをD言語で書いたが、 Halide側でIRに復元するためには C++レイヤーが必要なため
  6. 構築したかったシステム 最適化パス Halide プログラム 構文解析器(D言語) IRを構文解析器に渡 すためのパス Halide IR ユーザーから編集

    されたIRを受け取る S式→Halide IR(C++) S式 最適化パス 既存の最適化パスの間に、一旦ユーザーからの入力を受 け取り編集し最終的に後続の最適化パスに渡すためのパ ス(緑色で示した部分) を 差し込む。
  7. 作業の流れの概要 1. [調査フェーズ] Halide IRの構造を把握するために、 IR.hとIRPrinter.cppを読んだ。 a. どのようなノードがあり、それがどのように文字列化されるかを把握した。 (後述) 2.

    [定式化フェーズ] Halide IRの文法をPEGで定式化。 a. 曖昧な構文があったり、予約語がないというようなびっくり仕様だったので、制約を設けた。 (後述) 3. [実装フェーズ] D言語でPeggedというPEGのパーサジェネレータを使用して実装した。 a. 定式化したPEGをもとに、C++との接続のために曖昧性を排した S式にコンパイルする。
  8. 調査フェーズ • HalideのIR.hとIRPrinter.cppを調査した ◦ IRのデザインや各ノードについて把握したかったため ◦ わかったこと ▪ 変数の書き換えを許さないある種の SSA(静的単一代入)

    ▪ 多次元配列と1次元配列の保持の仕方が違う • 多次元配列: Call(参照) / Provide(値の設定) • 1次元配列: Load(参照) / Store(値の設定) ◦ なお、Load/Storeには条件をつけることもできる。 ▪ 個人的にびっくりしたこと • BlockがStatement(文)の配列であると思っていたが実際は偏った 2文木だった。 ◦ Block(first, rest)なので、Block(stmt1, Block(stmt2, ....))となる。
  9. 定式化フェーズ • IRPrinter.cppをひたすら読みながら文法を PEGで定式化していく。 ◦ PEGはBNFと違って曖昧性がなく、さらに D言語のパーサジェネレータ Peggedにそのまま渡せる ◦ PEGはルールを左から順に試していくことが決まっているので曖昧性がない

    • 定式化の例) LetStatement < :“let” Variable :“=” Expression endOfLine Statement • Halide IRの文字列表現は曖昧な文法になっていた ◦ そもそも機械にパースされることを想定していなそう ◦ 実施した対策は 2つある ▪ 改行を文の区切りにする ▪ 予約約語を導入する
  10. 曖昧な文法に対する対処 • 例1) let x = 4 in (x +

    1) という文字列が出現した場合に一意に Let式であると判定できない ◦ let x = 4というLet文があり ◦ in(x + 1)という関数呼び出しが Let文のbodyとして続くとも解釈できるため • 当初の解決方法 ◦ → 行末を文の区切りにする ▪ IRPrinter.cppの出力がそうなっていたので、手書きする場合には大変になるが妥協 ▪ 予約語を導入することでも解決できるが勝手にパーサー側で予約語を定義するのはためらった • 例2) select(condition, true_expr, false_expr) を一般の関数呼び出しと区別できない ◦ ほかにもminやmaxなどの個別にノードがあるものも普通の関数呼び出しとして区別したい ◦ → 予約語を導入 ◦ → 結果として例1も予約語を入れたことによって解消していることにスライドを書きながら気がついた ◦ → なので、現状のソースは例 1に対する解決法と例 2に対する解決方法の両方が使われている ◦ → 発表後、例1の解決法を除去し、例 2の解決法ですべて解決するかを試してみる
  11. 定式化の際に困ったことpart2(PEGの問題) • 次のようなルールがあったとする ◦ TopLevel = Rules ◦ Rules =

    RuleA / RuleB ◦ RuleA = TokenA TokenB ◦ RuleB = TokenA TokenB TokenC • このルールから生成されるパーサーに対して次の入力を入れて、正しくパースしたい • TokenA TokenB TokenC (RuleBに該当してほしい) • しかし、上記のルールだと、 TokenAとTokenBを見た時点でRuleAにマッチしてRuleAとなる • TokenCが残った状態で成功してしまう ←これをなんとかしたという問題 • 結論から言うとPEGでどうかくのがいいかわからなかったが、どう対処しようとしたかを記す
  12. 対策1 • ルールを次のようにする ◦ TopLevel = Rules EndMarker ◦ Rules

    = RuleA / RuleB ◦ RuleA = TokenA TokenB ◦ RuleB = TokenA TokenB TokenC ◦ EndMarker = “@@@@@@@” ← token中に出てこないような文字列で tokenの終端を表す • そして、入力も次のようにする ◦ TokenA TokenB TokenC EndMarker • これでEndMarkerまで読んでくれるようになりうまく行くのでは!? ◦ → そうはならない ◦ 理由: ▪ Rulesとしては、RuleAがマッチする ▪ パーサーは次の Tokenを見に行き、それが EndMarkerかを判定するが、残っているトークン列は • TokenC EndMarker ▪ なのでパース失敗となる ←失敗してほしいわけじゃなくて RuleBになってほしい。
  13. 対策2 • こんどはルールを次のようにする ◦ TopLevel = Rules ◦ Rules =

    RuleA EndMarker / RuleB EndMarker ◦ RuleA = TokenA TokenB ◦ RuleB = TokenA TokenB TokenC ◦ EndMarker = “@@@@@@@” • 入力は以前と同じで ◦ TokenA TokenB TokenC EndMarker • うまくいく!!!やったね! • とはならない。 ◦ 理由 ▪ すべての規則に EndMarkerをつけるのは本質的な解決じゃなさそう ▪ そもそも入力Token列をいじるのが大変というか無理 (部分Token列を適切にいじる必要がある )
  14. 対策3 • そもそもルールの順番を書き換えればいいのでは? ◦ それも一つの解決策 • つまり ◦ TopLevel =

    Rules ◦ Rules = RuleB / RuleA ← Bから先に試す ◦ RuleA = TokenA TokenB ◦ RuleB = TokenA TokenB TokenC • これだと、TokenA TokenBだとRuleA、TokenA TokenB TokenCだとRuleBとして正しくパースされる • これはルールの優先順序を決めることを意味する • 一方で、現実的にルールは 10 ~ 20 くらいあり、その優先順序を適切に決めるのは大変 ◦ → でもこれが本質的な解決策な気がする ...(PEGについてちゃんと勉強したい )
  15. 実装フェーズ • D言語で実装。 ◦ 引き継ぎも考えてあんまり Dのやばい機能は使わないようにした。 • パーサジェネレータとしては Peggedというライブラリを採用。 ◦

    PeggedはPEGの文字列表現を受理し、コンパイル時にその定義から Dのコードを生成する。 • 定式化フェーズで定式化した PEGでパーサをジェネレートする • 実装した規模の目安としては 6500行程度 • 動作フローとしてはHalideのコンパイラパスの間で呼び出 されることを想定している • 入力はHalide IRの文字列表現で、出力はパース結果の S式 ◦ D言語とC++のクラスを直接ブリッジするのは手間なので、 S式でブリッジする ◦ Halideのコンパイラパスとして実装されたパース結果を受け取るパスが S式をもとにHalide IRを復元 ◦ ↑このコンポーネントは時間が足りず未実装
  16. インターンの感想&反省 • 感想 ◦ 最高だった!! ▪ 構文解析器について朝倉さんに質問・議論できた。 ▪ 非常に勉強になり、構文解析についてちゃんと勉強したくなった ▪

    いままで他のプログラマと一緒に検討しながらちゃんと仕事をするという経験がなかったので非常に 刺激になったし、楽しかった。 ◦ 心残りとしては、 S式→C++でHalide IRに再構築の部分を書く時間がなかったこと。 ◦ でも、D言語で実装する分は一通り実装できてよかった。 • 反省 ◦ FPGAについてFixstarsの人たちに教えてもらおうと思ってたけど、時間が取れず聞けなかったのが残念 (頑 張って勉強したい )