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

5.5.6 文面アドレス



翻訳系による最適化の最も普通のものの一つは, 変数探索の最適化である. これまで実装して来たわれわれの翻訳系は, 評価器計算機のlookup-variable-value演算を使うコードを生成する. これは実行時環境でフレーム毎に外へ向って現在束縛している各変数と比較することで変数を探索する. この探索はフレームが深く入れ子になっていたり, 変数が多かったりすると高価になる. 例えば
(let ((x 3) (y 4))
  (lambda (a b c d e)
    (let ((y (* a b x))
          (z (+ c d x)))
      (* x y z))))
で返された手続きの作用で, 式(* x y z)を評価する時, xの値を探している問題を考えよう. let式はlambda組合せの構文シュガーに過ぎないから, これは
((lambda (x y)
   (lambda (a b c d e)
     ((lambda (y z) (* x y z))
      (* a b x)
      (+ c d x))))
 3
 4)
と等価である. lookup-variable-valuexを探索する度に, 記号xは(第一のフレームで)yzeq?でないか, (第二のフレームで) a, b, c, deeq?でないか決めなければならない. ここでわれわれのプログラムはdefineを使わない---変数は lambdaだけで束縛される---と仮定しよう. われわれの言語は 静的有効範囲だから, 式の実行環境は式が現れるプログラムの文面の構造と対応する構造を持っている.45 従って翻訳系は上の式を解析する時, 手続きを作用させる度に(* x y z)の変数は現在のフレームから二フレーム外で見つかり, そのフレームの最初の変数であると知ることが出来る.

   この事実を新しい変数探索演算lexical-address-lookupを考えて調べることが出来る. 引数として環境と二つの数: 何個のフレームを読み過すかを指定するフレーム数(frame number)と, そのフレームで何個の変数を読み過すかを指定する変位数(displacement number)からなる文面アドレス(lexical address)をとる. lexical-address-lookupは現在の環境から相対的な文面アドレスに格納した変数の値を出す. われわれの計算機にlexical-address-lookup演算を追加すると, 翻訳系にlookup-variable-valueではなく, この演算を使って変数を参照するコードを, 生成させることが出来る. 同様に翻訳したコードは, set-variable-value!の代りに lexical-address-set!を使うことが出来る.

   こういうコードを生成するためには, 翻訳系は参照を翻訳しようとする変数の文面アドレスを決めることが出来なければならない. プログラム中の変数の文面アドレスは, それがコードのどこにあるかに依存する. 例えば次のプログラムで式⟨e1⟩のxのアドレスは(2,0)---二フレーム後方で, そのフレームの最初の変数---である. その点でy はアドレス(0,0), cはアドレス(1,2)である. 式⟨e2⟩ではxは(1,0), yは(1,1), そしてcは(0,2)である.

((lambda (x y)
   (lambda (a b c d e)
     ((lambda (y z) ⟨e1⟩)
      ⟨e2⟩
      (+ c d x))))
 3
 4)

   翻訳系に文面アドレスを使ったコードを作り出させる一つの方法は, 翻訳時環境 (compile-time environment)というデータ構造を維持することである. これは, ある変数アクセス演算を実行する時に, どの変数が実行時環境のどのフレームのどの場所にあるかを覚えておくものである. 翻訳時環境は, それぞれが変数のリストを持つフレームのリストである. (もちろん, 翻訳時に値は計算しないので, 変数には値は束縛していない.) 翻訳時環境はcompileの追加の引数となり, 各コード生成器に渡される. compileへのトップレベルでの呼出しは, 空の翻訳時環境を使う. lambda本体を翻訳する時, 本体を形成する列が, 拡張した環境で翻訳出来るよう, compile-lambda-bodyは翻訳環境を, 手続きのパラメタを含むフレームで拡張する. 翻訳の各時点で, compile-variablecompile-assignmentは適切な文面アドレスを生成するため, 翻訳時環境を使う.

   問題5.39から5.43は文面探索を翻訳系に組み込むため, 文面アドレス戦略の概要を完成する方法を述べる. 問題5.44は翻訳時環境のもう一つの使い方を述べる.

問題 5.39


新しい探索演算を実装する手続きlexical-address-lookupを書け. それは二つの引数---文面アドレスと実行時環境---をとり, 指定した文面アドレスに格納した変数の値を返す. lexical-address-lookupは変数の値が*unassigned*の記号ならエラーとする.46 また, 指定した文面アドレスにある変数の値を変更する演算を実装する手続きlexical-address-set!を書け.

問題 5.40


翻訳系を修正し, 上述の翻訳時環境を維持するようにせよ. つまりcompileとそれぞれのコード生成器に翻訳時環境の引数を加え, compile-lambda-body で翻訳時環境を拡張せよ.

問題 5.41


引数として変数と翻訳時環境をとり, その環境に対する変数の文面アドレスを返す手続きfind-variableを書け. 例えば上に示したプログラムの断片では, 式⟨e1⟩翻訳中の翻訳時環境は((y z) (a b c d e) (x y))である. find- うに出さなければならない.

(find-variable 'c '((y z) (a b c d e) (x y)))
(1 2)

(find-variable 'x '((y z) (a b c d e) (x y)))
(2 0)

(find-variable 'w '((y z) (a b c d e) (x y)))
not-found


問題 5.42


問題5.41のfind-variableを使い, compile-variablecompile-assignmentを書き直して文面アドレス命令を出力するようにせよ. find-variablenot-foundを返す(つまり, その変数が翻訳時環境にない)場合は, コード生成器に以前のように束縛を探すため評価演算を使わせなければならない. (翻訳時に見つからない変数が出てくる唯一の場所は大域環境, つまり実行時環境の一部だが, 翻訳時環境の一部ではないところである.47 従って, 望むなら, envで見つかる全実行時環境を探させる代りに, 評価演算に演算(op~get-global-environment)で得られる大域環境を直接見させてもよい.) 修正した翻訳系を, 本節の最初の入れ子のlambda 組合せのようないくつかの単純な場合でテストせよ.

問題 5.43


われわれは4.1.6節で, ブロック構造の内部定義は「真の」defineと考えるべきでないと論じた. むしろ手続き本体は, 定義している内部変数は, set!を使って正しい値に初期化する通常のlambda変数として組み込まれているように解釈すべきである. 4.1.6節と問題4.16は, 超循環評価器を修正し, 内部定義を掃き出してこれを実現する方法を示した. 翻訳系を修正し, 手続き本体を翻訳する前に, 同じ変換を実行するようにせよ.

問題 5.44


本節では翻訳時環境を使って文面アドレスを作ることに注目した. しかし翻訳時環境には他の使い方もある. 例えば問題5.38では翻訳したコードをオープンコード基本手続きによって効率を高めた. われわれの実装はオープンコード手続きの名前を予約語として扱った. プログラムがそういう名前を再束縛するなら, 問題5.38に述べた機構は, 新しい束縛を無視し, それを基本手続きとしてオープンコードのままとする. 例えばxyの線形結合を計算する手続き
(lambda (+ * a b x y)
  (+ (* a x) (* b y)))
を考えよう. 引数+matrix, *matrixと四つの行列を持ってこれを呼び出したいが, オープンコード翻訳系は(+ (* a x) (* b y))+*を基本手続きの+*としてオープンコードのままとする. オープンコード翻訳系を修正し, 基本手続きの名前を含む式の正しいコードを翻訳するため, 翻訳時環境を調べるようにせよ. (プログラムがこれらの名前をdefineしたりset!したりしないうちは, コードは正しく働く.)



45 内部定義を許すと, それを掃き出さない限り, これは正しくない. 問題5.43参照

46 これは, 内部定義を消去する 掃き出し技法を実装するなら, 変数探索の必要な修正である(問題5.43参照). 文面アドレスが働くためには, これらの定義の消去が必要である.

47 文面アドレスは大域環境の変数のアクセスには, これらの名前はいつでも対話的に定義, 再定義されるので使えない. 問題5.43のように内部定義が掃き出された場合には, 翻訳系が見る唯一の定義はトップレベルのものであり, それらは大域環境として働く. 定義の翻訳は定義した名前を翻訳時環境に入れるとは限らない.

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