S59/02/19
阿閉雅宏の Z-80まにあくす Stage.02
解説:杉之原名人
 近頃SPの変わった使い方がでてますが、今回は"ON~GOTO"にあたるプログラムを紹介しませう。リスト1が"ON A+1 GOTO ~"となるでしょうか。ただし、Aの最大値は$FFです。ジャンプアドレスはCALL命令以降に並んでいるものです。リスト2は"ON A GOTO ~"で、CALL命令のすぐ後にAの最大値を入れてください。その後にジャンプアドレスを入れておいてください。リロケータブルなプログラムです。これは一種のサブルーチンと思ってください。
リスト1  ON (A+1) GOTO
8000 E3 EX (SP) , HL HL = リターン先アドレス(=ジャンプテーブル)
8001 D5 PUSH DE DE退避
8002 5F LD E , A DE = オフセットNo.
8003 16 00 LD D , 0
8005 19 ADD HL , DE HL = HL + オフセットNo. * 2
8006 19 ADD HL , DE
8007 5E LD E , (HL) DE = 目的ジャンプ先アドレス
8007 23 INC HL
8009 56 LD D , (HL)
800A EB EX DE , HL HL = 目的ジャンプ先アドレス
800B D1 POP DE DE復元
800C E3 EX (SP) , HL スタックに目的ジャンプ先アドレスを乗せる
800D C9 RET 目的のジャンプ先へジャンプ
9000 CD 00 80 CALL $8000 ON (A+1) GOTO ルーチンを呼び出す
9003 00 91 00 92 DEFW $9100, $9200 ジャンプ先アドレステーブル
9007 00 93 00 94 DEFW $9300, $9400
900B xx xx xx xx ↓ 以降最大256アドレス分
 これが基本的な"ON A GOTO"のプログラムです。最初にエクスチェンジ命令でデータの入っているアドレスをHLレジスタに入れます。次に HL=HL+A*2 をやって、ジャンプアドレスの入っているアドレスを計算して、そのデータをHLに入るようにして、エクスチェンジ命令でSPに入れ、RETして終わりです。
 最後のRETの意味でずが、まずCALL命令は戻り先アドレスをスタックに積むわけですから、そこに違うアドレスを入れてRET命令を実行すれば、さもそのアドレスにj「ジャンプした」かのように見えるわけです。このプログラムの場合、そのアドレスがAの値に対応するジャンプアドレスになります。
 9001と900C番地のEX(エクスチェンジ)命令ですが、これはPOP命令とPUSH命令で代用した方が高速なのですが、HLレジスタを破壊したくないため、敢えてこのようにしてあります。
 念のため説明しておきますが、9000番地からが実際の使用例になっています。
リスト2  ON A GOTO (Aの範囲限定版)
8000 E3 EX (SP) , HL HL = ジャンプテーブルエントリ数格納ポインタ
8001 D5 PUSH DE DE退避
9002 5E LD E , (HL) E = ジャンプテーブルエントリ数
9003 23 INC HL HL = ジャンプテーブル先頭ポインタ
9004 3D DEC A オフセット値-1
9005 BB CP E エントリ数を超えていたら、DE = エントリ数
9006 30 01 JR NC , LB01
9008 5F LD E ,A エントリ数以内なら、DE = オフセット値
LB01:
8009 16 00 LD D , 0
800B 19 ADD HL , DE HL = HL + オフセットNo. * 2
800C 19 ADD HL , DE
800D 5E LD E , (HL) DE = 目的ジャンプ先アドレス
800E 23 INC HL
800F 56 LD D , (HL)
8010 EB EX DE , HL HL = 目的ジャンプ先アドレス
8011 D1 POP DE DE復元
8012 E3 EX (SP) , HL スタックに目的ジャンプ先アドレスを乗せる
8013 C9 RET 目的のジャンプ先へジャンプ
9000 CD 00 80 CALL $8000 ON A GOTO ルーチンを呼び出す
9003 04 DEFB 4 ジャンプテーブルエントリ数
9004 00 91 00 92 DEFW $9100, $9200 ジャンプ先アドレステーブル × 4エントリ
9008 00 93 00 94 DEFW $9300, $9400
900C xx xx ↓ 以降はプログラムの続き
 リスト2は範囲を限定版です。基本的にはリスト1と同じですが、Aの範囲を限定している部分が異なります。8002~8009番地までが変更されている部分です。DEC A 以降でAの範囲オーバーをチェックしいます。1≦A≦E となるようにAをデクリメントしているのです。ここで、Aが0の場合はデクリメントされて$FFになるので大丈夫。そして、LD E , A の部分を飛ばす(つまり範囲外の場合)ときは、Eには最大値より1大きい(デクリメントを飛ばしているので)値が入っていますから、ジャンプテーブルの次のアドレスにうまく飛んでくれるというわけです。このプログラムもリロケータブルです。

 今年の冬は雪が凄まじいですね。何とかならないでしょうか。

解説 (杉之原名人)
コンパクトでとても効率のよいプログラムに仕上がっています。本文の解説にあるように、スタックの操作はPOPとPUSHを使用した方が17クロックも高速になるのですが、HLレジスタを保持するためにEXを使用しているところが芸の細かなところです。もし、これと別にHLを保持しようとすると、確実に17クロック以上必要になるからです。

このルーチンのさらなる高速化は、使用方法と機能に制限を設けると可能になります。ADD HL , DE という命令は11クロック消費するので、これを2回実行すると22クロックもかかります。そこでジャンプテーブル数を半分の128エントリに限定すれば、9002番地のLD命令の手前で ADD A , A を追加して事前にオフセット値を2倍にしておくことにより、ADD HL , DE が1回で済みます。これで7クロック短縮されたことになります。ただし、Aレジスタが破壊されることになりますが。

リスト2は非常に使い勝手の良い優秀なルーチンです。わずか20バイトのプログラムで全レジスタを保存したまま見事にテーブルジャンプ処理を実現しています。惚れ惚れします。(〃∀〃;) Aが0の場合にもジャンプテーブルの次のアドレスに飛ぶのが美味しい。Z-80プログラムの秀作といえるでしょう。