HAS060.X を使ってプログラムを書くときに使える小技を多数紹介している読み物です。月刊電脳倶楽部 144 号(2000 年 5 月号)の読み物横町のコーナーに収録したものを HTML 化しました。HAS060.X のマニュアルと併せて読んで下さい。


目次

  1. はじめに
  2. 注釈の書き方
    1. 注釈の書き方のパターン
  3. : (コロン) の使い方
  4. ローカルラベルの使い方
    1. @@
    2. 数字ローカルラベル
    3. ローカルラベルの実装方法
  5. ラベルを自動的に外部定義にする
  6. 疑似命令の先頭に付ける . (ドット) の意味
  7. 便利な疑似命令 reg
  8. 繰り返し 1 (.rept~.endm)
    1. .rept~.endm の実装方法
  9. 繰り返し 2 (.irp~.endm)
  10. 繰り返し 3 (.irpc~.endm)
  11. マクロ内ローカルラベル
  12. オペレーションサイズを解釈するマクロ
  13. 逆条件 (cc と Ncc)
  14. JBRA と JBSR と JBcc
  15. バイナリデータを埋め込む
  16. .not. と .notb. と .notw.
  17. おわりに

1. はじめに

ここでは X68k 用のアセンブラ HAS060.X を使ってプログラムを書くときに使える小技をいろいろ紹介します。アセンブラのプログラム書くときにこれらのテクニックを使いこなして、効率のよいプログラミングを目指しましょう。

AS.X や HAS.X では使えない機能が含まれているので注意して下さい。また、例を挙げるだけで細かい文法の説明を省略しているところがあります。是非、HAS060.X に添付されているマニュアルなども参照して下さい。

2. 注釈の書き方

プログラムの中に注釈を書くときは、* または ; で書き始めます。どちらを使っても構いませんが、1 つのプログラムの中では統一しておきましょう。

* はオペランドの先頭にも (ロケーションカウンタを参照するときに) 書くことができるので、どちらかというと注釈の開始には ; を使うことをお勧めします。私も ; を使っています。

2.1. 注釈の書き方のパターン

行頭に * または ; を書くと、その右側は行末まで注釈と見なされて無視されます。

;注釈

行頭に空白があっても構いません。

                                ;注釈

ラベルの後ろに空白を入れて書くこともできます。

ラベル:                         ;注釈

命令や疑似命令あるいはマクロの後ろにも注釈を書くことができます。このときは、空白を入れてから、なるべく ; で開始するようにして下さい。

        命令                    ;注釈
ラベル: 命令                    ;注釈

オペランドの後に空白を入れて書くこともできます。オペランドの後に空白があるときはそれ以降は * や ; がなくても無視されますが、注釈は * または ; で書き始めるように心がけて下さい。

        命令    オペランド      ;注釈
ラベル: 命令    オペランド      ;注釈

3. : (コロン) の使い方

一般的に、行頭のラベルの末尾には : を付ける習慣があります。

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 などでは、: を付けないほうが一般的です。

4. ローカルラベルの使い方

多くの細かい分岐やループを記述するのに、いちいちジャンプ先のラベルを考えるのは面倒です。ラベルを幾つも書いているとだんだんハナモゲラになってしまい、どこでどのラベルを参照しているのかがひと目でわからなくなってしまうこともあります。

そこで、HAS.X や HAS060.X では名無しのローカルラベルが使えるようになっています。名無しですが、ハナモゲラのラベルと比べればすぐ近くのジャンプ先がわかりやすく、便利なものです。

ローカルラベルと言っても、スコープ (ラベルを参照できる範囲) に明確な制限があるわけではありません。「同種の名無しローカルラベルが複数定義されているときは、前後どちらかの指定された方向で一番近くにあるものを参照する」という単純な決まりがあるだけです。

名無しのローカルラベルをあまり遠いところから参照すると可読性の悪いソースになってしまうので注意しましょう。

なお、ここで説明するローカルラベルは、マクロ内ローカルラベル (後述) とは関係ありません。

4.1. @@

ローカルラベル @@: は、直前の @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 つ以上離れるときは数字ローカルラベルを使ったほうが見やすいからです。

4.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 のソースでも数字ローカルラベルを大量に使用しているので参考にして下さい。

4.3. ローカルラベルの実装方法

使い方とは関係ありませんが、ローカルラベルの機能の理解を助けるために、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 近く消費しています。

5. ラベルを自動的に外部定義にする

ラベルに付ける : を 2 つ繋げて :: と書くと、そのラベルが自動的に外部定義になります。

ラベルを外部定義にしたいときは .xdef などを使って定義することもできますが、:: を使うと便利です。

        .xdef   foo
foo:

と

foo::

は同じ意味です。

6. 疑似命令の先頭に付ける . (ドット) の意味

アセンブラでは疑似命令を . で始めるという習慣があります。

        .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 などには . を付けないほうが一般的です。

7. 便利な疑似命令 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 の「オペランドなら何でも代入できる」という特徴を活かして、シンボルに文字列を割り当てています。このようにプログラムの先頭でプログラムのバージョンや日付をシンボルに割り当てておくと、後で更新するときにいちいちプログラム中の文字列データを探して書き換えなくて済むので便利です。

8. 繰り返し 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) に変換されることにも注目して下さい。

8.1. .rept~.endm の実装方法

.rept~.endm は名無しマクロの一種です。.rept からマクロの定義が開始され、.endm が出てきたら自動的にそこまでのコードを .rept で指定された回数だけ繰り返して展開しているだけなのです。

.rept の内部で発生したアセンブルエラーの多くが .endm の行で通知されるのは、マクロの内部で発生したアセンブルエラーがほとんどマクロの定義のときではなくてマクロの展開のときに発生するのと同じことです。

9. 繰り返し 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 の中では、マクロの場合と同様に &シンボル の形式で引数を参照できます。シンボルが独立しているときは & は無くても構いません。

10. 繰り返し 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. したものを、そうでなければその文字をそのまま出力する」、つまり、「文字列を大文字化して出力するマクロ」です。

11. マクロ内ローカルラベル

.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 などがあります。

12. オペレーションサイズを解釈するマクロ

命令に近い形のマクロを定義することができるようにするために、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 のマニュアルを参照して下さい。

13. 逆条件 (cc と Ncc)

ここで言う 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 でも大量に使用していますので、参考にしてみて下さい。

14. 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.s の部分で分岐の条件が ls になっているのは、jbhi の hi の逆条件である nhi というのが ls と等価だからです。この命令列で、「hi ならば jmp する」という jbhi の機能を実現しているというわけです。

15. バイナリデータを埋め込む

アセンブラのソースの中にスプライトデータなどのバイナリデータを埋め込みたいときがあります。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
                    :

16. .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 = -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

となって、これで問題解決です。

17. おわりに

思いついたところをいろいろ挙げてみましたが、いかがでしたか?

「こんな使い方があったのか!」という発見がありましたか?


更新履歴

2018 年 10 月 23 日

https://stdkmd.net/hastips/ に引っ越しました。

── 続きを読む ──── 続きを隠す ──

2015 年 4 月 23 日

http://stdkmd.com/hastips/ に引っ越しました。

2000 年 10 月 10 日

HTML を更新

2000 年 9 月 14 日

『HAS060.X を使いこなすテクニック』と改題し、HTML 化して STUDIO KAMADA で公開

2000 年 4 月 6 日

月刊電脳倶楽部 144 号 (2000 年 5 月号) の読み物横町のコーナーに収録