今回はイミディエイト・オフセットで同じアドレスを指すのに2通りの命令がある話。一つはスケールド12ビット、もう一つはスケール無9ビット。12ビットあればいいじゃん、と思わないこともないのですが、心の広いArm様です。アン・アラインドなアクセスはお勧めしないけれど一律禁止ではありません。よってスケールしない命令も必要と。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
※実機確認には以下を使用しております。
-
- Raspberry Pi 4 model B、Cortex-A72コア(ARMv8-A)
- Raspberry Pi OS (64bit) bullseye
- gcc (Debian 10.2.1-6) 10.2.1 20210110
ARMv8もいろいろレベルがあり、Arm Cortex-A72はARMv8の中でもベーシックな(命令数の少ない)ARMv8p0です。
今回実験する命令
今回実験する命令のうち、ldr 命令は既にやってみたことがあるものです。イミディエイト・オフセットをとる場合、そのビット幅は12ビット、符合付です。そして処理するデータ幅に応じてオフセットの値をスケールすることも可能です。
新たに実験してみるのは ldur 命令です。ldr命令との違いの u は、unscaled offsetを表すようです。知らんけど。9ビット符合付のイミディエイト・オフセットをとるのは ldr 命令における pre/postインデックスアドレシングに似てますが、データ幅に応じてオフセットの値をスケールすることはない命令です。
ある範囲のメモリに関しては、上記2つの方法でまったく同じアドレスを指し示すことが可能です。
今回は、ことさらに ldr と ldur の命令エンコードの違いを実感するために、2命令のみ実験してます。
実験に使ったアセンブリ言語ソース
例によって手抜き(関数プロローグもエピローグもない)1命令1関数スタイルです。これまた手抜きでWレジスタをデスティネーションとする32ビット幅の場合のみです。
ldr と ldur で「アセンブラ的には」同じオフセットに見える「ベース+オフセット」アドレシングを使って、同一メモリへアクセスしてます。ここでポイントは、
-
- アセンブラ的にはオフセットは(スケーリングを気にせず)バイト値で書く
- ldr命令の場合、ソース上のオフセット指定にスケーリングを加味して機械語命令にエンコードされる
- ldur命令の場合、ソース上のオフセット指定そのままで機械語の命令にエンコードされる
ソースが以下に。
.globl ldrWSR, ldrWUR .text .balign 4 ldrWSR: ldr w0, [x1, #4] ret ldrWUR: ldur w0, [x1, #4] ret
実験に使用したC言語ソース
上記のアセンブリ言語関数を呼び出すテスト用のCソースが以下に。
#include <stdio.h> #include <stdint.h> uint32_t tgtW[4]; extern uint32_t ldrWSR(uint32_t, uint32_t *); extern uint32_t ldrWUR(uint32_t, uint32_t *); void initTGT() { tgtW[0] = 0x00234567; tgtW[1] = 0x11234567; tgtW[2] = 0x22234567; tgtW[3] = 0x33234567; } int main(void) { uint32_t uresult; initTGT(); uresult =ldrWSR(0, tgtW); printf ("ldrWSR : 0x%08x\n", uresult); uresult =ldrWUR(0, tgtW); printf ("ldrWUR : 0x%08x\n", uresult); return 0; }
ビルドして、逆アセンブル、そして実行
ビルドは以下で。
上記で生成したバイナリの該当アセンブリ言語記述関数部分を逆アセンブルしたものが以下に。
ldrとldurがどのようにエンコードされているのかは上記で分かりますが、どこがイミディエイト・オフセット部分やらイマイチわかりにくいです。
そこで図にしてみたものが以下に。上がLDR命令、下がLDUR命令です。どちらもoffsetは #4 という記述ですが、スケーリング(sized)の有無でエンコードされた表現が異なることがわかります。
別に同じメモリを読んでいるだけなので、同じ値だわな。まあ、命令のエンコードとか「コマケー話」に関心なければドーデモいい話なんだけれども。