|
Table of Content | Chapter Six (Part 5) |
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.
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.
| 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.
| 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
| 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.
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.
The 80x86 supports twelve string instructions:
movs (move string) lods (load string element into the accumulator)
stos (store accumulator into string element) scas (Scan string and check for match against
the value in the accumulator) cmps (compare two strings). ins (input a string from an I/O port) outs (output a string to an I/O port rep (repeat a string operation) repz (repeat while zero) repe (repeat while equal) repnz (repeat while not zero) repne (repeat while not equal) 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.
|
Table of Content | Chapter Six (Part 5) |
Chapter Six: The 80x86 Instruction
Set (Part 4)
26 SEP 1996