教科書のSection 2.9に出てくる32bit即値の話。RISCプロセッサというのは、命令長が固定な為、命令長が32bitであれば、32bitの即値をそのまま入れることはできません。
クロスアセンブラの環境ができたので、早速各CPUの比較をしてみましょう。各コマンドは本当であれば、ターゲットCPUの指定や、エンディアンの設定等があるのですが、本質的ではないのでデフォルトの状態で試してみました。
int f(void) { register int a; a = 0x12345678; return 0; }
元のなるのはこのようなソースで、レジスタに32bitの即値を入れてみます。gccによって出されたソースを見ながら、必要なところだけピックアップして比べてみました。Cソースから、アセンブラの出力の仕方は、xxx-gcc -S -c foo.cです。
mipsの場合
main: li $a0, 0x12340000 # 0x12340000 ori $a0, $a0 ,0x5678
MIPSは教科書に載っているとおり、上位16bitを設定し、下位16bitを論理演算によって設定しています。上の2行を、mips_imm.asmという名前で保存して
YUKI.N>sde-as -o mips_imm.o mips_imm.asm
とすると、オブジェクトファイルmips_imm.oが作られます。
YUKI.N>sde-objdump -D mips_imm.o
とすることで、オブジェクトファイルの逆アセができます。
mips_imm.o: file format elf32-tradbigmips Disassembly of section .text: 00000000 <main>: 0: 3c041234 lui a0,0x1234 4: 34845678 ori a0,a0,0x5678
各命令の下位16ビットに定数(1234と5678)が入っているのがわかります。
sparcの場合
sethi %hi(305419264), %g1 or %g1, 632, %g1
sparcはビット幅が違うだけでMIPSに近いです。sethi命令で、上位22bitに値を設定し、続くor命令で下位ビットを立てています。10進の305419264が16進で0x12345400で、10進の632が16進の0x278です。この2つのorを取ると、16進で0x12345678になります。
YUKI.N>sparc-elf-as -o sparc_imm.o sparc_imm.asm
YUKI.N>sparc-elf-objdump -D sparc_imm.o
sparc_imm.o: file format elf32-sparc Disassembly of section .text: 00000000 <.text>: 0: 03 04 8d 15 sethi %hi(0x12345400), %g1 4: 82 10 62 78 or %g1, 0x278, %g1 ! 0x12345678
最初の命令の48d15は、123454を下位2bitを切った部分(右に2bitシフト)した値です。
shの場合
mov.l .L2,r1 .L2: .align 2 .long 305419896
SHも同じように逆アセしてみると、表示が若干違うことに気がつきます。
00000000 <.f>: 0: d1 00 mov.l 4 <.f+0x4>,r1 ! 0x12345678 2: 00 09 nop 4: 12 34 mov.l r3,@(16,r2) 6: 56 78 mov.l @(32,r7),r6
SHは32bitプロセッサなのですが、命令は16bit固定になっています。そのために、32bitの即値を入れるには、メモリに入っている32bitの値をプログラムがあるテキスト領域に格納し、相対番地でアクセスしています。他のRISCプロセッサが32bitの即値を入れるのに32bit×2命令 = 8バイトの領域を取っていますが、SHの場合nopの所にも命令が入れられるので、48bit = 6バイトですんでいます。組み込みで使われることを意識した命令セットになっています。
armの場合
LDR r1, =0x12345678
アセンブラ自体はこの疑似命令で即値の代入ができます。
YUKI.N>arm-elf-as -o arm_imm.o arm_imm.s
YUKI.N>arm-elf-objdump -D arm_imm.o
同じように逆アセして確認してみましょう。
Disassembly of section .text: 00000000 <.text>: 0: e51f1004 ldr r1, [pc, #-4] ; 4 <.text+0x4> 4: 12345678 eornes r5, r4, #125829120 ; 0x7800000
SHと同じパターンですね。SHと違って、ldr(mov)命令自体が32bitなので、合計8バイト必要になります。
ARM固有の変な命令として、バレルシフタがあります。
LDR r1, =0x80000001
このような即値の場合は、なんと1命令でやってしまいます。
Disassembly of section .text: 00000000 <.text>: 0: e3a01106 mov r1, #-2147483647 ; 0x80000001
ARMの場合、変な所にバレルシフタが入っており、上手くやると1命令で32bitの上位と下位に合わせ技で値をセットできます。下位12bitの106がポイントで、06(0000_0110)が即値、1がローテートの量(2bit)を示しています。0000_0110が2bitローテートされた結果、32bitの1000_0000 0000_0000 0000_0000 0000_0000 0000_0000 0000_0000 0000_0000 0000_0001になり、見事0x8000001がレジスタにロードされます。上手くこのパターンに入ると、32bit=4バイトで32bitの即値のロードができます。
x86の場合
movl 0x12345678, %eax
見ての通り一番シンプルな結果になります。
00000000 <.text>: 0: a1 78 56 34 12 mov 0x12345678,%eax 5: 90 nop
mov命令1バイトと、ロードする値32bitが並ぶ形で、合計5バイトです。
最後に
今回の例は、gccが出したコードをそのまま使ってみました。単純な32bitのロードでも、設計者の思いが見えてくるようで面白いです。