Logical, Shift, and Rotate Instructions
AND, OR, XOR, NOT, TEST, CMP plus shifts and rotates — bit manipulation mastery
Open interactive version (quiz + challenge)Real-world analogy
What is it?
Logical instructions (AND, OR, XOR, NOT, TEST) manipulate individual bits for masking, setting, toggling, and testing. CMP performs non-destructive subtraction for comparisons. Shift instructions (SHL, SHR, SAR) multiply or divide by powers of 2. Rotate instructions (ROL, ROR, RCL, RCR) circularly shift bits, with RCL/RCR including the carry flag for multi-word operations. These instructions are essential for bit-level data processing and fast arithmetic.
Real-world relevance
Bitwise operations are everywhere in systems programming. AND masks are used in network subnet calculations (IP AND mask). XOR is the basis of many encryption algorithms and checksums (CRC). Shift-and-add is how hardware multipliers work. Device driver code constantly uses TEST to check status register bits. Modern compilers use shifts and adds instead of MUL for multiplication by constants (e.g., x*10 = x*8 + x*2) because shifts are faster.
Key points
- AND — Bitwise AND — AND performs bit-by-bit AND: a bit is 1 only if both corresponding source and destination bits are 1. Used to clear specific bits (masking), test bit patterns, and isolate bit fields. Clears CF and OF, updates ZF, SF, PF.
- OR — Bitwise OR — OR performs bit-by-bit OR: a bit is 1 if either source or destination bit (or both) is 1. Used to set specific bits without affecting others. Clears CF and OF, updates ZF, SF, PF.
- XOR — Bitwise Exclusive OR — XOR outputs 1 when bits differ, 0 when they match. Used to toggle bits, create checksums, and as the fastest way to zero a register (XOR AX,AX). Clears CF and OF, updates ZF, SF, PF.
- NOT — Bitwise Complement — NOT flips every bit: 0 becomes 1, 1 becomes 0 (one's complement). Uniquely, NOT does NOT affect any flags. This distinguishes it from NEG (which computes two's complement and does affect flags).
- TEST — Non-Destructive AND — TEST performs AND but discards the result — it only sets flags. The destination operand is unchanged. Used to check if specific bits are set or if a register is zero, without modifying data. Commonly followed by JZ/JNZ.
- CMP — Compare (Non-Destructive SUB) — CMP subtracts source from destination but discards the result — it only sets flags. Used before conditional jumps (JE, JNE, JG, JL, JA, JB). The operands are unchanged after CMP.
- SHL/SAL — Shift Left — SHL (Shift Left) shifts bits left by 1 or CL positions. Zeros fill from the right. Each left shift multiplies by 2. The last bit shifted out goes to CF. SHL and SAL (Shift Arithmetic Left) are identical operations.
- SHR and SAR — Shift Right — SHR (Shift Right) shifts bits right, filling zeros from the left — unsigned divide by 2. SAR (Shift Arithmetic Right) preserves the sign bit (MSB), filling with copies of the sign — signed divide by 2. The last bit shifted out goes to CF.
- ROL and ROR — Rotate — ROL (Rotate Left) shifts bits left, and the bit that falls off the MSB wraps around to the LSB. ROR (Rotate Right) does the opposite. No bits are lost — they just circle around. CF receives a copy of the last bit rotated.
- RCL and RCR — Rotate Through Carry — RCL rotates bits left through the carry flag — CF becomes bit 0, old MSB goes to CF. RCR rotates right through carry — CF becomes the MSB, old bit 0 goes to CF. This creates a 9-bit (byte) or 17-bit (word) rotation that is perfect for multi-word shifts.
Code example
; Logical, shift, and rotate showcase
; === Bit Masking with AND ===
; Extract bits 4-7 from AL (upper nibble)
MOV AL, 0B7h ; AL = 10110111b
AND AL, 0F0h ; AL = 10110000b (B0h)
; To get the value 0-15, shift right:
MOV CL, 4
SHR AL, CL ; AL = 00001011b (0Bh = 11)
; === Setting bits with OR ===
; Set bit 5 to enable a feature flag
MOV AL, [status] ; Read current status byte
OR AL, 20h ; Set bit 5 (00100000b)
MOV [status], AL ; Write back
; === Toggle with XOR ===
; Toggle an LED connected to bit 0
MOV AL, [port_data]
XOR AL, 01h ; Flip bit 0
MOV [port_data], AL
; === Fast zero check ===
TEST AX, AX ; Sets ZF without changing AX
JZ handle_zero ; Branch if AX is zero
; === Multiply by 12 using shifts ===
; AX × 12 = AX × 8 + AX × 4
MOV BX, AX ; BX = AX (original)
SHL AX, 1 ; AX = AX × 2
SHL AX, 1 ; AX = AX × 4
MOV CX, AX ; CX = AX × 4
SHL AX, 1 ; AX = AX × 8
ADD AX, CX ; AX = AX×8 + AX×4 = AX × 12
; === 32-bit shift left using RCL ===
; Shift DX:AX left by 1 bit
SHL AX, 1 ; Shift low word, bit 15 -> CF
RCL DX, 1 ; CF enters bit 0 of DX
; === Rotate to examine each bit ===
MOV CX, 8 ; Check all 8 bits
MOV BL, AL ; BL = value to examine
check_bit:
ROL BL, 1 ; Rotate left, MSB -> CF -> bit 0
JC bit_is_one ; CF=1 means that bit was 1
; bit is 0...
bit_is_one:
DEC CX
JNZ check_bitLine-by-line walkthrough
- 1. AND AL, 0F0h with AL=B7h — the mask 0F0h has upper nibble all 1s and lower nibble all 0s. AND preserves only the upper nibble: B7h AND F0h = B0h.
- 2. SHR AL, CL with CL=4 shifts the upper nibble down to the lower position: B0h >> 4 = 0Bh (11 decimal). This is how you extract a bit field.
- 3. OR AL, 20h sets bit 5 regardless of its current value. The mask 20h = 00100000b has only bit 5 set, so OR forces that bit to 1 and leaves all others unchanged.
- 4. XOR AL, 01h toggles bit 0: if it was 0, it becomes 1; if 1, it becomes 0. Applying XOR 01h again restores the original value — XOR is its own inverse.
- 5. TEST AX, AX performs AX AND AX = AX but does not store the result. It only sets flags. ZF=1 if AX is zero. This is a common pattern for null pointer checks.
- 6. The multiply-by-12 sequence: SHL×1 gives ×2, SHL×2 gives ×4, SHL×3 gives ×8. Then AX×8 + saved AX×4 = AX×12. Shifts are much faster than the MUL instruction.
- 7. SHL AX, 1 / RCL DX, 1 — the SHL pushes the MSB of AX into CF. Then RCL shifts DX left and pulls CF into its bit 0. This propagates the carry from the low word to the high word.
- 8. The bit-checking loop uses ROL to rotate each bit into the CF position, then JC tests it. After 8 rotations, BL is back to its original value.
Spot the bug
; Goal: Test if bit 3 of AL is set
MOV AL, [input] ; AL = some value
AND AL, 08h ; Mask bit 3
JNZ bit3_set ; Jump if bit 3 was 1
; ... bit 3 is 0 ...
bit3_set:
; ... bit 3 is 1 ...
; Programmer then tries to use AL later
MOV [output], AL ; Store original value??Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- 8086 Logical Instructions (GeeksforGeeks)
- Shifts and Rotates in 8086 (YouTube)
- Bit Manipulation in Assembly (TutorialsPoint)
- x86 Shift and Rotate Reference (x86 Reference)