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.
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: IF ( test expression using A and B is TRUE ) THEN Perform the many statements that are the body of the IF here... ENDIF Continue with rest of program ; implementing the IF using Assembly Language: IF: Evaluate the test expression using A and B CONDITIONALLY JUMP to ENDIF (the end of the IF statement) if FALSE 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 conditional jump around the IF body must take place if the test expression is FALSE, not if it is TRUE.
Each type of conditional JUMP instruction checks the settings of the various status flags: Negative, Zero, Positive, Carry, Overflow, etc. Some of the conditional jumps are simple tests on one of the status flags, e.g. JC, JZ, etc., but some are jumps that must test several flags, e.g. JBE, JA, JLE, JG, 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 conditionally JUMP according to the setting of the flags.
Conditional jumps that check the flags are placed either after actual arithmetic (e.g. ADD, SUB, etc.) or after a compare statement CMP that sets the flags as if SUB had been done but doesn't change any values. Let's look at how the CMP statement relates to the flag settings for the Carry and Zero flags, and the Intel conditional jump statements that make use of those flags:
Instruction: CMP A,B Relationship: A < B A <= B A == B A >= B A > B A != B Flags always set: C NZ NC Z NC NC NZ NZ Intel Jumps: JC/JB/JNAE JBE/JNA JZ/JE JAE/JNB/JNC JA/JNBE JNZ/JNE
Note that some of the conditional jumps above are simple tests on one of the two flags, e.g. JC, JZ, but that some jumps must test both flags, e.g. JBE, JA.
As an example of turning high-level language into assembly language, 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:
IF: 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?
All the 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.
Intel CPUs provide conditional 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 than 1.) However, when considered as
signed, 2's complement values, FFFFh is negative (-1), so FFFFh is
said to be less than 0001h. The Intel conditional JUMP instructions
reflect this distinction:
Opcodes Type of conditional JUMP JA, JAE (=JNC) Above, Above or Equal (=No Carry); unsigned JB (=JC), JBE Below (=Carry), Below or Equal; unsigned JG, JGE Greater, Greater or Equal; signed JL, JLE Less, Less or Equal; signed JE, JZ Equal, Zero JNE, JNZ Not Equal, Not Zero
After comparing two quantities, select the conditional JUMP instruction that is appropriate for whether the two quantities are signed (e.g. JG) or unsigned (e.g. JA).
Some conditional jumps are simple tests on one of the status flags, e.g. JC, JZ, etc., and some are jumps that must test several flags, e.g. JBE, JA, JLE, JG, etc. You can see which flags are used in each conditional jump at this URL: http://unixwiz.net/techtips/x86-jumps.html
Given this pseudocode fragment, code it as both a signed and and unsigned test (remember that we always invert the JUMP condition to jump to the ENDIF):
if ( a >= b ) then ...
As an unsigned test:
IF: MOV CX,A CMP CX,B JB ENDIF ; JumpBelow: skip the body if A < B (unsigned) ... ...
As a signed test:
IF: MOV CX,A CMP CX,B JL ENDIF ; JumpLess: skip the body if A < B (signed) ... ...
As an assembly-language programmer, you are expected to know what type of data you are comparing (signed or unsigned) and choose the appropriate conditional JUMP. A high-level language compiler makes these choices for you when you declare your variables as "signed" or "unsigned".
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:
Compare 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.)IF: 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 IF
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.
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.
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 ) do 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:
WHILE: 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 WHILE ; WHILE is a loop - jump up to test again ENDWH: INC A ; end of WHILE statement - continue on
The 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 < 9; i++ ) do sum = sum + i endfor // re-arranged into a WHILE statement i = 0 while ( i < 9 ) do sum = sum + i i = i + 1 endwhile
; turned into Intel assembler MOV I,0 WHILE: CMP I,9 ; do mathematics to set the status flags JGE ENDWH ; if I is >= 9 - jump around MOV AX,I ; one operand must be in a register ADD SUM,AX ; sum = sum + i INC I ; i = i + 1 JMP WHILE ; WHILE is a loop - start over at loop test 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.
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:
Since 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:if ( a != b ) then if ( c >= d ) then ... IF body goes here ... endif endif
IF1: 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 IF2: 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.
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. We rewrite the code as follows:
Each GOTO becomes a JUMP statement in Assembler:if ( a != b ) goto dobody; if ( c >= d ) goto dobody; goto around dobody: ... IF body goes here ... ... IF body goes here ... around: ... linear code resumes here ...
IF1: 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 IF2: 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 DOBODY: ... IF body goes here ... ... IF body goes here ... AROUND: ... linear code resumes here ...Unlike previous examples, where the JUMPs branched around the IF body, the above code uses the DOBODY labels to 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.