Shifts, sign extension, and bitfields
prerequisite
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
| instruction | what fills the gap | effect on the value |
|---|---|---|
lsl d, n, k (logical shift left) | zeros, on the right | multiplies 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 left | divides 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 left | divides a signed value by 2 to the power k, rounding down |
ror d, n, k (rotate right) | the bits that fell off the right end | none: 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.
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 * 4Array 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.
| instruction | reads | fills the bits above with |
|---|---|---|
sxtb | the low 8 bits | copies of bit 7 |
sxth | the low 16 bits | copies of bit 15 |
sxtw | a w register, writing an x register | copies of bit 31 |
uxtb | the low 8 bits | zeros |
uxth | the low 16 bits | zeros |
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.
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 lowwidthbits ofnintod, starting at bitlsb, and leaves every other bit ofdas it was.ubfx d, n, lsb, width(unsigned bitfield extract) copieswidthbits ofn, starting at bitlsb, down to bit 0 ofd, and fills the rest ofdwith 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:
| bits | 15 to 11 | 10 to 5 | 4 to 0 |
|---|---|---|---|
| field | red | green | blue |
| width | 5 | 6 | 5 |
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.
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
| instruction | what it does |
|---|---|
sbfx d, n, lsb, width | like 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, width | extracts a field of n into the low bits of d and keeps the other bits of d |
ubfiz d, n, lsb, width | like bfi, but sets every other bit of d to 0 |
sbfiz d, n, lsb, width | like 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
- What does
lsl w1, w1, 3do to 5? w9holds -12. What doasr w1, w9, 2andlsr w1, w9, 2give?w9holds0x80. What dosxtb w1, w9anduxtb w1, w9give?- Which instruction copies bits 4 to 7 of
w0into the low bits ofw1, with zeros above them?
answers
show answers
- 40, which is 5 times 8.
- -3 and 1073741821.
- -128 and 128.
ubfx w1, w0, 4, 4.
Practice
- Double, double, double: multiply by 8 with
lslinstead ofmul. - Binary broadcast and Bit population census: print a number in binary, and count its 1 bits, with
lsrandand. - 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 withbfi. - Predict: binary logic (challenge): what
lsl,lsr,bicandsxtbleave in a register. - Fill in the blank: binary logic (core) and Advanced quiz: binary logic:
asragainstlsr,sxtb, and the difference betweenbfi,ubfizandbfxil. - 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,clzand a swap made of threeeorinstructions leave in a register.