シフト演算命令 ()

シフト演算とは論理演算と同じようにビット列を操作する演算です。シフト演算命令には、単純な左右へのシフトの他に、複雑な動作をするビットフィールド移動命令があります。単純な左右へのシフトも内部的にはビットフィールド移動命令などで実現されているため、ここではビットフィールド移動命令もシフト演算のカテゴリーとして含めています。

シフト演算

シフト演算の基本は、レジスタのデータを左に1ビットずらす(シフト)と値は 2 倍になり、右に1ビットシフトすると値は 1/2 になることです。 乗除算より高速に実行できるため、うまく使うとプログラムを大幅に高速化できます。

サンプル

シフト演算のサンプルプログラムで実際に実行して試してみましょう。

shift.s

stdio.s を置いたディレクトリに、次のコードを shift.s というファイル名で作成します。 赤く表示されている命令がシフト演算の命令です。 16進数表示で「0x876543210fedcba9」、符号付きの10進数表示では「-8,690,466,096,661,279,831」となる64ビットの大きな数値を 8 ビットシフトして、値の変化を表示するプログラムとなっています。 この後の実行例では、赤く表示されている命令をコメントアウトされている命令の1つと入れ替えて、いろいろなシフト演算を試しています。

.include        "stdio.s"
.text
.global _start

num:    .quad  0x876543210fedcba9

_start:
        ldr    x2, num
        mov    x0, x2
        bl     PrintHex16
        bl     NewLine
        mov    x1, #22
        bl     PrintRight
        bl     NewLine

        lsl    x0, x2, #8 
//        asr    x0, x2, #8 
//        lsr    x0, x2, #8 
//        ror    x0, x2, #8
//        ror    x0, x2, #64-8   // rol

        bl     PrintHex16
        bl     NewLine
        mov    x1, #22
        bl     PrintRight
        bl     NewLine
        bl     Exit
論理左シフト

論理左シフト(LSL)はデータを左側にずらし、空いた右側のビットに 0 が入ります。シフト量を n ビットとすると、2 の n 乗の乗算に相当します。次の例は「0x876543210fedcba9」という数値を 8 ビット論理左シフトした結果を示します。整数に 256 を乗算する計算に相当します。 左側にあふれるビットは捨てられます。この例では左側にあふれてしまうため、結果は256倍になりません。

// LSL   x0, x2, #8
as -o lsl.o shift.s     // アセンブル
ld -o lsl lsl.o         // リンク
./lsl                   // 実行

876543210FEDCBA9        // シフト前
  -8690466096661279831
6543210FEDCBA900        // シフト後
   7296712173568108800
算術右シフト

算術右シフトは(ASR)はデータを右側にずらし、空いた左側のビットには最上位ビットと同じビットが入ってきます。つまり、最上位ビットが 1 ならば左側から 1 が、0 ならば左側から 0 が入ってきます。シフト量を n ビットとすると、2 の n 乗の符号付き除算に相当します。次の例は「0x876543210fedcba9」という数値を 8 ビット算術右シフトした結果を示します。符号付き整数を256で除算する計算に相当します。シフト前の数値が正の整数の場合は、結果も正の整数となります。

// ASR   x0, x2, #8
as -o asr.o shift.s     // アセンブル
ld -o asr asr.o         // リンク    
./asr                   // 実行      

876543210FEDCBA9        // シフト前
  -8690466096661279831
FF876543210FEDCB        // シフト後
    -33947133190083125
論理右シフト

論理右シフト (LSR)はデータを右側にずらし、空いた左側のビットに常に 0 が入ります。シフト量を n ビットとすると、2 の n 乗の符号無し除算に相当します。次の例は「0x876543210fedcba9」という数値を 8 ビット論理右シフトした結果を示します。符号無し整数を 256 で除算する計算に相当します。

// LSR   x0, x2, #8
as -o lsr.o shift.s     // アセンブル
ld -o lsr lsr.o         // リンク    
./lsr                   // 実行      

876543210FEDCBA9        // シフト前
  -8690466096661279831
00876543210FEDCB        // シフト後
     38110460847844811
右ローテート

右ローテート命令(ROR)は 64 ビットの最上位と最下位のビットが環状につながっているとして、右にシフト(時計回り回転)します。次の例は「0x876543210fedcba9」という数値を 8 ビット右ローテートした結果を示します。右側にあふれ出る「A9」が左側に回り込んで入ってきます。

// ROR   x0, x2, #8
as -o ror.o shift.s     // アセンブル
ld -o ror ror.o         // リンク    
./ror                   // 実行      

876543210FEDCBA9        // シフト前
  -8690466096661279831
A9876543210FEDCB        // シフト後
  -6230900220451885621
左ローテート

左ローテート命令はありませんが、右ローテート(ROR)のシフト量を (64 - ビット数) とすることで左ローテートを実現できます。 次の例は「0x876543210fedcba9」という数値を 8 ビット左ローテートした結果を示します。 左側にあふれ出る「87」が右側から入ってきます。

// ROR   x0, x2, #64-8
as -o rol.o shift.s     // アセンブル
ld -o rol rol.o         // リンク    
./rol                   // 実行      

876543210FEDCBA9        // シフト前
  -8690466096661279831
6543210FEDCBA987        // シフト後
   7296712173568108935

アドレッシングモード

シフト演算命令には、シフト量を定数で指定するイミディエートとレジスタで指定するモードの2種類のアドレッシングモードがあります。

イミディエート

汎用レジスタ Rn の内容を定数(6ビット)で指定したビット数だけシフトし、 結果を汎用レジスタ Rd に格納します。6ビットの定数では 0 から 63 までの定数が指定できます。

    LSR    Wd, Wn, #imm6
    LSR    Xd, Xn, #imm6
レジスタ

汎用レジスタ Rn を汎用レジスタ Rm で指定したビット数だけシフトし、 結果を汎用レジスタ Rd に格納します。

    LSR    Wd, Wn, Wm
    LSR    Xd, Xn, Xm

ASR

ASR命令 (Arithmetic Shift Right) は算術右シフトを実行します。算術右シフトはRn レジスタの内容を Rm レジスタで指定したビット数だけ右にシフトします。 左側(最上位)のビットは符号ビットがコピーされます。 例えば、符号付整数 -10 を 1 ビット算術右シフトすると -5 となります。16進表記では 0xFFFFFFFFFFFFFFF6 を右に 1 ビットシフトすると 0xFFFFFFFFFFFFFFFB となります。 シフト量にレジスタを使う ASR 命令は ASRV の別名です。

    ASR     Wd, Wn, Wm        // ASRV Wd, Wn, Wm と同じ
    ASR     Xd, Xn, Xm        // ASRV Xd, Xn, Xm と同じ

次の形式ではレジスタ Rn の内容を定数で指定したビット数だけ右にシフトしてレジスタ Rd に返します。

    ASR     Wd, Wn, #shift    // SBFM Wd, Wn, #shift, #31
    ASR     Xd, Xn, #shift    // SBFM Xd, Xn, #shift, #63

LSR

LSR命令 (Logical Shift Right)は論理右シフトを実行します。論理右シフトはレジスタ Rn の内容をレジスタ Rm で指定したビット数だけ右にシフトしてレジスタ Rd に返します。 左側(最上位)のビットには 0 が入ります。0xFFFFFFFFFFFFFFF6 は 0x7FFFFFFFFFFFFFFB となります。符号付整数の10進表記ではとすると -10 は1ビットの論理右シフトで 9223372036854775803 になります。 シフト量にレジスタを使う LSR 命令は LSRV の別名です。

    LSR    Wd, Wn, Wm          // LSRV Wd, Wn, Wm と同じ
    LSR    Xd, Xn, Xm          // LSRV Xd, Xn, Xm と同じ

レジスタ Rn の内容を定数で指定したビット数だけ右にシフトしてレジスタ Rd に返します。

    LSR    Wd, Wn, #shift      // UBFM Wd, Wn, #shift, #31
    LSR    Xd, Xn, #shift      // UBFM Xd, Xn, #shift, #63

LSL

LSL命令 (Logical Shift Left)は論理左シフトを実行します。論理左シフトはレジスタ Rn の内容をレジスタ Rm で指定したビット数だけ左にシフトしてレジスタ Rd に返します 。右側(最下位)のビットには 0 が入ります。シフト量にレジスタを使う LSL 命令は LSLV の別名です。

    LSL    Wd, Wn, Wm          // LSLV   Wd, Wn, Wm と同じ
    LSL    Xd, Xn, Xm          // LSLV   Xd, Xn, Xm と同じ

レジスタ Rn の内容を定数で指定したビット数だけ左にシフトしてレジスタ Rd に返します。

    LSL     Wd, Wn, #shift     // UBFM Wd, Wn, #(-shift MOD 32), #(31-shift)
    LSL     Xd, Xn, #shift     // UBFM Xd, Xn, #(-shift MOD 64), #(63 - shift)

ROR

レジスタ Rn の内容をレジスタ Rm で指定したビット数(レジスタのビット数で除算した剰余)でローテイトした値をレジスタ Rd に返します。 ローテイトの結果右端からあふれたビットは、左から挿入されます。 ローテイトするビット数にレジスタを使う ROR 命令は RORV の別名です。

    ROR    Wd, Wn, Wm          // RORV Wd, Wn, Wm と同じ
    ROR    Xd, Xn, Xm          // RORV Xd, Xn, Xm と同じ

レジスタ Rn の内容を定数で指定したビット数でローテイトした値をレジスタ Rd に返します。

    ROR    Wd, Ws, #shift      // EXTR Wd, Ws, Ws, #shift
    ROR    Xd, Xs, #shift      // EXTR Xd, Xs, Xs, #shift

EXTR

レジスタ Wn とレジスタ Wm のペアを連結してデータのビット列を抽出します。定数には抽出する最下位のビット位置を指定します。

    EXTR    Wd, Wn, Wm, #lsb
    EXTR    Xd, Xn, Xm, #lsb

分かり難いので、実際に試してみます。

num0:      .quad   0xabcdefabcdefabcd
num1:      .quad   0x1234567890123456
//
    ldr     x11, num0
    ldr     x12, num1
//
    extr    x0, X11, x12, #8    // CD12345678901234
    extr    x0, X11, x12, #16   // ABCD123456789012
    extr    x0, X11, x12, #24   // EFABCD1234567890

上記のコードの extr命令では、x0 に右側のコメントの値が設定されます。 次の表のように x11 と x12 のレジスタ2つを連結したデータから、 右側から定数で指定したビット数だけ左にずらした位置のデータ (レジスタのビット数) が抽出されます。 ROR命令とする場合はレジスタを同じレジスタを並べることで実現しています。

x11 x12
abc def abc def abc d12 345 678 901 234 56
CD12345678901234 8
ABCD123456789012 16
EFABCD1234567890 24

命令エンコード

命令 313029 282726 252423 222120 191817 161514 131211 100908 070605 040302 0100
ASR (sbfm imm) z 0 0 1 0 0 1 1 0 N immr x 1 1 1 1 1 Rn Rd
ASRV z 0 0 1 1 0 1 0 1 1 0 Rm 0 0 1 0 1 0 Rn Rt
LSR (ubfm imm) z 1 0 1 0 0 1 1 0 N immr x 1 1 1 1 1 Rn Rd
LSRV z 0 0 1 1 0 1 0 1 1 0 Rm 0 0 1 0 0 1 Rn Rt
LSL (ubfm imm) z 1 0 1 0 0 1 1 0 N immr x 1 1 1 1 1 Rn Rd
LSLV z 0 0 1 1 0 1 0 1 1 0 Rm 0 0 1 0 0 0 Rn Rt
RORV z 0 0 1 1 0 1 0 1 1 0 Rm 0 0 1 0 1 1 Rn Rt
EXTR z 0 0 1 0 0 1 1 1 N 0 Rm imm6 Rn Rt

ビットフィールドムーブ命令

レジスタ内の最下位のビットを含む連続したビット列(最も右側のビット列)を移動する命令です。 移動先レジスタをゼロクリアして 移動するビット列の最上位を符号フラグとして移動先レジスタの 上位ビットに符号をコピーする SBFM 命令、 移動先のレジスタの内容の対応するビット列だけ上書きして元の内容を保持する BFM 命令、移動先レジスタをゼロクリアしてビット列だけ を書き込む UBFM 命令があります。

次の例は、x2レジスタの下位24ビット「BCDEF0」を x3 レジスタに 3種類の命令で右から40ビットの位置が先頭になるようにビットフィールドムーブした場合の結果を示しています。

                            x2    : 923486789ABCDEF0
                            x3    : 0123456789ABCDEF (使われない)
 SBFM X3, X2, #40, #23 -->  x3    : FFFFBCDEF0000000

                            x2    : 923486789ABCDEF0
                            x3    : BBBBBBBBBBBBBBBB (保持される)
 BFM  X3, X2, #40, #23 -->  x3    : BBBBBCDEF0BBBBBB

                            x2    : 923486789ABCDEF0
                            x3    : 0123456789ABCDEF (使われない)
 UBFM X3, X2, #40, #23 -->  x3    : 0000BCDEF0000000

アセンブラがビットフィールドムーブ命令に翻訳して実行する命令として シフト演算や符号拡張等の命令があります。ビットフィールドムーブ命令は 動作が複雑なので、SBFM、BFM、UBFM を実際に実行した結果も記載しました。 じっくり追ってみてください。

SBFM

この命令は Signed Bitfield Move の略で、ソースレジスタ(Wn、Xn)の 下位ビット側の任意の桁数 (imms+1) を、転送先レジスタの右シフト (immr)するビット位置にコピーします。 imms にはコピー元のビット列の最も左側のビット番号(0から63)を 指定します。immr には右にシフトする量(0から63)を指定します。 上位ビットには符号ビット、下位ビットには0が入ってきます。

  SBFM Wd, Wn, #immr, #imms
  SBFM Xd, Xn, #immr, #imms

x2 に 0x823456789ABCDEF0 という値が入っている場合に移動するビット数(imm6s+1)、 右にローテイトするビット数(imm6r) にいろいろな値を入れた場合の SBFM命令を実行した結果を次に示します。移動するデータを 24 ビット とした場合は符号ビットが「1」、48ビットとした場合は符号ビットは 「0」となります。

 x2    : 823456789ABCDEF0
 x5    : 5555555555555555
 SBFM X5, X2, #60, #23 --> FFFFFFFFFBCDEF00
 SBFM X5, X2, #56, #23 --> FFFFFFFFBCDEF000
 SBFM X5, X2, #60, #47 --> 00056789ABCDEF00
 SBFM X5, X2, #56, #47 --> 0056789ABCDEF000
 SBFM X5, X2, #24, #47 --> 000000000056789A
 SBFM X5, X2, #16, #23 --> FFFFFFFFFFFFFFBC
 SBFM X5, X2,  #8, #23 --> FFFFFFFFFFFFBCDE

SBFM はアセンブラによって色々な命令から翻訳されて使われています。

ASR

ASR 命令 (Arithmetic Shift Right) は転送元レジスタの内容を定数で指定したビット数だけ右にシフトして転送先レジスタにコピーします。左側(最上位)のビットは符号ビットがコピーされます。

    ASR Wd, Wn, #shift               // SBFM Wd, Wn, #shift, #31
    ASR Xd, Xn, #shift               // SBFM Xd, Xn, #shift, #63
SBFIZ

SBFIZ (Signed Bitfield Insert in Zero) 命令は、転送元レジスタの最下位ビットのビット番号が lsb、幅 が width のビット列を転送先レジスタの下位にコピーします。

    SBFIZ    Xd, Xn, #lsb, #width    // SBFM Wd, Wn, #(-lsb MOD 32), #(width-1)
    SBFIZ    Wd, Wn, #lsb, #width    // SBFM Xd, Xn, #(-lsb MOD 64), #(width-1)
SBFX

SBFX (Signed Bitfield Extract) 命令は、転送元レジスタの最下位ビットのビット番号が lsb、幅 が width のビット列を取り出して符号拡張して転送先レジスタにコピーします。

    SBFX     Wd, Wn, #lsb, #width    // SBFM Wd, Wn, #lsb, #(lsb+width-1)
    SBFX     Xd, Xn, #lsb, #width    // SBFM Xd, Xn, #lsb, #(lsb+width-1)
SXTB

SXTB (Signed Extend Byte) 命令は転送元レジスタの下位8ビットを符号拡張して転送先レジスタにコピーします。

    SXTB     Wd, Wn                  // SBFM Wd, Wn, #0, #7
    SXTB     Xd, Wn                  // SBFM Xd, Xn, #0, #7
SXTH

SXTH (Sign Extend Halfword) 命令は転送元レジスタの下位16ビットを符号拡張して転送先レジスタにコピーします。

    SXTH     Wd, Wn                  // SBFM Wd, Wn, #0, #15 
    SXTH     Xd, Wn                  // SBFM Xd, Xn, #0, #15
SXTW

SXTW (Sign Extend Word) 命令は転送元レジスタの下位32ビットを符号拡張して転送先レジスタにコピーします。

    SXTW     Xd, Wn                  // SBFM Xd, Xn, #0, #31

BFM

この命令は Bitfield Move の略で、ソースレジスタ(Wn、Xn)の 下位ビット側の任意の桁数 (imms+1) を、転送先レジスタの 右シフト(immr)したビット位置にコピーします。 imms にはコピー元のビット列の最も左側のビット番号(0から63)を 指定します。immr には右にシフトする量(0から63)を指定します。 転送先レジスタの他のビットは変化しません。

    BFM     Wd, Wn, #imm6r, #imm6s    // sf = 0 && N = 0
    BFM     Xd, Xn, #imm6r, #imm6s    // sf = 1 && N = 1

結果を返す x5 に 0x5555555555555555、x2 に 0x823456789ABCDEF0 という値が 入っている場合に移動するビット数(imm6s+1)、 右にローテイトするビット数(imm6r) にいろいろな値を入れた場合の BFM命令を実行した結果を次に示します。

 x2    : 823456789ABCDEF0
 x5    : 5555555555555555
 BFM  X5, X2, #60, #23 --> 555555555BCDEF05
 BFM  X5, X2, #56, #23 --> 55555555BCDEF055
 BFM  X5, X2, #60, #47 --> 55556789ABCDEF05
 BFM  X5, X2, #56, #47 --> 5556789ABCDEF055
 BFM  X5, X2, #24, #47 --> 555555555556789A
 BFM  X5, X2, #16, #23 --> 55555555555555BC
 BFM  X5, X2,  #8, #23 --> 555555555555BCDE

x5 に最初に格納されていた値 (0x5555555555555555) がBFM命令実行後も 残っていることが確認できます。

BFM もアセンブラによって翻訳されて使われます。

BFI

BFI命令 (Bitfield Insert) は、転送元レジスタの指定した幅(width)の下位ビットを転送先レジスタの転送位置となる最下位ビットのビット番号(Least-significant bit) を指定してコピーします。

                            <--- width --->
before     -----------------bbbbbbbbbbbbbbb
 
                       <--- width --->
                                     lsb
after      ------------bbbbbbbbbbbbbbb-----
    BFI    Wd, Wn, #lsb, #width    // BFM Wd, Wn, #(-lsb MOD 32), #(width-1)
    BFI    Xd, Xn, #lsb, #width    // BFM Xd, Xn, #(-lsb MOD 64), #(width-1)
BFXIL

BFXIL命令 (Bitfield eXtract and Insert at Low end) は、転送元レジスタの最下位ビットのビット番号が lsb 幅 が width のビット列を転送先レジスタの下位にコピーします。BFI と逆の動作となります。

                        <--- width --->
                                      lsb
before      ------------bbbbbbbbbbbbbbb-----

                             <--- width --->
after       -----------------bbbbbbbbbbbbbbb
    BFXIL  Wd, Wn, #lsb, #width    // BFM Wd, Wn, #lsb, #(lsb+width-1)
    BFXIL  Xd, Xn, #lsb, #width    // BFM Xd, Xn, #lsb, #(lsb+width-1)

UBFM

この命令は Unsigned Bitfield Move の略で、ソースレジスタ(Wn、Xn)の 下位ビット側の任意の桁数 (imms+1) を、デスティネーションレジスタの 右シフト(immr)したビット位置にコピーします。 imms にはコピー元のビット列の最も左側のビット番号(0から63)を 指定します。immr には右にシフトする量(0から63)を指定します。 コピー先の前後のビットは 0 になります。

    UBFM Wd, Wn, #imm6r, #imm6s     // sf = 0 && N = 0
    UBFM Xd, Xn, #imm6r, #imm6s     // sf = 1 && N = 1

x2 に 0x823456789ABCDEF0 という値が入っている場合に移動するビット数(imm6s+1)、 右にローテイトするビット数(imm6r) にいろいろな値を入れた場合の UBFM命令を実行した結果を次に示します。

 x2    : 823456789ABCDEF0
 x5    : 5555555555555555
 UBFM X5, X2, #60, #23 --> 000000000BCDEF00
 UBFM X5, X2, #56, #23 --> 00000000BCDEF000
 UBFM X5, X2, #60, #47 --> 00056789ABCDEF00
 UBFM X5, X2, #56, #47 --> 0056789ABCDEF000
 UBFM X5, X2, #24, #47 --> 000000000056789A
 UBFM X5, X2, #16, #23 --> 00000000000000BC
 UBFM X5, X2,  #8, #23 --> 000000000000BCDE
LSL

LSL命令 (Logical Shift Left) は転送元レジスタの内容を定数で指定したビット数だけ左にシフトしてレジスタに返します。

    LSL     Wd, Wn, #shift         // UBFM Wd, Wn, #(-shift MOD 32), #(31-shift)
    LSL     Xd, Xn, #shift         // UBFM Xd, Xn, #(-shift MOD 64), #(63-shift)
LSR

LSR 命令は転送元レジスタの内容を定数で指定したビット数だけ右にシフトしてレジスタに返します。

    LSR     Wd, Wn, #shift         // UBFM Wd, Wn, #shift, #31
    LSR     Xd, Xn, #shift         // UBFM Xd, Xn, #shift, #63
UBFIZ

UBFIZ (Unsigned Bitfield Insert in Zero) 命令は転送先レジスタを0にした後、転送元レジスタの最下位ビットのビット番号が lsb、幅 が width のビット列を転送先レジスタの下位にコピーします。

    UBFIZ   Wd, Wn, #lsb, #width   // UBFM Wd, Wn, #(-lsb MOD 32), #(width-1)
    UBFIZ   Xd, Xn, #lsb, #width   // UBFM Xd, Xn, #(-lsb MOD 64), #(width-1)
UBFX

UBFX (Unsigned Bitfield Extract) 命令は転送先レジスタを0にした後、、転送元レジスタの最下位ビットのビット番号が lsb、幅 が width のビット列を転送先レジスタの下位にコピーします。コピーするビット列をゼロ拡張して転送する動作となります。

    UBFX    Wd, Wn, #lsb, #width   // UBFM Wd, Wn, #lsb, #(lsb+width-1)
    UBFX    Xd, Xn, #lsb, #width   // UBFM Xd, Xn, #lsb, #(lsb+width-1)
UXTB

UXTB (Unsigned Extend Byte) 命令は転送元レジスタの下位8ビットをゼロ拡張して転送先レジスタにコピーします。

    UXTB    Wd, Wn                 // UBFM Wd, Wn, #0, #7
UXTH

UXTH (Sign Extend Halfword) 命令は転送元レジスタの下位16ビットをゼロ拡張して転送先レジスタにコピーします。

    UXTH    Wd, Wn                 // UBFM Wd, Wn, #0, #15

命令エンコード

命令 313029 282726 252423 222120 191817 161514 131211 100908 070605 040302 0100
SBFM z 0 0 1 0 0 1 1 0 N immr imms Rn Rd
BFM z 0 1 1 0 0 1 1 0 N immr imms Rn Rd
UBFM z 1 0 1 0 0 1 1 0 N immr imms Rn Rd

次回は整数を扱う命令の最後として分岐命令、条件実行命令などを予定しています。


続く...




このページの目次