ぱたへね

はてなダイアリーはrustの色分けができないのでこっちに来た

2章 2.9 32bit即値のロード

教科書の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のロードでも、設計者の思いが見えてくるようで面白いです。