denseなオペコード『を』考察する

この記事は悠里・大宇宙界隈 Advent Calendar 2018の14日目の記事です。

1. 発端

opcode.html、opcodeについてあまり考えていないという話が知られていてだな。

炭酸ソーダさんの案はあって、あれはコード長を多少冗長にする代わりにかなり回路の実装が単純されそうである。

ということで、その逆の「2003lk互換であって、かつかなりdenseな機械語」を考えてみようと思った。

2. 統計

実際のコードで2003lkの諸々の頻度がどんな感じなのか気になるし、統計を取っていく。

まずは頂いたc-compiler.sからコメントを取り除いてc-compiler__.sを作る。

2-1. ソート

まずはソートして(c-compiler-sorted.s)、あとラベルを吹っ飛ばそう。あ、'c'iで行きます

2-2. lifem

まずはlifemを手動で数える。

とりあえずlifem ラベル名が628個、lifem 4bitの数が69個、lifem 4294967295が33個、lifem 673775616~2037671208が114個。lifem8 0は392個、それ以外のlifem8が11121個(多いな)。

2-3. xokとkue

こいつらは命令ではない。xokが22個。kueが141個。

2-4. ラベル絡み

malkrz xx ラベル名が496個。krz xx ラベル名が243個。inj f5@ xx ラベル名が1421個。

krz f5+4@ ラベル名が270個。krz f5+8@ ラベル名が107個。krz f5+12@ ラベル名が28個。krz f5+16@ ラベル名が3個。krz f5+24@ ラベル名が47個。

krz f0/f1 ラベル名が12個。krz f0/f1/f2 ラベル名@が79個。

2-5. 残り

残りはc-compiler-sorted-nolabel.s。f0/f1/f2/f3を全部regと改名したものがreg_unified.sである。

とりあえず uniq -cする。上の方はこんな感じ

 788 krz f5+4@ REG
 660 krz f5+8@ REG
 573 krz REG f5
 457 krz f5+12@ REG
 434 nta f5 12
 428 nta f5 16
 425 ata f5 16
 410 ata f5 12
 400 nta f5 8
 370 krz xx f5@
 365 ata f5 8
 159 krz REG f5+16@
 134 krz REG f5+20@
 128 krz REG f5+24@
 120 nta f5 20
 119 ata f5 20
 115 krz f5+16@ REG
 100 krz REG@ REG
  92 ata f5 32
  91 fi REG 0 clo
  90 krz f5+28@ REG

スタック関連を数えてみるか。

まとめると

  1. f5に足されるオフセットは4の倍数のみ(まあそれはそう)(スタック上のchar配列に書き込むときのために一応サポートだけはしておくべきだが)
  2. f5@ ~ f5+12@ [2bit] で 54.21%
  3. f5@ ~ f5+28@ [3bit] で 73.83%
  4. f5@ ~ f5+60@ [4bit] で 84.78%
  5. f5@ ~ f5+124@ [5bit] で 87.34%
  6. f5@ ~ f5+252@ [6bit] で 91.39%
  7. f5@ ~ f5+508@ [7bit] で 93.98%
  8. f5@ ~ f5+1020@ [8bit] で 95.63%
  9. f5

という感じである。さてこの状況で短縮表現は何ビットにするかという話がある。残りを32ビットで表すとすると僅差で6bit(期待値8.23bit) < 4bit(期待値8.26bit) < 5bit(期待値8.41bit) だが、そもそもメモリ空間が32bitである。となると符号付き16bitにしておけばそうそう困ることはない(とはいえローカル変数のバイト数が30000行くことはありそうよな、まあそのときは32bit作っておけばいいか)ことを考えると4bit(5.83) < 5bit(6.39) < 3bit(6.40) なので、まあ4bitでいいか。

ということでf5@ ~ f5+60@をSTACK_NEAR、それ以外をSTACK_FARと改名してunified.sとした。上位はこんな感じ。

2805 krz STACK_NEAR REG
1423 krz REG STACK_NEAR
 664 krz REG STACK_FAR
 588 krz REG f5
 434 nta f5 12
 428 nta f5 16
 425 ata f5 16
 410 ata f5 12
 400 nta f5 8
 370 krz xx STACK_NEAR
 365 ata f5 8
 277 krz STACK_NEAR 0
 241 krz REG REG
 208 krz REG 0
 148 krz STACK_NEAR 1
 131 krz REG@ REG
 120 nta f5 20
 119 ata f5 20
 107 fi REG 0 clo
  99 krz REG REG@

f5に足される値も調べてみるか。

調べてみたら、ata f5は4 ~ 32で92.90%、nta f5は4 ~ 32で96.57%だそうだ。ふむふむ。

あ、krz xx STACK_NEARは全部krz xx f5@です。まあ特殊扱いすることになるでしょうな

2-6. いままでのあらすじ

xx周り

1421 inj f5@ xx ラベル名
 496 malkrz xx ラベル名
 370 krz xx f5@
 243 krz xx ラベル名

xxはこれだけ。

残るラベル名

 455 krz STACK_NEAR ラベル名
  79 krz REG ラベル名@
  12 krz REG ラベル名

一般的な命令(トップ目)

2805 krz STACK_NEAR REG
1520 nta f5 4~32
1504 ata f5 4~32
1423 krz REG STACK_NEAR
 664 krz REG STACK_FAR
 588 krz REG f5
 370 krz xx STACK_NEAR

2-7. 残りをね

krz STACK_NEAR 即値

即値については…もう単純に全部の即値を一貫して扱うか。オフセットは一応分けよう。

頑張った結果(imm.s)、期待値はこうなった。

0	23.88905282
1	19.66037179
2	18.60666863
3	17.59840661
4	17.21923871
5	15.34110357
6	14.14753615
7	12.65801121
8	11.7816465
9	11.34139864
10	11.33726763
11	11.74358218
12	12.5665388
13	13.39805252

やっぱり符号なし8bitになりそうですな。負の数は32bit使うことになりそう。x86でそうなってるのはそうなってるからなんだなぁ。

今度はオフセット。REG+REG@は50件。それ以外のREG+値@は

0	21.324861
1	21.09213662
2	21.34868944
3	18.01826847
4	12.91818904
5	9.653693407
6	9.65528197
7	8.588562351
8	9.39158062
9	10.22398729
10	10.27958697
11	11.21683876

ということで符号なし7bitか8bit。じゃあ符号なし8bitでええやん。

なるほどなぁ。