6.2 80x86 Integer Arithmetic Instructions


6.2 80x86 Integer Arithmetic Instructions

Before describing how to encode arithmetic expressions in assembly language, it would be a good idea to first discuss the remaining arithmetic instructions in the 80x86 instruction set. Previous chapters have covered most of the arithmetic and logical instructions, so this section will cover the few remaining instructions you'll need.

6.2.1 The MUL and IMUL Instructions

The multiplication instructions provide you with another taste of irregularity in the 80x86's instruction set. Instructions like add, sub, and many others in the 80x86 instruction set support two operands, just like the mov instruction. Unfortunately, there weren't enough bits in the 80x86's opcode byte to support all instructions, so the 80x86 treats the mul (unsigned multiply) and imul (signed integer multiply) instructions as single operand instructions, just like the inc, dec, and neg instructions.

Of course, multiplication is a two operand function. To work around this fact, the 80x86 always assumes the accumulator (AL, AX, or EAX) is the destination operand. This irregularity makes using multiplication on the 80x86 a little more difficult than other instructions because one operand has to be in the accumulator. Intel adopted this unorthogonal approach because they felt that programmers would use multiplication far less often than instructions like add and sub.

Another problem with the mul and imul instructions is that you cannot multiply the accumulator by a constant using these instructions. Intel quickly discovered the need to support multiplication by a constant and added the intmul instruction to overcome this problem. Nevertheless, you must be aware that the basic mul and imul instructions do not support the full range of operands that intmul does.

There are two forms of the multiply instruction: unsigned multiplication (mul) and signed multiplication (imul). Unlike addition and subtraction, you need separate instructions for signed and unsigned operations.

The multiply instructions take the following forms:

Unsigned Multiplication:

      mul( reg8 );     // returns "ax"      mul( reg16 );    // returns "dx:ax"      mul( reg32 );    // returns "edx:eax"      mul( mem8 );     // returns "ax"      mul( mem16 );    // returns "dx:ax"      mul( mem32 );    // returns "edx:eax" 

Signed (Integer) Multiplication:

      imul( reg8 );    // returns "ax"      imul( reg16 );   // returns "dx:ax"      imul( reg32 );   // returns "edx:eax"      imul( mem8 );    // returns "ax"      imul( mem16 );   // returns "dx:ax"      imul( mem32 );   // returns "edx:eax" 

The "returns" values above are the strings these instructions return for use with instruction composition in HLA. (i)mul, available on all 80x86 processors, multiplies 8-, 16-, or 32-bit operands.

When multiplying two n-bit values, the result may require as many as 2*n bits. Therefore, if the operand is an 8-bit quantity, the result could require 16 bits. Likewise, a 16-bit operand produces a 32-bit result and a 32-bit operand requires 64 bits to hold the result.

The (i)mul instruction, with an 8-bit operand, multiplies AL by the operand and leaves the 16-bit product in AX. So

        mul( operand8 ); or     imul( operand8 ); 

computes

      AX := AL * operand8 

"*" represents an unsigned multiplication for mul and a signed multiplication for imul.

If you specify a 16-bit operand, then mul and imul compute

      DX:AX := AX * operand16 

"*" has the same meanings as the preceding computation and DX:AX means that DX contains the H.O. word of the 32-bit result and AX contains the L.O. word of the 32-bit result. If you're wondering why Intel didn't put the 32-bit result in EAX, just note that Intel introduced the mul and imul instructions in the earliest 80x86 processors, before the advent of 32-bit registers in the 80386 CPU.

If you specify a 32-bit operand, then mul and imul compute the following:

      EDX:EAX := EAX * operand32 

"*" has the same meanings as again, and EDX:EAX means that EDX contains the H.O. double word of the 64-bit result and EAX contains the L.O. double word of the 64-bit result.

If an 8x8, 16x16, or 32x32-bit product requires more than 8, 16, or 32 bits (respectively), the mul and imul instructions set the carry and overflow flags. mul and imul scramble the sign and zero flags.

Note

Especially note that the sign and zero flags do not contain meaningful values after the execution of these two instructions.

To help reduce some of the syntax irregularities with the use of the mul and imul instructions, HLA provides an extended syntax that allows the following twooperand forms:

Unsigned Multiplication:

      mul( reg8, al );      mul( reg16, ax );      mul( reg32, eax );      mul( mem8, al );      mul( mem16, ax );      mul( mem32, eax );      mul( constant8, al );       mul( constant16, ax );       mul( constant32, eax ); 

Signed (Integer) Multiplication:

      imul( reg8, al );      imul( reg16, ax );      imul( reg32, eax );      imul( mem8, al );      imul( mem16, ax );      imul( mem32, eax );      imul( constant8, al );      imul( constant16, ax );      imul( constant32, eax ); 

The two operand forms let you specify the (L.O.) destination register as the second operand. By specifying the destination register you can make your programs easier to read. Note that just because HLA allows two operands here, you can't specify an arbitrary register. The destination operand must always be AL, AX, or EAX, depending on the source operand.

HLA provides a form that lets you specify a constant. The 80x86 doesn't actually support a mul or imul instruction that has a constant operand. HLA will take the constant you specify and create a "variable" in a read-only segment in memory and initialize that variable with this value. Then HLA converts the instruction to the "(i)mul( memory );" instruction. Note that when you specify a constant as the source operand, the instruction requires two operands (because HLA uses the second operand to determine whether the multiplication is 8, 16, or 32 bits).

You'll use the mul and imul instructions quite a bit when you learn about extended precision arithmetic in the chapter on Advanced Arithmetic. Unless you're doing multiprecision work, however, you'll probably just want to use the intmul instruction in place of the mul or imul because it is more general. However, intmul is not a complete replacement for these two instructions. Besides the number of operands, there are several differences between the intmul and the mul/imul instructions. Specifically for the intmul instruction:

  • There isn't an 8x8-bit intmul instruction available.

  • The intmul instruction does not produce a 2*n bit result. That is, a 16x16 multiply produces a 16-bit result. Likewise, a 32x32-bit multiply produces a 32-bit result. These instructions set the carry and overflow flags if the result does not fit into the destination register.

6.2.2 The DIV and IDIV Instructions

The 80x86 divide instructions perform a 64/32 division, a 32/16 division, or a 16/8 division. These instructions take the following form:

      div( reg8 );                // returns "al"      div( reg16 );               // returns "ax"      div( reg32 );               // returns "eax"      div( reg8, AX );            // returns "al"      div( reg16, DX:AX );      div( reg32, EDX:EAX );      div( mem8 );                // returns "al"      div( mem16 );               // returns "ax"      div( mem32 );               // returns "eax"      div( mem8, AX );            // returns "al"      div( mem16, DX:AX );        // returns "ax"      div( mem32, EDX:EAX );      // returns "eax"      div( constant8, AX );       // returns "al"      div( constant16, DX:AX );   // returns "ax"      div( constant32, EDX:EAX ); // returns "eax"      idiv( reg8 );                // returns "al"      idiv( reg16 );               // returns "ax"      idiv( reg32 );               // returns "eax"      idiv( reg8, AX );            // returns "al"      idiv( reg16, DX:AX );        // returns "ax"      idiv( reg32, EDX:EAX );      // returns "eax"      idiv( mem8 );                // returns "al"      idiv( mem16 );               // returns "ax"      idiv( mem32 );               // returns "eax"      idiv( mem8, AX );            // returns "al"      idiv( mem16, DX:AX );        // returns "ax"      idiv( mem32, EDX:EAX );      // returns "eax"      idiv( constant8, AX );       // returns "al"      idiv( constant16, DX:AX );   // returns "ax"      idiv( constant32, EDX:EAX ); // returns "eax" 

The div instruction computes an unsigned division. If the operand is an 8-bit operand, div divides the AX register by the operand leaving the quotient in AL and the remainder (modulo) in AH. If the operand is a 16-bit quantity, then the div instruction divides the 32-bit quantity in DX:AX by the operand leaving the quotient in AX and the remainder in DX. With 32-bit operands div divides the 64-bit value in EDX:EAX by the operand leaving the quotient in EAX and the remainder in EDX. Like mul and imul, HLA provides special syntax to allow the use of constant operands even though these instructions don't really support them.

You cannot, on the 80x86, simply divide one 8-bit value by another. If the denominator is an 8-bit value, the numerator must be a 16-bit value. If you need to divide one unsigned 8-bit value by another, you must zero extend the numerator to 16 bits. You can accomplish this by loading the numerator into the AL register and then moving zero into the AH register. Then you can divide AX by the denominator operand to produce the correct result. Failing to zero extend AL before executing div may cause the 80x86 to produce incorrect results! When you need to divide two 16-bit unsigned values, you must zero extend the AX register (which contains the numerator) into the DX register. To do this, just load zero into the DX register. If you need to divide one 32-bit value by another, you must zero extend the EAX register into EDX (by loading a zero into EDX) before the division.

When dealing with signed integer values, you will need to sign extend AL into AX, AX into DX, or EAX into EDX before executing idiv. To do so, use the cbw, cwd, cdq, or movsx instructions. If the H.O. byte, word, or double word does not already contain significant bits, then you must sign extend the value in the accumulator (AL/AX/EAX) before doing the idiv operation. Failure to do so may produce incorrect results.

There is one other issue with the 80x86's divide instructions: You can get a fatal error when using this instruction. First, of course, you can attempt to divide a value by zero. The second problem is that the quotient may be too large to fit into the EAX, AX, or AL register. For example, the 16/8 division "$8000 / 2" produces the quotient $4000 with a remainder of zero. $4000 will not fit into 8-bits. If this happens, or you attempt to divide by zero, the 80x86 will generate an ex.DivisionError exception or integer overflow error (ex.IntoInstr). This usually means your program will display the appropriate dialog box and abort your program. If this happens to you, chances are you didn't sign or zero extend your numerator before executing the division operation. Because this error may cause your program to crash, you should be very careful about the values you select when using division. Of course, you can use the try..endtry block with the ex.DivisionError and ex.IntoInstr to trap this problem in your program.

The 80x86 leaves the carry, overflow, sign, and zero flags undefined after a division operation. Therefore, you cannot test for problems after a division operation by checking the flag bits.

The 80x86 does not provide a separate instruction to compute the remainder of one number divided by another. The div and idiv instructions automatically compute the remainder at the same time they compute the quotient. HLA, however, provides mnemonics (instructions) for the mod and imod instructions. These special HLA instructions compile into the exact same code as their div and idiv counterparts. The only difference is the "returns" value for the instruction (because these instructions return the remainder in a different location than the quotient). The mod and imod instructions that HLA supports are

      mod( reg8 );                 // returns "ah"      mod( reg16 );                // returns "dx"      mod( reg32 );                // returns "edx"      mod( reg8, AX );             // returns "ah"      mod( reg16, DX:AX );         // returns "dx"      mod( reg32, EDX:EAX );       // returns "edx"      mod( mem8 );                 // returns "ah"      mod( mem16 );                // returns "dx"      mod( mem32 );                // returns "edx"      mod( mem8, AX );             // returns "ah"      mod( mem16, DX:AX );         // returns "dx"      mod( mem32, EDX:EAX );       // returns "edx"      mod( constant8, AX );        // returns "ah"      mod( constant16, DX:AX );    // returns "dx"      mod( constant32, EDX:EAX );  // returns "edx"      imod( reg8 );                // returns "ah"      imod( reg16 );               // returns "dx"      imod( reg32 );               // returns "edx"      imod( reg8, AX );            // returns "ah"      imod( reg16, DX:AX );        // returns "dx"      imod( reg32, EDX:EAX );      // returns "edx"      imod( mem8 );                // returns "ah"      imod( mem16 );               // returns "dx"      imod( mem32 );               // returns "edx"      imod( mem8, AX );            // returns "ah"      imod( mem16, DX:AX );        // returns "dx"      imod( mem32, EDX:EAX );      // returns "edx"      imod( constant8, AX );       // returns "ah"      imod( constant16, DX:AX );   // returns "dx"      imod( constant32, EDX:EAX ); // returns "edx" 

6.2.3 The CMP Instruction

The cmp (compare) instruction is identical to the sub instruction with one crucial semantic difference: It does not retain the difference it computes; it only sets the condition code bits in the flags register. The syntax for the cmp instruction is similar to sub (though the operands are reversed so it reads better), the generic form is

      cmp( LeftOperand, RightOperand ); 

This instruction computes "LeftOperand - RightOperand" (note the reversal from sub). The specific forms are

      cmp( reg, reg );           // Registers must be the same size (8, 16,                                 // or 32 bits)      cmp( reg, mem );           // Sizes must match.      cmp( reg, constant );      cmp( mem, constant ); 

Note that both operands are "source" operands, so the fact that a constant appears as the second operand is okay.

The cmp instruction updates the 80x86's flags according to the result of the subtraction operation (LeftOperand - RightOperand). The 80x86 sets the flags in an appropriate fashion so that we can read this instruction as "compare LeftOperand to RightOperand." You can test the result of the comparison by checking the appropriate flags in the flags register using the conditional set instructions (see the next section) or the conditional jump instructions (see the chapter on low level control structures).

Probably the first place to start when exploring the cmp instruction is to take a look at exactly how the cmp instruction affects the flags. Consider the following cmp instruction:

      cmp( ax, bx ); 

This instruction performs the computation AX - BX and sets the flags depending upon the result of the computation. The flags are set as follows (also see Table 6-1):

  • Z: The zero flag is set if and only if AX = BX. This is the only time AX - BX produces a zero result. Hence, you can use the zero flag to test for equality or inequality.

  • S: The sign flag is set to one if the result is negative. At first glance, you might think that this flag would be set if AX is less than BX but this isn't always the case. If AX=$7FFF and BX= -1 ($FFFF), subtracting AX from BX produces $8000, which is negative (and so the sign flag will be set). So, for signed comparisons, anyway, the sign flag doesn't contain the proper status. For unsigned operands, consider AX=$FFFF and BX=1. AX is greater than BX, but their difference is $FFFE, which is still negative. As it turns out, the sign flag and the overflow flag, taken together, can be used for comparing two signed values.

  • O: The overflow flag is set after a cmp operation if the difference of AX and BX produced an overflow or underflow. As mentioned above, the sign flag and the overflow flag are both used when performing signed comparisons.

  • C: The carry flag is set after a cmp operation if subtracting BX from AX requires a borrow. This occurs only when AX is less than BX where AX and BX are both unsigned values.

Table 6-1: Condition Code Settings After CMP

Unsigned Operands

Signed Operands


Z: equality/inequality

Z: equality/inequality

C: Left < Right (C=1)
Left > Right (C=0)

C: no meaning

S: no meaning

S: see discussion in this section

O: no meaning

O: see discussion in this section

Given that the cmp instruction sets the flags in this fashion, you can test the comparison of the two operands with the following flags:

 cmp( Left, Right ); 

For signed comparisons, the S (sign) and O (overflow) flags, taken together, have the following meaning:

  • If ((S=0) and (O=1)) or ((S=1) and (O=0)) then Left < Right when using a signed comparison.

  • If ((S=0) and (O=0)) or ((S=1) and (O=1)) then Left >= Right when using a signed comparison.

Note that (S xor O) is one if the left operand is less than the right operand. Conversely, (S xor O) is zero if the left operand is greater or equal to the right operand.

To understand why these flags are set in this manner, consider the following examples:

 Left           minus      Right           S      O ------                    ------          -      - $FFFF (-1)      -         $FFFE (-2)      0      0 $8000           -         $0001           0      1 $FFFE (-2)      -         $FFFF (-1)      1      0 $7FFF (32767)   -         $FFFF (-1)      1      1 

Remember, the cmp operation is really a subtraction; therefore, the first example above computes (-1)-(-2) which is (+1). The result is positive and an overflow did not occur, so both the S and O flags are zero. Because (S xor O) is zero, Left is greater than or equal to Right.

In the second example, the cmp instruction would compute (-32768)-(+1), which is (-32769). Because a 16-bit signed integer cannot represent this value, the value wraps around to $7FFF (+32767) and sets the overflow flag. The result is positive (at least as a 16-bit value), so the CPU clears the sign flag. (S xor O) is one here, so Left is less than Right.

In the third example, cmp computes (-2)-(-1), which produces (-1). No overflow occurred so the O flag is zero, the result is negative so the sign flag is one. Because (S xor O) is one, Left is less than Right.

In the fourth (and final) example, cmp computes (+32767)-(-1). This produces (+32768), setting the overflow flag. Furthermore, the value wraps around to $8000 (-32768) so the sign flag is set as well. Because (S xor O) is zero, Left is greater than or equal to Right.

You may test the flags after a cmp instruction using HLA high level control statements and the boolean flag expressions (e.g., @c, @nc, @z, @nz, @o, @no, @s, @ns, and so on). Table 6-2 lists the boolean expressions HLA supports that let you check various conditions after a compare instruction.

Table 6-2: HLA Condition Code Boolean Expressions

HLA Syntax

Condition

Comment


@c

Carry set

Carry flag is set if the first operand is less than the second operand (unsigned). Same condition as @b and @nae.

@nc

Carry clear (no carry)

Carry flag is clear if the first operand is greater than or equal to the second (using an unsigned comparison). Same condition as @nb and @ae.

@z

Zero flag set

Zero flag is set if the first operand equals the second operand. Same condition as @e.

@nz

Zero flag clear (no zero)

Zero flag is clear if the first operand is not equal to the second. Same condition as @ne.

@o

Overflow flag set

This flag is set if there was a signed arithmetic overflow as a result of the comparison operation.

@no

Overflow flag clear (no overflow)

The overflow flag is clear if there was no signed arithmetic overflow during the compare operation.

@s

Sign flag set

The sign flag is set if the result of the compare (subtraction) produces a negative result.

@ns

Sign flag clear (no sign)

The sign flag is clear if the compare operation produces a non-negative (zero or positive) result.

@a

Above (unsigned greater than)

The @a condition checks the carry and zero flags to see if @c=0 and @z=0. This condition exists if the first (unsigned) operand is greater than the second (unsigned) operand. This is the same condition as @nbe.

@na

Not above

The @na condition checks to see if the carry flag is set (@c) or the zero flag is set (@z). This is equivalent to an unsigned "not greater than" condition. Note that this condition is the same as @be.

@ae

Above or equal (unsigned greater than or equal)

The @ae condition is true if the first operand is greater than or equal to the second using an unsigned comparison. This is equivalent to the @nb and @nc conditions.

@nae

Not above or equal

The @nae condition is true if the first operand is not greater than or equal to the second using an unsigned comparison. This is equivalent to the @b and @c conditions.

@b

Below (unsigned less than)

The @b condition is true if the first operand is less than the second using an unsigned comparison. This is equivalent to the @nae and @c conditions.

@nb

Not below

This condition is true if the first operand is not less than the second using an unsigned comparison. This condition is equivalent to the @nc and @ae conditions.

@be

Below or equal (unsigned less than or equal)

The @be condition is true when the first operand is less than or equal to the second using an unsigned comparison. This condition is equivalent to @na.

@nbe

Not below or equal

The @be condition is true when the first operand is not less than or equal to the second using an unsigned comparison. This condition is equivalent to @a.

@g

Greater (signed greater than)

The @g condition is true if the first operand is greater than the second using a signed comparison. This is equivalent to the @nle condition.

@ng

Not greater

The @ng condition is true if the first operand is not greater than the second using a signed comparison. This is equivalent to the @le condition.

@ge

Greater or equal (signed greater than or equal)

The @ge condition is true if the first operand is greater than or equal to the second using a signed comparison. This is equivalent to the @nl condition.

@nge

Not greater or equal

The @nge condition is true if the first operand is not greater than or equal to the second using a signed comparison. This is equivalent to the @l condition.

@l

Less than (signed less than)

The @l condition is true if the first operand is less than the second using a signed comparison. This is equivalent to the @nge condition.

@nl

Not less than

The @ng condition is true if the first operand is not less than the second using a signed comparison. This is equivalent to the @ge condition.

@le

Less than or equal (signed)

The @le condition is true if the first operand is less than or equal to the second using a signed comparison. This is equivalent to the @ng condition.

@nle

Not less than or equal

The @nle condition is true if the first operand is not less than or equal to the second using a signed comparison. This is equivalent to the @g condition

@e

Equal (signed or unsigned)

This condition is true if the first operand equals the second. The @e condition is equivalent to the @z condition.

@ne

Not equal (signed or unsigned)

@ne is true if the first operand does not equal the second. This condition is equivalent to @nz.

You may use the boolean conditions appearing in Table 6-2 within an if statement, while statement, or any other HLA high level control statement that allows boolean expressions. Immediately after the execution of a cmp instruction, you would typically use one of these conditions in an if statement, for example:

      cmp( eax, ebx );      if( @e ) then           << do something if eax = ebx >>      endif; 

Note that the example above is equivalent to:

      if( eax = ebx ) then           << do something if eax = ebx >>      endif; 

6.2.4 The SETcc Instructions

The set on condition (or setcc) instructions set a single byte operand (register or memory) 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 Tables 6-3, 6-4, and 6-5, respectively. These instructions store a zero into the corresponding operand if the condition is false; they store a one into the 8-bit operand if the condition is true.

Table 6-3: SET CC 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

Table 6-4: 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 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. You might notice the setp, setpe, and setnp instructions above. They check the parity flag. These instructions appear here for completeness, but this text will not spend too much time discussing parity flag (its use is somewhat obsolete).

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 additional 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.

Table 6-5 lists the corresponding signed comparisons.

Table 6-5: SETcc Instructions for Signed Comparisons

Instruction

Description

Condition

Comments


SETG

Set if greater (>)

Sign = Ovrflw and 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

Note the correspondence between the setcc instructions and the HLA flag conditions that may appear in boolean instructions.

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

 // Bool := A <= B           mov( A, eax );           cmp( eax, B );           setle( bool );           // bool is a boolean or byte                                    // variable. 

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

 // Bool := ((A <= B) and (D = E))           mov( A, eax );           cmp( eax, B );           setle( bl );           mov( D, eax );           cmp( eax, E );           sete( bh );           and( bl, bh );           mov( bh, Bool ); 

6.2.5 The TEST Instruction

The 80x86 test instruction is to the and instruction what the cmp instruction is to sub. That is, the test instruction computes the logical AND of its two operands and sets the condition code flags based on the result; it does not, however, store the result of the logical AND back into the destination operand. The syntax for the test instruction is similar to and; it is

 test( operand1, operand2 ); 

The test instruction sets the zero flag if the result of the logical AND operation is zero. It sets the sign flag if the H.O. bit of the result contains a one. test always clears the carry and overflow flags.

The primary use of the test instruction is to check to see if an individual bit contains a zero or a one. Consider the instruction "test( 1, AL);". This instruction logically ANDs AL with the value one; if bit one of AL contains zero, the result will be zero (setting the zero flag) because all the other bits in the constant one are zero. Conversely, if bit one of AL contains one, then the result is not zero, so test clears the zero flag. Therefore, you can test the zero flag after this test instruction to see if bit zero contains a zero or a one (e.g., using a setz or setnz instruction).

The test instruction can also check to see if all the bits in a specified set of bits contain zero. The instruction "test( $F, AL);" sets the zero flag if and only if the L.O. four bits of AL all contain zero.

One very important use of the test instruction is to check to see if a register contains zero. The instruction "test( reg, reg );" where both operands are the same register will logically AND that register with itself. If the register contains zero, then the result is zero and the CPU will set the zero flag. However, if the register contains a non-zero value, logically ANDing that value with itself produces that same non-zero value, so the CPU clears the zero flag. Therefore, you can test the zero flag immediately after the execution of this instruction (e.g., using the setz or setnz instructions or the @z and @nz boolean conditions) to see if the register contains zero. Examples:

      test( eax, eax );      setz( bl ); // BL is set to one if EAX contains zero.           .           .           .      test( bx, bx );      if( @nz ) then           << do something if bx <> 0 >>      endif; 




The Art of Assembly Language
The Art of Assembly Language
ISBN: 1593272073
EAN: 2147483647
Year: 2005
Pages: 246
Authors: Randall Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net