AArch64 Playground
4.16 · Shifts, sign extension, and bitfields

Shifts, sign extension, and bitfields

A shift slides every bit of a register left or right by some number of places. Sliding left by one place doubles an unsigned value, the way writing a 0 on the right of a decimal number multiplies it by ten; sliding right halves it.

This lesson covers the four shift instructions, how to widen a narrow value to fill a bigger register, and the bitfield instructions, which read or write a group of neighboring bits in one step.

Shifting and rotating

instructionwhat fills the gapeffect on the value
lsl d, n, k (logical shift left)zeros, on the rightmultiplies by 2 to the power k, as long as no 1 bit falls off the top
lsr d, n, k (logical shift right)zeros, on the leftdivides an unsigned value by 2 to the power k, dropping the remainder
asr d, n, k (arithmetic shift right)copies of the sign bit, on the leftdivides a signed value by 2 to the power k, rounding down
ror d, n, k (rotate right)the bits that fell off the right endnone: the bits go around in a circle

Bits pushed off the end are lost, except with ror. The amount k is 0 to 31 for a w register and 0 to 63 for an x register. It can also come from a register, as in lsl w1, w2, w3; then only the low 5 bits of the amount count (6 for an x register). There is no rotate left: rotating a word left by k is the same as ror by 32 - k.

For example, 7 is 0111. Shifted left by 4, it becomes 0111 0000, which is 112.

Watching each shift

The program below applies each shift to a small value and prints the result. Each value is needed in w9 only until the printf call, so it does not matter that printf may change w9.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 1try it: run it, or step one instruction at a timeOpen in playground

It prints 112 and 12 for the two unsigned shifts, then -25 for -100 asr 2. The same pattern shifted with lsr prints 1073741799: lsr fills the top with zeros, so the sign bit turns off and a negative number becomes a large positive one.

The fifth line shows how the two kinds of division round. asr rounds down, toward minus infinity, so -7 asr 1 is -4. sdiv rounds toward zero, so -7 divided by 2 is -3. Shifting is only a replacement for signed division when rounding down is what you want.

The last line rotates 1 right by one place: the bit that falls off bit 0 comes back in at bit 31, giving 0x80000000.

Shifts inside other instructions

Many instructions accept a shift on their last register operand, applied before the operation itself. This line adds x3 times 4 to x2 in one instruction:

        add     x1, x2, x3, lsl 2           // x1 = x2 + x3 * 4

Array code uses this to scale an index by the size of an element, as Arrays in memory, in one and two dimensions shows.

Widening: sign and zero extension

Sometimes a narrow value has to fill a wider register: a byte from a string, a halfword from a file, or an int that has to be added to a 64-bit address. There are two ways to fill the new upper bits:

  • Zero extension fills them with 0s. That keeps an unsigned value the same.
  • Sign extension fills them with copies of the narrow value's sign bit. That keeps a signed value the same.
instructionreadsfills the bits above with
sxtbthe low 8 bitscopies of bit 7
sxththe low 16 bitscopies of bit 15
sxtwa w register, writing an x registercopies of bit 31
uxtbthe low 8 bitszeros
uxththe low 16 bitszeros

There is no uxtw instruction, because none is needed: any instruction that writes a w register sets bits 32 to 63 of the matching x register to 0. A plain mov w2, w9 already zero-extends.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 2try it: run it, or step one instruction at a timeOpen in playground

It prints -16 and 240 for the byte 0xf0, -32768 and 32768 for the halfword 0x8000, and -5 against 4294967291 for the word -5.

pitfall

The last line of that program is a common bug. An int moved with a w register and then printed with %ld, or added to a 64-bit value, is zero-extended, so -5 turns into 4294967291. Widen it with sxtw first, or keep using the w register and %d.

Bitfields: packing and unpacking

A bitfield is a group of neighboring bits that holds one value. Its position is given by its lowest bit, the lsb, and its width in bits. Two instructions cover most jobs:

  • bfi d, n, lsb, width (bitfield insert) copies the low width bits of n into d, starting at bit lsb, and leaves every other bit of d as it was.
  • ubfx d, n, lsb, width (unsigned bitfield extract) copies width bits of n, starting at bit lsb, down to bit 0 of d, and fills the rest of d with zeros.

Without them, packing a field takes a mask, a shift and an orr, and unpacking takes a shift and an and.

A common 16-bit pixel format keeps a color in three fields:

bits15 to 1110 to 54 to 0
fieldredgreenblue
width565

The program below packs red 20, green 45 and blue 12 into one pixel with bfi, pulls them back out with ubfx, and then clears the green field. Only the pixel has to survive a printf call, so it is the one value kept in w19. As in the other programs here, main uses w19 without saving it, which a subroutine you write yourself may not do.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 3try it: run it, or step one instruction at a timeOpen in playground

The packed line shows 0xa5ac. Red 20 is 10100, green 45 is 101101, and blue 12 is 01100. Side by side that is 10100 101101 01100, regrouped as 1010 0101 1010 1100, which is 0xa5ac. ubfx gets 20, 45 and 12 back.

The last step inserts six zero bits taken from wzr over the green field. Only that field changes, and the pixel becomes 0xa00c.

More bitfield instructions

instructionwhat it does
sbfx d, n, lsb, widthlike ubfx, but fills the rest of d with copies of the field's top bit, so the field reads as a signed number
bfxil d, n, lsb, widthextracts a field of n into the low bits of d and keeps the other bits of d
ubfiz d, n, lsb, widthlike bfi, but sets every other bit of d to 0
sbfiz d, n, lsb, widthlike ubfiz, but fills the bits above the field with copies of the field's top bit

For example, sbfx on the red field of 0xa5ac gives -12: 10100 read as a 5-bit two's complement number is -16 + 4.

pitfall

Common mistakes from this lesson, each with a broken program and its fix that you can run:

Check yourself

  1. What does lsl w1, w1, 3 do to 5?
  2. w9 holds -12. What do asr w1, w9, 2 and lsr w1, w9, 2 give?
  3. w9 holds 0x80. What do sxtb w1, w9 and uxtb w1, w9 give?
  4. Which instruction copies bits 4 to 7 of w0 into the low bits of w1, with zeros above them?

answers

show answers
  1. 40, which is 5 times 8.
  2. -3 and 1073741821.
  3. -128 and 128.
  4. ubfx w1, w0, 4, 4.

Practice

  • Double, double, double: multiply by 8 with lsl instead of mul.
  • Binary broadcast and Bit population census: print a number in binary, and count its 1 bits, with lsr and and.
  • Nibbles trade places: swap the two hex digits of a byte with shifts and orr.
  • Unpack the pixel: pull four 8-bit channels out of a word with ubfx, and trade two of them with bfi.
  • Predict: binary logic (challenge): what lsl, lsr, bic and sxtb leave in a register.
  • Fill in the blank: binary logic (core) and Advanced quiz: binary logic: asr against lsr, sxtb, and the difference between bfi, ubfiz and bfxil.
  • Fill in the blank: bit manipulation (core): the instruction or bit number that sets, flips, tests, extracts or inserts the bits described.
  • Predict: bit manipulation (challenge): what ubfx, bfi, ror, clz and a swap made of three eor instructions leave in a register.