Emacs Lispには、Lispで書いた関数を より効率よく実行可能なバイトコード(byte-code)と呼ばれる 特別な表現に変換するコンパイラ(compiler)があります。 コンパイラはLispの関数定義をバイトコードで置き換えます。 バイトコード関数を呼び出すと、 バイトコードインタープリタ(byte-code interpreter)が その定義を評価します。
(真のコンパイル済みコードのように)計算機ハードウェアが直接実行するかわりに、 バイトコードインタープリタがバイトコンパイル済みのコードを評価するので、 バイトコードは、再コンパイルせずに計算機から計算機に移せます。 しかしながら、真のコンパイル済みコードほど速くはありません。
EmacsバイトコンパイラがLispファイルをコンパイルするときには、 `--unibyte'を指定してEmacsを起動したとしても、 ファイルで特に指定しなければ、 つねにファイルをマルチバイトテキストとして読みます。 コンパイルしても、コンパイルせずに同じファイルを実行した場合と同じ結果を 得るようにするためです。 See section 非ASCII文字のロード。
一般に、Emacsの任意の版は、それよりまえの版でバイトコンパイルしたコードを 実行できますが、その逆は真ではありません。 Emacs 19.29では互換性のない大きな変更を行いましたから、 それ以降の版でコンパイルしたファイルは、 特別なオプションを指定しない限り、それ以前の版ではまったく動きません。 See section 説明文字列とコンパイル。 さらに、Emacs 19.29では、キーボード文字の修飾ビットを変更しました。 その結果、19.29よりまえの版でコンパイルしたファイルは、 修飾ビットを含む文字定数を使っているとそれ以降の版では動作しません。
バイトコンパイル中に生起するエラーについては、 See section コンパイル時の問題のデバッグ。
バイトコンパイルした関数は、Cで書いた基本関数ほど効率よくはありませんが、 Lispで書いた版よりはよほど速く動きます。 例を示しましょう。
(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 (silly-loop 100000) => ("Fri Mar 18 17:25:57 1994" "Fri Mar 18 17:26:28 1994") ; 31秒 (byte-compile 'silly-loop) => [コンパイルしたコードは省略] (silly-loop 100000) => ("Fri Mar 18 17:26:52 1994" "Fri Mar 18 17:26:58 1994") ; 6秒
この例では、解釈実行するコードでは実行に31秒必要でしたが、 バイトコンパイルしたコードでは6秒でした。 この結果は代表的なのもですが、実際の結果は大きく変動します。
関数byte-compile
で、
個々の関数定義やマクロ定義をバイトコンパイルできます。
byte-compile-file
で1つのファイル全体をコンパイルしたり、
byte-recompile-directory
やbatch-byte-compile
で
複数個のファイルをコンパイルできます。
バイトコンパイラは、 各ファイルに対するエラーメッセージや警告メッセージを `*Compile-Log*'と呼ばれるバッファに出力します。 読者のプログラムに関してここに報告されたことがらは、 問題点を指摘しますが、必ずしもエラーとは限りません。
バイトコンパイルする可能性のあるファイルにマクロ呼び出しを書くときには 注意してください。 マクロ呼び出しはコンパイル時に展開されるので、 正しくコンパイルするためにはマクロは定義済みである必要があります。 詳しくは、See section マクロとバイトコンパイル。
通常、ファイルをコンパイルしてもファイルの内容を評価したり、
ファイルをロードしません。
しかし、ファイルのトップレベルに書いたrequire
は実行します。
コンパイル時に必要なマクロ定義が存在することを保証する1つの方法は、
それらを定義するファイルを要求(require
)することです
(see section 機能)。
コンパイルしたプログラムを実行するときに
マクロ定義ファイルのロードを防ぐには、
require
の呼び出しの周りにeval-when-compile
を書きます
(see section コンパイル時の評価)。
byte-compile
は、symbolのコンパイル済みの新たな定義を返す。
symbolの定義がバイトコード関数オブジェクトであると、
byte-compile
はなにもせずにnil
を返す。
Lispはどんなシンボルに対しても関数定義を1つだけ記録するので、
それがすでにコンパイル済みであると、
コンパイルまえのコードはどこにもないのである。
したがって、『同じ定義をコンパイルし直す』方法はない。
(defun factorial (integer) "Compute factorial of INTEGER." (if (= 1 integer) 1 (* integer (factorial (1- integer))))) => factorial (byte-compile 'factorial) => #[(integer) "^H\301U\203^H^@\301\207\302^H\303^HS!\"\207" [integer 1 * factorial] 4 "Compute factorial of INTEGER."]
結果は、バイトコード関数オブジェクトである。 この文字列には実際のバイトコードが入っている。 その各文字は、命令や命令のオペランドである。 ベクトルには、特別な命令に符号化される特定の基本関数を除いて、 関数が使うすべての定数、変数名、関数名が入っている。
defun
を読み取り、
それをコンパイルして、結果を評価する。
実際に関数定義であるdefun
でこのコマンドを使うと、
その関数をコンパイルしたものをインストールすることになる。
入力ファイルから一度に1つずつフォームを読みながらコンパイルを行う。 それが関数定義やマクロ定義であると、 コンパイルした関数定義やマクロ定義を書き出す。 他のフォームは一塊にして、各塊をコンパイルして書き出し、 ファイルを読むとコンパイルしたコードが実行されるようにする。 入力ファイルを読むときにすべてのコメントを捨てる。
このコマンドはt
を返す。
対話的に呼び出すとファイル名を問い合わせる。
% ls -l push* -rw-r--r-- 1 lewis 791 Oct 5 20:31 push.el (byte-compile-file "~/emacs/push.el") => t % ls -l push* -rw-r--r-- 1 lewis 791 Oct 5 20:31 push.el -rw-rw-rw- 1 lewis 638 Oct 8 20:25 push.elc
`.el'ファイルに対応する`.elc'ファイルが存在しない場合には、
flagが動作を指示する。
それがnil
であると、そのようなファイルは無視する。
nil
以外であると、そのような各ファイルをコンパイルするかどうか
ユーザーに問い合わせる。
このコマンドの戻り値は予測できない。
byte-compile-file
を実行する。
この関数はEmacsをバッチモードで実行しているときにだけ使うこと。
完了するとEmacsを終了するからである。
1つのファイルでエラーが発生しても、後続のファイルの処理には影響しないが、
エラーを起こしたファイルに対する出力ファイルは生成せず、
Emacsのプロセスは0以外の状態コードで終了する。
% emacs -batch -f batch-byte-compile *.el
byte-code
を呼び出すような本体として定義される。
この関数を読者自身で呼び出さないこと。
この関数の正しい呼び出しを生成する方法はバイトコンパイラだけが知っている。
Emacs 18版では、バイトコードは関数byte-code
をつねに呼び出すことで
実行していた。
現在では、バイトコード関数オブジェクトの一部としてバイトコードを実行するのが
普通であり、byte-code
を明示的に呼び出すことは稀である。
バイトコンパイルしたファイルからロードした関数や変数では、 それらの説明文字列は、必要に応じてそのファイルを動的に参照します。 これはEmacs内のメモリを節約しロード処理も速くなります。 というのは、ファイルのロード処理で説明文字列を処理する必要がないからです。 説明文字列を実際に参照するのは遅くなりますが、 普通、ユーザーをいらいらさせるほとではありません。
説明文字列を動的に参照することには欠点があります。
読者のサイトでEmacsを通常の手順でインストールした場合には、 これらの問題は普通起こらないはずです。 新版のインストールには別のディレクトリを使いますから、 旧版をインストールしてある限り、そのファイル群は意図した場所に 無変更で残っているはずです。
しかしながら、読者自身がEmacsを構築して、 構築したディレクトリからEmacsを使う場合、 Lispファイルを編集して再コンパイルすると、 しばしばこの問題を経験するでしょう。 そのような場合には、再コンパイルしたあとでファイルを再ロードすれば 問題を解決できます。
旧版ではこの機能を使えないので、
Emacsの(19.29以降の)最近の版でバイトコンパイルしたファイルは
旧版ではロードできません。
byte-compile-dynamic-docstrings
にnil
を設定すれば、
コンパイル時にこの機能をオフにできます。
Emacsの旧版にロードできるようにファイルをコンパイルできるのです。
すべてのファイルをこのようにコンパイルしたり、あるいは、
この変数をファイルにローカルな束縛に指定して1つのソースファイルだけを
このようにコンパイルしたりもできます。
そのようにする1つの方法は、つぎの文字列をファイルの先頭行に追加することです。
-*-byte-compile-dynamic-docstrings: nil;-*-
nil
以外であると、
バイトコンパイラは、説明文字列を動的にロードするように設定した
コンパイル済みファイルを生成する。
説明文字列を動的に扱う場合、 コンパイル済みのファイルではLispリーダの特別な構文`#@count'を 使います。 この構文は後続のcount文字を読み飛ばします。 また、`#$'という構文も使います。 これは、『文字列としてのこのファイルの名前』を表します。 Lispのソースファイルでは、これらの構文を使わないのが最良です。 これらは人が読むファイル向けに設計したものではないからです。
ファイルをコンパイルするとき、 動的関数ロード(dynamic function loading、 遅延ロード(lazy loading)ともいう)機能を指定できます。 動的関数ロードでは、ロードするときにファイル内の関数定義をすべて 読むわけではありません。 そのかわりに、各関数定義には、 そのファイルを指す埋め草が入っています。 それぞれの関数を初めて呼び出したときに、 その完全な定義をファイルから読み取り、埋め草を置き換えます。
動的関数ロードの利点は、ファイルをロードするよりかなり速いことです。 ユーザーが呼び出せる数多くの別々の関数を収めたファイルにおいては、 それらの1つだけを使って残りのものを使わないのであれば、 これは有利なことです。 キーボードコマンドを提供する特別なモードには、 しばしばこのような使い方のパターンがあります。 ユーザーがモードを起動しても、提供するコマンドの一部しか使わないのです。
動的関数ロードの機能には、ある種の欠点もあります。
Emacsのファイル群をインストールした普通の状況では、 このような問題は起きないはずです。 しかし、Lispファイルを読者が変更すると起こりえます。 これらの問題を回避するもっとも簡単な方法は、 再コンパイルするたびに新たにコンパイルしたファイルを ただちに再ロードすることです。
バイトコンパイラは、コンパイル時に変数byte-compile-dynamic
が
nil
以外であれば、動的関数ロードの機能を使います。
動的ロードは特定のファイルで必要なだけですから、
この変数をグローバルに設定しないでください。
そのかわりにファイルにローカルな変数束縛を使って
特定のソースファイルだけでこの機能をオンにします。
たとえば、ソースファイルの先頭行につぎのテキストを書けば、
そのようにできます。
-*-byte-compile-dynamic: t;-*-
nil
以外であると、
バイトコンパイラは、動的関数ロードを使うように設定した
コンパイル済みのファイルを生成する。
プログラムのコンパイル時に評価されるようなコードを書くための機能です。
bodyを別のファイルに収め、そのファイルをrequire
で参照しても
同じ結果を得ることができる。
bodyが大きい場合には、そのほうが好ましい。
Common Lispに関した注意:
トップレベルでは、
Common Lispの(eval-when (compile eval) ...)
の常套句に似ている。
それ以外の箇所では、Common Lispの`#.'リーダマクロは
(解釈実行時ではなければ)eval-when-compile
が行うことに近い。
バイトコンパイルした関数は、特別なデータ型、 バイトコード関数オブジェクト(byte-code function objects)です。
内部的には、バイトコード関数オブジェクトはベクトルによく似ています。 しかし、評価時にこのデータ型が呼び出すべき関数として現れると、 特別に扱います。 バイトコード関数オブジェクトの表示表現はベクトルに似ていますが、 開き角括弧`['のまえに余分に`#'が付きます。
バイトコード関数オブジェクトには、少なくとも4つの要素が必要です。 最大個数に制限はありませんが、最初の6つ個の要素にだけ 普通の用途があります。 つぎのとおりです。
nil
。
説明文字列がファイルに収めてあれば、値は数かリストである。
実際の説明文字列を取得するには関数documentation
を使う
(see section 説明文字列の参照)。
nil
。
バイトコード関数オブジェクトの例を表示表現でつぎに示します。
#[(&optional arg) "^H\204^F^@\301^P\302^H[!\207" [arg 1 forward-sexp] 2 254435 "p"]
バイトコードオブジェクトを作る基本的な方法は、
make-byte-code
を使うことです。
バイトコード関数の要素を自分で作ったりしないでください。 それらに整合性がないと、 その関数を呼び出すとEmacsがクラッシュすることもあります。 これらのオブジェクトの作成は、バイトコンパイラに任せるべきです。 バイトコンパイラは整合した要素を作成します(と期待する)。
バイトコードオブジェクトの要素はaref
で参照できます。
同じ要素群のベクトルをvconcat
で作ることもできます。
人間はバイトコードを書きません。 それはバイトコンパイラの仕事です。 しかし、好奇心を満たすために逆アセンブラを用意してあります。 逆アセンブラはバイトコンパイルしたコードを人が読める形式に変換します。
バイトコードインタープリタは、単純なスタックマシンとして実装してあります。 値を自前のスタックに積み、計算に使うためにスタックから取り出し、 計算結果そのものはスタックにまた積みます。 バイトコード関数から戻るときには、スタックから値を取り出して 関数値としてその値を返します。
スタックに加えて、変数とスタックのあいだで値を転送することで、 バイトコード関数は、普通のLisp変数を使ったり、 束縛したり、値を設定できます。
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
Go to the first, previous, next, last section, table of contents.