chibicc compiler を6800向けに改造する (13) strcpy その2

BASICMASTER, 昔のパソコン

chibicc compiler を6800向けに改造する (4) strcpy | ず@沖縄 の続きです。

前回も書いたように、MC6800の泣きどころはインデックスレジスタが1つしかないことと、スタック操作が苦手なこと。例としてstrcpyを使います。


unsigned char   *strcpy(unsigned char *dst, unsigned char *src)
{
        unsigned char   *p=dst;

        while (*dst++ = *src++){
        }

        return p;
}

アセンブラでゴリゴリ書いてみる

src/dstをスタック上に置くと、IXレジスタとの連携がうまくいかなくて長いコードになります。

src++ や dst++ は、ldx/inx/stx にしたいのですが、スタックへの格納のためにIXを使う必要があり、衝突してしまいます。

2番目のインデックスレジスタ(IY)や、スタック相対アクセスができれば良いのですが、そんな命令は6800には無いわけで。6803や6809が羨ましいです。

仕方がないので、インクリメントにはinc命令を使っています。

(なお、chibicc/fccに合わせた関数プロローグとエピローグも必要ですが、省略しています)

ちなみに chibicc-6800のlibcに含まれるstrcpyは、ループアンローリングしているので、このコードよりも2倍速いです。


_strcpy:
	(関数プロローグ)
        tsx
        ldab    3,x     ; dst
        ldaa    2,x     
        pshb
        psha            ; p: 0,s
loop:   tsx
        ldx     6,x     ; src
        ldab    0,x
        tsx
        inc     7,x
        bne     L1
        inc     6,x
L1:     tsx
        ldx     4,x     ; dest
        stab    0,x
	tsx
        inc     5,x
        bne     L2
        inc     4,x
L2:     tstb
        bne     loop
L3:     pulb
        pula
	(関数エピローグ)
        rts

6809ならループ部分はこうなります。このコードをコンパイラが出すのは大変ではありますが…


	ldx	6,x	; src
	ldy	4,x	; dst
loop:	ldb	0,x++
	stb	0,y++
	bne	loop


現在(2025/11/07)のchibiccでコンパイルしてみる

先にポインタをインクリメントしておいて、後から辻褄合わせのdexを行なっているので、dexの分だけ長く/遅くなっています。

IXに読み出してからインクリメントしたいのですが、スタックのアクセスにもIXを使うのでこの順番になっています。

手作業で描いたものよりも(少しだけ)長いけど、悪くないと思います。


_my_strcpy:
        jsr __prologue_2
        des
        des
        tsx
        stx @bp
        ldab 3,x
        ldaa 2,x
        stab 1,x
        staa 0,x
L_begin_59:
        tsx
        inc 8+1,x	; src++ を実行しておいて
        bne L_61
        inc 8,x
L_61:
        ldx 8,x		; src-1 を IXに入れる
        dex
        ldab 0,x	; AccB = (IX)
        tsx
        inc 2+1,x	; dst++ を実行しておいて
        bne L_62
        inc 2,x
L_62:
        ldx 2,x		; dst-1 を IXに入れる
        dex
        stab 0,x	; (IX) = AccB
        jeq __L_0	; 0 なら loop終了
__L_1:
        jmp L_begin_59
__L_0:
        tsx
        ldab 1,x
        ldaa 0,x
L_return_58:
        lds @bp
        jmp __ins3_popx_stxbp


ソースを少し変えてみる(++を後回しに)

++操作を後回しにすれば、dex は必要なくなります。


my_strcpy2(unsigned char *dst, unsigned char *src)
{
  unsigned char *r = dst;

  while (*dst = *src) {
    dst++;
    src++;
  }
  return r;
}

dex が消えました。厳密には ループ終了後の dst,srcが異なりますが、この関数ではループ終了後はreturnするだけなので問題ありません。


_my_strcpy2:
        jsr __prologue_2
        des
        des
        tsx 
        stx @bp
        ldab 3,x
        ldaa 2,x
        stab 1,x
        staa 0,x
L_begin_54:
        tsx
        ldx 8,x
        ldab 0,x
        tsx
        ldx 2,x
        stab 0,x
        jeq __L_2
        tsx 
        inc 3,x
        bne L_56
        inc 2,x
L_56:
        inc 9,x
        bne L_57
        inc 8,x
L_57:   
__L_3:  
        jmp L_begin_54
__L_2:  
        tsx 
        ldab 1,x
        ldaa 0,x
L_return_53: 
        lds @bp
        jmp __ins3_popx_stxbp


ソースを少し変えてみる(スタック変数を使わないように)

諸悪の根源は、ループで使っている変数がスタック上にあることです。

関数の最初でstaticな領域にコピーして、それを使うように変更。再起呼び出しやリエントラントな関数では使えませんが、この場合は大丈夫。


my_strcpy5(unsigned char *dst, unsigned char *src)
{
  static unsigned char *sp;
  static unsigned char *dp;

  sp = src;
  dp = dst;

  while (*dp = *sp) {
    dp++;
    sp++;
  }
  return src;
}

__L_10 や __L_11 がstaticな変数の領域です。レジスタ追跡ができれば、2番目のldx __L_11 は削除できますが、そこまでは最適化できていません。

うまくオプティマイズして、最初のCコードから、これぐらいのオブジェクトコードが出せるといいのですが、現在の方法(ASTベース)では難しそうです。


_my_strcpy5:
        jsr __prologue_2
        tsx
        stx @bp
        ldx 6,x
        stx __L_10
        tsx
        ldx 0,x
        stx __L_11
L_begin_45:
        ldx __L_10
        ldab 0,x
        ldx __L_11
        stab 0,x
        jeq __L_12
        ldx __L_11
        inx
        stx __L_11
        ldx __L_10
        inx
        stx __L_10
__L_13: 
        jmp L_begin_45
__L_12:
        tsx
        ldab 7,x
        ldaa 6,x
L_return_44:
       lds @bp
        jmp __ins1_popx_stxbp