|
|
|
|
行番号 |
4バイト |
10 |
00 00 00 0A |
長さ |
1バイト |
15 |
0F |
文字列 |
可変長 |
PRINT "ABC" |
50 52 49 4E 54 20 22 41 42 43 22 |
行末の'\0' |
1バイト |
'\0' |
00 |
境界合わせの詰め物 |
可変長 |
'\0' '\0' '\0' |
00 00 00 |
MOV.L @R3+,R2 ; 行番号取り込み MOV.B @R3+,R0 ; 長さバイト取り込み EXTU.B R0,R0 ; 符号無し化 ADD R0,R3 ; この結果、R3は次の行の先頭を指す ; (このコード例はパイプラインストールをまったく考慮していません) |
10 FOR I=0 TO 25 20 FOR J=0 TO 15 30 D=PEEK(I*16+J-12288) 40 H=(D/16) AND 15:L=D AND 15 50 IF(H>9) H=H+7 60 IF(L>9) L=L+7 70 PRINT CHR(H+48),CHR(L+48)," ", 80 NEXT 90 PRINT " ", 100 FOR J=0 TO 15 110 D=PEEK(I*16+J-12288) 120 IF((D<32) OR (D>126)) D=46 130 PRINT CHR(D), 140 NEXT 150 PRINT 160 NEXT 170 END |
RUN 00 00 00 0A 0F 46 4F 52 20 49 3D 30 20 54 4F 20 .....FOR I=0 TO 32 35 00 00 00 00 00 14 13 20 20 46 4F 52 20 4A 25....... FOR J 3D 30 20 54 4F 20 31 35 00 00 00 00 00 00 00 1E =0 TO 15........ 1B 20 20 20 20 44 3D 50 45 45 4B 28 49 2A 31 36 . D=PEEK(I*16 2B 4A 2D 31 32 32 38 38 29 00 00 00 00 00 00 28 +J-12288)......( : (中略) : 52 49 4E 54 00 00 00 00 00 00 00 A0 07 4E 45 58 RINT.........NEX 54 00 00 00 00 00 00 AA 07 45 4E 44 00 00 00 00 T........END.... 00 00 00 00 03 00 00 00 01 00 03 0A 5E 9E 57 FF ............^.W. OK |
XLIST: BSR SKIPSP ; LISTの後の空白をスキップ MOV EXEP,R3 ; 実行ポインタをR3にコピー(遅延スロット) MOV R3,EXEP ; 空白スキップ後のR3を実行ポインタに書き戻す BSR GETRNG ; 行範囲獲得ルーチン NOP ; しまった! 今気づいたけど、MOV R3,EXEPをここに入れればよかった・・・ ; 言い訳がましいようですが、CMBではここは NOP を入れざるをえないプログラム構造になっていました MOV.L @(LRANGT-WRKTOP,GBR),R0 ; 獲得した行範囲の最初の行番号 BSR SCNHSL ; R2と等しいか大きい行番号を探す、結果の番地はR3に MOV R0,R2 ; 最初の行番号をR2にセット(遅延スロット) |
LISTLP: MOV.L @R3,R2 ; 行番号を獲得 MOV.L R3,@-R15 ; 行先頭番地を保存 MOV.L @(LRANGE-WRKTOP,GBR),R0 ; 行範囲の最後の行番号を獲得 TST R2,R2 ; 今見ている行の行番号は0か? BT LISTE ; 0ならプログラムの最後なので終了 CMP/HI R0,R2 ; 行番号が行範囲の最後の行番号より大きいか? BT LISTE ; 大きければその行は表示せずに終了 |
パイプラインとは別の話ですが、「いかに遅延スロットに有効な命令を入れるか」もまたSHプログラミングの醍醐味です。SHのアセンブラプログラムをイヤがる人が多いのはパイプラインやら遅延スロットやらが煩わしいからだと思いますが、趣味でやるぶんにはパズルを解くような楽しさがあり、やめられません。さんざん悩んだ末ツボにはまったコードを書けたときの爽快感は格別です。 |
BSR LSTLIN ; 1行表示 NOP BSR CHKBRK ; CTRL-Cチェック NOP MOV.L @R15+,R3 ; 保存したR3を復元 MOV.B @(4,R3),R0 ; 次の行までのオフセット ADD #5,R3 ; 行番号と長さをスキップ EXTU.B R0,R0 ; 符号無し化 BRA LISTLP ; ループ先頭に戻る ADD R0,R3 ; 次の行の先頭を計算(遅延スロット) |
LISTE: BRA TOPPRM NOP |
LSTLIN: STS.L PR,@-R15 ; PRを保存 BSR UDOTR ; R2の値を十進符号無し右詰め表示 MOV.L @R3+,R2 ; R2に行番号獲得(遅延スロット) BSR SPACE ; 空白1文字表示 ADD #1,R3 ; 長さバイトをスキップ(遅延スロット) BSR PUTSTR ; R2が指す文字列表示 MOV R3,R2 ; 行の文字列部分の先頭→R2(遅延スロット) BRA CRLF ; 改行 LDS.L @R15+,PR ; PRを復帰(遅延スロット) |
; メインルーチン MAIN0: BSR SUB_A ; Aを呼び出す MAIN1: (なんたら) ; メイン処理の続き : ; サブルーチンA SUB_A: (かんたら) ; A本来の処理 BSR SUB_B ; Bを呼び出す SUB_A1: RTS : ; サブルーチンB SUB_B: (うんたら) ; B本来の処理 RTS |
; メインルーチン MAIN0: BSR SUB_A ; Aを呼び出す MAIN1: (なんたら) ; メイン処理の続き : ; サブルーチンA SUB_A: (かんたら) ; A本来の処理 BRA SUB_B ; Bに飛ぶ : ; サブルーチンB SUB_B: (うんたら) ; B本来の処理 RTS |
; メインルーチン MAIN0: BSR SUB_A ; Aを呼び出す NOP ; 遅延スロット MAIN1: (なんたら) ; メイン処理の続き : ; サブルーチンA SUB_A: STS.L PR,@-R15 ; PRを保存 (かんたら) ; A本来の処理(B以外のルーチンを呼び出す BSR もある) BSR SUB_B ; Bを呼び出す NOP ; 遅延スロット SUB_A1: LDS.L @R15+,PR ; PRを復帰 RTS NOP ; 遅延スロット : ; サブルーチンB SUB_B: (うんたら) ; B本来の処理 RTS NOP ; 遅延スロット |
こういうアーキテクチャはアセンブラプログラマの立場からすると面倒くさくて困るのですが、スピードの点からは有利なのです。サブルーチンBのような末端ルーチンをループ中から呼びだす場合、H8のような戻り番地をスタックに積むCPUだとループを回るごとに毎回戻り番地のスタックへの保存と復帰が行われますが、SHのようにレジスタに保存するCPUだと戻り番地保存のためのスタックアクセスは1回も行われません。
BSR、RTS 自体は遅延スロットより先に実行されます。つまりPRの保存や復帰を遅延スロットに書いても手遅れなのです。 |
; メインルーチン MAIN0: BSR SUB_A ; Aを呼び出す NOP ; 遅延スロット MAIN1: (なんたら) ; メイン処理の続き : ; サブルーチンA SUB_A: STS.L PR,@-R15 ; PRを保存 (かんたら) ; A本来の処理(B以外のルーチンを呼び出す BSR もある) BRA SUB_B ; Bに飛ぶ LDS.L @R15+,PR ; PRを復帰(遅延スロット) : ; サブルーチンB SUB_B: (うんたら) ; B本来の処理 RTS NOP ; 遅延スロット |