6800のアセンブラプログラム最適化(1)(ベーシックマスター開発 その25)

BASICMASTER, 昔のパソコン

昔は1バイトでも短いプログラムの方が大事だった。私の買ったベーシックマスターは全メモリが16KBぐらい。狭い。

メモリ追加して 32KB使えるようになっても言語処理系を入れるとカツカツだった。
(メモリも今のようなモジュールじゃなくて、16ピンDIPのやつです。8個買って16KB)。

ところが、MB6800用のGAME言語のクロスコンパイラを書いていると、メモリよりも速度を重視した方が良いんじゃないかと思う。

セルフ開発ではないので、開発ツールと言語処理系のメモリが丸々空く。

32KBのメモリを積んだBASICMASTERでも、ビデオRAMやBASICのワークエリアが2.5KBほど使う。

昔はOSは存在しないので、メモリ管理は自分で行う。

機械語プログラムは切りの良いアドレス(4KB単位)で使っていたので、そこでも端数が出る。ギチギチに詰めると機能拡張したときに再配置が必要になるし。

なんだかんだでGAME68+コンパイラだと約18KBしか残らない。KUMAJIRIは処理系は小さいけどエディタが別に必要なので、結局同じぐらい。

実に狭い。

クロス環境はメモリ使えて幸せ

クロス開発だと言語処理系もエディタも不要になる。切りの良い $1000- を使うとしても、28KB残る。

エミュレーターを使って Jrモードで走らせれば、$AFFFまで使えるので 40KBである。天国である。
(BASIC ROMをメモリに切り替えると53KBになるけど、40KBでも充分)

ということで、プログラム作成方法もメモリを切り詰める方ではなくて、速度を出す方に頭を切り替えるようにしている。

今回はコンパイラ出力の高速化を考えてみる。


関係演算の高速化

関係演算結果のAND/ORを考える。今回はAND処理。昔のマイコンの言語処理系にはANDやORは無かった。

その代わりに関係演算の結果が0か1になることを利用して、掛け算(*)でAND、足し算(+)でOR処理を行っていた。

BASICMASTERでも足し算でOR処理を行うのが普通だった。マニュアルの例にもある。

ベーシックマスターレベル2II取扱説明書 P.35から引用

次に買ったFM-8だと、MS系BASIC使っててAND/OR/XORがあった。便利だった。いつごろから論理演算普及したんだろ。

乗算・加算を使っていても、インタプリタ場合は構文解釈のオーバーヘッドが大きくて乗算の遅さが目立たない。昔はそれでも良かったのだけど、コンパイルすると遅さが目立つ。

ということで、コンパイル時に関係演算結果同士の乗算ならANDを取ることにした。ここまでは良い。

素直に6800で書くと遅いので頑張ってみる

MC6800はレジスタが少ない上に、ベーシックマスターでは使える0ページも少ないので、一時的な保存にはスタックを使うことになる。

(A=1)*(B=1) のような式をコンパイルすると、A=1の結果をスタックに積んでB=1を計算して、その後両方のANDを取るわけだ。

(これぐらい簡単な式ならコンパイル時に全部展開した方が速いんだけど、説明上スタックに積むものとする)。

問題

スタックトップに0か1が積まれていて、AccBにも0か1がある。
両者のANDをとってAccBに入れたい。どうすれば速く・短くなるか。
なるべく他のレジスタは壊したくない。

普通に計算(Xが壊れる)

命令の後のコメントは必要サイクル数とメモリバイト数。パイプライン処理はないので、サイクル数は素直に足せば良い。

TSX ; 4 1
ANDB 0,X ; 5 2
INS ; 4 1

13cycle 4bytes 短いけど遅い。

スタック操作・インデックスアクセスが遅いんだよな、MC6800。

足してシフト(Aが壊れる)

両方1の時だけ足して2になるので、これを1にすれば良いだろ?という案。ABA命令も使えるし。

PULA ; 4 1
ABA ; 2 1
TAB ; 2 1
LSRB ; 2 1

10cycle 4bytes。良い感じだが、AccA壊れるのがちょっと嫌。

未定義命令のNBA($14)を使う

NBAは AccA = AccA AND AccB を行う。ABA/CBA/SBAのAND命令版。

エミュレーターは未定義命令も対応してるのだろうか

PULA ; 4 1
NBA ; 2 1
TAB ; 2 1

8cycle 3bytes。ABA案のLSRBが無くなる分だけ短い。たぶん最短。

キャリーと足す(Aが壊れる)

値は0か1なのだから、キャリーに保存しておいてADC命令で足す。

PULA ; 4 1
LSRA ; 2 1
ADCB #0 ; 2 2
LSRB ; 2 1

10cycle 5bytes。AccAは0になるのがいまいち。

関係演算の結果なので、AccAが0になるのは便利な場合もあるのではあるが。

先にシフト(XもAも壊れない)

PULとLSRの順序を逆にすれば、使うのはAccBだけで済む。

LSRB ; 2 1
PULB ; 4 1
ADCB #0 ; 2 2
LSRB ; 2 1

10cycle 5bytes。NBA案を除けば最速だし、AccAも壊れない。

メモリは素直な案よりも1バイト多く使う。昔なら素直案を採用したんだろうな。

もっと良い方法あるかなー?

資料

MC6800の未定義命令を検索するとByte Magazineの1977年12月号P.46に行き着く。この本には、NBA以外にもSTAA/STAB/STS/STXのImmediateモードや、悪名高いHCFが紹介されている。

下記のページでは “0x14​​ は、両方のアキュムレータ (A と B) 間で AND を実行し、結果を A に格納します。ただし、後期の MC6800P のみがこれをサポートするようです”とある。