今回はロード命令の続きと、ストア命令ADR命令レジスタペア転送命令MOV命令の解説です。

ロード命令 (2) ()

前回はロード命令の アドレッシングとしてイミディエートオフセットレジスタオフセット を紹介しましたが、ロード命令のアドレッシングモードには、もう1つ PC 相対リテラルアドレッシングがあります。

PC 相対リテラルアドレッシング

ラベルの位置にあるデータをレジスタに読み込むアドレッシングモードです。 次の例では「Label」というラベルの位置に 64ビットの 12,345,678,901 という 数値が格納されています。 このラベルの位置の数値を X0 レジスタに設定します。

         LDR X0, Label
         ..
         ..
  Label: .quad  12345678901
addressing

「LDR X0, Label」の Label にはメモリアドレスが入るように見えますが、 32ビットの命令の中に64ビットのメモリアドレスを詰め込むことは不可能です。 リテラルアドレッシングでは、ラベルをアセンブラがプログラムカウンタ (PC) とラベルのアドレスとの相対値に変換します。 PC に対する符号付オフセット からメモリアドレスを計算して、そこに格納されている内容を 汎用レジスタに設定します。PCとの相対距離は19ビット(512K)で与えますが、 単位は32ビットです。 したがって内部で 4 倍されて、次に実行する命令の アドレスから ±1MB 離れた範囲のアドレスを指定できます。 STR 命令には対応する PC 相対リテラルアドレッシングは存在しません。

LDR ダブルワード(64ビット) → 64ビット

ラベル位置に格納された 64ビットのデータを32ビットの汎用レジスタに読み込みます。

    LDR     Xt, label

LDRSW 符号付きワード(32ビット) → 64ビット

ラベル位置に格納された 32ビットのデータを符号拡張して、64 ビットの汎用レジスタに読み込みます。

    LDRSW   Xt, label

LDR ワード(32ビット) → 32ビット

ラベル位置に格納された 32ビットのデータを32ビットの汎用レジスタに読み込みます。

    LDR     Wt, label

命令エンコード

命令 313029 282726 252423 222120 191817 161514 131211 100908 070605 040302 0100
LDR 32bit 0 0 0 1 1 0 0 0 imm19 (4倍されて, ±1MB) Rt
LDR 64bit 0 1 0 1 1 0 0 0 imm19 (4倍されて, ±1MB) Rt
LDRSW 1 0 0 1 1 0 0 0 imm19 (4倍されて, ±1MB) Rt

ストア命令

ストア命令 (STR) はレジスタに格納されている値をメモリに書き込みます。

レジスタ-メモリ転送 読み 命令
メモリからレジスタへコピー ロード LDR
レジスタからメモリへコピー ストア STR
レジスタからレジスタへコピー ムーブ MOV

ロード命令の逆の動作ですが、ストア命令ではレジスタのビット幅と同じか、より小さいビット幅のメモリに転送するため、ロード命令のようなビット幅の拡張は起きません。 したがって符号の有無で別の命令とならないため、命令の数がロード命令 (9種類) より少ない 4 種類になっています。

str
メモリ 転送元(32) 転送元(64)
ビット数 Wt Xt
64ビット - STR [1]
32ビット STR [2] -
16ビット STRH [3] -
8ビット STRB [4] -

ストア命令の アドレッシングは、ロード命令と同じイミディエートオフセットレジスタオフセット です。 ロード命令にある PC 相対リテラルアドレッシングは STR 命令には存在しません。

[1] STR ダブルワード(64ビット) → 64ビット

ARM64 にとって、一番自然なサイズである64 ビットレジスタのデータをメモリにコピーする命令です。

  STR   Xt, [base], #simm9      // ポストインデックス
  STR   Xt, [base, #simm9]!     // プレインデックス
  STUR  Xt, [base {,#simm9}]    
  STR   Xt, [base {,#uimm12}]  
  STR   Xt, [base, Wm {,SXTW|UXTW {#0 | #3}} ]  // レジスタオフセット
  STR   Xt, [base, Xm {,LSL|SXTX {#0 | #3}} ]   // レジスタオフセット

命令エンコード

命令 313029 282726 252423 222120 191817 161514 131211 100908 070605 040302 0100
STR (post) 1 1 1 1 1 0 0 0 0 0 0 imm9 0 1 Rn Rt
STR (pre) 1 1 1 1 1 0 0 0 0 0 0 imm9 1 1 Rn Rt
STUR (simm9) 1 1 1 1 1 0 0 0 0 0 0 imm9 0 0 Rn Rt
STR (uimm12) 1 1 1 1 1 0 0 1 0 0 uimm12 Rn Rt
STR (reg) 1 1 1 1 1 0 0 0 0 0 1 Rm option S 1 0 Rn Rt

[2] STR ワード(32ビット) → 32ビット

32 ビットレジスタのデータをメモリにコピーする命令です。

  STR   Wt, [base], #simm9       // ポストインデックス
  STR   Wt, [base, #simm9]!      // プレインデックス
  STUR  Wt, [base {,#simm9} ]    
  STR   Wt, [base {,#imm12} ]    
  STR   Wt, [base, Wm {,SXTW|UXTW {#0 | #2}} ]  // レジスタオフセット
  STR   Wt, [base, Xm {,LSL|SXTX {#0 | #2}} ]   // レジスタオフセット

命令エンコード

命令 313029 282726 252423 222120 191817 161514 131211 100908 070605 040302 0100
STR (post) 1 0 1 1 1 0 0 0 0 0 0 simm9 0 1 Rn Rt
STR (pre) 1 0 1 1 1 0 0 0 0 0 0 simm9 1 1 Rn Rt
STUR (simm9) 1 0 1 1 1 0 0 0 0 0 0 simm9 0 0 Rn Rt
STR (uimm12) 1 0 1 1 1 0 0 1 0 0 uimm12 Rn Rt
STR (reg) 1 0 1 1 1 0 0 0 0 0 1 Rm option S 1 0 Rn Rt

[3] STRH ハーフワード(32ビット) → 16ビット

32 ビットレジスタの下位16ビットのデータをメモリにコピーする命令です。

  STRH  Wt, [base], #simm9      // ポストインデックス
  STRH  Wt, [base, #simm9]!     // プレインデックス
  STURH Wt, [base {,#simm9}]
  STRH  Wt, [base {,#uimm12}]   // 符号なしオフセット
  STRH  Wt, [base, Wm {,SXTW|UXTW {#0 | #1}} ]  // レジスタオフセット
  STRH  Wt, [base, Xm {,LSL|SXTX {#0 | #1}} ]   // レジスタオフセット

命令エンコード

命令 313029 282726 252423 222120 191817 161514 131211 100908 070605 040302 0100
STRH (post) 0 1 1 1 1 0 0 0 0 0 0 simm9 0 1 Rn Rt
STRH (pre) 0 1 1 1 1 0 0 0 0 0 0 simm9 1 1 Rn Rt
STURH (simm9) 0 1 1 1 1 0 0 0 0 0 0 simm9 0 0 Rn Rt
STRH (uimm12) 0 1 1 1 1 0 0 1 0 0 uimm12 Rn Rt
STRH (reg) 0 1 1 1 1 0 0 0 0 0 1 Rm option S 1 0 Rn Rt

[4] STRB バイト(32ビット) → 8ビット

32 ビットレジスタの下位 8 ビットのデータをメモリにコピーする命令です。

  STRB  Wt, [base], #simm9        // ポストインデックス
  STRB  Wt, [base, #simm9]!       // プレインデックス
  STURB Wt, [base {,#simm9}]
  STRB  Wt, [base {,#uimm12}]     // 符号なしオフセット
  STRB  Wt, [base, Wm {,SXTW|UXTW {#0}} ]  // レジスタオフセット
  STRB  Wt, [base, Xm {,LSL|SXTX {#0}} ]   // レジスタオフセット

命令エンコード

命令 313029 282726 252423 222120 191817 161514 131211 100908 070605 040302 0100
STRB (post) 0 0 1 1 1 0 0 0 0 0 0 simm9 0 1 Rn Rt
STRB (pre) 0 0 1 1 1 0 0 0 0 0 0 simm9 1 1 Rn Rt
STURB (simm9) 0 0 1 1 1 0 0 0 0 0 0 simm9 0 0 Rn Rt
STRB (uimm12) 0 0 1 1 1 0 0 1 0 0 uimm12 Rn Rt
STRB (reg) 0 0 1 1 1 0 0 0 0 0 1 Rm option S 1 0 Rn Rt

PC相対アドレス計算

アセンブリコードのラベル位置のアドレスをレジスタに設定する場合に使用する命令です。 直接ラベルのアドレスをレジスタに転送するように見えますが、ロード命令の PC 相対リテラルアドレッシング と同じく、アセンブラがプログラムカウンタ (PC) にオフセットを加えるPC 相対アドレッシングに変更します。ロード命令の PC 相対リテラルではラベルの位置のメモリの内容をレジスタにコピーし、ADR命令ではラベルの位置のメモリアドレスをレジスタに設定します。混乱しやすいので注意して下さい。

ADR

プログラムカウンタ (PC) に ±1MB の範囲のオフセットを加えて、指定したレジスタ(Xd) に書き込みます。ADR命令のオフセットの値には immHi の19ビットと immL の2ビットを加えた21ビットの範囲の整数が指定できるため、PCの値±1MB の範囲が指定可能となります。

    ADR     Xd, label

ADRP

プログラムカウンタ (PC) に ±4GB の範囲のオフセットを加えたアドレスを 4KBを単位としたページアドレスに変換して、指定したレジスタ(Xd) に書き込みます。 ADRP命令のオフセットの値には immHi の19ビットと immL の2ビットを加えた21ビットの範囲の整数を 4096倍(12bit) した整数が指定できるため、PCの値±4GB の範囲の 4KB を単位としたページアドレスを指定できます。

    ADRP    Xd, label

命令エンコード

命令 313029 282726 252423 222120 191817 161514 131211 100908 070605 040302 0100
ADR 0 immL 1 0 0 0 0 immHi (19bit) Rd
ADRP 1 immL 1 0 0 0 0 immHi (19bit) Rd

レジスタペアのロード/ストア命令

2つのレジスタの内容の 16 バイトのデータを1命令でメモリに読み書きできる命令です。 スタックポインタにレジスタを退避/復帰する場合によく使います。 特にスタックポインタは 16 バイトでアラインする必要がある、つまり、メモリにアクセスする場合にスタックポインタの値は16で割り切れる必要があります。 いつも 2 つのレジスタをひとまとめにして退避/復帰するほうがエラーを予防できると思います。

アドレッシング

レジスタペアのロード/ストア命令では、符号付き7ビットのイミディエートオフセットアドレッシングの1種類のみです。 7ビットの符号付き定数(+127..-128) にレジスタのサイズ (Xnでは8バイト、Wnでは4バイト) を乗じた範囲のオフセットを使うイミディエートオフセットには3種類あって、オフセットを先にベースレジスタに加える「プレインデックス」、アクセス後にオフセットをベースレジスタに加える「ポストインデックス」、ベースレジスタを更新しない「符号付オフセット」があります。

プレインデックス

プレインデックスは、ベースレジスタと呼ぶスタックポインタ (SP) または 64 ビット汎用レジスタ (Xn) が格納している アドレスに定数を加えたメモリアドレスから読み出し、 そのアドレスをベースレジスタに書き戻します。 結果として、ベースレジスタの値に定数を加えたメモリアドレス を使ってベースレジスタを「事前に」更新し、そこにアクセス していることになります。ベースレジスタを「事前に」更新する ことから「プレインデックス」と呼んでいます。 次の擬似コードでは、memory[ adr ] でメモリアドレス adr に 格納されている内容を示します。 base はベースレジスタ(Xn または SP)、Xt1、Xt2 は転送先のレジスタを 示します。 プレインデックスはオペランドの最後に「!」を付けて表します。

    LDP   Xt1, Xt2, [base,  #simm7]!     // プレインデックス
    LDP   Wt1, Wt2, [base,  #simm7]!     // プレインデックス

      // プレインデックス
      base = base + simm7;
      Rt1 = memory[base];
      Rt2 = memory[base + 4|8];

ポストインデックス

ポストインデックスはベースレジスタの示すメモリアドレスから 読みだした後、オフセットに指定した定数をベースレジスタに加算します。 つまり、ベースレジスタのメモリアドレスにアクセスした「後に」 ベースレジスタを別のメモリアドレスに更新することから 「ポストインデックス」と呼んでいます。

    LDP   Xt1, Xt2, [base], #simm7      // ポストインデックス
    LDP   Wt1, Wt2, [base], #simm7      // ポストインデックス

      // ポストインデックス
      Rt1 = memory[base];
      Rt2 = memory[base + 4|8];
      base = base + simm7;

符号付き7ビットオフセット

ベースレジスタにオフセットを加えたメモリからロードします。 プレインデックスと同じアドレスを読み書きしますが、ベースレジスタは更新しません。

    LDP   Xt1, Xt2, [base, #simm7]      // base への書き戻しなし
    LDP   Wt1, Wt2, [base, #simm7]      // base への書き戻しなし

      Rt1 = memory[base];
      Rt2 = memory[base + simm7];

LDP

LDP 命令は連続したメモリから2つのレジスタに読み込む命令です。 レジスタをスタックから復帰する場合に良く使います。

stp

ポストインデックスでロード

    LDP   Xt1, Xt2, [base], #simm7
    LDP   Wt1, Wt2, [base], #simm7

base は汎用レジスタまたはスタックポインタ(SP)。

プレインデックスでロード

    LDP     Xt1, Xt2, [base, #simm7]!
    LDP     Wt1, Wt2, [base, #simm7]!

base は汎用レジスタまたはスタックポインタ(SP)。

符号付オフセットでロード

    LDP     Xt1, Xt2, [Xn {, #simm7}]
    LDP     Wt1, Wt2, [Xn {, #simm7}]

base は汎用レジスタまたはスタックポインタ(SP)。

STP

STP 命令は 2つのレジスタ (Xt1、Xt2) の内容を、メモリアドレスを保持するレジスタ (Xn) にオフセットの値を加えたアドレスを先頭とするメモリに書き込み、レジスタをスタックに退避する場合に良く使います。

ldp

オフセットの値(simm7)は、32ビットレジスタ (Wn) の場合は -256/+252 の範囲、64ビットレジスタの場合は -512/+504 の範囲を指定できます。

ポストインデックスのストア

    STP     Xt1, Xt2, [base], #simm7
    STP     Wt1, Wt2, [base], #simm7

base は汎用レジスタまたはスタックポインタ(SP)。

プレインデックスのストア

    STP     Xt1, Xt2, [base, #simm7]!
    STP     Wt1, Wt2, [base, #simm7]!

base は汎用レジスタまたはスタックポインタ(SP)。

符号付オフセットのストア

    STP     Xt1, Xt2, [base {, #simm7}]
    STP     Wt1, Wt2, [base {, #simm7}]

base は汎用レジスタまたはスタックポインタ(SP)。

命令エンコード

命令 313029 282726 252423 222120 191817 161514 131211 100908 070605 040302 0100
STP post-index z 0 1 0 1 0 0 0 1 0 imm7 Rt2 Rn Rt
LDP post-index z 0 1 0 1 0 0 0 1 1 imm7 Rt2 Rn Rt
STP pre-index z 0 1 0 1 0 0 1 1 0 imm7 Rt2 Rn Rt
LDP pre-index z 0 1 0 1 0 0 1 1 1 imm7 Rt2 Rn Rt
STP offset z 0 1 0 1 0 0 1 0 0 imm7 Rt2 Rn Rt
LDP offset z 0 1 0 1 0 0 1 0 1 imm7 Rt2 Rn Rt

オフセットの値は内部的には 7 ビットの符号付きオフセット (-64/+63) が使われ、レジスタのサイズにしたがって 4 倍 (Wn)、または 8 倍 (Xn) されて実際のオフセット(-256/+252、-512/+504) となります。アセンブラが 7 ビットの符号付きオフセットに変換します。

STP による PUSH

STP 命令をプレインデックスアドレッシングで使って、2つのレジスタの内容をスタックにプッシュします。スタックポインタを先に -16 してから、そこにレジスタの内容をコピーします。 先に指定したレジスタがメモリアドレスの小さい側にコピーされます。スタックトップは先に指定したレジスタの内容になります。

push_stp

LDP による POP

LDP 命令をポストインデックスアドレッシングで使って、スタックの内容を2つのレジスタにポップします。 スタックポインタの示す内容を先に(前に)指定したレジスタにコピーして、そのあとで2番目のレジスタにコピーします。 最後にスタックポインタの値を +16 します。

pop_ldp

bl 命令(後述) を使ってサブルーチン呼び出しする場合は、呼びだされた側のコードで必ず X30 (リンクレジスタ) に格納された戻り先のアドレスをスタックに退避し、リターンする前に X30 を復帰する必要があります。呼び出された側のコードで絶対に bl 命令を使わない場合は X30 を保存しなくても大丈夫ですが、後から処理を追加した場合に見つけにくいバグの原因となります。


MOV命令

レジスタに格納されているデータを別のレジスタへコピーする命令です。 MOV専用の命令が用意されているわけではなく、アセンブラが B = A + 0 のように加算命令などを使って実現されています。

レジスタ-メモリ転送 読み 命令
メモリからレジスタへコピー ロード LDR
レジスタからメモリへコピー ストア STR
レジスタからレジスタへコピー ムーブ MOV

MOV (SP-汎用レジスタ間)

レジスタおよびスタックポインタ間でデータをコピーします。 この命令は加算するイミディエートを 0 にした ADD 命令で実現されています。

    MOV     Wd|WSP, Wn|WSP  // ADD Wd|WSP, Wn|WSP, #0 の別名
    MOV     Xd|SP, Xn|SP    // ADD Xd|SP, Xn|SP, #0 の別名

MOV (汎用レジスタ間)

汎用レジスタから汎用レジスタに値をコピーします。 この命令は、ORR 命令で実現されています。

    MOV     Wd, Wm          // ORR Wd, WZR, Wm の別名
    MOV     Xd, Xm          // ORR Xd, XZR, Xm の別名

MOV (定数)

レジスタに定数を設定する MOV命令は、定数値によりアセンブラが ORR、MOVZ、MOVN、MOVK を選択して使います。アセンブラが 1 命令で設定できない定数の場合は 「Error: immediate cannot be moved by a single instruction 」というエラーとなります。

任意の定数を命令に埋め込む場合は以下のように最大 4 命令が必要です。

    // x0  <-- 0x0123456789ABCDEF
    movz    x0, #0xCDEF
    movk    x0, #0x89AB, LSL #16
    movk    x0, #0x4567, LSL #32
    movk    x0, #0x0123, LSL #48

任意の定数を汎用レジスタに設定する場合はロード命令でメモリから読み込むのが普通でしょう。

MOV(ビットマスクイミディエート)

ビットマスク用の定数をレジスタに設定します。定数は、ビットマスク用の32ビットまたは64ビットのパターンを指定できます。 2、4、8、16、32、64ビットを単位として、連続した 0 または 1 のパターンを繰り返した値を指定できます。例えば 0x000000001ffffc00、0xfffffffffc0fffff、0x007f007f007f007f、0x7070707070707070、0x5555555555555555といった即値です。 この命令は ORR(イミディエート)の別名です。

    MOV     Wd|WSP, #imm   // ORR Wd|WSP, WZR, #imm の別名
    MOV     Xd|SP, #imm    // ORR Xd|SP, XZR, #imm の別名

MOVK

16 ビットの定数をレジスタに設定します。他のビットは変化しません。16 ビットの定数はシフトを指定することができます。32 ビットの用レジスタの場合は 0 (デフォルト) または 16ビットの左シフト、64 ビットレジスタの場合は 0 (デフォルト)、16、32、48 のいずれかを指定できます。

    MOVK  Wd, #imm16 {,LSL #shift}
    MOVK  Xd, #imm16 {,LSL #shift}

MOVN

16 ビットの定数をビット反転してレジスタに設定します。16 ビットの定数はシフトを指定することができます。32 ビットの用レジスタの場合は 0 (デフォルト) または 16ビットの左シフト、64 ビットレジスタの場合は 0 (デフォルト)、16、32、48 のいずれかを指定できます。

  MOVN Wd, #uimm16 {,LSL #shift}
  MOVN Xd, #uimm16 {,LSL #shift} 

MOVZ

16 ビットの定数をビット反転してレジスタに設定します。16 ビットの定数はシフトを指定することができます。32 ビットの用レジスタの場合は 0 (デフォルト) または 16ビットの左シフト、64 ビットレジスタの場合は 0 (デフォルト)、16、32、48 のいずれかを指定できます。


命令エンコード

zが1ならば64ビットレジスタ、sft は 0, 16, 32, 48 のシフト量に対応します。

命令 313029 282726 252423 222120 191817 161514 131211 100908 070605 040302 0100
MOVK z 1 1 1 0 0 1 0 1 sft imm16 Rd
MOVZ z 1 0 1 0 0 1 0 1 sft imm16 Rd
MOVN z 0 0 1 0 0 1 0 1 sft imm16 Rd

次は演算命令




このページの目次