The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Six (Part 3)

Table of Content

Chapter Six (Part 5)

CHAPTER SIX:
THE 80x86 INSTRUCTION SET (Part 4)
6.6.4 - The Bit Operations
6.6.4.1 - TEST
6.6.4.2 - The Bit Test Instructions: BT, BTS, BTR, and BTC
6.6.4.3 - Bit Scanning: BSF and BSR
6.6.5 - The "Set on Condition" Instructions
6.7 - I/O Instructions
6.8 - String Instructions

6.6.4 The Bit Operations

Bit twiddling is one of those operations easier done in assembly language than other languages. And no wonder. Most high-level languages shield you from the machine representation of the underlying data types. Instructions like and, or, xor, not, and the shifts and rotates make it possible to test, set, clear, invert, and align bit fields within strings of bits. Even the C++ programming language, famous for its bit manipulation operators, doesn't provide the bit manipulation capabilities of assembly language.

The 80x86 family, particularly the 80386 and later processors, go much farther, though. Besides the standard logical, shift, and rotate instructions, there are instructions to test bits within an operand, to test and set, clear, or invert specific bits in an operand, and to search for set bits. These instructions are

        test    dest, source
        bt      source, index
        btc     source, index
        btr     source, index
        bts     source, index
        bsf     dest, source
        bsr     dest, source

The specific forms are

        test    reg, reg
        test    reg, mem
        test    mem, reg                (*)
        test    reg, imm
        test    mem, imm
        test    eax/ax/al, imm

        bt      reg, reg                (3)
        bt      mem, reg                (3)
        bt      reg, imm                (3)
        bt      mem, imm                (3)

        btc uses the same formats as bt. (3)
        btr uses the same formats as bt. (3)
        bts uses the same formats as bt. (3)

        bsf     reg, reg                (3)
        bsr     reg, mem                (3)

        bsr uses the same formats as bsf. (3)

3- This instruction is only available on 80386 and later processors.
*- This is the same instruction as test reg,mem

Note that the bt, btc, btr, bts, bsf, and bsr require 16 or 32 bit operands.

The bit operations are useful when implementing (monochrome) bit mapped graphic primitive functions and when implementing a set data type using bit maps.

6.6.4.1 TEST

The test instruction logically ands its two operands and sets the flags but does not save the result. Test and and share the same relationship as cmp and sub. Typically, you would use this instruction to see if a bit contains one. Consider the following instruction:

		test	al, 1

This instruction logically ands al with the value one. If bit zero of al contains a one, the result is non-zero and the 80x86 clears the zero flag. If bit zero of al contains zero, then the result is zero and the test operation sets the zero flag. You can test the zero flag after this instruction to decide whether al contained zero or one in bit zero.

The test instruction can also check to see if one or more bits in a register or memory location are non-zero. Consider the following instruction:

		test	dx, 105h

This instruction logically ands dx with the value 105h. This will produce a non-zero result (and, therefore, clear the zero flag) if at least one of bits zero, two, or eight contain a one. They must all be zero to set the zero flag.

The test instruction sets the flags identically to the and instruction:

6.6.4.2 The Bit Test Instructions: BT, BTS, BTR, and BTC

On an 80386 or later processor, you can use the bt instruction (bit test) to test a single bit. Its second operand specifies the bit index into the first operand. Bt copies the addressed bit into the carry flag. For example, the instruction

		bt	ax, 12

copies bit twelve of ax into the carry flag.

The bt/bts/btr/btc instructions only deal with 16 or 32 bit operands. This is not a limitation of the instruction. After all, if you want to test bit three of the al register, you can just as easily test bit three of the ax register. On the other hand, if the index is larger than the size of a register operand, the result is undefined.

If the first operand is a memory location, the bt instruction tests the bit at the given offset in memory, regardless the value of the index. For example, if bx contains 65 then

		bt	TestMe, bx

will copy bit one of location TestMe+8 into the carry flag. Once again, the size of the operand does not matter. For all intents and purposes, the memory operand is a byte and you can test any bit after that byte with an appropriate index. The actual bit bt tests is at bit position index mod 8 and at memory offset effective address + index/8.

The bts, btr, and btc instructions also copy the addressed bit into the carry flag. However, these instructions also set, reset (clear), or complement (invert) the bit in the first operand after copying it to the carry flag. This provides test and set, test and clear, and test and invert operations necessary for some concurrent algorithms.

The bt, bts, btr, and btc instructions do not affect any flags other than the carry flag.

6.6.4.3 Bit Scanning: BSF and BSR

The bsf (Bit Scan Forward) and bsr (Bit Scan Reverse) instructions search for the first or last set bit in a 16 or 32 bit quantity. The general form of these instructions is

		bsf	dest, source
		bsr	dest, source

Bsf locates the first set bit in the source operand, searching from bit zero though the H.O. bit. Bsr locates the first set bit searching from the H.O. bit down to the L.O. bit. If these instructions locate a one, they clear the zero flag and store the bit index (0..31) into the destination operand. If the source operand is zero, these instructions set the zero flag and store an indeterminate value into the destination operand.

To scan for the first bit containing zero (rather than one), make a copy of the source operand and invert it (using not), then execute bsf or bsr on the inverted value. The zero flag would be set after this operation if there were no zero bits in the original source value, otherwise the destination operation will contain the position of the first bit containing zero.

6.6.5 The "Set on Condition" Instructions

The set on condition (or setcc) instructions set a single byte operand (register or memory location) to zero or one depending on the values in the flags register. The general formats for the setcc instructions are

                setcc   reg8
                setcc   mem8

Setcc represents a mnemonic appearing in the following tables. These instructions store a zero into the corresponding operand if the condition is false, they store a one into the eight bit operand if the condition is true.

SETcc Instructions That Test Flags
Instruction Description Condition Comments
SETC Set if carry Carry = 1 Same as SETB, SETNAE
SETNC Set if no carry Carry = 0 Same as SETNB, SETAE
SETZ Set if zero Zero = 1 Same as SETE
SETNZ Set if not zero Zero = 0 Same as SETNE
SETS Set if sign Sign = 1 -
SETNS Set if no sign Sign = 0 -
SETO Set if overflow Ovrflw=1 -
SETNO Set if no overflow Ovrflw=0 -
SETP Set if parity Parity = 1 Same as SETPE
SETPE Set if parity even Parity = 1 Same as SETP
SETNP Set if no parity Parity = 0 Same as SETPO
SETPO Set if parity odd Parity = 0 Same as SETNP

The setcc instructions above simply test the flags without any other meaning attached to the operation. You could, for example, use setc to check the carry flag after a shift, rotate, bit test, or arithmetic operation. Likewise, you could use setnz instruction after a test instruction to check the result.

The cmp instruction works synergistically with the setcc instructions. Immediately after a cmp operation the processor flags provide information concerning the relative values of those operands. They allow you to see if one operand is less than, equal to, greater than, or any combination of these.

There are two groups of setcc instructions that are very useful after a cmp operation. The first group deals with the result of an unsigned comparison, the second group deals with the result of a signed comparison.

SETcc Instructions for Unsigned Comparisons
Instruction Description Condition Comments
SETA Set if above (>) Carry=0, Zero=0 Same as SETNBE
SETNBE Set if not below or equal (not <=) Carry=0, Zero=0 Same as SETA
SETAE Set if above or equal (>=) Carry = 0 Same as SETNC, SETNB
SETNB Set if not below (not <) Carry = 0 Same as SETNC, SETAE
SETB Set if below (<) Carry = 1 Same as SETC, SETNAE
SETNAE Set if not above or equal (not >=) Carry = 1 Same as SETC, SETB
SETBE Set if below or equal (<=) Carry = 1 or Zero = 1 Same as SETNA
SETNA Set if not above (not >) Carry = 1 or Zero = 1 Same as SETBE
SETE Set if equal (=) Zero = 1 Same as SETZ
SETNE Set if not equal () Zero = 0 Same as SETNZ

The corresponding table for signed comparisons is

SETcc Instructions for Signed Comparisons
Instruction Description Condition Comments
SETG Set if greater (>) Sign = Ovrflw or Zero=0 Same as SETNLE
SETNLE Set if not less than or equal (not <=) Sign = Ovrflw or Zero=0   Same as SETG
SETGE Set if greater than or equal (>=) Sign = Ovrflw Same as SETNL
SETNL Set if not less than (not <) Sign = Ovrflw Same as SETGE
SETL Set if less than (<) Sign Ovrflw Same as SETNGE
SETNGE Set if not greater or equal (not >=) Sign Ovrflw   Same as SETL
SETLE Set if less than or equal (<=) Sign Ovrflw or Zero = 1 Same as SETNG
SETNG Set if not greater than (not >) Sign Ovrflw or Zero = 1 Same as SETLE
SETE Set if equal (=) Zero = 1 Same as SETZ
SETNE Set if not equal () Zero = 0 Same as SETNZ

The setcc instructions are particularly valuable because they can convert the result of a comparison to a boolean value (true/false or 0/1). This is especially important when translating statements from a high level language like Pascal or C++ into assembly language. The following example shows how to use these instructions in this manner:

; Bool := A <= B

                mov     ax, A   ;Assume A and B are signed integers.
                cmp     ax, B
                setle   Bool    ;Bool needs to be a byte variable.

Since the setcc instructions always produce zero or one, you can use the results with the logical and and or instructions to compute complex boolean values:

; Bool := ((A <= B) and (D = E)) or (F <> G)

                mov     ax, A
                cmp     ax, B
                setle   bl
                mov     ax, D
                cmp     ax, E
                sete    bh
                and     bl, bh
                mov     ax, F
                cmp     ax, G
                setne   bh
                or      bl, bh
                mov     Bool, bh

For more examples, see Chapter Nine.

The setcc instructions always produce an eight bit result since a byte is the smallest operand the 80x86 will operate on. However, you can easily use the shift and rotate instructions to pack eight boolean values in a single byte. The following instructions compare eight different values with zero and copy the "zero flag" from each comparison into corresponding bits of al:

                cmp     Val7, 0
                setne   al              ;Put first value in bit #0
                cmp     Val6, 0         ;Test the value for bit #6
                setne   ah              ;Copy zero flag into ah register.
                shr     ah, 1           ;Copy zero flag into carry.
                rcl     al, 1           ;Shift carry into result byte.
                cmp     Val5, 0         ;Test the value for bit #5
                setne   ah
                shr     ah, 1
                rcl     al, 1
                cmp     Val4, 0         ;Test the value for bit #4
                setne   ah
                shr     ah, 1
                rcl     al, 1
                cmp     Val3, 0         ;Test the value for bit #3
                setne   ah
                shr     ah, 1
                rcl     al, 1
                cmp     Val2, 0         ;Test the value for bit #2
                setne   ah
                shr     ah, 1
                rcl     al, 1
                cmp     Val1, 0         ;Test the value for bit #1
                setne   ah
                shr     ah, 1
                rcl     al, 1
                cmp     Val0, 0         ;Test the value for bit #0
                setne   ah
                shr     ah, 1
                rcl     al, 1

; Now AL contains the zero flags from the eight comparisons.
6.7 I/O Instructions

The 80x86 supports two I/O instructions: in and out. They take the forms:

                in      eax/ax/al, port
                in      eax/ax/al, dx
                out     port, eax/ax/al
                out     dx, eax/ax/al

port is a value between 0 and 255.

The 80x86 supports up to 65,536 different I/O ports (requiring a 16 bit I/O address). The port value above, however, is a single byte value. Therefore, you can only directly address the first 256 I/O ports in the 80x86's I/O address space. To address all 65,536 different I/O ports, you must load the address of the desired port (assuming it's above 255) into the dx register and access the port indirectly. The in instruction reads the data at the specified I/O port and copies it into the accumulator. The out instruction writes the value in the accumulator to the specified I/O port.

Please realize that there is nothing magical about the 80x86's in and out instructions. They're simply another form of the mov instruction that accesses a different memory space (the I/O address space) rather than the 80x86's normal 1 Mbyte memory address space.

The in and out instructions do not affect any 80x86 flags.

Examples of the 80x86 I/O instructions:

                in      al, 60h         ;Read keyboard port

                mov     dx, 378h        ;Point at LPT1: data port
                in      al, dx          ;Read data from printer port.
                inc     ax              ;Bump the ASCII code by one.
                out     dx, al          ;Write data in AL to printer port.
6.8 String Instructions

The 80x86 supports twelve string instructions:

You can use the movs, stos, scas, cmps, ins and outs instructions to manipulate a single element (byte, word, or double word) in a string, or to process an entire string. Generally, you would only use the lods instruction to manipulate a single item at a time.

These instructions can operate on strings of bytes, words, or double words. To specify the object size, simply append a b, w, or d to the end of the instruction's mnemonic, i.e., lodsb, movsw, cmpsd, etc. Of course, the double word forms are only available on 80386 and later processors.

The movs and cmps instructions assume that ds:si contains the segmented address of a source string and that es:di contains the segmented address of a destination string. The lods instruction assumes that ds:si points at a source string, the accumulator (al/ax/eax) is the destination location. The scas and stos instructions assume that es:di points at a destination string and the accumulator contains the source value.

The movs instruction moves one string element (byte, word, or dword) from memory location ds:si to es:di. After moving the data, the instruction increments or decrements si and di by one, two, or four if processing bytes, words, or dwords, respectively. The CPU increments these registers if the direction flag is clear, the CPU decrements them if the direction flag is set.

The movs instruction can move blocks of data around in memory. You can use it to move strings, arrays, and other multi-byte data structures.

movs{b,w,d}:    es:[di] := ds:[si]
                if direction_flag = 0 then
                        si := si + size;
                        di := di + size;
                else
                        si := si - size;
                        di := di - size;
                endif;

Note: size is one for bytes, two for words, and four for dwords.

The cmps instruction compares the byte, word, or dword at location ds:si to es:di and sets the processor flags accordingly. After the comparison, cmps increments or decrements si and di by one, two, or four depending on the size of the instruction and the status of the direction flag in the flags register.

cmps{b,w,d}:    cmp ds:[si], es:[di]
                if direction_flag = 0 then
                        si := si + size;
                        di := di + size;
                else
                        si := si - size;
                        di := di - size;
                endif;

The lods instruction moves the byte, word, or dword at ds:si into the al, ax, or eax register. It then increments or decrements the si register by one, two, or four depending on the instruction size and the value of the direction flag. The lods instruction is useful for fetching a sequence of bytes, words, or double words from an array, performing some operation(s) on those values and then processing the next element from the string.

lods{b,w,d}:    eax/ax/al := ds:[si]
                if direction_flag = 0 then
                        si := si + size;
                else
                        si := si - size;
                endif;

The stos instruction stores al, ax, or eax at the address specified by es:di. Again, di is incremented or decremented according to the size of the instruction and the value of the direction flag. The stos instruction has several uses. Paired with the lods instruction above, you can load (via lods), manipulate, and store string elements. By itself, the stos instruction can quickly store a single value throughout a multi-byte data structure.

stos{b,w,d}:    es:[di] := eax/ax/al
                if direction_flag = 0 then
                        di := di + size;
                else
                        di := di - size;
                endif;

The scas instruction compares al, ax or eax against the value at location es:di and then adjusts di accordingly. This instruction sets the flags in the processor status register just like the cmp and cmps instructions. The scas instruction is great for searching for a particular value throughout some multi-byte data structure.

scas{b,w,d}:    cmp eax/ax/al, es:[di]
                if direction_flag = 0 then
                        di := di + size;
                else
                        di := di - size;
                endif;

The ins instruction inputs a byte, word, or double word from the I/O port specified in the dx register. It then stores the input value at memory location es:di and increments or decrements di appropriately. This instruction is available only on 80286 and later processors.

ins{b,w,d}:     es:[di] := port(dx)
                if direction_flag = 0 then
                        di := di + size;
                else
                        di := di - size;
                endif;

The outs instruction fetches the byte, word, or double word at address ds:si, increments or decrements si accordingly, and then outputs the value to the port specified in the dx register.

outs{b,w,d}:    port(dx) := ds:[si]
                if direction_flag = 0 then
                        si := si + size;
                else
                        si := si - size;
                endif;

As explained here, the string instructions are useful, but it gets even better! When combined with the rep, repz, repe, repnz, and repne prefixes, a single string instruction can process an entire string. For more information on these prefixes see the chapter on strings.

Chapter Six (Part 3)

Table of Content

Chapter Six (Part 5)

Chapter Six: The 80x86 Instruction Set (Part 4)
26 SEP 1996