次: Simple Advice, 前: Byte-Code Objects, 上: Byte Compilation
人間はバイトコードを書きません。 それはバイトコンパイラの仕事です。 しかし、好奇心を満たすために逆アセンブラを用意してあります。 逆アセンブラはバイトコンパイルしたコードを人が読める形式に変換します。
バイトコードインタープリタは、単純なスタックマシンとして実装してあります。 値を自前のスタックに積み、計算に使うためにスタックから取り出し、 計算結果そのものはスタックにまた積みます。 バイトコード関数から戻るときには、スタックから値を取り出して 関数値としてその値を返します。
スタックに加えて、変数とスタックのあいだで値を転送することで、 バイトコード関数は、普通の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