Arduino は 8-bit の世界

Arduino で使われている Atmel 社の AVR マイクロコントローラーは 8-bit RISC アーキテクチャを採用している。僕は8ビット CPU で C 言語を使うのは初めての経験だったので,これだけでもちょっと面白いことだと思った。

AVR 用 C コンパイラーでは, int 型の大きさは16ビットに設定されている。8ビット CPU なんだから int も8ビットなんじゃないの?と思うかもしれないけれど, intポインター型と互換でなくてはいけないから*1,やはり16ビットになる。でも AVR の汎用レジスターの大きさは8ビットだから,結果として「int は扱いの重い型」という具合になる。

例えば次のようなコードがあるとする。

int mulTen(int x) {
  return x * 10;
}

このコードをビルドした後に avr-objdump を使ってディスアセンブリすると,次のようなアセンブリコードになっていることが分かる。

00000000 <_Z6mulTeni>:
   0:	9c 01       	movw	r18, r24
   2:	ac 01       	movw	r20, r24
   4:	93 e0       	ldi	r25, 0x03	; 3
   6:	44 0f       	add	r20, r20
   8:	55 1f       	adc	r21, r21
   a:	9a 95       	dec	r25
   c:	01 f4       	brne	.+0      	; 0xe <_Z6mulTeni+0xe>
   e:	22 0f       	add	r18, r18
  10:	33 1f       	adc	r19, r19
  12:	42 0f       	add	r20, r18
  14:	53 1f       	adc	r21, r19
  16:	ca 01       	movw	r24, r20
  18:	08 95       	ret

ずいぶん複雑なコードだ!何をしているのかというと, x+x を3回繰り返すことによって8倍した後に, x を2度足すことによって,最終的に10倍の値を得ている。 Arduino に使われている ATmega は乗算命令 (mul) を2クロックで実行できるのだから,普通に乗算の組み合わせで実現した方が幾分シンプルにできると思うのだけれど……いずれにせよ,少し手間のかかることをしなければ int の掛け算はできない。

そこで,これを以下のように8ビット型に書き直してみる。

int8_t mulTen(int8_t x) {
  return x * 10;
}

すると,アセンブリコードは以下のようになる。

00000000 <_Z6mulTena>:
   0:	9a e0       	ldi	r25, 0x0A	; 10
   2:	89 9f       	mul	r24, r25
   4:	80 2d       	mov	r24, r0
   6:	11 24       	eor	r1, r1
   8:	08 95       	ret

ずいぶんシンプルなコードになった。 AVR の mul 命令では,乗算の結果が r0r1 に格納されるので,それを ret の前で返値として格納し直している。それだけのコード。

Arduino を使う典型的なケースでは,このような細かい話はほとんど問題にならないだろうと思う。でも,例えば Auduino シンセサイザーなどのように非常にタイトな処理を行う場合には,やはりこの辺りのことに気を配らなければならなくなってくる。

*1:この認識は正しくないようです。詳しくはコメントを参照してください