NBCL:N-BASIC コンパイラ 詳細

ここでは v1.03 時点の仕様について解説します。 後のバージョンでは違いがあるかもしれません。

なお、ここでは各種 Byte 数を「アドレス.2b」のように表します。




入出力ファイル

NBCL が扱うファイルは以下の通りです。



BCI:独自中間言語

最初に .cmt から BCI へ変換します。 なるべく汎用性を重視したフォーマットにしましたが、N-BASIC は文法が命令によってバラバラなので、変換はかなり面倒です。

アーカイブ内の nbasic/tobci.txt は変換規則をなんとなく書いてみたファイルですが、 今のところ使用していません。おそらくいろいろと問題が出ると思います。

.bci ファイルは行単位の記述になっており、 最初の行はバージョン、その後は、

のいずれかとなっています。このうち代入と命令は、

といった規則になっています。 詳しい説明は省略しますが、変換して N-BASIC と比較すればだいたいわかると思います。

なお、-i オプションで .bci ファイルを出力した場合、 自動的に再び読み込んで内部構造が変化しないかチェックされます。 元々はデバッグ用ですが、念のため残してあります。




VM:仮想マシン

BCI を直接 Z80 コードには変換せず、一種の VM(Virtual Machine:仮想マシン)命令に変換します。 実行速度は犠牲になりますが、コードサイズが比較的小さくて済みます。

たとえば「X=A+B」(全て整数変数とする)という N-BASIC プログラムを変換するとします。

; Z80 : 16Bytes
	LD HL,(VARI_A)
	LD DE,(VARI_B)
	OR A
	ADC HL,DE	; ADD HL,DE では P/V フラグが変化しない
	JP V,_ERROR_OVERFLOW
	LD (VARI_X),HL

; VM : 10Bytes
	DB VM_VARI	; A の値をスタックに積む
	DW VARI_A
	DB VM_VARI	; B の値をスタックに積む
	DW VARI_B
	DB VM_ADD	; スタックから値を 2 つ取り出し、加算してスタックに積む
	DB VM_LETI	; スタックから値を取り出し、X に格納
	DW VARI_X

; VM 最適化 : 7Bytes
	DB VM_ADDI_VVV	; A の値と B の値を加算して X に格納
	DW VARI_X
	DW VARI_A
	DW VARI_B

N-BASIC では 6Bytes(式と ':' もしくは行終端)なので、16Bytes と 10Bytes(最適化すると 7Bytes)の差は結構大きいわけです。

ただし実行速度差も大きいので、-O 3 オプションで一部の命令を直接 Z80 コードに変換します。 今のところ、コードサイズがかなり大きくなる割に効果は限定的です。

上記の例でなんとなくわかると思いますが、VM 命令の大部分はスタックを利用します。たとえば WIDTH 命令なら、

; WIDTH 80,25
	DB VM_IMMU8	; 値 80 をスタックに積む
	DB 80
	DB VM_IMMU8	; 値 25 をスタックに積む
	DB 25
	DB VMI_WIDTH	; スタックから値を 2 つ取り出し、WIDTH 命令を実行

このようにパラメータをスタックに積んでおいて実行します。

ところで、この例では VM 命令を 3 つも使用していますが、仮に、

; WIDTH 80,25
	DB VMI_WIDTH_IMM,80,25

といった VM 命令を追加すれば 1 命令で済み、VM コードサイズも小さくなります。VM 命令数は速度にも直結するので、命令をまとめるのは重要です。

ただ、パラメータの種類ごとに VM 命令を用意しなければならないので、 ライブラリが大きくなることも考慮しなければなりません。

WIDTH は初めに 1 回実行する程度なので割に合わない感じがしますが、 頻繁に実行する命令では効果的です。現在は代入や計算式の他に POKECHR$ も最適化しています。




ライブラリ概要

BCI を VM コードに変換した後、VM を実行する Z80 コードが別途必要になります。それが nbasic フォルダにあるライブラリ群です。

ライブラリの簡単な解説は _libmanual.txt にありますが、

が基本部分、

などが VM 実行部分となっています(他にもありますが省略)。 細かくモジュールに分け、必要なモジュールのみリンクしています。

ライブラリを作るにあたって、少々速度が犠牲になっても、 コードサイズがあまり大きくならないように意識しています。

ただ、現時点では正常動作することが最優先で、 まだ突き詰めてはいません。少しずつ改良していこうと思います。

開始~メインループは base.z80s にあります。 主要ラベルは以下の通りです。

メインループでは DE レジスタが VM 命令アドレスです。 この DE レジスタを退避する際、スタックの都合で PUSH DE しにくい場合に、

	LD (_VMLOOP2+1),DE
	(VM処理)
	JPR _VMLOOP2

などとしています。




ライブラリ詳細

なるべくコードサイズを小さくするため、N-BASIC ROM 内ルーチンやワークを利用しています。 特に下記ワーク(と関連する ROM 内ルーチン)を頻繁に利用しています。

値の型は、数値に関してはそれぞれのサイズになっています。

文字列型のディスクリプタは「長さ.1b, 文字列本体アドレス.2b」の計 3Bytesです(長さが 0 の場合の文字列本体アドレスは不定です)。文字列本体は 0 終端ではないので気を付けてください。

変数はコンパイラがワークを静的確保します。 ただし配列変数の本体と文字列の本体は動的確保します。

変数ラベル名の命名規則とサイズは以下の通りです(* は変数名)。

配列変数は本体開始アドレスを表します(まだ DIM で確保されていなければ 0)。

本体は以下のフォーマットになっています。

スタックのフォーマットは以下の通りです(下位アドレスから)。



コンパイルオプション

NBCL のコンパイルオプションについて補足しておきます。

inserted by FC2 system