1. Instruction Overview
Starting from the Intel 80386 processor, the instruction set introduced a powerful set of bit manipulation instructions. These instructions allow programmers to precisely manipulate individual binary bits within operands, including operations such as testing, setting, clearing, complementing, and searching. This greatly enhances the efficiency and convenience of performing bit-level operations (such as handling bitmaps, hardware registers, flag sets, permission masks, etc.).
Bit manipulation instructions are mainly divided into two categories:
- Bit Test and Set Instructions (BT, BTC, BTR, BTS)
- Bit Scan Instructions (BSF, BSR)
This article will focus on the first group: Bit Test and Set Instructions.
Bit Test and Set Instructions (BT, BTC, BTR, BTS)
This group of instructions is used to test a specific bit in an operand and store its original value in the carry flag <span>CF</span>, while also optionally modifying the state of that bit.
2. Instruction Format
These four instructions share the same format:
BT dest, bit_index
BTC dest, bit_index
BTR dest, bit_index
BTS dest, bit_index
-
**
<span>dest</span>(OPRD1)**: - Is the target operand being tested.
- Can be a 16-bit or 32-bit general-purpose register or memory operand.
-
**
<span>bit_index</span>(OPRD2)**: - Is the bit index, specifying which bit in
<span>dest</span>to operate on. - Can be an 8-bit immediate value (e.g.,
<span>5</span>) or a general-purpose register of the same length as<span>dest</span>.
3. Core Mechanism and Address Calculation
This is the core concept that needs to be understood for this group of instructions. The CPU uses the following method to determine the specific bit to operate on:
Let:
<span>n</span>=<span>dest</span>operand’s bit width (16 or 32).<span>bit_index</span>is treated as a signed integer.
Calculation:
<span>offset</span>=<span>bit_index</span>MOD<span>n</span>
- This gives the actual bit offset within the target unit (register or single memory unit) (0 to n-1).
<span>disp</span> = <span>bit_index</span>DIV<span>n</span>- This gives an index offset, which is only meaningful if
<span>dest</span>is a memory operand.
Final position of the operation bit:
- If
<span>dest</span>is a register: directly operate on the<span>offset</span>bit in that register.<span>disp</span>is ignored. - If
<span>dest</span>is a memory address: the effective address operated on is<span>[dest_base_address + (disp * element_size)]</span>, and the<span>offset</span>bit of the word or double word at that address is operated on. This allows the instruction to efficiently access large bit arrays (bitmaps), as<span>bit_index</span>can be much greater than 15 or 31.
Example for understanding:
<span>BT EAX, 5</span>:<span>n=32</span>,<span>offset=5 MOD 32 = 5</span>,<span>disp=5 DIV 32=0</span>. Since the target is the register<span>EAX</span>, directly test the<span>EAX</span>5th bit.<span>BT [array], 35</span>: Assume<span>array</span>is a<span>WORD</span>(16-bit) array.<span>n=16</span>,<span>offset=35 MOD 16 = 3</span>,<span>disp=35 DIV 16 = 2</span>.- The actual memory address operated on is
<span>[array + (2 * 2)]</span>(since each element occupies 2 bytes), and test the 3rd bit of the word at that address.
4. Detailed Functionality of Instructions
All instructions first place the <span>original value of the bit specified by </span><code><span>bit_index</span> in the carry flag <span>CF</span>.
| Instruction | Full Name | Operation Performed |
|---|---|---|
<span>BT</span> |
Bit Test | <span>CF</span> = <span>dest[bit_index]</span>(only tests, does not modify the target bit) |
<span>BTC</span> |
Bit Test and Complement | <span>CF</span> = <span>dest[bit_index]</span><span>dest[bit_index]</span> = NOT<span>dest[bit_index]</span>(after testing, complements the target bit) |
<span>BTR</span> |
Bit Test and Reset | <span>CF</span> = <span>dest[bit_index]</span><span>dest[bit_index]</span> = 0(after testing, resets the target bit) |
<span>BTS</span> |
Bit Test and Set | <span>CF</span> = <span>dest[bit_index]</span><span>dest[bit_index]</span> = 1(after testing, sets the target bit) |
5. Impact on Flags
- **CF (Carry Flag)**: Set according to the original value of the tested bit. This is the main output of this instruction group.
- OF, SF, ZF, AF, PF: Status undefined. The values of these flags after instruction execution are random and should not be relied upon.
Code Examples
Example 1: Basic Bit Test with Conditional Jump
Check if the 10th bit of the <span>AX</span> register is set.
MOV AX, 100000000000b ; or MOV AX, 0x400 (10th bit is 1)
BT AX, 10 ; test the 10th bit, its value (1) -> CF
JC Bit_Is_Set ; if CF=1, jump
; ... handling for bit being 0 ...
JMP Done
Bit_Is_Set:
; ... handling for bit being 1 ...
Done:
Example 2: Atomically Clearing a Flag Bit Using BTR
Assume there is a status word in memory <span>StatusFlags</span> and we need to check and clear its 3rd bit (e.g., indicating “busy” status).
.DATA
StatusFlags DW 0
.CODE
; Check and clear the "busy" flag (3rd bit)
BTR StatusFlags, 3 ; CF = original busy state, while clearing the 3rd bit
JC Was_Busy ; if originally busy, jump
; ... handling for originally not busy ...
JMP Done
Was_Busy:
; ... handling for originally busy, now cleared ...
Done:
Example 3: Atomically Setting a Lock Using BTS
Implement a simple spinlock. <span>LockVariable</span> being 0 indicates the lock is free, while 1 indicates the lock is occupied.
.DATA
LockVariable DD 0
.CODE
Acquire_Lock:
BTS LockVariable, 0 ; test and set the 0th bit, original value -> CF
JC Locked ; if CF=1 (was locked), jump to wait
; ... successfully acquired lock, execute critical section code ...
BTR LockVariable, 0 ; release lock, clear the 0th bit
JMP Done
Locked:
; ... wait or other handling ...
JMP Acquire_Lock ; try to acquire lock again
Done:
Example 4: Toggling a Switch State Using BTC
Toggle the 7th bit (most significant bit) in the <span>BL</span> register.
MOV BL, 01010101b
BTC BL, 7 ; CF = original 7th bit (0), then the 7th bit is complemented -> BL becomes 11010101b
; After execution: CF = 0, BL = 0xD5
Example 5: Accessing a Large Bitmap (Memory Operand)
Assume <span>BitMap</span> is the starting address of a storage bit array. Check the status of the 100th bit.
.DATA
BitMap DB 256 DUP(0) ; a 256-byte bitmap (2048 bits)
.CODE
MOV EDI, OFFSET BitMap
MOV EAX, 100 ; we want to check the 100th bit
BT [EDI], EAX ; CPU automatically calculates: offset=100 MOD 32=4, disp=100 DIV 32=3
; Actual test address: [EDI + 3*4] for the 4th bit of the double word
; (Note: here it is assumed to be 32-bit operation, but BitMap is a byte array, the assembler needs to clarify the type)
; A safer way is to explicitly specify operand size:
BT DWORD PTR [EDI], 100 ; explicitly tell the assembler that dest is a 32-bit memory operand
JC Bit_100_Set
Conclusion
The bit test and set instructions group (<span>BT</span>, <span>BTC</span>, <span>BTR</span>, <span>BTS</span>) introduced by the 80386 provides powerful and efficient bit manipulation capabilities:
- Atomicity: When the target operand is memory, these instructions typically execute atomically (completing read-modify-write in a single instruction), which is crucial for implementing locks, semaphores, and other synchronization primitives.
- Efficiency: Accomplishes the functionality of traditional “shift, mask, test, modify” multiple instructions in a single instruction.
- Flexibility: Through complex address calculations, it can easily access large bit arrays (bitmaps) far exceeding the width of a single register, making it very suitable for operating system and embedded system development.
- Conditional Judgement: By combining with conditional jump instructions like
<span>JC</span>/<span>JNC</span>, it can succinctly implement program branches based on specific bit states.
Mastering these instructions is an important skill for low-level system programming and performance-critical code optimization.