Chapter 7

Controlling Program Flow


Very few programs execute all lines sequentially from .STARTUP to .EXIT. Rather, complex program logic and efficiency dictate that you control the flow of your program jumping from one point to another, repeating an action until a condition is reached, and passing control to and from procedures. This chapter describes various ways for controlling program flow and several features that simplify coding program-control constructs.

The first section covers jumps from one point in the program to another. It explains how MASM 6.1 optimizes both unconditional and conditional jumps under certain circumstances, so that you do not have to specify every attribute. The section also describes instructions you can use to test conditional jumps.

The next section describes loop structures that repeat actions or evaluate conditions. It discusses MASM directives, such as .WHILE and .REPEAT, that generate appropriate compare, loop, and jump instructions for you, and the .IF, .ELSE, and .ELSEIF directives that generate jump instructions.

The “Procedures” section in this chapter explains how to write an assembly-language procedure. It covers the extended functionality for PROC, a PROTO directive that lets you write procedure prototypes similar to those used in C, an INVOKE directive that automates parameter passing, and options for the stack-frame setup inside procedures.

The last section explains how to pass program control to an interrupt routine.

Jumps

Jumps are the most direct way to change program control from one location to another. At the processor level, jumps work by changing the value of the IP (Instruction Pointer) register to a target offset and, for far jumps, by changing the CS register to a new segment address. Jump instructions fall into only two categories: conditional and unconditional.

Unconditional Jumps

The JMP instruction transfers control unconditionally to another instruction. JMP’s single operand contains the address of the target instruction.

Unconditional jumps skip over code that should not be executed, as shown here:

; Handle one case
label1: .
        .
        .
        jmp continue

; Handle second case
label2: .
        .
        .
        jmp continue
        .
        .
        .
continue:

The distance of the target from the jump instruction and the size of the operand determine the assembler’s encoding of the instruction. The longer the distance, the more bytes the assembler uses to code the instruction. In versions of MASM prior to 6.0, unconditional NEAR jumps sometimes generated inefficient code, but MASM can now optimize unconditional jumps.

Jump Optimizing

The assembler determines the smallest encoding possible for the direct unconditional jump. MASM does not require a distance operator, so you do not have to determine the correct distance of the jump. If you specify a distance, it overrides any assembler optimization. If the specified distance falls short of the target address, the assembler generates an error. If the specified distance is longer than the jump requires, the assembler encodes the given distance and does not optimize it.

The assembler optimizes jumps when the following conditions are met:

  You do not specify SHORT, NEAR, FAR, NEAR16, NEAR32, FAR16, FAR32, or PROC as the distance of the target.

  The target of the jump is not external and is in the same segment as the jump instruction. If the target is in a different segment (but in the same group), it is treated as though it were external.

 

If these two conditions are met, MASM uses the instruction, distance, and size of the operand to determine how to optimize the encoding for the jump. No syntax changes are necessary.

 

Note

This information about jump optimizing also applies to conditional jumps on the 80386/486.

 

Indirect Operands

An indirect operand provides a pointer to the target address, rather than the address itself. A pointer is a variable that contains an address. The processor distinguishes indirect (pointer) operands from direct (address) operands by the instruction’s context.

You can specify the pointer’s size with the WORD, DWORD, or FWORD attributes. Default sizes are based on .MODEL and the default segment size.

        jmp     [bx]          ; Uses .MODEL and segment size defaults
        jmp     WORD PTR [bx] ; A NEAR16 indirect call

If the indirect operand is a register, the jump is always a NEAR16 jump for a 16-bit register, and NEAR32 for a 32-bit register:

        jmp     bx            ; NEAR16 jump
        jmp     ebx           ; NEAR32  jump

A DWORD indirect operand, however, is ambiguous to the assembler.

jmp     DWORD PTR [var]       ; A NEAR32 jump in a 32-bit segment;
                              ; a FAR16 jump in a 16-bit segment

In this case, your code must clear the ambiguity with the NEAR32 or FAR16 keywords. The following example shows how to use TYPEDEF to define NEAR32 and FAR16 pointer types.

NFP     TYPEDEF PTR NEAR32
FFP     TYPEDEF PTR FAR16
        jmp     NFP PTR [var] ; NEAR32 indirect jump
        jmp     FFP PTR [var] ; FAR16  indirect jump

You can use an unconditional jump as a form of conditional jump by specifying the address in a register or indirect memory operand. Also, you can use indirect memory operands to construct jump tables that work like C switch statements, Pascal CASE statements, or Basic ON GOTO, ON GOSUB, or SELECT CASE statements, as shown in the following example.

 

NPVOID  TYPEDEF NEAR PTR
        .DATA
ctl_tbl NPVOID  extended,    ; Null key (extended code)
                ctrla,       ; Address of CONTROL-A key routine
                ctrlb        ; Address of CONTROL-B key routine
        .CODE
        .
        .
        .
        mov     ah, 8h       ; Get a key
        int     21h
        cbw                  ; Stretch AL into AX
        mov     bx, ax       ; Copy
        shl     bx, 1        ; Convert to address
        jmp     ctl_tbl[bx]  ; Jump to key routine

extended:  
        mov     ah, 8h       ; Get second key of extended key
        int     21h
        .                    ; Use another jump table
        .                    ;   for extended keys
        .
        jmp     next
ctrla:  .                    ; CONTROL-A code here
        .
        .
        jmp     next
ctrlb:  .                    ; CONTROL-B code here
        .
        .
        jmp     next
        .
        .
next:   .                    ; Continue

In this instance, the indirect memory operands point to addresses of routines for handling different keystrokes.

Conditional Jumps

The most common way to transfer control in assembly language is to use a conditional jump. This is a two-step process:

1.  First test the condition.

2.  Then jump if the condition is true or continue if it is false.

 

 

All conditional jumps except two (JCXZ and JECXZ) use the processor flags for their criteria. Thus, any statement that sets or clears a flag can serve as a test basis for a conditional jump. The jump statement can be any one of 30 conditional-jump instructions. A conditional-jump instruction takes a single operand containing the target address. You cannot use a pointer value as a target as you can with unconditional jumps.

Jumping Based on the CX Register

JCXZ and JECXZ are special conditional jumps that do not consult the processor flags. Instead, as their names imply, these instructions cause a jump only if the CX or ECX register is zero. The use of JCXZ and JECXZ with program loops is covered in the next section, “Loops.”

Jumping Based on the Processor Flags

The remaining conditional jumps in the processor’s repertoire all depend on the status of the flags register. As the following list shows, several conditional jumps have two or three names JE (Jump if Equal) and JZ (Jump if Zero), for example. Shared names assemble to exactly the same machine instruction, so you may choose whichever mnemonic seems more appropriate. Jumps that depend on the status of the flags register include:

Instruction

Jumps if

JC/JB/JNAE

Carry flag is set

JNC/JNB/JAE

Carry flag is clear

JBE/JNA

Either carry or zero flag is set

JA/JNBE

Carry and zero flag are clear

JE/JZ

Zero flag is set

JNE/JNZ

Zero flag is clear

JL/JNGE

Sign flag overflow flag

JGE/JNL

Sign flag = overflow flag

JLE/JNG

Zero flag is set or sign overflow

JG/JNLE

Zero flag is clear and sign = overflow

JS

Sign flag is set

JNS

Sign flag is clear

JO

Overflow flag is set

JNO

Overflow flag is clear

JP/JPE

Parity flag is set (even parity)

JNP/JPO

Parity flag is clear (odd parity)

 

The last two jumps in the list, JPE (Jump if Parity Even) and JPO (Jump if Parity Odd), are useful only for communications programs. The processor sets the parity flag if an operation produces a result with an even number of set bits. A communications program can compare the flag against the parity bit received through the serial port to test for transmission errors.

The conditional jumps in the preceding list can follow any instruction that changes the processor flags, as these examples show:

; Uses JO to handle overflow condition
        add     ax, bx          ; Add two values
        jo      overflow        ; If value too large, adjust

; Uses JNZ to check for zero as the result of subtraction
        sub     ax, bx          ; Subtract
        mov     cx, Count       ; First, initialize CX
        jnz     skip            ; If the result is not zero, continue
        call    zhandler        ; Else do special case

As the second example shows, the jump does not have to immediately follow the instruction that alters the flags. Since MOV does not change the flags, it can appear between the SUB instruction and the dependent jump.

There are three categories of conditional jumps:

  Comparison of two values

  Individual bit settings in a value

  Whether a value is zero or nonzero

 

Jumps Based on Comparison of Two Values

The CMP instruction is the most common way to test for conditional jumps. It compares two values without changing either, then sets or clears the processor flags according to the results of the comparison.

Internally, the CMP instruction is the same as the SUB instruction, except that CMP does not change the destination operand. Both set flags according to the result of the subtraction.

 

You can compare signed or unsigned values, but you must choose the subsequent conditional jump to reflect the correct value type. For example, JL (Jump if Less Than) and JB (Jump if Below) may seem conceptually similar, but a failure to understand the difference between them can result in program bugs. Table 7.1 shows the correct conditional jumps for comparisons of signed and unsigned values. The table shows the zero, carry, sign, and overflow flags as ZF, CF, SF, and OF, respectively.

Table 7.1    Conditional Jumps Based on Comparisons of Two Values

 

Signed Comparisons
Instruction             Jump if True

Unsigned Comparisons
Instruction             Jump if True

 

JE

ZF = 1

JE

ZF = 1

JNE

ZF = 0

JNE

ZF = 0

JG/JNLE

ZF = 0 and SF = OF

JA/JNBE

CF = 0 and ZF = 0

JLE/JNG

ZF = 1 or SF OF

JBE/JNA

CF = 1 or ZF = 1

JL/JNGE

SF OF

JB/JNAE

CF = 1

JGE/JNL

SF = OF

JAE/JNB

CF = 0

 

The mnemonic names of jumps always refer to the comparison of CMP’s first operand (destination) with the second operand (source). For instance, in this example, JG tests whether the first operand is greater than the second.

        cmp     ax, bx ; Compare AX and BX
        jg      next1  ; Equivalent to: If ( AX > BX ) goto next1

        jl      next2  ; Equivalent to: If ( AX < BX ) goto next2

Jumps Based on Bit Settings