2014年10月27日月曜日

プログラミング言語と静的処理系の駄考察

今日はドラゴンブックから離れて、自分が日頃考えている俺言語のあり方(カッコイイw)について少し駄文を連ねてみたいと思う次第です。

まず、プログラミング言語処理系と静的処理系についてです。プログラミング言語で何かを書いて実行するには、勿論処理系が必要となります。プログラミング言語にとってプログラミング言語処理系はマンダトリです。それに対して静的処理系はあったらうれしいな♡とか、コーディング規約守ってるか、メモリーリークしてないか、とか、わりと書いたあと知りたい事実を知るために必要になる感じで、やや一歩遅れて歩んでる感じがします。

そして大事なことは、すべてのプログラミング言語処理系が対応する静的処理系を持っている訳ではないということです。プロセデュアルなプログラミング言語では、CFG(Control Flow Graph)を使った静的解析が手法として確立されている感がありますが、より高次の記述ができる言語やスクリプト言語などは、静的処理系が発達している例があまり無いような気がしています。

理由はおそらく次の通りです。


  1. CFGを作るだけではダメで、alias解析など、いろいろ解析しないとプログラムのデーターフローが解析できない
  2. そもそもCFGとはかけ離れた言語の構造を持っている
  3. 関数型言語のように、カリッカリに最適化されるため、コード断片との一対一対応が取れない
  4. (そして)静的処理系の作成にモチベーションを見出せない(=マニーにならない、など)
そこで私はこう考えます。

プログラミング言語にとって、視覚化はまさしく、これからのコンパイラにとって必要な機能になるのではないかと考えています。なぜなら、従来のプログラミング言語は闇を探るような作業を強いられる場面があまりにも多かったと考えるからです。私たち(プログラミング言語設計者)は言語の構造や細かい設定、そしてデータフローなどをもっと様々な表現形で表出すべきだと思うのです。

そして、そのためにプログラミング言語を二つの層に分け、静的処理系の対象をより低次の言語に限定し、その言語自体は静的解析が可能である、単純な構造を持たせるべきなのでは、と私は考えています。そしてできれば高次の表現において新しいプログラムのデータフローなどを新しい表現形で表現できればいいな、と思うのです。

よって、次の二つの言語を作ることを本ブログの目的としています。

  1. HL(High Level Language): スクリプト言語のような高次の記述を持つ言語。
    LL(後述)の操作が可能
  2. LL(Low Level Language): 制限されたLLVM IRに簡単に変換できるような低次の言語。HLの内部に(GCCのasm記述のように)LLを記述することが可能
そしてそのLLの静的処理系を作りたいなと考えています。夢は大きいですね!

2014年10月18日土曜日

掛け算の順序

度々ネットなどで「子供が掛け算の順序を間違えたら×を食らった」などと拡散されることのあるこの「掛け算の順序問題」、根が深いらしく色々な意見がネット上にあふれていますが、コンパイラ理論(プログラミング言語の表現をコンピュータが実行可能な形式に直すプログラムをコンパイラと言います)的には部分的には合点できる点もあるというお話です。

一般的なプログラミング表現である、" val = 2 * 3 * 4" について、解説してみたいと思います。

まず、機械語と呼ばれるコンピュータの理解できる表現においては、数字を三つ持つ掛け算は(一部の例外を除いて)ありません。つまり2 * 3 * 4を一度に計算できる方法は無いのです。そこで、初めに二つを掛け合わせ、最後にもう一つを掛け合わせます。

文脈自由文法的に書くと(意図的に曖昧性がある文法にしてあります)、

 assign -> lvalue = expr
 lvalue  -> 変数名

(変数名は、具体的な規則は省略しますが正規表現で書くと
 [a-zA-Z_][0-9a-zA-Z_]* です。変数名からvalが導出できるとします)

 expr    -> expr * expr | digits ("|"はorの意味で、A|BはAかBが導出されます)
 expr    -> digits
 digits   -> digits digit | digit
 digit     -> 0|1|2|3|4|5|6|7|8|9

この表現は、左の非終端記号(文字の集まりだと思ってください)が、"->" の右の非終端記号や終端記号(具体的な文字)の列に書き換えられることを表しています。

assignがスタートの非終端記号です。assign を複数回書き換えると、val = 2 * 3 * 4を導出できることを示してみたいと思います。

順に導出してみましょう。

assign
lvalue = expr
val      = expr
val      = expr * expr
val      = digits * expr   (ここ注目!)
val      = digits * expr * expr
val      = digits * digits * expr
val      = digits * digits * digits
val      = digit * digits * digits
val      = digit * digit * digit
val      = 2 * 3 * 4               (一気に導出!)

たしかに、上記文脈自由文法から導出できました!

と、言いたいところですが、この文法は曖昧です(残念!)

具体的には

val      = digits * expr   (ここ注目!)

は、

val      = expr * digits

とも導出できます。つまり文法規則から一意な導出が決まらないのです。

掛け算が複数あるだけの表現においてはこれはさほど問題にならないのですが、
優先度の違う二項演算子(-+*/)では優先度があるため、文法が曖昧だと
導出が複数できてしまい、その結果、違う解釈ができてしまうため、計算結果が
違う導出候補が出来てしまいます。困った困った。

曖昧性のない文法を作るために、昔の人も苦心しました。その結果、
次のように書けば、曖昧性がない文法になることを発見したのです。

変更前:expr    -> expr * expr | digits ("|"はorの意味で、A|BはAかBが導出されます)

変更後: expr    -> expr * digits | digits

やってみましょう。

assign
lvalue = expr
val      = expr
val      = expr * digits
val      = expr * digits * digits
val      = digits * digits * digits
val      = digit * digits * digits
val      = digit * digit * digits
val      = digit * digit * digit
val      = 2 * 3 * 4               (一気に導出!)

たしかに、一意に導出できています。

ここで機転の利く方は、次の文法も書けるので、まだ曖昧だよと指摘するかもしれません。

変更前:expr    -> expr * expr | digits ("|"はorの意味で、A|BはAかBが導出されます)

変更後: expr    -> digits * expr | digits

右辺のdigitsとexprが逆の位置にありますね。
たしかに、この文法の場合、導出が変わってきます。やってみましょう。

assign
lvalue = expr
val      = expr
val      = digits * expr
val      = digits * digits * expr
val      = digits * digits * digits
val      = digit * digits * digits
val      = digit * digit * digits
val      = digit * digit * digit
val      = 2 * 3 * 4               (一気に導出!)

つまり導出の仕方が文法により変わり、どちらか一方の文法を採用しないといけないならば、どちらでやるか決めないといけないことがわかります。

ここで色々話をすっ飛ばして結論を言うと、
一番目の曖昧性回避の文法の方はleft associative、
二番目の曖昧性回避の文法の方はright associative、です!

left associativeは、最も左が導出の最後になる文法ルールで、
right associativeは、最も右が導出の最後になる文法ルールです。

で、結論をさらに言うと、四則演算などほとんどの二項演算子(-+*/など)はleft associative、例外として代入とべき乗だけ(=と**)は right associativeと
計算機科学の世界では決まっています。つまり曖昧な文法を避ける避け方が二つあり、
そのどちらを取るかは演算子ごとに決まっているのです。

  *   * *   *   * *   *   * *   *   * *   *   * *   *   * *   *   * *   *   * *   *   * *   *   * * 
 * *   *   * *   *   * *   *   * *   *   * *   *   * *   *   * *   *   * *   *   * *   *   * *   * 

さて本題に戻ります。掛け算の順序についてです。
掛け算は left associativeなので、

val = (2*3)*4

という導出が行われます。つまりこれを機械語風に表現すると

val1 = 2 * 3
val2 = val1 * 4
val   = val2

です。掛け算は左から順に計算していることがわかります。


僕は掛け算の順序の議論を把握しているわけではありませんが、
掛け算の順序にこだわる擁護派は、このleft associativeであることを
言いたかったのではないかと推測するわけです。

もちろん掛け算の連続であればどの順序で掛けても結果は同じです。
ただ、計算機科学的には3つの数値を一緒に掛けることはできないことから、
計算機内部では2回の計算に分けられ、それは左から順に掛け算を行っていく
のです。つまり順序は存在するわけです。

さて長くなりました。
この文書の目的は掛け算の順序派を擁護することが目的なのではなく、
掛け算の順序の存在がコンパイラ理論的にはあるよという見方の提案を
することです。

お家でお子様と一緒に式を導出しながら、掛け算の順序の存在について
議論してみるのもいいかなと思います。

では〜。

(文責kimrin)

 

Dragon Book front-end

purple dragon bookのJava front-endって、2章で詳しく解説されているので、まずは2章を精読して全体の感じ(Lexer->Parser->AST->Syntax Directed Translation)を掴む事にしました。なのでコードを書きたいところを抑えて、2章の完全理解を目指します。

2章が全て理解できたらJulia版dragon book front-endを作ります(移植します)。
で、後で気がついたのですが、purple dragon bookのAppendix Aに全ソースコードが公開されていてかつstanfordのサイトでもtarファイルでソースコードが公開されているのですが、これってpublishedされた時点でcopyrightだなーと。

派生物としてJulia版を公開するとき、LICENSEは結構デリケートな問題になりそうなので、必要なコード断片をgist経由で本blogに掲載するに止め、公開はやめとくことにしました。すみません。ただコード断片で重要な箇所は提示できると思いますし、実際に動いてできたLLVM IRや実行結果もコンソール表示として確認できるので、まぁ、公開してほしい人がコンタクトを取ってこない 限りいいかなぁ、と。

というわけで、priority listは次の通りです♡


  1. purple dragon book 2章の完全理解
  2. Julia版dragon book front-end作成(出力はLLVM IR)
  3. purple dragon book 3章の理解(を進めながら)
  4. (2.)のfront-endを上から下まで(ソースコード入力からELF(Linux,MacOS X)までのパスを通す仕組みを作る(C/C++のコーディングの必要があるかもしれません)
  5. 3章の理解を元に、Lexical scanner generatorの作成
    1. flex(1)のソースコードリーディング(NFA/DFAの求め方、テーブルの作り方、UTF-8対応)
    2. JLexのソースコードリーディング(RegExpのdescendant parser周りを見ながら、正規表現parserをどうやって作るか学習する
  6. 4章を理解する
    1. LL(1)
    2. SLR(1)
    3. LALR(1)
  7. 以上三つのどれか一つを作ってみる!
    1. bisonなどを参考にする

こんな感じでしょうかー。
なんだか作業メモみたいになっちゃってすみません。
次のエントリーでは2章でどんなことを学んだか展開できるといいなと思います。


2014年10月17日金曜日

purple dragon bookのAppendix Aにあるfront-endについて

色々考えたのですが、いきなりLexican scanner generatorをJuliaでゴリゴリ書き始めるのも一つですが、LLVM系の知識が足りないのをどう獲得していこうかと考えていました。

そこで、Lexier -> Parser -> LLVM front-end という順を少し変更して、まずはpurple dragon bookのAppendix Aにある、簡単な言語のfront-end (Javaで書かれています)を、Juliaに移植してみてはどうだろうか、と思うに至った訳です。

このJavaで書かれた900行弱のfront-endは簡単なプログラミング言語のフロントエンドで、three address codeを出力します。dragon bookのfront-endのプログラミング言語とthree address codeとは、こんな感じの表現です。

# purple dragon book の俺言語
{
int a; int b; int c; int d; float term;

a = b + c;
a = b - c;
a = b * c;
a = b / c;
a = -b;

d = a - b - c;
d = a * b * c;
d = a + b * c;
d = a * b + c;

d = (a - b) - c;
d = a - (b - c);
d = (a + b) * c;
d = a * (b + c);

        term = b*b -4.0*a*c;

}

# 出力されたthree address code
L1: a = b + c
L3: a = b - c
L4: a = b * c
L5: a = b / c
L6: a = minus b
L7: t1 = a - b
d = t1 - c
L8: t2 = a * b
d = t2 * c
L9: t3 = b * c
d = a + t3
L10: t4 = a * b
d = t4 + c
L11: t5 = a - b
d = t5 - c
L12: t6 = b - c
d = a - t6
L13: t7 = a + b
d = t7 * c
L14: t8 = b + c
d = a * t8
L15: t9 = b * b
t10 = 4.0 * a
t11 = t10 * c
term = t9 - t11
L2:

仮にJava front-endをそのまま100%意味を温存してJuliaに移植してみても、肝心のthree address codeが動くエミュレーターなどがなければ、実装してもサンプルのソースコードをrunすることはできません。そこで、dragon book流のthree address codeではなく、LLVM IRを出力したらどうか、と考えつきました。

おそらくこのぐらいの大きさのプログラミング言語(ソース言語)ならば、LLVM IRのほんの小さなsub setしか使わないと思いますし、LLVM IRの簡単な勉強になるかなぁと思った次第です。ただそのためにソース言語は少し変更しなければ成らないかもしれません。

ひとまずGitHubにrepo掘って、少しづつ移植してみたいと思いました。

同時にLLVM IRの勉強のために http://llvm.org/releases/3.5.0/docs/tutorial/index.html などを読み進めながら移植を進めたいと思います。

同時にpurple dragon bookのchapter 3(Lexical Analysis)を読んで、Lexの構造について勉強したいと思います。基本的なことは分かっているつもりですが、実装するのに必要な細かい知識、深い知識が欠けていると思うので、少しづつ読み進めたいと思います。

というわけで、TODOリスト


  1. purple dragon bookのfront-endをJulia移植する
  2. http://llvm.org/releases/3.5.0/docs/tutorial/index.html のチュートリアルを読んで、LLVM IRとLLVM front-endの実装に詳しくなる
  3. purple dragon bookのLexical Analysisの章を精読する
ですね。

あ、ちなみにpurple dragon bookは洋書(古い版)を読んでいます。紹介した和書ではないです。

2014年10月15日水曜日

書きたいことを並べてみよう(ブログ構想)

コンパイラ技術のブログを始めるにあたって、筆者(kimrin)が書きたいものと皆さん(オーディエンス)の知りたいことが一致するといいなと思ってます。
なので「この辺知りたい」「この辺解説してほしい」などありましたらコメントでお願いしますです。

基本的にパープルドラゴンブックを読む進めて行きたいと思います。ドラゴンブックを咀嚼して、反芻して、自分なりの直感的な解釈ができたらなと思います。

本来ならコンパイラ技術を蓄積するに当たって、フロントエンド(パーサーなど)からバックエンド(機械語生成)まで幅広く学ばねばとは思うのですが、最近はLLVMのような便利な物があるので、これを使わない手はなかろう、と言う感じで次のものを目指したいと思います。
  • 俺言語を作るw
  • (具体的には)LLVM用の俺言語フロントエンドを作る
さらに、フロントエンドを作るプログラミング言語として今回Julia言語(#JuliaLang)を選びました!ぱちぱち! Lexer, Parserは自分で手書きしてもいいのですが、せっかくなら機械の力を借りたいところです。しかしまだJulia言語には公式のlex, yacc相当のプログラムがありません。そこで、
  • Julia言語のLexical scanner generator (flexみたいなの)を作る!
  • そしてJulia言語のparser(LL(1)かLALR(1))を作る
  • そのために必要なJulia言語の基礎を与える
と、壮大な(ほんと壮大だ!)目標を掲げてみたいと思います!

ドラゴンブックで対応するのが3章のLexical scannerの章と、4章5章のparserやsyntax directed translationsのあたりでしょうか。最終的にIR(ドラゴンブックだと3番地コード)の生成が出来ればそれでOKかなぁということで、ドラゴンブック後半の最適化については全面的にLLVMにまかせちゃお♡という計画です。

最終的に目標として俺言語を作る、でもLLVMで楽して作っちゃおう、でもってコンパイルドライバを作って前処理からリンクまで一気にできるコマンドを開発しちゃおう、と、
これだけで3年は掛かるだろうって内容です。ご確認ください。。。

このブログを通してオーディエンスの方が、コンパイラの基礎知識が備わるだけでなくJulia言語の基礎が備わるようになればいいなと思っています。そして、試行錯誤の過程も惜しむ事無く公開して行きたいと思います。できればブログ自体が本に出来るようなcomplehensiveな内容を目指しているのですが、Lexer, Parserの章がかなり大きくなってしまうかな、って感じです。

まずはLLVMを僕が知らなすぎるので、まずは勉強って感じでしょうか(きつね本もありますし)。そしてflex,JLexなどのソースコードリーディングをして、オレオレLexer generatorをまずは作ってみるところを目指します!

自分でもどんなブログになるのか想像がつきませんが(おい)叱咤激励鞭でぺんぺんの程、どうぞ宜しくお願い致します。

どんな俺言語を作るかについてはまだ考えていません。
まずはJulia言語Lexer generatorを目指したいと思いますです。

個人的には二層の言語の層があって、下の簡単な言語を上位の言語が操作できるような体系を考えていますが、まだ具体的にその構想を形にはしていません。まだできてません。

まぁあの、正直どこまで続くか分かりませんが、どうぞ生暖かく見守っていただけたらと思う次第ですー。

あ、Julia言語を選んだ理由ですが、その言語が僕は好きだから、という単純な理由です。それ以上でもそれ以下でもありませんw

とりあえず、ここから読み始めてます。

http://llvm.org/docs/tutorial/LangImpl1.html

かしこw

2014年10月10日金曜日

ブログを始めるにあたって

このブログは私kimrinがコンパイラ技術について色んな試行錯誤を経て最終的に1冊の本になるような情報を蓄積していくことを目的とした、自己鍛錬のブログです♡

2006年くらいにAspectJ(Javaの方言)の静的解析系を作った事がある程度のレベルです。なので初心者ではないですが、フルスクラッチでコンパイラを書いた、というレベルでもないです。

このブログがコンパイラ技術に興味を持っている人にとって、興味をそそるようなブログになることを願っています。どうぞ宜しくお願い致します。

ちなみに私は今日40歳の誕生日を迎えました。技術者として一つの節目を迎えたわけです。そこで自分に「何がやりたい?」と自問してみると、やっぱり昔取った杵柄(きねづか)の、コンパイラ技術について、さらに深めて行きたいしできれば一つのプログラミング言語を作れたらという思いがとても強かったです。なので僕にとって今日はコンパイラ記念日ですw

これだけだとコンパイラについて何の情報もないので、コンパイラの教科書について僕の持っている情報を展開しておきましょう。一方的な解説なので、もし間違いなどありましたらご指摘願います。

Information & Computing 別巻38

「コンパイラ[第2版]」
~ 原理・技法・ツール ~

A.V.エイホ
M.S.ラム
R.セシィ
J.D.ウルマン 著
原田賢一(慶應義塾大学名誉教授) 訳

http://www.saiensu.co.jp/?page=book_details&ISBN=ISBN978-4-7819-1229-5

表紙にドラゴンの絵が書かれているので、「ドラゴンブック」といいます。特に、最近(といっても5年以上前ですが。。。)出た第二版は「パープルドラゴンブック」と呼ばれ、世界中でコンパイラの教科書として使用されているようです。

僕は原書をある程度つまみ食いしたたちですが、あの英文を読んでいると心が落ち着きます(そこかw)。

恐らくC言語やJava言語あたりを想定しているような記述が中心です。関数型言語については若干解説があります。あとJVMのような仮想機械についての話も若干解説があります。ただターゲットは昔からのコンパイラ言語、という感じがします。

ドラゴンブックはまずLexer, Parserの基本的な技術を中心に進み、記号表などの話のあとでDAGから3番地命令を生成するところあたりがハイライトかと思います。そして最適化の話があって、CFG(コントロールフローグラフ)上での最適化の話があって、と言う感じです。最終章付近には静的解析系の話なんかもあります。

僕の所感は「教科書ですね」です。古来からのコンパイラ技術の解説が惜しみなくされていて、コンパイラ技術の総合デパートという感じで楽しいです。ただ、やはり実用性や即効性(すぐに投入できる技術)としては(ターゲットの技術にも依りますが)ちょっと教科書しちゃってるかな、って感じがしますです。

想定されている読者はコンピュータサイエンスを学ぶ大学生です(多分)。なのでGCCの最適化について知りたい読者や、コンパイルオプションについて知りたい人にとって、おそらくこの本はやや回り道です。しかしコンパイラに必要な技術を体系的に学びたい人にとっては、この本は福音とも呼べる内容です。

個別の章について見て行くと、Lexer, Parser(LL(k), LALR(1))の章はコンパイラ技術のパーサーの部分についての章ですが、コンパイラコンパイラがある言語でコンパイラを書く人にとっては完璧に理解する必要はないと思います。逆にLexical scanner generator や compiler-compilerを作りたい人はこれらの章は必見です。コンパイラの技術の中心は、先ほども触れた、式をDAGにして、そのDAGから3番地コードを導くところが本質です。3番地コードとは、仮想的なCPUインストラクションで、非常に基本的な命令が備わっている命令セットと思えばいいと思います。

後半は最適化が中心ですが、僕が個人的に一番楽しいのでは、と思う箇所はやはりCFGのグラフを使った最適化だと思います。またdataflow analysisのところも読んでいて楽しかったです。

最新コンパイラ構成技法

Andrew W. Appel (著 )/ 神林靖 (訳 )/ 滝本宗宏 (訳 )/ 翔泳社 (出版社 )
http://www.seshop.com/product/detail/11456/

こちらは表紙にトラが書かれているのでタイガーブックと呼ばれています。中日ファンはドラゴンブックを、阪神ファンはタイガーブックを選んでください(嘘です。ごめんなさいw)

AppelさんはSMLと呼ばれる言語ファミリーの実装者のお一人と聞いています。type inferenceなどについてはTAPLがよい解説書だと思われますが、こちらの本はtigerというtype inferenceの無い言語を作って行く本です。ちなみにAppel先生はプリンストン大学の先生なのですが、トラはプリンストン大学のシンボルなのだそうです。

ややドラゴンブックよりも新しいトピックを、急ぎ足で取り扱っている感じがします。そのためSSA形式などのやや先進的な内容についての記述があり有り難いです。どちらかというとドラゴンブックが教科書的にコンパイラの諸処の問題を扱っているのに対して、タイガーブックはすぐ使えるような技術を具体的に記述している感じがします。

ただ、洋書は間違いが多かったと聞いています。和書では直っているといいですね。

コンパイラ技術に興味があるなら、両方持っている、が正解でしょう。