人間はバイトコードを書きません。 それはバイトコンパイラの仕事です。 しかし、好奇心を満たすために逆アセンブラを用意してあります。 逆アセンブラはバイトコンパイルしたコードを人が読める形式に変換します。
バイトコードインタープリタは、単純なスタックマシンとして実装してあります。 値を自前のスタックに積み、計算に使うためにスタックから取り出し、 計算結果そのものはスタックにまた積みます。 バイトコード関数から戻るときには、スタックから値を取り出して 関数値としてその値を返します。
スタックに加えて、変数とスタックのあいだで値を転送することで、 バイトコード関数は、普通のLisp変数を使ったり、 束縛したり、値を設定できます。
この関数はobjectの逆アセンブルしたコードを出力する。 streamを指定すると、そこへ出力する。 さもなければ、逆アセンブルしたコードはストリーム
standard-output
へ 出力する。 引数objectは関数名かラムダ式である。特別な例外として、この関数を対話的に使うと、 ‘*Disassemble*’という名前のバッファへ出力する。
disassemble
関数の使用例を2つ示します。
バイトコードとLispソースとの対応を取れるように
特別なコメントを追加してありますが、
これらはdisassemble
の出力には現れません。
これらの例は、最適化してないバイトコードです。
現在、バイトコードは、普通、最適化しますが、
目的は果たせるので、例を書き換えてありません。
(defun factorial (integer) "Compute factorial of an integer." (if (= 1 integer) 1 (* integer (factorial (1- integer))))) ⇒ factorial (factorial 4) ⇒ 24 (disassemble 'factorial) -| byte-code for factorial: doc: Compute factorial of an integer. args: (integer) 0 constant 1 ; スタックに1を積む 1 varref integer ; 環境からinteger
の値を取得し、 ; スタックに積む 2 eqlsign ; スタックの先頭から2つの値を ; 取りさって比較し、 ; 結果をスタックに積む 3 goto-if-nil 10 ; スタックの先頭から値を取りさり ; 検査する。nil
ならば10へ飛び、 ; さもなければつぎへ進む 6 constant 1 ; スタックに1を積む 7 goto 17 ; 17へ飛ぶ(この場合、関数は1を返す) 10 constant * ; スタックにシンボル*
を積む 11 varref integer ; スタックにinteger
の値を積む 12 constant factorial ; スタックにfactorial
を積む 13 varref integer ; スタックにinteger
の値を積む 14 sub1 ; スタックからinteger
を取りさり、 ; 減した新たな値をスタックに積む ; スタックの現在の内容はつぎのとおり ; −integer
を減らした値 ; −factorial
; −integer
の値 ; −*
15 call 1 ; スタックの最初(先頭)要素を使って ; 関数factorial
を呼び出す ; 戻り値をスタックに積む ; スタックの現在の内容はつぎのとおり ; −factorial
の ; 再帰呼び出しの結果 ; −integer
の値 ; −*
16 call 2 ; スタックの最初の要素の2つ ; (先頭の2つ)を引数として ; 関数*
を呼び出し ; 結果をスタックに積む 17 return ; スタックの先頭要素を返す ⇒ nil
関数silly-loop
は、少々複雑です。
(defun silly-loop (n) "Return time before and after N iterations of a loop." (let ((t1 (current-time-string))) (while (> (setq n (1- n)) 0)) (list t1 (current-time-string)))) ⇒ silly-loop (disassemble 'silly-loop) -| byte-code for silly-loop: doc: Return time before and after N iterations of a loop. args: (n) 0 constant current-time-string ;current-time-string
を ; スタックの先頭に積む 1 call 0 ; 引数なしでcurrent-time-string
を ; 呼び出し、結果をスタックに積む 2 varbind t1 ; スタックから値を取りさり、 ;t1
に束縛する 3 varref n ; 環境からn
の値を取得し、 ; 値をスタックに積む 4 sub1 ; スタックの先頭から1を引く 5 dup ; スタックの先頭の値を複製する ; つまり、スタックの先頭の値を ; コピーして、それをスタックに積む 6 varset n ; スタックの先頭から値を取りさり、 ; 値をn
に束縛する ; つまり、dup varset
は ; スタックの先頭の値を取りさらずに ;n
にコピーする 7 constant 0 ; スタックに0を積む 8 gtr ; スタックから2つの値を取りさり、 ; nが0より大きいか調べ、 ; 結果をスタックに積む 9 goto-if-nil-else-pop 17 ;n
<= 0ならば17へ飛ぶ ; (whileループから抜ける) ; さもなければ、スタックの先頭から ; 値を取りさり、つぎへ進む 12 constant nil ; スタックにnil
を積む ; (これはループの本体) 13 discard ; ループの本体の結果を捨てる ; (whileループは副作用のために ; つねに評価される) 14 goto 3 ; whileループの先頭へ飛ぶ 17 discard ; スタックの先頭の値を取りさって、 ; whileループの結果を捨てる。 ; これは、9での飛び越しのために ; 取りさっていない値nil
18 varref t1 ;t1
の値をスタックに積む 19 constant current-time-string ;current-time-string
を ; スタックに積む 20 call 0 ; ふたたびcurrent-time-string
を ; 呼び出す 21 list2 ; スタックの先頭から2つの値を取りさり ; それらのリストを作り、 ; リストをスタックに積む 22 unbind 1 ; ローカルの環境のt1
の束縛を解く 23 return ; スタックの先頭の値を返す ⇒ nil