標準TTLだけ(!)でCPUをつくろう!(組立てキットです!)
(ホントは74HC、CMOSなんだけど…)
[第586回]

●整数BASICコンパイラ(SBASIC)についてもう少しくわしく

本日も朝からずっとND80ZVの取扱説明書の作成作業をしておりました。
説明を書いておりますと、
いや、これはまずいなあ、
というところが出て来たりして、するとプログラムの変更作業とか、プログラムリストの見直しとかをしなくてはいけないことになって、なかなか時間がかかります。

整数BASICコンパイラ(SBASIC)について、もう少し別のプログラムでも説明をしたいと思っているのですが、本日はそんなわけで、プログラムを走らせてみたり、写真などを準備したりする時間がありませんでした。
ですから、今回は先回お見せしましたSBASICコンパイラで作成された機械語(マシン語)のプログラムをネタにしまして、どうしてこういうプログラムが生成されるのか、といったあたりを、説明することにいたします。

前回お見せしました、SBASICテストプログラムのソースリストです。

'SBASIC TEST2 10/8/17
	ORG=$8004
	OUT $83,$80
*LOOP:B%=0
	OUT $80,0
*LOOP1:B%=B%+1:IF B%<=A% GOTO *LOOP1
	B%=0
	OUT $80,1
*LOOP2:B%=B%+1:IF B%<=A% GOTO *LOOP2
	GOTO *LOOP

このソースプログラムをSBASICコンパイラでコンパイルして、機械語のプログラムファイルsbtest2.binが作られたのですが、どのような機械語のプログラムが生成されたかを確認するために、sbtest2.binをZ80逆アセンブラZDAS.COMで逆アセンブルして得られたリストが、下に示すリストです。
このリストも前回お見せしました。

8004 218300         LD HL,$0083
8007 4D             LD C,L
8008 218000         LD HL,$0080
800B ED69           OUT (C),L
800D 210000         LD HL,$0000
8010 2242F4         LD ($F442),HL
8013 218000         LD HL,$0080
8016 4D             LD C,L
8017 210000         LD HL,$0000
801A ED69           OUT (C),L
801C 2A42F4         LD HL,($F442)
801F E5             PUSH HL
8020 210100         LD HL,$0001
8023 D1             POP DE
8024 CD643E         CALL $3E64
8027 2242F4         LD ($F442),HL
802A 2A42F4         LD HL,($F442)
802D E5             PUSH HL
802E 2A40F4         LD HL,($F440)
8031 D1             POP DE
8032 B7             OR A
8033 ED52           SBC HL,DE
8035 CB7C           BIT 7,H
8037 210100         LD HL,$0001
803A 2803           JR Z,03
803C 2D             DEC L
803D 1802           JR 02
803F 7D             LD A,L
8040 B7             OR A
8041 C21C80         JP NZ,$801C
8044 210000         LD HL,$0000
8047 2242F4         LD ($F442),HL
804A 218000         LD HL,$0080
804D 4D             LD C,L
804E 210100         LD HL,$0001
8051 ED69           OUT (C),L
8053 2A42F4         LD HL,($F442)
8056 E5             PUSH HL
8057 210100         LD HL,$0001
805A D1             POP DE
805B CD643E         CALL $3E64
805E 2242F4         LD ($F442),HL
8061 2A42F4         LD HL,($F442)
8064 E5             PUSH HL
8065 2A40F4         LD HL,($F440)
8068 D1             POP DE
8069 B7             OR A
806A ED52           SBC HL,DE
806C CB7C           BIT 7,H
806E 210100         LD HL,$0001
8071 2803           JR Z,03
8073 2D             DEC L
8074 1802           JR 02
8076 7D             LD A,L
8077 B7             OR A
8078 C25380         JP NZ,$8053
807B C30D80         JP $800D
807E C9             RET

ソースプログラムは短いものでしたが、コンパイルの結果作成された機械語のプログラムはかなり長いものになってしまいました。
どうしてこんなに長くなってしまうのだろう、と疑問に思われたかもしれません。

このあたりがコンパイラの欠点といいますか弱点なのです。
これがCコンパイラなどになりますと、ほんの数行のソースプログラムなのに、コンパイルすると、数十Kバイトものプログラムファイルが生成されたりします。

アセンブラですと、プログラマが人間の頭で考えて、その場合の目的に合ったプログラミングをしますから、短くて効率のよいプログラムになります。
ところがコンパイラの場合には、ソースプログラムが汎用的な性格をもった言語、命令で組み立てられるために、どうしても汎用的な翻訳の仕方をすることになってしまいます。
もちろんコンパイラも、ソースプログラムを細かく場合分けをして、最適な機械語プログラムに変換するように工夫することによって、短くて効率のよい機械語プログラムに変換することもできるようになりますが、そうしようとすればするだけ、コンパイラ自体のサイズが大きくなって重たいものになってしまいます。

このSBASICコンパイラは、そのような効率を追求するために作ったものではありませんから、それほど能率のよい変換はできないのです。
Z80BASICインタプリタでは遅すぎて困るが、かといって、Z80アセンブラでプログラムを書くのはちょっとつらい、という場合に簡単に利用できる、ということを目的に作ったものですから、多少非能率なところがあっても、その目的には十分かなったものだと思います。

さて、そこで、どこがどうなると、このように長くなってしまうのか、ということの説明です。
上の逆アセンブルリストと、SBASICコンパイラでバイナリファイルとともに作成された、下のsbtest2.wkファイルとを見比べながら説明をいたします。

0000 'SBASIC TEST2 10/8/17
0000 	ORG=$8004
8004 	OUT $83,$80
800D *LOOP:800D B%=0
8013 	OUT $80,0
801C *LOOP1:801C B%=B%+1:802A IF B%<=A% GOTO *LOOP1
8044 	B%=0
804A 	OUT $80,1
8053 *LOOP2:8053 B%=B%+1:8061 IF B%<=A% GOTO *LOOP2
807B 	GOTO *LOOP
807E 

最初の8004は、OUT命令です。
ソースプログラムでは、$83、$80ともに16進数の定数ですが、Z80のOUT命令では、アドレスとして定数は使えますが、出力データはレジスタの値しか許されません。
定数は計算式で引用されるかも知れませんから、一旦はHLレジスタに入れます。
Z80のOUT命令はアドレスが定数の OUT (n),Aがスタンダードですから、nの部分に今回のプログラム例では定数の$83を置くこともできるのですが、SBASICコンパイラではより汎用性が高い、OUT (C),rを使っています。
アセンブラならば、
LD A,80
OUT (83),A
とわずか2行(機械語なら4バイト)で済んでしまうところが、9バイトになってしまうのは、そういう理由からなのです。

800DはB%=0です。
ここでも、定数の0を一旦HLレジスタに入れています。
アドレスF442は変数B%のアドレスです。

8013のOUT命令は、上で説明したOUT命令と同じです。

その次の801Cは、計算式B%=B%+1です。
ここは+1ですから、Z80アセンブラならINC命令を使うところですが、ソースプログラムが一般的な計算式ですから、BASICの計算式を計算するルーチンをCALLするように翻訳されます。
3E64は整数の加算ルーチンです。
DEレジスタにB%の値を入れ、HLレジスタに1を入れて加算ルーチン(3E64)をCALLします。
結果はHLレジスタに入れられますから、それをB%のアドレス(F442)に入れます。

そのあとすぐにまたF442からHLレジスタに値を入れています(アドレス802A)。
これはムダな作業なのですが、コンパイラはソースプログラムをBASICの命令の単位で機械的に翻訳していきますから、こういうムダも避けられません。

さてその802AではIF文の比較を行っています。
ここはちょっと見ると、何をやってるの?
と理解に苦しむような翻訳を行っています。
でも間違いではありません。

ここではB%<=A%の条件をチェックしています。
前回も説明しましたが、Z80の命令には16ビットの比較命令はありませんから、SBC HL,DEを使います。
HLにA%(アドレスF440)の値を入れ、DEにB%(アドレスF442)の値を入れて、SBC HL,DEを実行します。
実行の結果がマイナス(Hレジスタのビット7が1)になったら、結果は偽(=0)です。
それ以外は結果は真(1)になります。
結果の値はHLレジスタに入れられます。

ということを802Aから8041のところでやっているのですが、わかりますでしょうか?
JRは相対ジャンプ命令です。
JR nはその命令の書かれた次のアドレスからnバイトをジャンプします。
アドレス803AのJR 03は803Fへジャンプします。
アドレス803DのJR 02は8041へジャンプします。

アドレス8041のJP NZ,$801Cは、結果が真(1)のときに実行される、GOTO *LOOP1をそのまま機械語の命令に翻訳したものです。

以上説明したところより下の部分は、以上の説明をしたところの繰り返しです。
2010.8.18upload

前へ
次へ
ホームページトップへ戻る