Original document:
http://bitsavers.org/pdf/mit/cadr/Knight-LISP_Machine_Macro_Instruction_Set-1979.pdf
--
水曜, 2月21日, 1979年 01:05:39 AI:LMDOC:MACRO 28
Lispマシン マクロ命令セット.
著者: Thomas Knight
(日本語訳: たけおか
初出:2011/SEP/04
2011/NOV/12 大宮君の別訳を参考に修正
大宮くんに大いに感謝します)
この文書は Lispマシンの解釈される命令コード、以下"macrocode
(マクロコード)"とする,について述べている。
マクロコードは、bit効率が良く、LISPによく合うように設計されている。
Lispのマクロコードへのコンパイルは、後で例示するように、非常に直接的で
ある。
(※訳注:この文書では、十進数には末尾に"."を付けて、それが十進表記である
ことを示す。何の断りもなく、末尾に何も付かない数は、八進表記である)
マクロコードは"QCMP"と呼ばれる"marcocompiler(マクロ・コンパイラ)"によっ
て生成された。
マクロコードを手で書く必要がまったく無い、なぜなら、それは、LISP
ソース・コードと密接に対応しており、LISPで同じ事を書くのが簡単である。
QCMPが関数に対して実行される時、
それは関数を"macrocompile(マクロコンパイル)"する、と、言われる。
それは、各関数のコンパイルごとに、FEFとして参照される、"Function Entry
Frame(関数エントリ・フレーム)"を生成する結果のLISPポインタは
DTP-FEF-POINTERというデータ型を持ち、最低8ワードの長さのメモリ・ブロッ
クであるFEFを指す、
FEFはいくつかのセクションを持つ。最初のセクションは7ワード長で、FEFの
形式についての様々な情報とどんな関数が呼び出されるべきかについての情報
を持っている。これらのワードのビットの配置については、FORMAT文書を参照の
こと。
FEFの次のセクションは、VALUEセルへのポインタとシンボル(記号)の
FUNCTIONセルを持つ。これらのデータ型DTP-EXTERNAL-VALUE-CELL-POINTERの
ポインタは, "exit vector"として使用され、すなわち、コンパイルド・コード
は、スペシャル変数をアクセスするためのこれらのポインタを参照できる
その次のセクションは、Argument Description List(ADL,引数記述リスト)を
持つ。ADLは関数が渡されることを期待する各引数ごとにエントリを1つ持ち、
引数に関する全ての情報:必須/オプショナル/残り(rest),初期化方法/それが
与えられない,ローカル/スペシャル, データ型チェック情報, などなど、を持
つ。時々、ADLは無しで済まされることがあり、かわりに、"fast argument
option(高速引数オプション)"が使われる。これは、小さく単純な関数のために、
時間とメモリの節約を助ける。詳細はFORMAT文書でみつかるだろう。
ADLの次のセクションは、関数が参照したがるであろう様々な定数を持つ:もし
関数が(FOO '(A B))を含んでいるとき、マイクロコードがそれを参照できるよ
うに、リスト(A B)は定数エリアに置かれるであろう。
FEFの残りは、実際のマクロ命令それ自身である。各マクロ命令は16bit長で、
2つのマクロ命令がLispマシンの各ワードに格納されている。4つの概念的なマ
イクロ命令の"classes(クラス)"があり、各々、異なる方法でフィールドに分解
される。
クラスI:
--------------------------------------------
| 3 | 4 | 3 | 6 |
--------------------------------------------
DEST. OPCODE REGISTER OFFSET
9つのクラスI命令があり、OPCODEフィールドの0から10(8進)が割り当てられて
いる。各命令は、そのアドレスがREGISTERとOFFESTフィールドから計算される
1つのソース、と、DESTINATIONフィールドによって与えられる1つのデスティネー
ションを持つ。
OPCODE NAME
------ ----
0 CALL
アドレスによって指定される関数を呼び出すため、スタック上に
コール・ブロックを開く。関数の戻り値はすべてデスティネーションへ行く。
実際の制御が移るのは、引数の格納が終了してからである。
(デスティネーションのNEXTとLASTを参照)
1 CALL0
引数を持たない関数の場合のCALLである。
制御は即座に移る。
2 MOVE Eの内容をデスティネーションに転送
3 CAR Eの内容のCARをデスティネーションに置く
4 CDR 同様
5 CADR 同様
6 CDDR 同様
7 CDAR 同様
8 CAAR 同様
実効アドレス(Effective address)、E、は"register"とoffsetから計算される。
命令は実際に、"register"フィールドによって指定される便利な場所への相対
アドレシングを使用する。
レジスタは:
REG 機能
=== ========
0 FEF
現在走行中のFEFの位置の先頭。
これは、マクロ・コードは、値と関数セルへ、と、定数エリアへ、
のポインタを、どのようにアドレスするかの方法である。
1 FEF+100 0に100(8進)を加算したものと同一。
2 FEF+200 同様
3 FEF+300 同様
4 CONSTANTS PAGE
これは、T,NIL,小さな数などの、広く使用される定数のペー
ジである。機械には、ただ一つの定数ページがある。それら
(※訳注:よく使われる定数)は、すべて、1ページに保持され、
各FEF定数エリア内で重複されることが無いように、多くの全
FEFから共有される。
5 LOCAL BLOCK
これはPDL上のローカル・ブロックのアドレスであり、ローカ
ル変数をアドレスするために使用される
6 ARG POINTER
これは、PDLへの引数ポインタであり、関数の引数をアドレス
するために使用される。
7 PDL
offeset(オフセット)は77(8進)でなければならない。スタックの先
頭がポップされ、それがオペランドとして使用される。
offsetが取りうる他の値は、現在使用できない。
(各機能のためにPDLフレームがどのように、ヘッダ、引数ブロック、ローカル・
ブロック、中間結果スタック、に分割されているかについては、FORMAT文書を参照)
注意: 最初の4つのアドレッシング・モードのすべてで、
FEFへの実効8bitオフセットとなるように提供されている。
注意: レジスタ-オフセット体系の同じものが、クラスII命令でも使用されている。
"effective address(実効アドレス)"の計算が余計に複雑なのは、不可視ポイン
タ(Invisible pointers)から来ている。レジスタとオフセットが最初の実効ア
ドレスEを計算するのに使用され,その場所のワードがアクセス(命令がEをデス
ティネーションとして使用したとしても)されるとする。もし、ワードのデータ
型が"Effective Address Invisible(実効アドレス不可視)"であれば、ワードの
ポインタ・フィールドが実効アドレスEとして使用される。例えば、これがスペ
シャル変数の値セルのアクセスに使用されるとする。FEF"レジスタ"が使用さ
れ、アドレスされたFEF中の場所には、実効アドレス不可視ポインタ,それは要求された値セ
ルを指している、が入っている。この仕組みは、命令中のビットを節約し、
スペシャル変数セルをアドレスするための追加の命令の使用を必要としない。
デスティネーション・フィールドはもう少し複雑である。全体のはじめに、デ
スティネーションへの結果を転送する前に、2つの"indicators(インジケータ)"が
セットされる。インジケータは、PDP11でのNやZのような、プロセッサ・ステー
タス・フラグと対応付けて、各々ビットとして格納される。それは、ATOMイン
ジケータと呼ばれ、操作の結果がアトムであれば、セットされ、NILインジケー
タは、結果がNILであれば、セットされる。クラスIII命令(BRANCH,分岐)がその
インジケータを見るだろう。
注意:実際のところ、本当の物理的なインジケータは全くない。その代わり、最
後の計算結果が内部レジスタにセーブされ、BRANCH(分岐)命令によって参照さ
れる。その機能的な効果は同じである。
デスティネーションは:
DEST 機能
==== ========
0 IGNORE
これはもっとも単純である;結果は単に捨てられる。フラグを
セットするために、有用である。
1 TO STACK
スタック上にデスティネーションをプッシュする。引数をク
ラスIV命令などに引き渡すのに便利である。
2 TO NEXT
実際のところ、これはTO STACKと同様であるが、
オープンな関数への次の引数が計算されたときに、
それを格納するために使用される。
3 TO LAST
これは、オープンな関数の最後の引数を格納するのに、使用
される。それは、同時に、スタックにも結果をプッシュし、
そして、オープン・コール・ブロックを"activates(アクティ
ベート)"する。すなわち、制御は、最後のCALL命令によって
指定された関数に渡り、その関数によって返される値はcall
命令のデスティネーション・フィールドによって指定される
デスティネーションに送られるだろう。
4 RETURN
命令の結果をこの関数の値として返す。(すなわちreturn
form subroutine(サブルーチンからの戻り)である)
5 TO NEXT,QUOTED
これは、エラーチェックについてを除いてTO NEXTと同様であ
る。プッシュしようとしているワードのUSER-CONTROLビット
がセットされていると、呼び出される関数に、quoteされた引
数を得ようとしていることを知らせる(もしそれが扱えるな
ら)。(実装されていない)
6 TO LAST,QUOTED 同様
7 TO NEXT LIST
これは相当にトリッキーだ。lispの"LIST"関数を効率よく実
行するためのLIST(クラスIV)命令と連携して使う。LIST命令
に記述している。
注意: 5と6(QUOTED)デスティネーションは、11/03/76 (※訳注1976年11月だろ
うか?)現在、未実装である。
注意:同じDESTINATIONフィールドがクラスIV命令で使用されている。
クラスII:
-------------------------------------------
| 7 | 3 | 6 |
-------------------------------------------
OPCODE REGISTER OFFSET
クラスII命令はデスティネーション・フィールドを持たない;操作の結果は(な
んであれ)、スタックにプッシュされる(クラスI命令のTO STACKやTO NEXTデス
ティネーションのように)か、実効アドレスに格納される。"register"と
offsetはクラスI命令と同じ方法で使われる、ただし、ソースの代わりにデスティ
ネーションをたまに使用するE計算を除いて。
命令は、Opcodeの最初の3ビットによって3つのサブグループに分けられ[これはマイクロ
コードでは、Non-destinational命令グループ1,2,3として参照されている]、次
の4ビットで次のように命令が分けられる: (欄の左側中の"-"は、この命令がス
タックをポップすることを意味し; "+"はなにかをスタックにプッシュすること
を意味している。)
GRP. OPCODE 機能
==== ====== =========
11 0 不使用
11 1 + C(E)をスタック・トップと加算し、
結果をスタック・トップと置き換える
11 2 - C(E)をスタック・トップから減ずる。
11 3 * 同様
11 4 / 同様
11 5 AND 同様
11 6 XOR 同様
11 7 OR 同様
- 12 0 = \ C(E)とスタック・トップを比較し,
- 12 1 > ├テストの条件が真なら、NILインジケータは
- 12 2 < │クリアされ、そうでなければセットされる。
- 12 3 EQ / スタックはポップされる。
12 4 SCDR C(E)のCDRを得て、それをEへ格納する。
12 5 SCDDR 同様
12 6 1+ 同様
12 7 1- 同様
13 0 BIND Eにあるセルをそれ自身にバインドする。
linear binding pdlが使用される。
- 13 1 BINDNIL EにあるセルをNILにバインドする。
13 2 BINDPOP Eにあるセルをスタックからポップした値とバインドする。
13 3 SETNIL E内にNILを格納する。
13 4 SETZERO E内にfixnum0を格納。
+ 13 5 PUSH-E Eの場所へのポインタをスタックにプッシュする。
13 6 MOVEM スタック・トップのデータをEへ転送する。
- 13 7 POP スタック・トップをEへポップする。
クラスIII
--------------------------------------------
| 3 | 4 | 9 |
--------------------------------------------
BRANCH (14) OFFSET
CODE
クラスIII命令は分岐のためのものである。分岐を起こすか、また、ときどきは、
失敗したときにそれを行うか、を決定する、3ビットのBranch Code(分岐コード)
フィールドがある。
それは次のようにデコードされる:
BRANCH
CODE 機能
===== =======
0 ALWAYS 常に分岐
1 NILIND もしNILインジケータがセットされていれば分岐、
そうでなければそのまま継続。
2 NOT NILIND もしNILインジケータがセットされていなければ分岐、
そうでなければそのまま継続。
3 NILIND ELSE POP ┬ これら2つは、NILINDとNOT NILINDと同様である、
4 NOT NILIND ELSE POP ┘ ただし、条件が不成立の時にスタックを
ポップすることを除いて。
5 ATOMIND もしNILインジケータがセットされていれば分岐、
そうでなければそのまま継続。
6 NOT ATOMIND 同様
もしoffsetへの分岐を実行する判断がなされたら、offsetは符号付き(2の補
数)として、PCへ加算される(すなわち、PDP-11が使用していたような、相対ジャ
ンプである)。もし、offsetが777(8進)であったら、それは、長距離分岐
(long-distance branch)の意味に解釈され、本当のオフセットは、次の(16ビッ
ト)ハーフ・ワードから取られる。PCは常にインクリメント済みのPCに加えられ
る;すなわち、短いケースでは命令のアドレス+1、長いケースでは、命令のアド
レス+2である。
クラスIV
--------------------------------------------
| 3 | 4 | 9 |
--------------------------------------------
DEST. (15) OPCODE
クラスIV(その他)命令は、スタック上にそれらの引数を取り、クラスI命令と
同様に働くデスティネーション・フィールドを持つ。それらはすべて、オペ
コード・ビット中に15(8進)を持ち、実際のopcodeは、最後の9ビット中にある。
よって、それらは512.までありえる。それらのほとんどは、解釈を行うLISPか
ら直接に呼ばれるだろう、そして、クラスIとII中に重複した機能が存在する。
[実装上の注意: CONSマシンのディスパッチ・メモリを使用してディスパッチを
行うには、クラスIV命令は、はるかに多すぎるので、ルーチンのスタート場所
は、メイン・メモリの中に保持されている。ディスパッチ・テーブルのベース
(基底)は、どんな時でも、A-Y-MISC-BASE中に保持されている。]
これらの関数のほとんどは、インタープリティブ・レベルから呼び出し可能
(通常LISPユーザがこれらを見れる)なので、核システムの一部を形づくってお
り、それは、Lispマシン核システム(Lisp Machine nuclear System)文書
(LMNUC >)中に記述されている。
最初の200(8進)個のクラスIV操作は、メイン・メモリ中のディスパッチ・テー
ブルの中には無く、代わりに特別にチェックされている。これらは"LIST"命令
であり、NEXT-LISTデスティネーションと協調して働く。
操作0-77は、LIST 0からLIST 77と呼ばれる。
list命令は、既定のコンス領域[A-CNSADF]にN個のQを確保し、
それらはCDR-NEXT, CDR-NILスタイルのNILのリストに初期化される。
そして、命令は3ワードをスタック上にプッシュする:
1) 新しく確保されたブロックへのポインタ,
2) LIST命令のデスティネーション・フィールド,
3) 新しく確保されたブロックへのポインタ(別なコピー)。
注意: CALL命令中のデスティネーションは、即座には使用されない;
それは、後ほど使用するために、セーブされる。
LIST命令の実行が終わった後に、その先の命令がデスティネーション
NEXT-LISTへ格納できる;マクロ-コードは、ソース・コード中のLIST関
数の次の引数を計算すると、マクロ・コードは、引数の計算結果をNEXT LISTへ格納
する。(※訳注:この文、よくわからず。原文 once the macro-code computes the
next arg of what was the LIST function in the source code it(*1)
stores it(*2) to NEXT LIST. (*1: macro-code、*2: computeの終わったthe
next arg と考えて訳してみた))
デスティネーションNEXT LISTが行うことは:スタック・トップのワードを
次に確保されたセルへのポインタとして取ることである。
ポインタがポップされ、それが指すところに命令の結果が格納される、
そして、ポインタのCDRをスタックにプッシュし戻す。
もしCDRがNILなら,我々はLIST操作を終了させなければならない。よって、スタッ
クからNILをポップして捨て、そして、新たに確保した領域へのポインタを(そ
れの別なコピーを熟慮してスタック上に格納して) LIST命令のデスティネーショ
ンへ送る(それもスタック上に格納される)、そして、LISTがプッシュした残
りの2つのワードをポップする。
クラスV
オペコード16,17(8進)は将来の拡張のために予約され、使用されない。
例:
(非常に)典型的なLisp関数、階乗の計算を示す。
(defun fact (x)
(cond ((zerop x) 1)
(t (* x (fact (1- x))))))
以下は、コンパイラによって生成されたマクロ・コードである。
Lispマシン上でDISASSEMBLEによって出力させた。
22 NOVE D-PDL ARG|0 ;X
23 MISC D-IGNORE ZEROP
24 BR-NIL 26
25 MOVE D-RETURN 'I
26 MOVE D-PDL ARG|0 ;X
27 CALL D-PDL FEF|10 ;@FUNCTION-CELL FACT
30 MOVE D-PDL ARG|0 ;X
31 MISC D-LAST 1-
32 * PDL-POP
33 MOVE D-RETURN PDL-POP
最初の(行22)は、引数0(x)をスタックにプッシュする、そして(行23)はゼロ
と一致するかをチェックする。行23は、その他関数(miscellaneous function)
であるZEROPを使用し、それは値がZEROでなければ、"インジケータ"をNILにセッ
トする。行24は"インジケータ"をテストする分岐命令であり;もしNILがセットさ
れていれば、26へ分岐するであろう。もし、NILがセットされていなければ(数
値が0であった)、そのまま25へ抜け、それは値1をリターンする。
もし数が0でなければ(ソース中のCONDの第2節)、制御は行26へ移り、それは
PDL上へXをプッシュする(行32の乗算の第一引数である)。次の行27は、FACTへ
の呼び出しを開く。行30は、Xから1を減ずる(その他関数1-を使って)、そして
結果を"destination LAST"へ転送する。この結果、それはすなわち、FACTの再
帰呼び出しへの第一番目でかつ唯一の引数である。この結果は、行27のCALL命
令のデスティネーション・フィールドであるから、PDL上に残される。
さて、今、 (FACT (1- x))とXがPDL上にあり、そしてそれらは行32の
multiply命令で乗算される。乗算命令はPDL上に結果を置いて終わり、行33によっ
て結果は見つけられて、結果をリターンする。
--EOF
たけおか(竹岡尚三)のLisp インデックス
たけおか(竹岡尚三)のホームページ