検索エンジンから来た人に注意

このマニュアルは、Emacs ver. 19.x 向けのマニュアルを Mule 2.x の開発にあたり邦訳したものを、 電脳外道学会がミラーリングしているものであり、旧版製品パラノイアであるところの椅子人の趣味によるものです。

しかしながら、現在の Emacs の主流は ver. 20 以降であり、ver 19.x と ver 20.x とでは、仕様の違いが少なからずあります。

したがって、一般的な Emacs ユーザーにとっては、 このマニュアルと実機の動作とが符合しない場合があります。
特に、公開用の Emacs Lisp コードを書こうとする人は、 新バージョンのマニュアル(英語)を参照することを強くおすすめします。


トップページ&リンク | マニュアルの目次 | 検索
Go to the first, previous, next, last section, table of contents.


バイトコンパイル

GNU Emacs Lispにはコンパイラ(compiler)があります。 これは、Lispで書いた関数を バイトコード(byte-code)と呼ばれる、より効率的に実行できる特殊な表現に 変換します。 コンパイラはLisp関数定義をバイトコードで置き換えます。 バイトコードになった関数が呼び出されると、その関数定義は バイトコード・インタプリタ(byte-code interpreter)によって評価されます。

バイトコンパイルされたコードはマシンのハードウェアによって 直接実行されるのではなく、 バイトコード・インタプリタによって評価されるため (本当のコンパイルされたコードはハードウェアによって 直接実行されるのですが)、 バイトコードは再コンパイルなしに、あるマシンから別のマシンに移して 動作させることが可能です。 しかし、バイトコードは本当のコンパイルされたコードほどは 速くありません。

一般的にどのバージョンのEmacsにおいても、 それよりも少し前のバージョンのEmacsで 作ったバイトコンパイル・コードであれば実行することができます。 ただし、その逆は成り立ちません。 特に Emacs バージョン 19.29 でコンパイルされたプログラムは それ以前のバージョンでは動作しません。 See section 説明文字列とコンパイル。 19.29より前のバージョンでコンパイルしたファイルは、 修飾ビット(modifier bits)の立った文字定数が入っている場合、 動作しないことがあります。これは、 Emacs 19.29になって修飾ビットの番号がつけ直されたためです。

バイトコンパイル中に起きたエラーを調べる方法は See section コンパイル時の問題のデバッグ

バイトコンパイルされたコードの性能

バイトコンパイルされた関数は、C言語で書かれているプリミティブ関数ほどは 効率的ではありませんが、Lispで書いたままのもの よりはかなり速く実行されます。 例を挙げましょう。

(defun silly-loop (n)
  "N回繰り返して、ループの前後の時間を返す。"
  (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で行ない、複数のファイルの コンパイルはbyte-recompile-directorybatch-byte-compileで 行ないます。

バイトコンパイルを実行すると、`*Compile-Log*'というバッファに 警告メッセージが表示されることがあります。 これは、プログラム中で 問題となりそうなものを指摘するもので、必ずしもエラーではありません。

マクロを使っているコードをバイトコンパイルするときは注意してください。 マクロの呼出しはコンパイルされるときに展開されてしまいます。 そこで、正しくコンパイルするためには、マクロはコンパイル時にすでに 定義されていなければいけません。 詳細はsection マクロとバイトコンパイルを参照してください。

通常、ファイルをコンパイルしても、そのファイルの内容が評価されたり、 ファイルがロードされたりすることはありません。 しかし、ファイルのトップ・レベルにあるrequireはいずれも実行されます。 必要なマクロ定義をコンパイル時に使えるようにする確実な方法の一つは マクロを定義しているファイルをrequireしておくことです (see section 機能)。 コンパイルしたプログラムを動作させる際に マクロ定義ファイルをロードしないようにするには、 requireの呼出しをeval-when-compileで囲んで プログラムを書いてください(see section コンパイル時に行なわれる評価)。

Function: byte-compile symbol
この関数はsymbolの関数定義をバイトコンパイルし、その元の定義を コンパイルしたものに置き換えます。 symbolの関数定義はその関数の実際のコードでなくてはいけません。 つまり、コンパイラはほかのシンボルへ間接的に関数定義をたどることは しません。 byte-compileは新たにコンパイルされたsymbolの関数定義を 返します。

symbolの関数定義がバイトコード関数オブジェクトの場合、 byte-compileは何もせずnilを返します。 Lispでは、すべてのシンボルはそれぞれただ一つの関数定義しか保持しません。 そして、それがすでにコンパイルされている場合、コンパイルする前のコードは どこからも得られません。 そのため"同じ関数定義を2度コンパイルする"方法はありません。

(defun factorial (integer)
  "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 "INTEGERの階乗を計算する。"]

この結果はバイトコード関数オブジェクトです。 そのオブジェクトに含まれる文字列が実際のバイトコードで、 その中の各文字が命令や命令の作用対象(operand)です。 また、ベクタは、その関数で使われているすべての定数、変数名、関数名を含みます。 ただし、特別な命令として作られている特定のプリミティブは現れません。

Command: compile-defun
このコマンドはポイントを含むdefunを読み込んで、コンパイルし、 結果を評価します。 (10) このコマンドを、実際に関数を定義するdefunで実行した場合は、結果として その関数のコンパイルされたバージョンが使われるようになります。

Command: byte-compile-file filename
この関数はfilenameという名前のLispコード・ファイルを バイトコード・ファイルにコンパイルします。 出力ファイル名はfilenameの最後に`c'をつけたものになります。

コンパイル時には、入力ファイルを読み込みながら一度に一つの式が処理されます。 その式が関数やマクロの定義であれば、コンパイルした関数やマクロの 定義がファイルに書き出されます。 それ以外の式はまとめられ、それぞれは コンパイルされ、そのファイルが読まれたとき、 コンパイル・コードが実行されるように書き込まれます。 入力ファイルが読み込まれるとき、すべてのコメントは無視されます。

このコマンドは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

Command: byte-recompile-directory directory flag
この関数は、directoryにあって再コンパイルが必要な`.el'ファイル すべてを再コンパイルします。 `.elc'ファイルが存在しても`.el'ファイルより古い場合、 そのファイルは再コンパイルが必要とされます。

`.el'ファイルが対応する`.elc'ファイルを持たない場合 には、flagによりどうするかが決まります。 この値がnilの場合はそのファイルは無視されます。 もし非nilの場合、ユーザに対しそれぞれのファイルを コンパイルするかどうかを問い合わせます。

このコマンドの返り値は不定です。

Function: batch-byte-compile
この関数はコマンド行に指定されたファイルに対して、 byte-compile-fileを実行します。 この関数はEmacsのバッチ走行でのみ使ってください。 関数の終了時にEmacsを終了させてしまうからです。 一つのファイルでエラーが起こっても、それに続くファイル は処理され続けます。 (エラーの起きたファイルからは、もちろんコンパイル・コードは作成されません。)
% emacs -batch -f batch-byte-compile *.el

Function: byte-code code-string data-vector max-stack
この関数が実際にバイトコードを解釈(interpret)します。 バイトコンパイルされた関数は実際にはbyte-codeを呼び出している 関数本体で定義されます。 この関数を自分で呼び出すのはやめてください。 この関数を正しく呼び出す方法はバイトコンパイラにしかわからないからです。

新しいEmacs (バージョン19とそれ以降)では、バイトコードは通常、 バイトコード関数オブジェクトの一部として実行されます。 byte-codeを明示的に呼び出すことで実行されることは本当にまれです。

説明文字列とコンパイル

バイトコンパイルされたファイルからロードされた関数と変数は、 必要になったときに、その説明文字列をファイルから動的に取ってきます。 これによりEmacs内部の領域を節約できます。 また、ファイルをロードするときに説明文字列自体を処理する必要が なくなるので、ロードも早くなります。 この結果、説明文字列を実際に参照するのが遅くなりますが、 多くの場合、ユーザをいらいらさせるほどのものではありません。

説明文字列を動的に参照することには以下のような問題点があります。

ただし、Emacsのインストールに通常の手段を用いていれば これらの問題はまず起りません。 新しいバージョンをインストールするときには今までと違う名前の 新しいディレクトリを使います。 古いバージョンがインストールされているかぎり、 古いファイルは期待されているところに変更されずに残っているはずです。

しかし、もしEmacsを自分自身で作成し、自分が作成したディレクトリで 使用している場合には、Lispファイルを編集し再コンパイルした場合、 時々、この問題に出くわすでしょう。 この問題が起きたなら再コンパイル後、ファイルを再ロードすれば 解決できます。

Emacs 19.29で作成したバイトコンパイル・ファイルは、 それよりも古いバージョンの Emacsではロードできません。古いバージョンではこの機能をサポートして いないからです。 そのため、 byte-compile-dynamic-docstringsnilに設定することで この機能を使わないようにできます。 一度これを行うと、コンパイルしたファイルは、古いバージョンのEmacsでも ロードできるようになります。 これはグローバルに設定することもできますし、 変数をファイルにローカル束縛するように指定することで、 一つのソース・ファイルに対してこの設定ができます。 その一つのやり方が以下のものです。

-*-byte-compile-dynamic-docstrings: nil;-*-

Variable: byte-compile-dynamic-docstrings
この値が非nilの場合には、バイトコンパイラが生成する コンパイル済みファイルが、 説明文字列の動的なロードを行うように設定されます。
動的な説明文字列機能により、コンパイルされたファイルは、 特別なLispリーダ構文要素(construct)、`#@count 'を使用して 書かれます。 この構文要素は次のcount個の文字を読み飛ばします。 また、`#$'という構文要素も使われるようになります。これは"文字列にした このファイルの名前"を表します。 Lispソース・ファイルにはこれらの構文要素を使わないのが一番よいでしょう。

個々の関数の動的なロード

コンパイルするときに動的な関数ロード(dynamic function loading)という 機能(遅延ロード(lazy loading)としても知られています)を 有効にするように選択できます。 動的な関数ロードにより、ファイルをロードしてもそのファイルの 関数定義の全部が読み込まれることはなくなります。 そのかわりに、それぞれの関数定義にはファイルを参照する「名札」が入ります。 それぞれの関数が最初に呼び出されるとき、ファイルから完全な関数定義が 読み込まれ、「名札」と置き換わります。

動的な関数ロードの利点はファイルのロードがとても速くなることです。 たくさんのコマンドが独立して納められているファイルで、 そのうちの一つを使っても、すぐには (もしくは、その後ずっと)それ以外のコマンドを 使うかどうかわからないような場合には便利です。 たくさんのキーボード・コマンドをもつ専門化したモードの場合、 コマンドは特定のパターンで使用されることが多いものです。 そのような場合、 ユーザはモードを呼び出したとしても、そのモードのもつごく少数の コマンドしか使いません。

動的なローディング機能は以下のような欠点を持っています。

もし、ファイルの新しいバージョンをコンパイルしたのなら、 すぐに新しくコンパイルしたファイルをロードするのが最もよいでしょう。 これにより将来起りうる問題を防ぐことができるからです。

バイトコンパイラが動的な関数ロード機能を使うのは、コンパイル時に変数 byte-compile-dynamicが非nilである場合です。 ただし、この変数をグローバルに設定しないでください。 特定のファイルにしか動的なロード機能は望ましくありません。 そのかわり、ファイルローカルな変数束縛を行うことで 特定のソース・ファイルでのみ機能を有効にしてください。

-*-byte-compile-dynamic: t;-*-

Variable: byte-compile-dynamic
この値が非nilの場合には、バイトコンパイラが生成する コンパイル済みファイルが 動的な関数ロードを実行するように設定されます。

Function: fetch-bytecode function
この関数は、 functionの関数定義がまだ完全にはロードされていない場合、 関数を含むバイトコンパイルされたファイルからこの関数を即座にロードします。 引数functionはバイトコード関数オブジェクトでも関数名でも 構いません。

コンパイル時に行なわれる評価

ここで挙げる機能により、プログラムをコンパイルするときに実行される コードを書くことができます。

Special Form: eval-and-compile body
この形式でbodyを囲むことにより、その中のコードがコンパイル時にも 実行時にも(コンパイルされている、されていないに関わらず)、評価される ようにします。

同様の結果を得るにはbodyを別のファイルに納め、 そのファイルをrequireで参照します。 このように実行したいコードがかなりな量ある場合は、requireを 使う方がよいでしょう。

Special Form: eval-when-compile body
この形式でbodyを囲むことにより、そのコードは コンパイル時に評価され、 コンパイルされたプログラムがロードされるときには評価されないように なります。 コンパイラによる評価結果は定数になってコンパイルされたプログラムに 現れます。 プログラムがコンパイルされていなければ、インタプリタで 解釈されるときbodyは通常どおり評価されます。

トップ・レベルでみると、これはCommon Lispでの語法である (eval-when (compile eval) ...)と似ています。 それ以外の場所では、Common Lispの`#.'リーダ・マクロ(こちらは インタプリタによる解釈時に実行されるわけではありません)が eval-when-compileの行うものに近くなります。

バイトコード関数オブジェクト

バイトコード関数は特別なデータ・タイプになります。 バイトコード関数オブジェクト(byte-code function objects) というタイプです。

内部的には、バイトコード関数オブジェクトはベクタにとてもよく似ています。 しかし、関数として呼ばれるべきところに現れたとき、評価子は このデータ・タイプを特別に扱います。 バイトコード関数オブジェクトの印字表現は、最初の`['の前に `#'が追加される以外はベクタの表現と似ています。

Emacsバージョン18にはバイトコード関数オブジェクトというデータ・タイプは なく、コンパイルされた関数はバイトコードを実行するために byte-codeという関数を使っていました。

バイトコード関数オブジェクトは少なくとも4個の要素を持たなくてはなりません。 要素数にかぎりはありませんが最初の6個の要素のみが実際には使用されます。 その6要素は以下のものです。

引数リスト
引数シンボルのリスト。
バイトコード
バイトコード命令を含む文字列。
定数
バイトコードが参照するLispオブジェクトのベクタ。 これは関数名や変数名として使われるシンボルを含みます。
スタックの大きさ
この関数が必要とするスタックの大きさの最大値。
説明文字列
説明文字列。もしなければnilになります。 これは数やリストの場合もあります。この場合、 説明文字列はファイルにしまわれています。 本当の説明文字列を得るには関数documentationを使います (see section 説明文字列へのアクセス).
対話引数
もしあれば、対話引数の指定。 これは文字列かLisp式のどちらかです。 対話的に使われる関数でなければこれはnilになります。

ここで印字表現によるバイトコード関数オブジェクトの例を示します。 コマンドbackward-sexpの定義です。

#[(&optional arg)
  "^H\204^F^@\301^P\302^H[!\207"
  [arg 1 forward-sexp]
  2
  254435
  "p"]

バイトコード・オブジェクトを生成する基本的な方法はmake-byte-code を用いることです。

Function: make-byte-code &rest elements
この関数はelementsをその要素とするバイトコード関数オブジェクトを 構築し、返します。

関数 byte-code に与える要素を自分でなんとかしようなどとしてはいけません。 要素に不整合があると、その関数を呼んだときEmacsがクラッシュして しまいかねないからです。 このオブジェクトを生成するのは常にバイトコンパイラに任せてください。 バイトコンパイラは要素に矛盾を生じさせません(というのが我々の 希望なのですが)。

バイトコード・オブジェクトの要素はarefを使えば アクセスできます。また、vconcatで同様の要素をもつベクタを 生成できます。

逆アセンブルされたバイトコード

人間はバイトコードを書きません。その仕事はバイトコンパイラに任されて います。 しかし、猫のような好奇心を満足させるために 逆アセンブラが提供されています。 逆アセンブラはバイトコンパイルされたコードを人間が読める形式に変換します。

バイトコード・インタプリタは単純なスタック・マシンとして インプリメントされています。 これは、インタプリタがそれ自身でもつスタックに値をプッシュしていき、 それをポップして計算に用い、その結果自身をスタックにプッシュし戻します。

バイトコード関数は返るときスタックから値をポップし、それを関数の 値として返します。

このスタック操作に加え、変数とスタックの間で値を移すことで、 通常のLisp変数の値を使い、束縛し、設定することが可能になります。

Command: disassemble object &optional stream
この関数はobjectの逆アセンブル・コードを印字します。 もしstream引数があれば、そこに出力されます。 さもなくば、逆アセンブルされたコードはストリームstandard-output に印字されます。 引数objectは関数名でもlambda式でも構いません。

特に例外として、この関数は対話的に使われると `*Disassemble*'というバッファに出力します。

ここでdisassemble関数を用いた例を二つあげます。 バイトコードとLispソースを関連づけやすいように説明のためのコメントを 付加しました。このコメントはdisassembleの出力には現れません。 この例では最適化されていないバイトコードを示します。 最近ではバイトコードはたいてい最適化されていますが、 説明のためにはこれで十分ですので この例を書き直すつもりはありません。

(defun factorial (integer)
  "整数integerの階乗を計算する。"
  (if (= 1 integer) 1
    (* integer (factorial (1- integer)))))
     => factorial

(factorial 4)
     => 24

(disassemble 'factorial)
     -| byte-code for factorial:
 doc: 整数integerの階乗を計算する。
 args: (integer)

0   constant 1              ; 1をスタックにプッシュする。

1   varref   integer        ; 環境からintegerの
                            ;   値を取得し、
                            ;   その値をスタックにプッシュする。

2   eqlsign                 ; スタックから一番上の
                            ;   二つの値をポップし、
                            ;   それを比較し、
                            ;   結果をスタックにプッシュする。

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をポップし、値から1を引き、
                            ;   新しく得た値をスタックに
                            ;   プッシュする。

                            ; スタックの現在の値は:
                            ;   - integerから1引いた値
                            ;   - factorial
                            ;   - integerの値
                            ;   - *

15  call     1              ; 関数factorialを呼び出す。
                            ;   このとき、スタックの
                            ;   最初(すなわち一番上)の
                            ;   要素を引数として用いる。
                            ;   返ってきた値をスタックに
                            ;   プッシュする。

                            ; スタックの現在の値は:
                            ;   - factorialの
                            ;        再帰呼出しの結果
                            ;   - integerの値
                            ;   - *

16  call     2              ; スタックの最初の二つ
                            ;   (すなわち、一番上の二つ)
                            ;   の要素を引数として、
                            ;   関数*を呼び出し、
                            ;   その結果をスタックにプッシュする。

17  return                  ; スタックの一番上の要素を返す。
     => nil

silly-loop関数はもう少し複雑です。

(defun silly-loop (n)
  "N回繰り返して、ループの前後の時間を返す。"
  (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: N回繰り返して、ループの前後の時間を返す。
 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                     ; スタックから一番上にある
                            ;   二つの値をポップし、
                            ;   nの値が0よりも大きいか調べ
                            ;   その結果をスタックにプッシュする。

9   goto-if-nil-else-pop 17 ; n <= 0ならば、17へ飛び
                            ;   (これによりループから抜ける)、
                            ;   それ以外ならば、スタックの
                            ;   一番上をポップして次に続く。

12  constant nil            ; nilをスタックにプッシュする
                            ;   (これがループの本体である)。

13  discard                 ; ループ本体の結果を捨てる
                            ;   (whileループは常に、
                            ;   副作用を使うために評価される)。

14  goto     3              ; whileループの最初に戻る。

17  discard                 ; スタックの一番上をポップすることで、
                            ;   whileループの結果を捨てる。
                            ;   この結果とは、9のgoto文では
                            ;   ポップされなかった値nilである。

18  varref   t1             ; t1の値をスタックにプッシュする。

19  constant current-time-string  ; current-time-stringを
                                  ;   スタックの一番上に
                                  ;   プッシュする。

20  call     0              ; 再度current-time-stringを
                            ;   呼び出す。

21  list2                   ; スタックの一番上の二つの要素を
                            ;   ポップし、そのリストを作成し、
                            ;   リストをスタックにプッシュする。

22  unbind   1              ; 局所環境でのt1の束縛を外す。

23  return                  ; スタックの一番上の値を返す。

     => nil


Go to the first, previous, next, last section, table of contents.