chibicc compiler を6800向けに改造する (13) strcpy その2
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



ディスカッション
コメント一覧
まだ、コメントがありません