それでは発表を始めさせていただきます。
今回は初めての方も多そうなので自己紹介させていただきます。 西永俊文といいます。10年近く趣味でArmのボードで遊んだりしています。 最近はCortex-M7マイコンでLinuxを動かしたり、当時JTAGデバッグの方法が公開されてなかったSynQuacerというボードをJTAGデバッグできるようにしたり、Raspberry Piを題材にハードウェアの初期化部分からコードを書いて開発していく講義を行ったりしていました。 その他の活動はgithub、speakerdeck、twitterを参照してください。
本資料は
- 本勉強会を開いてくださったぬるぽへさん
- 本資料のレビューに協力していただいたインターネットの闇さん
- これまでお世話になったみなさま
の協力により作られております。 皆様ありがとうございました。
今回の発表では、Armが何かを知らない人にArmアーキテクチャの名前や命令セットの名前を知ってもらい、今後の学習の役に立てていただくのが目的となります。 本日はこのあと深い発表がいくつもあるはずなので、この発表では基本的に広く浅くを意識して紹介を行う予定です。
それでは本題に入っていきましょう。
Armというと範囲が広いのですが、プロセッサとしてのArmはARM社の作ったRISCアーキテクチャプロセッサになります。
このARM社とはどういった会社かというと、イギリスに本社をもつ半導体IP、わかりやすく例えると半導体の設計図やその利用ライセンス等を設計・販売している会社です。 実はこの会社は2016年にsoftbankに買われたのですが、最近またNvidiaに買われそうというニュースが出てきており、今後が気になる状況になっています。
話をプロセッサとしてのarmに戻しましょう。 Armのプロセッサは省電力や低価格といった特徴があるため、故に昔から組み込み機器に多く使われていました。 今でもほぼすべての携帯電話、携帯ゲーム機、ルーターなどに採用されており、皆様の生活を支えています。 ここ数年は組み込みだけでなく、Surface Xやまだ出ていないMacbookなどのデスクトップ機やサーバー、更にはスーパコンピュータ方面にも拡大してきています。
このArmは前のスライドで紹介した省電力、低価格、低発熱といった特徴のほかに、設計と製造企業が分かれているという特徴があります。
皆様の中には「なぜArmプロセッサはx86と違ってBroadcomやクアルコム、NXPなど複数のメーカーから出てきているのか」疑問に思ったことのある方がいるかも知れません。その答えはこの特徴にあります。
まず、Arm社はプロセッサやペリフェラルの設計を行いますが、チップの製造は行っておらず、その設計や利用するためのライセンス等を売っています。 代わりにチップメーカがArm社からコアのIPやペリフェラルを購入し、自社で設計したペリフェラルなどをくっつけてSystem on Chip、略してSoCと呼ばれるチップを製造して販売しています。 このチップメーカーはBroadcomやNXPなど複数あるため、様々な会社のArmチップが売られています。
実際に使われているSoCの例として初代Raspberry Piに搭載されたBCM2835というチップを見てみましょう。
BCM2835というSoCは、ARMの設計したプロセッサとUARTのペリフェラルに、Broadcomの作ったGPIO、VideoCoreというGPUや割り込みコントローラ、Synopsysの作ったUSBコントローラ等を入れて、Boradcomが製造しています。
さて、ここからはArmアーキテクチャとアセンブリに関して少し詳しくご紹介していきましょう。
コンテンツの一覧はこの様になっています(2秒待つ)
最初にアーキテクチャとプロセッサの違いについてご紹介します。
Armにはx86などと同様に、アーキテクチャ名とプロセッサ名があります。 まずはアーキテクチャについて見ていきましょう
アーキテクチャは、MMUの仕様や命令セット仕様など、プロセッサの基本設計を決めるものとなっています。 そのため例えば、同じアーキテクチャのプロセッサであれば同じ命令セットが利用できるというようになっています。
アーキテクチャ名はARMvのあとに数字が入るようになっています。 最新世代はARMv8となっているのですが、最近は刻んでv8.1などの小数点部分でのアップデートがあったりもします。
アーキテクチャ名の後ろには、A, R, Mの1文字が入るようになっています。
この、アーキテクチャの後ろにつくA, R, Mはプロセッサファミリを表しています。 Aはスマートフォンやサーバー等、高性能なプロセッサファミリとなっており、MMUやTrueZoneなど多くの機能を備えています。 Rはリアルタイムシステム向けのファミリとなっているようですが、私自身はRのチップを見たことも触ったこともないので深くはご紹介できません。アーキテクチャマニュアルによると、MMUの代わりにMPUが搭載されているなどの差異があるようです。 Mはワンチップマイコン向けのファミリとなっており、使える命令セットがThumbまたはThumb-2命令のみなど、様々な制限があります。
次にプロセッサについて紹介しましょう。 プロセッサとはアーキテクチャの仕様をもとに作られるものです。 現行はCortexシリーズとなっており、A, R, Mのあとに数字がつくという表記になっています。 big.LITTLEがあるため例外はありますが、基本的には数字が大きいほうが世代が新しく性能がよく、新しい命令が使えたりするようになっています。
ここで小ネタになりますが、big.LITTLEというARMの省電力化のためのソリューションがあります。 これは高性能だけど電池を食うbigコアと低性能だけど省電力なLITTLEコアをセットにして搭載し、負荷に応じて使うコアを切り替えて消費電力を抑えるものです。 セットの例としてはCortex-A15のbigコアとA7のLITTLEコア、A73のbigコアとA53のLITTLEなどがあります。
話を戻して、アーキテクチャとプロセッサの差がわかったところで、ARMアーキテクチャの大体の歴史を眺めてみましょう。 なお、歴史の開始は私が生まれて物心がついた頃からとなっています。
まず私が初めて触ったARMデバイスはGBAかとおもいます。この頃はARMv3からv4アーキテクチャのARM7と呼ばれるプロセッサが使われていました。 次のARMv5ではARM9やARM10プロセッサの搭載されたゲーム機や、Intelの作ったXScaleというARMプロセッサなどが搭載されたPDAというスマートフォンの前身のようなものが世に出ていました。 iPhoneや初代Raspberry PiではARMv6アーキテクチャのARM11というプロセッサが使われていました。 このあとのARMv7アーキテクチャからCortexシリーズが始まり多くのスマートフォンやゲーム機、そしてサーバーやスパコンなどで使われるようになってきています。
次にARMの実行モードについて説明していきましょう。
ARMv7世代までは32bitモードしかなかったのですが、ARMv8で64bit実行のAArch64モードが増えました。 そしてこれまでの32bitモードはAArch32モードという名前がついて切り替えながら利用できるようになりました。
この各モードの呼ばれ方はOSや資料、機能や歴史的経緯などによっていくつか種類があります。 32bitモードはOABIという今ではサポートされていないABIを使ったarmという呼び方、EABIという新しいABIの使われているarmel、ハードウェア浮動小数点演算器を搭載したarmhf、単に32bitモードを指すA32があります。 64bitモードはlinuxディストリビューションでよく使われるarm64という呼び方と、クロスコンパイラやARMの資料でよく使われるA64の2種類があります。
実際のdebianのダウンロードリンクページがこちらになります。 arm64とソフトフロートのarmel、ハードフロートのarmhfが並んでいます。
ARMアセンブリを紹介するためにはレジスタの説明が必要なので、ここでご紹介します。 A32とA64でレジスタは別物になっているので、分けてご紹介します。
まずA32モードのレジスタを紹介します。 A32モードでは主に16個のレジスタを扱います。 汎用レジスタは32bitのものが13本あり、その他にスタックポインタ、リンクレジスタ、プログラムカウンタの3つのレジスタがあります。 リンクレジスタはARMの特徴的なレジスタで、関数ジャンプ後に戻ってくるためのアドレスを格納するためのレジスタになります。 branch with link命令を使ってジャンプを行うことで深さ1までであればこのレジスタのアドレスを参照して戻る事ができます。
次にA64レジスタを紹介します。 A32レジスタとの違いとしては、ほとんどのレジスタが64bit化されています。 汎用レジスタの数も増えて13本から31本まで増えました。 加えて、0を読み出せるゼロレジスタが増えたので、0を作るために即値のロード等を行う必要がなくなり少し便利になりました。 その他はSPやPCがシステムレジスタになって気軽にアクセスできなくなったこと以外はほぼ同じなので説明を省略します。
さて、ここからは軽くアセンブリの話をしていきたいと思います。 現在ARMアセンブリの命令セットはA32、A64、Thumbの大きく分けて3種類があります。 順番に紹介していきましょう。
A32アセンブリは32bit固定長命令セットです。 あまり他にみない面白い機能としては、条件付き命令実行とバレルシフタがあります。
条件付き命令実行は、任意の命令を条件つき実行にできる機能です。 例えばAND命令のあとにEQという条件をつけると、直前の演算結果が0のときにAND命令を実行できます。 何もつけない場合はコンパイラが無条件実行のALをつけてくれるようになっています。
オペコード的には頭4bitにこの条件付き実行のフィールドがあり、ほとんどの命令は無条件実行で0x0eが頭に来るので、ARMのバイナリをバイナリエディタで開くと0x0eが一定間隔で並ぶ様子が観測できます。
(次ページ)
実際にコードを書いてアセンブルし、逆アセンブル結果を見てみた結果がこちらになります。 andeqがちゃんとアセンブル通っていたり、andalとandが同じオペコードになっている様子が確認できます。
バレルシフタは命令内でレジスタの値をシフトできる機能になります。
例えばレジスタの値を1bitシフトしてから別のレジスタに入れたり、A32命令では16bitを超える即値はメモリからのロードする必要があるのですが、8bitをシフトして得られる値なら16bitを超える即値をmovでレジスタに入れたりできます。
メリットとしては命令数が削減できたり、でかい値をロードできたりがあるのですが、回路が複雑になるためかA64命令ではなくなってしまいました。(← この内容は誤りです。バレルシフタはA64でも利用可能です)
(次ページ)
実際のコードを見てみましょう。
通常8行目のように16bitを超える即値をロードするコードは相対アドレス指定でメモリからロードされるようになっています。 しかし、6行目と7行目のように、8bit以内の値からシフトして得られる即値はバレルシフタの機能のおかげで1命令で読み込めています。 メモリからのロードは時間がかかるので、コードサイズの削減だけでなく実行時間の節約も可能です。
ここからはA64アセンブリを紹介します。
A64アセンブリもA32と同様32bit固定長命令セットです。 今の所A32のような特徴的な機能があまりなく、素直で親切で書きやすい命令セットとなっています。
個人的な感想としては、レジスタ数がほぼ倍に増えたのに命令長を32bitに抑え、しかも一部命令が開発者にわかりやすい様になっている点が素晴らしいと思っています。
実際に階乗を計算するC言語のコードをA32とA64向けにコンパイルして逆アセンブリした結果を見比べてみましょう。
(次ページ)
左がA32のコード、右がA64のコードになります。
差分としてはA64ではレジスタを一斉にスタックに積むldm,stm命令がなくなって、stp/ldp命令で2個ずつしかできなくなったことや、bx命令で関数から戻っていたところがret命令でできるようになってちょっと開発者にわかりやすくなっている点が見受けられます。
最後に紹介するのはThumbアセンブリです。 これらは基本的に命令長を縮めて省メモリ化や高効率化を狙った命令となります。
Thumb命令には初代ThumbとThumb-2の2つバージョンがあります。
初代Thumb命令はARM7プロセッサのあたりで使えるようになった、ほぼ16bit固定長の命令セットになります。 現在では最もコンパクトなマイコン向けプロセッサのCortex-M0, M0+で利用されています。 汎用レジスタの数はA32と変わらないのですが、命令長の制限で殆どの命令がR0からR7までの8つしか扱えないなど、様々な制限があり書くのが大変な命令です。
Thumb2は命令長が16bit、32bit混合となったかわりに、使える命令が増えたりレジスタの制限がなくなるなど大変使い勝手が向上した命令セットです。 Cortex-M3以上のプロセッサで利用されています。また、Cortex-A世代でも利用が可能です。
(次ページ)
実際にThumbとThumb2で階乗のC言語コードをビルドした例がこちらになります。
左がThumb、右がThumb2のコードです。
今回はコードが短いので差がわかりにくいのですが、左のThumbのコードが16bit固定長になってることと、右のThumb2が混合命令長になってる事がわかるかなと思います。 あとは地味にThumb-2ではcbnzという0と比較する命令が使えているなどの差が見られます。