Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

3impにおけるDirect Function Invocationsの実装

1,287 views

Published on

Published in: Technology
  • Be the first to comment

  • Be the first to like this

3impにおけるDirect Function Invocationsの実装

  1. 1. <ul><ul><li>3imp における </li></ul></ul><ul><ul><li>Direct Function Invocations の </li></ul></ul><ul><ul><li>実装 </li></ul></ul><ul><ul><li>mokehehe </li></ul></ul>'09/04/05
  2. 2. 3imp <ul><li>3imp (Three Implementation Models for Scheme [ pdf ])という、Schemeコンパイラを実装するための論文がある </li></ul><ul><ul><li>SchemeのソースをコンパイルしVMで実行する </li></ul></ul><ul><li>スタックベースモデルの実装例 </li></ul>
  3. 3. スタックベースVMの改善点 <ul><li>4.7 できそうな改善 </li></ul><ul><ul><li>1. グローバル変数とプリミティブ関数 </li></ul></ul><ul><ul><li>2. 関数を直接実行する </li></ul></ul><ul><ul><li>3. 末尾再帰の最適化 </li></ul></ul><ul><ul><li>4. クロージャをヒープに作らない </li></ul></ul><ul><ul><li>5. 継続をジャンプに </li></ul></ul>
  4. 4. 関数の直接実行? <ul><li>まず、Schemeでの関数呼び出しとは </li></ul><ul><ul><li>例:(func 1 2) </li></ul></ul><ul><ul><li>括弧の先頭(func)が関数、残り(1 2)が引数 </li></ul></ul>
  5. 5. 関数とは <ul><li>lambda式 </li></ul><ul><ul><li>(lambda (仮引数...) 本体...) </li></ul></ul><ul><ul><li>名なし関数 </li></ul></ul>
  6. 6. 関数の直接呼び出しとは? <ul><li>名前のついた関数ではなく、lambda式を関数呼び出し形式の先頭に書いて直接呼び出す </li></ul><ul><ul><li>((lambda (...) ...) 引数...) </li></ul></ul><ul><li>そんな書き方普通しないからwww </li></ul>
  7. 7. let式 <ul><li>Schemeでローカル変数を導入するしくみ </li></ul><ul><li>よく使う </li></ul>(let ((x 111)) (* x x))
  8. 8. let式(2) <ul><li>実はlet式はマクロによって変換されてる </li></ul>((lambda (x) (* x x)) 111) <ul><li>関数の直接呼び出しの形が頻繁に発生! m9(^Д^)ぷぎゃー </li></ul>(let ((x 111)) (* x x))
  9. 9. コンパイル結果 (CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1) <ul><li>クロージャを生成し、それを呼び出すコードが出力される。 </li></ul>((lambda (x) (* x x)) 111) (let ((x 111)) (* x x)) マクロ展開 コンパイル
  10. 10. 実行時の動作 (CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1) <ul><li>初期状態 </li></ul>
  11. 11. 実行 (1) (CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1) <ul><li>CONST, ARGで定数をスタックに積む </li></ul>
  12. 12. 実行 (2) <ul><li>CLOSEでクロージャ生成 </li></ul><ul><ul><li>(1 . 1)は受け付ける引数の数 </li></ul></ul><ul><ul><li>0はキャプチャする自由変数の数 </li></ul></ul><ul><ul><li>(LREF〜)がコード本体 </li></ul></ul><ul><ul><li>malloc が発生 </li></ul></ul>(CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1)
  13. 13. 実行 (3) <ul><li>SHIFTで末尾呼び出し用にスタック操作 </li></ul>(CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1)
  14. 14. 実行 (4) <ul><li>APPLY 1で、クロージャに引数1個を適用 </li></ul><ul><ul><li>適用した引数の数をスタックに積む </li></ul></ul>(CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1)
  15. 15. 実行 (5) <ul><li>クロージャの本体に制御が移動 </li></ul><ul><li>LREF 0で最初の引数(111)を参照 </li></ul><ul><li>ARGでスタックに積む </li></ul>(CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1)
  16. 16. 実行 (6) <ul><li>同様 </li></ul>(CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1)
  17. 17. 実行 (7) <ul><li>GREF * で、グローバル関数*を参照 </li></ul>(CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1)
  18. 18. 実行 (8) <ul><li>SHIFTで末尾呼び出し用にスタック操作 </li></ul><ul><ul><li>一つ上のスタックフレームを潰す </li></ul></ul>(CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1)
  19. 19. 実行 (9) <ul><li>APPLY 2で、*に2個の引数を適用 </li></ul><ul><li>結果として 12321 を得る </li></ul><ul><li>スタックは空に </li></ul>(CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1)
  20. 20. 問題点 <ul><li>1ヶ所でしか使われない関数なのに、実行時にクロージャが生成されてしまうのは嬉しくない </li></ul><ul><li>関数のインライン展開のように、関数の本体を直接埋め込みたい </li></ul>
  21. 21. VMへの命令の追加 <ul><li>VMにEXPANDとSHRINKという命令を追加 </li></ul><ul><ul><li>EXPAND: スタックフレームを拡張 </li></ul></ul><ul><ul><li>SHRINK: スタックフレームを縮小 </li></ul></ul>
  22. 22. EXPAND <ul><li>EXPAND nで、n個の要素をスタックフレームに追加 </li></ul>現在の フレームm個 追加する 要素n個 フレームを拡張
  23. 23. SHRINK <ul><li>SHRINK nで、n個の要素をスタックフレームから取り除く </li></ul>
  24. 24. コンパイル時の動作 <ul><li>関数の直接呼び出しの形を見つけたら CLOSE〜APPLYの命令の代わりに、 EXPAND〜SHRINK命令を生成 </li></ul>
  25. 25. コンパイル結果 (CONST 111 ARG EXPAND 1 LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) (CONST 111 ARG CLOSE (1 . 1) 0 (LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2) SHIFT 1 APPLY 1) Before After <ul><li>クロージャ生成がなくなった! </li></ul>((lambda (x) (* x x)) 111) (let ((x 111)) (* x x)) =
  26. 26. 実行時の動作 <ul><li>初期状態 </li></ul>(CONST 111 ARG EXPAND 1 LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2)
  27. 27. 実行 (1) <ul><li>CONST, ARGで定数をスタックに積む </li></ul>(CONST 111 ARG EXPAND 1 LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2)
  28. 28. 実行 (2) <ul><li>EXPAND 1でスタックフレームを1つの引数分拡張する </li></ul>(CONST 111 ARG EXPAND 1 LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2)
  29. 29. 実行 (3) <ul><li>(LREF 0, ARG) x 2 </li></ul>(CONST 111 ARG EXPAND 1 LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2)
  30. 30. 実行 (4) <ul><li>SHIFT, *関数を呼び出し、結果12321 </li></ul><ul><li>末尾呼び出しによってSHRINKが省略されている </li></ul><ul><li>実行時にmallocなしで済むように なった! </li></ul>(CONST 111 ARG EXPAND 1 LREF 0 ARG LREF 0 ARG GREF * SHIFT 2 APPLY 2)
  31. 31. 結論 <ul><li>関数の直接呼び出しを実装することで、実行時の無駄なクロージャ生成が省けた </li></ul><ul><li>めでたしめでたし </li></ul>

×