Coding Assembler Control Flow
Most programs contain some form of conditional logic, e.g. IF statements that affect the usual linear flow of control in the program. Programming conditional logic means turning the pseudo-code logic into statements written in mnemonic form for the Assembler. The CPU has no IF, WHILE, FOR or other structured statements; we must use the control flow instructions available to us to implement structured code.
Conditional statements contain a test expression that returns a TRUE or FALSE, e.g. IF (count == 0), WHILE (sum >= MAX), etc. The simplest test expression is a test that compares two quantities, e.g. A<B. Let's name these two quantities with variable names A and B and work out the translation of two-variable test expressions to Assembler instructions. More complex expressions can be built using the techniques developed here.
IF and WHILE Statements
Note that the test in an IF statement and the test in a WHILE statement have exactly the same structure; both expressions return TRUE or FALSE and alter the flow of control around an enclosed body of statements:
if ( a == b ) then a = a + b // TRUE body endif
while ( a == b ) do a = a + b // TRUE body endwhile
In both the above code fragments, the control flow enters the TRUE body only if the test expression comparing A and B for equality is true. If we know how to code one test in Assembler instructions, we know how to code the other. We need only focus on the code for the test expression itself.
Basic IF Structure
The basic Assembler instructions that alter the flow of control are JUMP statements. The jumps can either be unconditional, e.g. JMP, or they can be conditional on the state of the status flags, e.g. JG, JA, JLE, JBE. We must use these instructions to implement our pseudocode IF and WHILE control flow statements. The pseudocode structure for a simple IF statement (no ELSE clause) is as follows:
// Simple IF statement structure pseudocode: Evaluate the test expression using A and B CONDITIONALLY JUMP to ENDIF (the end of the IF statement) Perform the many statements that are the body of the IF here... ENDIF: Continue with rest of program
Note the placement of the Conditional JUMP instruction, above. The function of the JUMP is to get around the body of the IF statement. We must code a test expression and JUMP instruction that let the flow of control go into the body of the IF only if the test expression is TRUE. If the test expression is FALSE, the JUMP should take control around the body of the IF statement. Pay careful attention to this detail: The jump must take place if the test expression is FALSE, not if it is TRUE.
Mathematics and Status Flags
Each type of JUMP instruction checks the settings of the various status flags: Negative, Zero, Positive, Carry, etc. Only mathematics, e.g. ADD, SUBTRACT, COMPARE, sets the status flags; therefore, to use conditional JUMP statements to alter the flow of control in our program, we must first perform some mathematics using our two variables A and B. The mathematics will set the CPU status flags based on the relationship between A and B, and we can then JUMP according to the setting of the flags.
For example, consider the following code fragment:
if ( a == b ) then a = a + b // TRUE body endif a = a + 1
We have written an expression that causes control flow to enter the TRUE body only if A is equal to B. Using the IF statement structure given above, we need to pick some mathematics to set the flag lights and follow the math with a a JUMP instruction that branches around the IF body if the test expression is FALSE.
If we want to test for equality, A == B, we must arrange to JUMP around the IF body on the inverse condition, that is, when A != B. So, we always pick a conditional JUMP that is the opposite of the test condition we are using. If the test is "equality", the JUMP is "jump if not equal". If the test is "less than", the JUMP is "jump if greater than or equal to", etc.
Using the above IF structure, here is a translation of the above pseudocode into Assembler instructions:
MOV DX,A ; one operand must be in a register CMP DX,B ; do math on A and B to set the status flags JNE ENDIF ; A is not equal to B - jump around MOV CX,B ; body of IF... (one op in register) ADD A,CX ; body of IF... (a = a + b) ENDIF: INC A ; end of IF statement - continue onIf A and B are equal, comparing them will cause the Zero flag to come on. The JNE instruction operates only if the Zero flag is off, so no jump will take place and the body of the IF statement will execute. If A and B are not equal, the Zero flag will not come on, and the JNE will then operate to skip over the body of the IF statement. We always invert the jump condition because it must must jump around the body of the IF statement.
All Assembler flow control statements (IF, WHILE) can be programmed using the above structure:
How do we choose what mathematics to perform, and which JUMP instruction to use? We will examine that later in this document.
IF with an ELSE clause
IF statements with ELSE clauses are only slightly more complex than simple IF statements. Here is a pseudocode program fragment using an IF/ELSE statement:
1. if ( a == b ) then 2. a = a + b 3. else 4. b = b - a 5. endif 6. b++
Here is a translation of the above pseudocode fragment into Assembler, with the statement numbers inserted as comments:
MOV DX,A ; 1: one operand must be in a register CMP DX,B ; do math on A and B to set flags JNE ELSE MOV AX,B ; 2: one operand must be in a register ADD A,AX ; a = a + b JMP ENDIF ELSE: MOV CX,A ; 4: one operand must be in a register SUB B,CX ; b = b - a ENDIF: INC B ; 6: ENDIF; after IFCompare the above Assembler code with that used in the simple IF statement. Note the identical placement of the test expression mathematics and JUMP instruction on line 1, above. The function of the first JUMP is still to get around the TRUE body of the IF statement; however, instead of jumping to the end of the IF statement, it now jumps to the first statement in the ELSE clause. (If the test expression of an IF/ELSE statement is FALSE, control passes over the TRUE body and into the FALSE body.)
A new "JMP ENDIF" instruction has been added to the end of the TRUE body of the IF statement (just before the ELSE label) to jump around the FALSE body that makes up the ELSE part of the IF. If this jump were not there, the flow of control would run from the statements at the end of the TRUE body into the statements at the beginning of the FALSE body.
A WHILE Statement
The following pseudocode fragment is almost identical to the simple IF example used earlier; only the keywords IF have been changed to WHILE:
while ( a == b ) then a = a + b // TRUE body endwhile a = a + 1
Again, we have written an expression that causes control flow to enter the TRUE body only if A is equal to B. We need to pick some mathematics to set the flag lights and use a conditional JUMP instruction to jump around the WHILE body. We use almost the same statement structure as for the simple IF statement. Here is a translation of the above pseudocode into Asembler instructions:
TEST: MOV DX,A ; one operand must be in a register CMP DX,B ; do math on A and B to set the status flags JNE ENDWH ; A is not equal to B - jump around MOV CX,B ; body of WHILE... (one op in register) ADD A,CX ; body of WHILE... (a = a + b) JMP TEST ; WHILE is a loop - jump up to test again ENDWH: INC A ; end of WHILE statement - continue onThe analysis of the above Asssembler code is similar to the analysis of the code for the simple IF statement: If A and B are equal, subtracting them will cause the Zero flag to come on. The JNE instruction needs the Zero flag off, so it does not jump and we enter the WHILE body. If A and B are not equal, the Zero flag will not come on, and the JNE will jump around the body of the WHILE statement.
The key difference between the above code and the code for the simple IF statement is the addition of a "JMP TEST" instruction at the bottom of the body of the WHILE statement. WHILE statements need to loop; IF statements do not. The jump returns control to the top of the WHILE loop, which is to the test expression that begins the loop.
FOR statements are simply a shorthand way of writing the equivalent WHILE statements. To code a FOR statement, turn it into the equivalent WHILE statement and then translate that into assembler:
// the original FOR statement for ( i=0; i < 10; i++ ) do x = x + i endfor
// translated into a WHILE statement i = 0 while ( i < 10 ) do x = x + i i = i + 1 endwhile
; turned into Intel assembler MOV I,0 TEST: CMP I,10 ; do mathematics to set the status flags JGE ENDWH ; if I is >= 10 - jump around MOV AX,I ; one operand must be in a register ADD X,AX ; x = x + i INC I ; i = i + 1 JMP TEST ; WHILE is a loop - start over ENDWH: ... ; end of WHILE statement - continue on
The Initialization clause of the FOR loop precedes the WHILE statement. The body of the WHILE loop ends with the Increment clause of the FOR loop, just before jumping back to the top of the loop. The WHILE loop itself has exactly the same structure as before:
A common error when programming FOR loops is to mis-code the jump to the top of the loop. Some people mistakenly code the jump to go to the first statement of the Initialization clause. The correct jump is to the first statement of the Test part of the WHILE loop, not to the Initialization clause.
All of the above control structures (IF, IF/ELSE, WHILE, FOR) have the same structure for their test conditions:
How do we choose what mathematics to perform using A and B, and which JUMP instruction to use?
Since we are testing the relationship between A and B - equal, less than, greater than, etc. - the operation that works best to set the CPU status flags is SUBTRACT. Subtracting A from B or B from A sets the flags according to the relative values of A and B; ADDING would not have the same effect.
Since we are often only performing the SUBTRACT to set the status flags - we don't actually want to use the value of the subtraction - most CPUs have a COMPARE (CMP) instruction that sets the flags as if the subtraction had been done, but it doesn't change any of the operands and doesn't save the results of the subtraction.
The only issue to resolve is which of the many JUMP instructions we need to use.
Greater vs. Above, Less vs. BelowIntel CPUs provide JUMP instructions for both unsigned binary mathematics and for signed, 2's complement mathematics.
When comparing 16-bit quantities, FFFFh is said to be above 0001h when
considered as unsigned values. (FFFFh is a much bigger unsigned number.)
However, when considered as signed, 2's complement values, FFFFh is negative, so
FFFFh is said to be less than 0001h. The Intel JUMP instructions reflect
Given this pseudocode fragment, code it as both a signed and and unsigned test:
if ( a > b ) then ...As a signed test (remember that we always invert the JUMP condition):
MOV CX,A CMP CX,B JLE ENDIF ; skip the body if A <= B (signed) ... ...As an unsigned test:
MOV CX,A CMP CX,B JBE ENDIF ; skip the body if A <= B (unsigned) ... ...As programmer, you are expected to choose the appropriate conditional JUMP.
Nested AND Logic
Here is a more complex example using two test expressions joined by AND:
if ( a != b && c >= d ) then ... body goes here ... endif
The above test expression is not a simple one involving two quantities, so the methods we have been using so far will not work directly. However, we can rewrite the above compound test expression into two nested IF statements, each of which uses a simple A/B-type expression that we know how to translate:
if ( a != b ) then if ( c >= d ) then ... IF body goes here ... endif endifSince the IF statements are nested, the body will not be executed unless both the outer AND inner IF conditions are both true. We can now translate these two IF statements directly into LMC assembler:
MOV DX,A ; one operand must be in a register CMP DX,B ; this is the outer IF test JE ENDIF1 ; skip the body if A == B MOV DX,C ; one operand must be in a register CMP DX,D ; this is the inner IF test JL ENDIF2 ; skip the body if C < D ... IF body goes here ... ENDIF2: ENDIF1:
Both ENDIF1 and ENDIF2 are actually labels for the same location; jumping to either label jumps to the same code at the end of the IF statement. We might therefore simplify the code and use only a single ENDIF label for both jumps.
Nested OR Logic
Here is another complex example, this time using two expressions joined by OR:
if ( a != b || c >= d ) then ... IF body goes here ... endif
The above two conditional test expressions are joined by OR, so we cannot implement a solution using two nested IF statements as we did when they were joined by AND. With a bit of help from deMorgan's theorem in Boolean logic, we can invert the condition so that it becomes one containing AND instead of OR, and then use the inverted condition to branch around the loop body:
First, we must apply deMorgan's theorem to invert the condition to an opposite condition that uses AND instead of OR:
original condition: a != b || c >= d inverted condition: !(a != b || c >= d) deMorgan applied: !(a != b) && !(c >= d) simplified: a == b && c < dWe then use that new inverted condition to branch around the loop body:
if ( a == b && c < d ) then goto around endif ... IF body goes here ... around: ... linear code resumes here ...We now have a compound condition joined by AND, a problem we can solve with nested IF statements, as we did in the previous section. The GOTO becomes a simple JUMP statement in Assembler, and we might label the ENDIF statement with the name DOBODY:
MOV DX,A ; one operand must be in a register CMP DX,B ; this is the outer IF test JNE DOBODY ; go to the body if A != B MOV DX,C ; one operand must be in a register CMP DX,D ; this is the inner IF test JGE DOBODY ; go to the body if C >= D JMP AROUND ; skip the body; deMorgan inverted test DOBODY: ... IF body goes here ... AROUND: ... linear code resumes here ...Unlike previous examples, where the JUMPs branched around the IF body, the above code uses a deMorgan inverted condition and the DOBODY labels lead to the IF body to implement the OR functionality.
At the expense of some clarity, one could optimize the code by combining the two adjacent JGE/JMP statements into a single JL statement; but, it wouldn't then be as clear a translation of the supplied pseudocode.
Web Author: Ian! D. Allen firstname.lastname@example.org Updated: 2003-09-23 11:44
Support free and non-commercial Internet.
This site works best in Any Browser, a campaign for non-specific WWW.
This work is licensed under a Creative Commons License.