[ 目次, 前節, 次節, 索引 ]

5.5  翻訳系



5.4節の積極制御評価器はレジスタ計算機であり, その制御器はSchemeプログラムを解釈する. 本節では, 制御器がScheme解釈系でないレジスタ計算機でScheme プログラムを走らせる方法を調べる.

   積極制御評価器計算機は万能である. ---それはSchemeで記述出来るどんな計算プロセスでも実行出来る. 評価器の制御器は望みの計算を実行するため, データパスの使用を調和を保ち総動員する. 従って評価器のデータパスは万能である: それらは適切な制御器があれば, われわれが望むどんな計算でも実行するのに十分である.33

   商用の汎用計算機は, 効率よく便利な万能的データパスを構成するレジスタと演算を中心に組織化したレジスタ計算機である. 汎用計算機の制御器は, ここで使ってきたのに似たレジスタ計算機言語の解釈系である. この言語は計算機の自分の言語 (native language)とか単に機械語 (machine language)という. 機械語で書いたプログラムは, 計算機のデータパスを使う命令列である. 例えば 積極制御評価器の命令列は, 特殊な解釈系計算機の制御器というよりは, 汎用計算機の機械語プログラムと考えることが出来る.

   高レベル言語とレジスタ計算機の間隙を橋渡しする通常の戦略は二つある. 積極制御評価器は, 解釈による戦略を示した. 計算機の自分の言語で書いた解釈系は, 評価を実行する計算機の自分の言語とは違う (原始言語(source language)という)ある言語で書いたプログラムを実行する計算機を形成する. 原始言語の基本手続きは, その計算機の自分の言語で書いたサブルーチンライブラリとして実装する. 解釈すべきプログラム (原始プログラム(source program)という)は, データ構造として表現される. 解釈系はこのデータ構造を渡り歩き, 原始プログラムを解析する. 解釈系はそうしながら, ライブラリの適切な基本サブルーチンを呼び出して, 原始プログラムが意図した振舞いをシミュレートする.

   本節ではもう一つの翻訳(compilation)戦略を調べる. ある原始言語と計算機の翻訳系は, 原始プログラムを, 計算機の自分の言語で書いた等価なプログラム (目的プログラム(object program)という)に変換する. 本節で実装する翻訳系は, Schemeで書いたプログラムを, 積極制御評価器計算機のデータパスを使って実行する命令列に変換する.34

   解釈と比べると, 翻訳は, 以下の翻訳系の概説で述べるように, プログラムの実行の効率を格段に高め得る. 他方, 解釈系は, 実行中の原始プログラムを実行時に調べたり修正したり出来るので, 対話的プログラム開発と虫取りには遥かに強力な環境を提供する. その上基本手続きの全ライブラリが実在するので, 虫取り中に新しいプログラムを構成し追加出来る.

   翻訳と解釈の相補的利点から, 最新のプログラム開発環境は, 混合戦略を追求する. Lispの解釈系は, 通常, 解釈される手続きと翻訳される手続きが相互に呼び出せるように作られる. そのため, プログラマは, プログラムの虫が取られたと思われる部分は翻訳し, 一方プログラムで解釈的開発と虫取りの流転の中の部分は, 実行の解釈モードに残しておく. かくして翻訳の効率上の利点を得ることが出来る. 5.5.7節では, 翻訳系を実装した後, それを解釈系と接合し, 統合解釈-翻訳開発システムをどう作るかを示す.

翻訳系の概観
われわれの翻訳系は, 構造においても実行する機能においても, 解釈系によく似ている. 従って式を解析するため, 翻訳系が使う機構は, 解釈系の使うものと同様になる. 更に翻訳されたコードと解釈されたコードの接合が容易にとれるよう, 翻訳系が解釈系と 同じレジスタの使い方に従うコードを生成するよう設計する: つまり環境はenvレジスタに置き, 引数リストはarglにためられ, 作用させるべき手続きはprocにあり, 手続きは答をvalに返し, 手続きが戻るべき場所はcontinueに置いてある. 一般に翻訳系は原始プログラムを, 解釈系が同じ原始プログラムを評価する時と本質的に同じレジスタ演算を実行する目的プログラムへ変換する.

   この説明は, 基本的翻訳系の実装の戦略を示す: 解釈系がなすのと同じ方法で式を渡り歩く. 式の評価中に解釈系が実行するレジスタ演算に出会った時は, その命令を実行せず, 命令列にため込む. こうして出来た命令列が目的コードである. 翻訳の解釈に対する 効率の利点を見よう. 解釈系が式---例えば(f 84 96)---を評価する度, 式を分類し(これは手続き作用であると発見し), 被演算子リストの最後をテストする(被演算子は二つあることを発見する)という作業を実行する. 翻訳系では, 式は, 翻訳時に命令列を生成する時にただ一回解析される. 翻訳系が作る目的コードは, 演算子と二つの被演算子を評価し, 引数リストを集め, (procの)手続きを(arglの)引数に作用させる命令を含むだけである.

   これは4.1.7節の解析評価器で実装したのと同じ最適化である. しかし翻訳したコードには, 効率を獲得する更なる機会がある. 解釈系が走る時, 言語のあらゆる式に使える処理を実行する. 対照的に, 翻訳したコードの任意の部分は, ある特定の式を実行するものである. これは, 例えばレジスタを退避するスタックの使用で, 大きな差になり得る. 解釈系が式を評価する時, それはあらゆる偶然に備えなければならない. 部分式の評価の前には, 解釈系は, 部分式はどんな評価を要求するか分らないので, 後に必要になるすべてのレジスタを退避する. 他方翻訳系は, 処理しようとしている式の構造を調べ, 不要なスタック演算を避けるコードを生成することが出来る.

   さしあたり, 組合せ(f 84 96)を考えよう. 解釈系が組合せの演算子を評価する前に, この評価の準備として, その値が後で必要になる被演算子と環境を持っているレジスタを退避する. 解釈系は次に演算子を評価し, 結果をvalに得, 退避したレジスタを回復し, 最後に結果をvalからprocへ移す. しかし今扱っているこの問題では, 演算子は記号fであり, その評価はどのレジスタも変更しない機械命令 lookup-variable-valueで実現出来る. 本節で実装する翻訳系は, この事実を利用し, 命令

(assign proc (op lookup-variable-value) (const f) (reg env))

を使って演算子を評価するコードを生成する. このコードは不必要な退避回復を避けるだけでなく, 探索の値を直接procへ代入するが, 解釈系の方は結果をvalに得て次にそれをprocへ移していた.

   翻訳系は環境へのアクセスも最適化する. コードを解析すると, 翻訳系は多くの場合, どのフレームに特定の変数が存在するかを知り, lookup-variable-value探索をせず, そのフレームへ直接アクセスする. 5.5.6節でそういう変数アクセスの実装法を論じる. しかしそれまでは上述のレジスタとスタックの最適化に注目しよう. 翻訳系が実行する他の最適化も多い. 例えば汎用のapply機構を使う代りに「展開した」基本演算にコード化する(問題5.38参照); しかしここではこれを強調しない. 本節の主目的は単純な(しかも面白い)文脈で翻訳処理を示すことである.


33 これは理論的な陳述である. われわれは評価器のデータパスが汎用計算機として特に便利だとか, データパスの効率のよい集合だといっているのではない. 例えば, 高性能の浮動点小数演算や, ビットベクタを強力に操作する演算の実装には特に優れているわけではない.

34 実際には 翻訳したプログラムを走らせる計算機は, expunevレジスタを使うことがないので, 解釈系の計算機より単純であり得る. 解釈系はこれらのレジスタを未評価の式の断片を保持するのに使った. しかし翻訳系では, これらの式はレジスタ計算機が走らせる翻訳したプログラムの中に構築される. 同じ理由で式の構文を扱う計算機命令は必要でない. しかし 翻訳したプログラムは, (翻訳された手続きオブジェクトを表現するため)積極制御評価計算機には現れなかった, いくつかの追加の計算機命令を使う.

[ 目次, 前節, 次節, 索引 ]