はじめに
ここでは X68k 用のアセンブラ
HAS060.X を使ってプログラムを書くときに使える小技をいろいろ紹介します。アセンブラのプログラム書くときにこれらのテクニックを使いこなして、効率のよいプログラミングを目指しましょう。
AS.X や HAS.X では使えない機能が含まれているので注意して下さい。また、例を挙げるだけで細かい文法の説明を省略しているところがあります。是非、HAS060.X に添付されているマニュアルなども参照して下さい。
目次
注釈の書き方
プログラムの中に注釈を書くときは、'*' または ';' で書き始めます。どちらを使っても構いませんが、1 つのプログラムの中では統一しておきましょう。
'*' はオペランドの先頭にも(ロケーションカウンタを参照するときに)書くことができるので、どちらかというと注釈の開始には ';' を使うことをお勧めします。私も ';' を使っています。
●注釈の書き方のパターン
行頭に '*' または ';' を書くと、その右側は行末まで注釈と見なされて無視されます。
┌────────────────────────────
│;注釈
行頭に空白があっても構いません。
┌────────────────────────────
│ ;注釈
ラベルの後ろに空白を入れて書くこともできます。
┌────────────────────────────
│ラベル: ;注釈
命令や疑似命令あるいはマクロの後ろにも注釈を書くことができます。このときは、空白を入れてから、なるべく ';' で開始するようにして下さい。
┌────────────────────────────
│ 命令 ;注釈
│ラベル: 命令 ;注釈
オペランドの後に空白を入れて書くこともできます。オペランドの後に空白があるときはそれ以降は '*' や ';' がなくても無視されますが、注釈は '*' または ';' で書き始めるように心がけて下さい。
┌────────────────────────────
│ 命令 オペランド ;注釈
│ラベル: 命令 オペランド ;注釈
':'(コロン)の使い方
一般的に、行頭のラベルの末尾には ':' を付ける習慣があります。
┌────────────────────────────
│foo:
〜
という具合です。ここで、ラベルと ':' の間を空けてはいけません。なお、この書き方では命令や疑似命令あるいはマクロと同名のラベルを定義することができます。
行頭から始まっていれば(ラベルの左側に空白がなければ)':' がなくてもラベルと見なされます。
┌────────────────────────────
│foo
〜
でもよいわけです。ただし、この方法で命令や疑似命令あるいはマクロなどと同名のラベルを定義するときは、同じ行の右側に命令や疑似命令あるいはマクロを書くことで行頭の単語が命令などでないことを明確にする必要があります。
逆に、行頭から始まっていなくても(ラベルの左側に空白があっても)':' が付いていればラベルと見なされます。
┌────────────────────────────
│ char:=$61
│ .dc.b char
これは char というシンボルに疑似命令 '='(疑似命令 set の別名)で $61 という値を与えています。2 行目の .dc.b で埋め込まれる値は $61(文字で言うと 'a')です。
HAS060.X に添付されている
HANOI.S や
K_MACRO.MAC が、行頭に空白を入れて ':' を付けてラベルを定義する記法を使っているので、参考にして下さい。
なお、行頭のシンボルに定数を与える equ、set、=(set の別名)、reg、fequ、fset などでは、':' を付けないほうが一般的です。
ローカルラベルの使い方
多くの細かい分岐やループを記述するのに、いちいちジャンプ先のラベルを考えるのは面倒です。ラベルを幾つも書いているとだんだんハナモゲラになってしまい、どこでどのラベルを参照しているのかがひと目でわからなくなってしまうこともあります。
そこで、HAS.X や HAS060.X では名無しのローカルラベルが使えるようになっています。名無しですが、ハナモゲラのラベルと比べればすぐ近くのジャンプ先がわかりやすく、便利なものです。
ローカルラベルと言っても、スコープ(ラベルを参照できる範囲)に明確な制限があるわけではありません。「同種の名無しローカルラベルが複数定義されているときは、前後どちらかの指定された方向で一番近くにあるものを参照する」という単純な決まりがあるだけです。
名無しのローカルラベルをあまり遠いところから参照すると可読性の悪いソースになってしまうので注意しましょう。
なお、ここで説明するローカルラベルは、マクロ内ローカルラベル(後述)とは関係ありません。
● '@@'
ローカルラベル '@@' は、直前の '@f' または直後の '@b' から参照できます。
┌────────────────────────────
│ bra @f ────┐
│@@: ←───┘
│@@: ←───┐
│ bra @b ────┘
なお、f は forward、b は backward の頭文字です。f や b は大文字で書いても同じ意味になります。
「一番近くにあるものを参照する」と書きましたが、@@: の場合は 2 番目、3 番目と離れているものを参照することもできます。'@@f' は 2 つ後の '@@' を、'@@b' は 2 つ前の '@@' を参照できます。
┌────────────────────────────
│ bra @@f ────┐
│@@: ←───┼┐
│@@: ←───┘│
│ bra @@b ─────┘
'@@@f' や '@@@b' なども同様に 1 つずつ離れてゆきます。
255 個まで離れている @@: を参照できるようになっていますが、実際には 2 つ以上離れている @@: を参照することはほとんどありません。2 つ以上離れるときは数字ローカルラベルを使ったほうが見やすいからです。
●数字ローカルラベル
数字ローカルラベル '1' は、直前の '1f' または直後の '1b' から参照できます。'2'、'3'、…も同様です。
HAS.X では '1' から '9' まで、HAS060.X では '1' から '9999' まで使えます。1 桁だと足りなくなることがありますが、普通は 2 桁もあれば足りると思います。
GRAM(65536 色モード)を buffer にコピーする
┌────────────────────────────
│ lea.l $00C00000,a0
│ lea.l buffer,a1
│ move.w #512-1,d2 ←────┐
│2: move.w #512-1,d1 ←───┐│
│1: move.w (a0)+,(a1)+ ││
│ dbra d1,1b ────┘│
│ dbra d2,2b ─────┘
〜
│buffer:
│ .dc.w 512*512
特定の数字ローカルラベルに意味を持たせておくと便利です。例えば、私はサブルーチンの終了位置に 99: を置くようにしています。
S44PLAY.X のソースでも数字ローカルラベルを大量に使用しているので参考にして下さい。
●ローカルラベルの実装方法
使い方とは関係ありませんが、ローカルラベルの機能の理解を助けるために、HAS.X や HAS060.X における名無しのローカルラベルの実装方法を説明します。
HAS.X では 10 種類、HAS060.X では 10000 種類の名無しローカルラベルを使えるようになっていますが、アセンブラの内部にそれぞれ今まで何回使われたかを数えているワークがあります。初期状態はすべて 0 回になっています。
@@: 0 回
1: 0 回
2: 0 回
:
この状態で例えば @@: というローカルラベルの定義が出てきたときは、それが自動的に @@#0: というラベルに置き換えられると考えて下さい(実際に @@#0 という名前が付けられるわけではありませんが、意味としてはそういうことです)。そして @@: の使用回数が 1 回増えて 1 回になります。同様に、2 番目に出てきた @@: は @@#1: になります。これが、同じ @@: というラベルを何度も定義できる仕掛けです。要するに内部で順番に番号を振っているだけです。
┌────────────────────────────
│@@: → @@#0:
│@@: → @@#1:
さて、次に @f と @b がどうなっているのか説明しましょう。例えば @@: が既に 2 回出てきているとき、ローカルラベルのカウンタは次のようになっています。
@@: 2 回
このとき、@f: は現在のカウンタの番号の @@: を参照します。つまり、@f というラベル参照は @@#2 というラベル参照に置き換えられるのです。@@#2: というラベルは直後に出てきた @@: に対して割り当てられるので、@f は直後の @@: を参照することができるというわけです。同様に、@b は現在のカウンタの番号から 1 を引いた @@: を参照します。つまり、今まで @@: が 2 回出てきた状態で使用された @b は、@@#1: というラベル参照に置き換えられます。
┌────────────────────────────
│@@: → @@#0:
│@@: → @@#1: ←────┐
│ bra @f → @@#2: ────┐│
│ bra @b → @@#1: ────┼┘
│@@: → @@#2: ←───┘
まとめると、
:
@@@b @@#(@@:の現在のカウンタ-3) を参照
@@b @@#(@@:の現在のカウンタ-2) を参照
@b @@#(@@:の現在のカウンタ-1) を参照
@f @@#(@@:の現在のカウンタ+0) を参照
@@f @@#(@@:の現在のカウンタ+1) を参照
@@@f @@#(@@:の現在のカウンタ+2) を参照
:
ということになります。
数字ローカルラベルでも同様です。
1b 1#(1:の現在のカウンタ-1) を参照
1f 1#(1:の現在のカウンタ+0) を参照
2b 2#(2:の現在のカウンタ-1) を参照
2f 2#(2:の現在のカウンタ+0) を参照
:
9999b 9999#(9999:の現在のカウンタ-1) を参照
9999f 9999#(9999:の現在のカウンタ+0) を参照
HAS060.X は 10000 種類のローカルラベルを使用できるようになっているので、カウンタのワークだけで 20KB 近く消費しています。
ラベルを自動的に外部定義にする
ラベルに付ける ':' を 2 つ繋げて '::' と書くと、そのラベルが自動的に外部定義になります。
ラベルを外部定義にしたいときは .xdef などを使って定義することもできますが、'::' を使うと便利です。
┌────────────────────────────
│ .xdef foo
│foo:
と
┌────────────────────────────
│foo::
は同じ意味です。
疑似命令の先頭に付ける '.' の意味
アセンブラでは疑似命令を '.' で始めるという習慣があります。
┌────────────────────────────
│ .text
│ .even
〜
などと書くわけです。
しかし、疑似命令に '.' を付けなければいけないという決まりがあるわけではありません。逆に、普通の命令に '.' を付けてもエラーにはなりません。
命令や疑似命令の先頭に '.' を付けたときと付けないときでは、次のような違いがあります。
命令や疑似命令と同名のマクロが定義されているとき、
'.' を付けたとき・・・・・・・命令や疑似命令を優先する
'.' を付けなかったたとき・・・マクロを優先する
例えば、次の例を見て下さい。
┌────────────────────────────
│bra .macro lab
│ bra.w lab
│ .endm
│ bra main
〜
│main:
〜
このプログラムでは bra 命令を強制的にワードサイズにするマクロを定義しようとしています。しかし、このままでは期待通りにアセンブルされません。マクロの中の「bra.w lab」のところで再び bra マクロ(=自分自身)を呼び出してしまうので、「マクロのネストが深すぎます」というエラーが出てしまうのです。そこで、マクロを次のように書き換えます。
┌────────────────────────────
│bra .macro lab
→│ .bra.w lab
│ .endm
│ bra main
〜
│main:
〜
変更したのは、2 行目の bra.w の先頭に '.' を付けただけです。これだけでこの bra.w はマクロよりも命令を優先して解釈されることになるので、自分自身を再帰的に呼び出すことはなくなり、期待通りにアセンブルされます。
上記の例のような特別な場合以外は、「疑似命令には '.' を付け、命令とマクロには '.' を付けない」という習慣に従いましょう。ただし、行頭のシンボルに定数を与える equ、set、=(set の別名)、reg、fequ、fset などには '.' を付けないほうが一般的です。
便利な疑似命令 reg
マニュアルでは reg はシンボルにレジスタリストを割り付ける疑似命令ということになっていますが、実際にはレジスタリストに限らず、命令のオペランドに書くことができる文字列ならば何でもシンボルに割り付けることができます。
つまり、実は reg はシンボルに任意のオペランドを割り付ける疑似命令なのです。
一般的な使い方としては、サブルーチンでスタックにセーブするレジスタをシンボルに覚えさせておくときに使います。
┌────────────────────────────
│save_regs reg d3-d7/a3-a6
│subroutine:
│ movem.l save_regs,-(sp)
〜
│ movem.l (sp)+,save_regs
│ rts
こうすると、「プログラムを書き換えているうちにプッシュするレジスタとポップするレジスタの数や種類が食い違ってしまった」などという間違いを減らせます。
しかし、私がよく使う使い方はこれです。
┌────────────────────────────
│PROGNAME reg 'S44PLAY'
│VERSION reg '1.02'
│DATE reg '2000.03.11'
〜
│banner:
│ .dc.b PROGNAME,'.X v',VERSION,' (',DATE,') by M.Kamada',0
〜
banner のところの文字列は「S44PLAY.X v1.02 (2000.03.11) by M.Kamada」となります。
上の例では、reg の「オペランドなら何でも代入できる」という特徴を活かして、シンボルに文字列を割り当てています。このようにプログラムの先頭でプログラムのバージョンや日付をシンボルに割り当てておくと、後で更新するときにいちいちプログラム中の文字列データを探して書き換えなくて済むので便利です。
くり返し その1(.rept〜.endm)
.rept はその名の通り、指定されたブロックを指定された回数だけ繰り返してアセンブルする疑似命令です。
例えば、テキスト VRAM に半角フォントを書き込むときは次のように書くことができます。
┌────────────────────────────
│;<a0.l:テキストVRAMアドレス
│;<a1.l:8×16ドットフォントアドレス
│d = 0
│ .rept 16
│ move.b (a1)+,(d,a0)
│d = d+128
│ .endm
テキスト VRAM の Y 方向 1 ドットのオフセットは 128 なので、デスティネーションのディスプレースメントを 128 ずつ増やしながら 16 回書き込んでいます。
アセンブル時にコマンドラインに -p -f,1 を指定してアセンブルリストを出力させてみると、.rept の挙動がよくわかります。
┌────────────────────────────
〜
│<test.s>
│ 1 00000000 ;<a0.l:テキストVRAMアドレス
│ 2 00000000 ;<a1.l:8×16ドットフォントアドレス
│ 3 00000000 =00000000 d = 0
│ 4 00000000 .rept 16
│ 5 00000000 move.b (a1)+,(d,a0)
│ 6 00000000 d = d+128
│ 7 00000000 .endm
│ 7 00000000*1099 move.b (a1)+,(d,a0)
│ 7 00000002*=00000080 d = d+128
│ 7 00000002*11590080 move.b (a1)+,(d,a0)
│ 7 00000006*=00000100 d = d+128
│ 7 00000006*11590100 move.b (a1)+,(d,a0)
│ 7 0000000A*=00000180 d = d+128
│ 7 0000000A*11590180 move.b (a1)+,(d,a0)
│ 7 0000000E*=00000200 d = d+128
│ 7 0000000E*11590200 move.b (a1)+,(d,a0)
│ 7 00000012*=00000280 d = d+128
│ 7 00000012*11590280 move.b (a1)+,(d,a0)
│ 7 00000016*=00000300 d = d+128
│ 7 00000016*11590300 move.b (a1)+,(d,a0)
│ 7 0000001A*=00000380 d = d+128
│ 7 0000001A*11590380 move.b (a1)+,(d,a0)
│ 7 0000001E*=00000400 d = d+128
│ 7 0000001E*11590400 move.b (a1)+,(d,a0)
│ 7 00000022*=00000480 d = d+128
│ 7 00000022*11590480 move.b (a1)+,(d,a0)
│ 7 00000026*=00000500 d = d+128
│ 7 00000026*11590500 move.b (a1)+,(d,a0)
│ 7 0000002A*=00000580 d = d+128
│ 7 0000002A*11590580 move.b (a1)+,(d,a0)
│ 7 0000002E*=00000600 d = d+128
│ 7 0000002E*11590600 move.b (a1)+,(d,a0)
│ 7 00000032*=00000680 d = d+128
│ 7 00000032*11590680 move.b (a1)+,(d,a0)
│ 7 00000036*=00000700 d = d+128
│ 7 00000036*11590700 move.b (a1)+,(d,a0)
│ 7 0000003A*=00000780 d = d+128
│ 7 0000003A*11590780 move.b (a1)+,(d,a0)
│ 7 0000003E*=00000800 d = d+128
〜
1 回目は d = 0 なので (d,a0) が (a0) に変換されることにも注目して下さい。
● .rept〜.endm の実装方法
.rept〜.endm は名無しマクロの一種です。.rept からマクロの定義が開始され、.endm が出てきたら自動的にそこまでのコードを .rept で指定された回数だけ繰り返して展開しているだけなのです。
.rept の内部で発生したアセンブルエラーの多くが .endm の行で通知されるのは、マクロの内部で発生したアセンブルエラーがほとんどマクロの定義のときではなくてマクロの展開のときに発生するのと同じことです。
くり返し その2(.irp〜.endm)
.irp もループ展開を行う疑似命令です。.rept と違い、.irp では 1 回展開する毎に特定のシンボルに指定された文字列を割り当てることができます。1 つの引数を持つ名無しマクロに毎回異なる引数を与えて展開する、と言ったほうがわかりやすいかも知れません。
.irp の最初の引数はその .irp〜.endm の中だけで有効なシンボルです。そのシンボルに 2 番目以降の引数が順に代入されながら、.irp〜.endm の中のコードが繰り返し展開されます。
┌────────────────────────────
│ .irp rgb,red,green,blue
│str_&rgb: .dc.b '&rgb',0
│ .endm
この場合は rgb というシンボルに red、green、blue が順に代入されます。展開結果は、
┌────────────────────────────
│ 3 00000000*72656400 str_red: .dc.b 'red',0
│ 3 00000004*677265656E00 str_green: .dc.b 'green',0
│ 3 0000000A*626C756500 str_blue: .dc.b 'blue',0
.irp〜.endm の中では、マクロの場合と同様に &シンボル の形式で引数を参照できます。シンボルが独立しているときは '&' は無くても構いません。
くり返し その3(.irpc〜.endm)
.irpc は .irp の変化形です。.irp が 2 番目以降の引数を順にシンボルに割り当ててゆくのに対して、.irpc は 2 番目の引数の文字を 1 文字ずつ順にシンボルに割り当ててゆきます。
┌────────────────────────────
│ea_jump_table:
│ .irpc rrr,01234567
│ .irpc mmm,01234567
│ .dc.w ea_&rrr&&mmm-ea_jump_table
│ .endm
│ .endm
.irpc を二重に使っているので、外側のループの引数の rrr の参照は &rrr で、内側のループの引数の mmm の参照が &&mmm になっていることに注目して下さい。外側のループが展開された時点で、&&mmm が &mmm に変化し、さらに内側のループが展開されるときに &mmm が文字に置き換わります。展開結果は、
┌────────────────────────────
│ 1 00000000 ea_jump_table:
│ 2 00000000 .irpc rrr,01234567
│ 3 00000000 .irpc mmm,01234567
│ 4 00000000 .dc.w ea_&rrr&&mmm-ea_jump_table
│ 5 00000000 .endm
│ 6 00000000 .endm
│ 6 00000000* .irpc mmm,01234567
│ 6 00000000* .dc.w ea_0&mmm-ea_jump_table
│ 6 00000000* .endm
│ 6 00000000*???? .dc.w ea_00-ea_jump_table
│ 6 00000002*???? .dc.w ea_01-ea_jump_table
│ 6 00000004*???? .dc.w ea_02-ea_jump_table
│ 6 00000006*???? .dc.w ea_03-ea_jump_table
│ 6 00000008*???? .dc.w ea_04-ea_jump_table
│ 6 0000000A*???? .dc.w ea_05-ea_jump_table
│ 6 0000000C*???? .dc.w ea_06-ea_jump_table
│ 6 0000000E*???? .dc.w ea_07-ea_jump_table
│ 6 00000010* .irpc mmm,01234567
│ 6 00000010* .dc.w ea_1&mmm-ea_jump_table
│ 6 00000010* .endm
│ 6 00000010*???? .dc.w ea_10-ea_jump_table
│ 6 00000012*???? .dc.w ea_11-ea_jump_table
│ 6 00000014*???? .dc.w ea_12-ea_jump_table
│ 6 00000016*???? .dc.w ea_13-ea_jump_table
〜
│ 6 00000070* .irpc mmm,01234567
│ 6 00000070* .dc.w ea_7&mmm-ea_jump_table
│ 6 00000070* .endm
│ 6 00000070*???? .dc.w ea_70-ea_jump_table
│ 6 00000072*???? .dc.w ea_71-ea_jump_table
│ 6 00000074*???? .dc.w ea_72-ea_jump_table
│ 6 00000076*???? .dc.w ea_73-ea_jump_table
│ 6 00000078*???? .dc.w ea_74-ea_jump_table
│ 6 0000007A*???? .dc.w ea_75-ea_jump_table
│ 6 0000007C*???? .dc.w ea_76-ea_jump_table
│ 6 0000007E*???? .dc.w ea_77-ea_jump_table
ここでは ea_00 などのシンボルが未定義なので値は ???? になっています。
.irpc の 2 番目の引数が '〜' で囲まれているときは、'〜' の中の文字列だけが文字を取り出す対象になります。
こんなこともできます。
┌────────────────────────────
│foo .macro str
│ .irpc c,str
│ .if ('a'<='&c').and.('&c'<='z')
│ .dc.b '&c'.and.$df
│ .else
│ .dc.b '&c'
│ .endif
│ .endm
│ .endm
│ foo 'm68k'
何をやっているか、だいたい予想がつくでしょうか?
┌────────────────────────────
│<test.s>
│ 1 00000000 foo .macro str
│ 2 00000000 .irpc c,str
│ 3 00000000 .if ('a'<='&c').and.('&c'<='z')
│ 4 00000000 .dc.b '&c'.and.$df
│ 5 00000000 .else
│ 6 00000000 .dc.b '&c'
│ 7 00000000 .endif
│ 8 00000000 .endm
│ 9 00000000 .endm
│ 10 00000000 foo 'm68k'
│ 10 00000000* .irpc c,'m68k'
│ 10 00000000* .if ('a'<='&c').and.('&c'<='z')
│ 10 00000000* .dc.b '&c'.and.$df
│ 10 00000000* .else
│ 10 00000000* .dc.b '&c'
│ 10 00000000* .endif
│ 10 00000000* .endm
│ 10 00000000* .if ('a'<='m').and.('m'<='z')
│ 10 00000000*4D .dc.b 'm'.and.$df
│ 10 00000001* .else
│ 10 00000001* .dc.b 'm'
│ 10 00000001* .endif
│ 10 00000001* .if ('a'<='6').and.('6'<='z')
│ 10 00000001* .dc.b '6'.and.$df
│ 10 00000001* .else
│ 10 00000001*36 .dc.b '6'
│ 10 00000002* .endif
│ 10 00000002* .if ('a'<='8').and.('8'<='z')
│ 10 00000002* .dc.b '8'.and.$df
│ 10 00000002* .else
│ 10 00000002*38 .dc.b '8'
│ 10 00000003* .endif
│ 10 00000003* .if ('a'<='k').and.('k'<='z')
│ 10 00000003*4B .dc.b 'k'.and.$df
│ 10 00000004* .else
│ 10 00000004* .dc.b 'k'
│ 10 00000004* .endif
「指定された文字列を 1 文字ずつ分解して、それぞれの文字が 'a'〜'z' の範囲内なら $df と .and. したものを、そうでなければその文字をそのまま出力する」、つまり、「文字列を大文字化して出力するマクロ」です。
マクロ内ローカルラベル
.macro〜.endm で定義されたマクロの中では、疑似命令 .local を使ってそのマクロの中だけで有効なラベルを定義することができます。それをマクロ内ローカルラベルと呼びます。
例えば、指定されたサブルーチンを指定された回数だけ呼び出すマクロは次のように定義できます。
┌────────────────────────────
│repeat .macro n,lab
│ .local loop
│ move.w #n,-(sp)
│loop:
│ jsr lab
│ subq.w #1,(sp)
│ bne loop
│ addq.l #2,sp
│ .endm
〜
│ repeat 10,subroutine
さて、マクロ内ローカルラベルの数が多くなってくると、いちいち .local でラベルを宣言するのは面倒です。そこで、HAS060.X にはマクロ内ローカルラベルが自動的に宣言されるという便利な機能が追加されています。マクロの中で '@' で始まるシンボルを使用すると、勝手にマクロ内ローカルラベルになるのです。ただし、@f、@F、@b、@B および @@〜 で始まるラベルはマクロ内ローカルラベルとしては使えません。
┌────────────────────────────
│repeat .macro n,lab
│ move.w #n,-(sp)
│@loop:
│ jsr lab
│ subq.w #1,(sp)
│ bne @loop
│ addq.l #2,sp
│ .endm
〜
│ repeat 10,subroutine
マクロ内ローカルラベルを大量に使用しているプログラムの例としては、
S44PLAY.X の core.s などがあります。
オペレーションサイズを解釈するマクロ
命令に近い形のマクロを定義することができるようにするために、HAS060.X ではマクロの使用時に指定されたオペレーションサイズをマクロの中で使用することができるようになっています。
マクロ定義の先頭で .sizem を使ってシンボルを宣言することで、マクロに指定されたオペレーションサイズを .sizem で指定したシンボルで受け取ることができます。
┌────────────────────────────
│inc .macro ea
│ .sizem sz
│ addq&sz #1,ea
│ .endm
│ inc.l d0
│ inc (a0)+
展開結果は、
┌────────────────────────────
│ 5 00000000 inc.l d0
│ 5 00000000*5280 addq.l #1,d0
│ 6 00000002 inc (a0)+
│ 6 00000002*5258 addq #1,(a0)+
なお、.sizem はマクロに指定された引数の個数を受け取るシンボルを宣言することもできます。詳しくは HAS060.X のマニュアルを参照して下さい。
cc と Ncc
ここで言う cc というのは、Bcc や DBcc と書くときの cc、つまり、ニモニックの中のコンディションコードを表す部分のことです。HAS060.X には、cc の代わりに Ncc を指定することで条件を逆にする機能があります。
┌────────────────────────────
│ .irp cc,hi,ls,cc,hs,cs,lo,ne,eq,vc,vs,pl,mi,ge,lt,gt,le
│if&cc .macro cmd
│ bn&cc @skip
│ cmd
│@skip:
│ .endm
│ .endm
│ tst.l d0
│ ifmi <neg.l d0>
.irp〜.endm の中に .macro〜.endm があることにも注目。これで一度に大量のマクロを定義しています。-p -f,1 でアセンブルしてみると、以下のようなアセンブルリストが得られます。
┌────────────────────────────
│<test.s>
│ 1 00000000 .irp cc,hi,ls,cc,hs,cs,lo,ne,eq,vc,vs,pl,mi,ge,lt,gt,le
│ 2 00000000 if&cc .macro cmd
│ 3 00000000 bn&cc @skip
│ 4 00000000 cmd
│ 5 00000000 @skip:
│ 6 00000000 .endm
│ 7 00000000 .endm
│ 7 00000000* ifhi .macro cmd
│ 7 00000000* bnhi @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifls .macro cmd
│ 7 00000000* bnls @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifcc .macro cmd
│ 7 00000000* bncc @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifhs .macro cmd
│ 7 00000000* bnhs @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifcs .macro cmd
│ 7 00000000* bncs @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* iflo .macro cmd
│ 7 00000000* bnlo @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifne .macro cmd
│ 7 00000000* bnne @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifeq .macro cmd
│ 7 00000000* bneq @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifvc .macro cmd
│ 7 00000000* bnvc @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifvs .macro cmd
│ 7 00000000* bnvs @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifpl .macro cmd
│ 7 00000000* bnpl @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifmi .macro cmd
│ 7 00000000* bnmi @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifge .macro cmd
│ 7 00000000* bnge @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* iflt .macro cmd
│ 7 00000000* bnlt @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifgt .macro cmd
│ 7 00000000* bngt @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 7 00000000* ifle .macro cmd
│ 7 00000000* bnle @skip
│ 7 00000000* cmd
│ 7 00000000* @skip:
│ 7 00000000* .endm
│ 8 00000000 4A80 tst.l d0
│ 9 00000002 ifmi <neg.l d0>
│ 9 00000002*6A02_00000006 bnmi ??0001
│ 9 00000004*4480 neg.l d0
│ 9 00000006* ??0001:
使用頻度は高くありませんが、Ncc はマクロで機械的に条件を入れ替えたいときに便利です。HAS060.X に添付されている
K_MACRO.MAC でも大量に使用していますので、参考にしてみて下さい。
JBRA、JBSR、JBcc
68000 では、bra 命令は bra.s と bra.w しか使えません。そのため、コードが 32KB を越えるプログラムを書くとき、bra ではオフセットが届かなくなってしまうことがあります。「jmp を使うとコードが長くなってしまう(3 ワード必要)のでなるべく bra(2 ワード)を使いたいけれど、どの分岐が bra で届くかわからない」というときは、jbra を使うと便利です。
jbra という命令は m68k の命令としては実在しません。アセンブラがオフセットに応じて bra か jmp に自動的に読み代えます。jbsr も同様です。jbcc は、bcc でオフセットが届かなければ、逆条件の bncc 命令で jsr や jmp 命令を飛び越えるような 4 ワードの命令列が自動的に生成されます。
┌────────────────────────────
│ jbhi foo
│ .rept 20000
│ nop
│ .endm
│foo:
これを展開すると、
┌────────────────────────────
│ 1 00000000 63064EF9(01)0000 jbhi foo
│ 9C48
│ 2 00000008 .rept 20000
│ 3 00000008 nop
│ 4 00000008 .endm
│ 5 00009C48 foo:
となります。jbhi が 4 ワードの命令列に展開されていることがわかります。この 4 ワードを逆アセンブルしてみるとわかりますが、jbhi の部分の 4 ワードの命令は、次のようなコードになっています。
┌────────────────────────────
│ bls.s L000006
│ jmp foo
│L000006:
bls のように分岐の条件が ls になっているのは、jbhi の hi の逆条件である nhi というのが ls と等価だからです。この命令列で、「hi ならば jmp する」という jbhi の機能を実現しているというわけです。
バイナリデータを埋め込む
アセンブラのソースの中にスプライトデータなどのバイナリデータを埋め込みたいときがあります。16 進数でダンプして .dc.b や $ を付けて整形してもよいのですが、面倒なので、HAS060.X ではバイナリデータの入っているファイルを直接プログラムに埋め込むことができるようにしました。
疑似命令 .insert は指定されたファイルをバイナリデータと見なしてプログラムに埋め込みます。埋め込むデータの範囲は指定できますが、データそのものは一切加工されません。
『ごーじゃすリバーシ』のソース(graph.s)で .insert を有効に利用しているので引用します。
┌────────────────────────────
│ .irp moji,0,1,2,3,4,5,6,7,8,9
│spc&moji: .insert sp/&moji.sp
│ .endm
│ .irp moji,き,け,ち,の,ス,パ,ベ,ル,レ,引,局,後,黒,手,終,勝,石,先,対,白,分,了
│spc&moji: .insert sp/&moji.sp
│ .endm
アセンブルリストを吐かせると、挿入されたバイナリデータがすべて出てきます。
┌────────────────────────────
│ 1 00000000 .irp moji,0,1,2,3,4,5,6,7,8,9
│ 2 00000000 spc&moji: .insert sp/&moji.sp
│ 3 00000000 .endm
│ 3 00000000*0000000000000000 spc0: .insert sp/0.sp
│ 0000000000000000
│ 0000000000000000
│ 0000000000000003
│ 0000000A0000001F
│ 0000005F0000008F
│ 000000AF000000CF
│ 000000DF000000FF
〜
│ 3 00000200*0000000000000000 spc1: .insert sp/1.sp
〜
│ 4 00001400 .irp moji,き,け,ち,の,ス,パ,ベ,ル,レ,引,局,後,黒,手,終,勝,石,先,対,白,分,了
│ 5 00001400 spc&moji: .insert sp/&moji.sp
│ 6 00001400 .endm
│ 6 00001400*0000000000000000 spcき: .insert sp/き.sp
│ 0000000000000000
│ 0000000000000000
│ 0000000000004444
│ 00008FFF00008FFF
│ 0000488600000000
│ 00000000008ECCCC
│ 008FFFFF008FFFFF
〜
│ 6 00003E00*0000000000000000 spc了: .insert sp/了.sp
〜
.not. と .notb. と .notw.
.not. は単項演算子です。もっと詳しく言うと、ビット毎の論理否定演算子というやつです。ですから、
.not.$55555555 = $AAAAAAAA
.not.$AAAAAAAA = $55555555
です。
では、次のソースをアセンブルしたらどうなるでしょう?
┌────────────────────────────
│ .dc.b .not.$55
│ .dc.b .not.$AA
答えは「1 行目が $AA で 2 行目が $55 になる」ですか? 残念ながらハズレです。アセンブルしてみるとわかりますが、2 行目だけエラーになります。
┌────────────────────────────
│<test.s>
│ 1 00000000 AA .dc.b .not.$55
│test.s 2: Error: オーバーフローしました
│ 2 00000001 .dc.b .not.$AA
理由は簡単、
.not.$55 = $FFFFFFAA
.not.$AA = $FFFFFF55
で、2 番目の $FFFFFF55 = -171 は .dc.b の引数に許される「符号なしまたは符号あり 8 ビット整数(具体的には -128〜255)」の範囲外ということでオーバーフローエラーになります。
しかし、この少々分かり難い問題に遭遇してしまい、「アセンブラのバグではないか?」と“バグ報告”を寄せて下さった方が何人かいらっしゃったのです。報告を寄せて下さった方には何故そうなるのかをきちんと説明したのですが、いちいち「(.not.$AA).and.$FF」または「$AA.xor.$FF」と書いてもらうのも面倒だろうと思ったので、専用の演算子を作ってしまいました。
HAS060.X のマニュアルからそのまま引用してしまいますが、.notb. と .notw. の定義は次のようになっています。
.NOTB.<式> ::= (.NOT.<式>).AND.$FF
.NOTW.<式> ::= (.NOT.<式>).AND.$FFFF
つまり、
.notb.$55 = $000000AA
.notb.$AA = $00000055
.notw.$5555 = $0000AAAA
.notw.$AAAA = $00005555
という具合です。先ほどのソースも、
┌────────────────────────────
│ .dc.b .notb.$55
│ .dc.b .notb.$AA
と書けば、
┌────────────────────────────
│<test.s>
│ 1 00000000 AA .dc.b .notb.$55
│ 2 00000001 55 .dc.b .notb.$AA
となって、これで問題解決です。
思いついたところをいろいろ挙げてみましたが、いかがでしたでたか?
「こんな使い方があったのか!」という発見がありましたか?