AArch64 Playground
4.15 · Binary logic: masks, flags, and tst

Binary logic: masks, flags, and tst

The logic instructions treat a register as a row of separate bits. Bit 5 of the result depends only on bit 5 of each operand, and nothing carries from one position to the next the way it does in addition. That makes them the tool for changing or checking chosen bits while leaving the others alone.

And, or, exclusive or, not

Each logic operation takes one bit from each operand and gives one bit back:

aba AND ba OR ba XOR b
00000
01011
10011
11110

AND is 1 only in the rows where a and b are both 1. OR is 1 whenever at least one of them is. Exclusive or (XOR) gives 1 where the two bits differ. NOT takes a single bit and flips it. Applied to whole registers, 1100 AND 1010 is 1000, OR is 1110, and XOR is 0110.

AArch64 spells these as instructions, each working on all 32 bits of a w register or all 64 bits of an x register at once:

instructionresult
and d, n, mn AND m
orr d, n, mn OR m
eor d, n, mn XOR m
bic d, n, mn AND (NOT m), called bit clear
orn d, n, mn OR (NOT m)
eon d, n, mn XOR (NOT m)
mvn d, mNOT m

Two familiar instructions are logic instructions underneath: mov x19, x20 is orr x19, xzr, x20 (OR with zero changes nothing), and mvn is orn with the zero register as its first source.

Masks

A mask is a value whose 1 bits mark the positions you want to work on. Each instruction does one job with it:

  • orr with a mask sets those bits to 1, because x OR 1 is 1 and x OR 0 is x.
  • bic with a mask clears those bits to 0.
  • eor with a mask flips (toggles) those bits, because x XOR 1 is NOT x and x XOR 0 is x.
  • and with a mask keeps those bits and clears all the others. and w1, w0, 0xff keeps the low byte of w0.

A flag is one bit used as a yes-or-no answer. The N, Z, C and V condition flags that cmp sets are flags in exactly this sense. A program can pack its own flags into one register, which saves space and lets one instruction change several at once.

Status effects as flags

A game might keep a character's status effects as four flags in one word:

bitmaskeffect
00x1poisoned
10x2invisible
20x4flying
30x8shielded

The program below names each mask with define, so the code says POISONED instead of 0x1. In FLYING | SHIELDED, the | means OR, worked out by the assembler before the program runs, so that operand is the constant 0xc. bic takes its mask from a register here, so the mask goes into w9 first.

The status word has to survive every printf call, so it lives in w19. This main uses w19 without saving it first, as the programs in these lessons do. A subroutine you write yourself has to save any of x19 to x28 that it changes.

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

The status goes 0x0, 0x1, 0xd, 0xf, 0xe, 0xc. In binary that is 0000, 0001, 1101, 1111, 1110, 1100: each step changes one bit, except the third, where one mask sets two bits at once.

The two eor lines use the same mask, so the second one undoes the first: flipping a bit twice puts it back. bic is safe to repeat as well: clearing a bit that is already 0 leaves it at 0.

pitfall

A logic instruction cannot take just any constant. The immediate in and, orr, eor, ands and tst has to be a bitmask immediate: one run of consecutive 1s, which may be rotated and repeated across the register. 0xc (1100), 0xff and 0xfffffffe qualify. 0x5 (0101) does not, so orr w19, w19, POISONED | FLYING fails to assemble. Put a mask like that in a register with mov first and use the register form. Zero and all ones are never allowed as a bitmask immediate either.

Testing bits with tst

tst ANDs its two operands, throws the result away, and keeps only the condition flags, the same way cmp is a subs that throws its result away. If none of the mask's bits are 1 in the register, the AND is zero and the Z flag is set. So after a tst:

  • b.eq branches when every masked bit is 0.
  • b.ne branches when at least one masked bit is 1.

ands does the same AND but also writes the result to its destination. Both set N from the top bit of the result and Z when the result is zero, and both clear C and V.

The program below starts from a status value and prints the name of each effect that is on. Each test branches past its message with b.eq when the bit is clear, the same shape as an if with no else. Before the four single-bit tests, one tst with the mask 0xf checks all four bits at once, and prints no effects when none is set.

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

With the starting value 0xa it prints status = 0xa, then invisible and shielded: 0xa is 1010, so bits 1 and 3 are on. Change START to 0x0 to see the no effects line, or to 0xf to see all four names.

Check yourself

  1. w0 holds 0110 and w1 holds 0011. What do and, orr and eor of the two give?
  2. Which instruction turns bit 4 of w19 off and leaves every other bit as it was, with the mask 0x10 in w9?
  3. After tst w19, 0x4, which branch jumps when bit 2 of w19 is 1?
  4. Why does eor w19, w19, 0x3 done twice leave w19 unchanged?

answers

show answers
  1. 0010, 0111 and 0101.
  2. bic w19, w19, w9.
  3. b.ne: the AND is not zero when the bit is 1.
  4. Each eor flips bits 0 and 1, and flipping a bit twice puts it back.

Practice