HAS060.X を使ってプログラムを書くときに使える小技を多数紹介している読み物です。月刊電脳倶楽部 144 号(2000 年 5 月号)の読み物横町のコーナーに収録したものを HTML 化しました。HAS060.X のマニュアルと併せて読んで下さい。
ここでは 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
はシンボルに任意のオペランドを割り付ける疑似命令なのです。
一般的な使い方としては、サブルーチンでスタックにセーブするレジスタをシンボルに覚えさせておくときに使います。
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
の「オペランドなら何でも代入できる」という特徴を活かして、シンボルに文字列を割り当てています。このようにプログラムの先頭でプログラムのバージョンや日付をシンボルに割り当てておくと、後で更新するときにいちいちプログラム中の文字列データを探して書き換えなくて済むので便利です。
.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
で指定された回数だけ繰り返して展開しているだけなのです。
.rept
の内部で発生したアセンブルエラーの多くが .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
の中では、マクロの場合と同様に &シンボル
の形式で引数を参照できます。シンボルが独立しているときは &
は無くても構いません。
.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
というのは、Bcc
や DBcc
と書くときの cc
、つまり、ニモニックの中のコンディションコードを表す部分のことです。HAS060.X には、cc
の代わりに Ncc
と書くことで条件を逆にする機能があります。勿論、N
は not の意味です。
機械的に条件を反転させることができるので、例えばこんなことができます。
.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:
ifmi
マクロの展開結果を見ると、mi
のときだけ neg.l d0
を実行するために mi
でないときだけ neg.l d0
を飛び越えていることがわかります。mi
でないときだけ分岐するのが bnmi
命令です。
使用頻度は高くありませんが、上記のように Ncc
という書き方はマクロで機械的に条件を逆にしたいときに便利です。HAS060.X に添付されている K_MACRO.MAC でも大量に使用していますので、参考にしてみて下さい。
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.s
の部分で分岐の条件が 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.
は単項演算子です。もっと詳しく言うと、ビット毎の論理否定演算子というやつです。ですから、
.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 = -86 |
.not.$AA = $FFFFFF55 = -171 |
で、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
となって、これで問題解決です。
思いついたところをいろいろ挙げてみましたが、いかがでしたか?
「こんな使い方があったのか!」という発見がありましたか?
https://stdkmd.net/hastips/ に引っ越しました。
── 続きを読む ──── 続きを隠す ──
http://stdkmd.com/hastips/ に引っ越しました。
HTML を更新
『HAS060.X を使いこなすテクニック』と改題し、HTML 化して STUDIO KAMADA で公開
月刊電脳倶楽部 144 号 (2000 年 5 月号) の読み物横町のコーナーに収録