2.11 Loops
Loops represent the final basic control structure (sequences, decisions, and loops) that make up a typical program. Like so many other structures in assembly language, you'll find yourself using loops in places you've never dreamed of using loops. Most HLLs have implied loop structures hidden away. For example, consider the BASIC statement "IF A$ = B$ THEN 100". This IF statement compares two strings and jumps to statement 100 if they are equal. In assembly language, you would need to write a loop to compare each character in A$ to the corresponding character in B$ and then jump to statement 100 if and only if all the characters matched. In BASIC, there is no loop to be seen in the program. In assembly language, this very simple IF statement requires a loop to compare the individual characters in the string1. This is but a small example which shows how loops seem to pop up everywhere.
Program loops consist of three components: an optional initialization component, a loop termination test, and the body of the loop. The order with which these components are assembled can dramatically change the way the loop operates. Three permutations of these components appear over and over again. Because of their frequency, these loop structures are given special names in high-level languages: WHILE loops, REPEAT..UNTIL loops (do..while in C/C++), and infinite loops (e.g., FOREVER..ENDFOR in HLA).
2.11.1 While Loops
The most general loop is the WHILE loop. In HLA it takes the following form:
while( expression ) do <<statements>> endwhile;There are two important points to note about the WHILE loop. First, the test for termination appears at the beginning of the loop. Second as a direct consequence of the position of the termination test, the body of the loop may never execute. If the termination condition is always true, the loop body will never execute.
Consider the following HLA WHILE loop:
mov( 0, I ); while( I < 100 ) do inc( I ); endwhile;The "mov( 0, I );" instruction is the initialization code for this loop. I is a loop control variable, because it controls the execution of the body of the loop. "I<100" is the loop termination condition. That is, the loop will not terminate as long as I is less than 100. The single instruction "inc( I );" is the loop body. This is the code that executes on each pass of the loop.
Note that an HLA WHILE loop can be easily synthesized using IF and JMP statements. For example, the HLA WHILE loop presented above can be replaced by:
mov( 0, I ); WhileLp: if( I < 100 ) then inc( i ); jmp WhileLp; endif;More generally, any WHILE loop can be built up from the following:
<< optional initialization code>> UniqueLabel: if( not_termination_condition ) then <<loop body>> jmp UniqueLabel; endif;Therefore, you can use the techniques from earlier in this chapter to convert IF statements to assembly language along with a single JMP instruction to produce a WHILE loop. The example we've been looking at in this section translates to the following "pure" 80x86 assembly code2:
mov( 0, i ); WhileLp: cmp( i, 100 ); jnl WhileDone; inc( i ); jmp WhileLp; WhileDone:2.11.2 Repeat..Until Loops
The REPEAT..UNTIL (do..while) loop tests for the termination condition at the end of the loop rather than at the beginning. In HLA, the REPEAT..UNTIL loop takes the following form:
<< optional initialization code >> repeat <<loop body>> until( termination_condition );This sequence executes the initialization code, the loop body, then tests some condition to see if the loop should repeat. If the boolean expression evaluates to false, the loop repeats; otherwise the loop terminates. The two things to note about the REPEAT..UNTIL loop are that the termination test appears at the end of the loop and, as a direct consequence of this, the loop body always executes at least once.
Like the WHILE loop, the REPEAT..UNTIL loop can be synthesized with an IF statement and a JMP . You could use the following:
<< initialization code >> SomeUniqueLabel: << loop body >> if( not_the_termination_condition ) then jmp SomeUniqueLabel; endif;Based on the material presented in the previous sections, you can easily synthesize REPEAT..UNTIL loops in assembly language. The following is a simple example:
repeat stdout.put( "Enter a number greater than 100: " ); stdin.get( i ); until( i > 100 ); // This translates to the following IF/JMP code: RepeatLbl: stdout.put( "Enter a number greater than 100: " ); stdin.get( i ); if( i <= 100 ) then jmp RepeatLbl; endif; // It also translates into the following "pure" assembly code: RepeatLabel: stdout.put( "Enter a number greater than 100: " ); stdin.get( i ); cmp( i, 100 ); jng RepeatLbl;2.11.3 FOREVER..ENDFOR Loops
If WHILE loops test for termination at the beginning of the loop and REPEAT..UNTIL loops check for termination at the end of the loop, the only place left to test for termination is in the middle of the loop. The HLA FOREVER..ENDFOR loop, combined with the BREAK and BREAKIF statements, provide this capability. The FOREVER..ENDFOR loop takes the following form:
forever << loop body >> endfor;Note that there is no explicit termination condition. Unless otherwise provided for, the FOREVER..ENDFOR construct simply forms an infinite loop. Loop termination is typically handled by a BREAKIF statement. Consider the following HLA code that employs a FOREVER..ENDFOR construct:
forever stdin.get( character ); breakif( character = `.' ); stdout.put( character ); endfor;Converting a FOREVER loop to pure assembly language is trivial. All you need is a label and a JMP instruction. The BREAKIF statement in this example is really nothing more than an IF and a JMP instruction. The "pure" assembly language version of the code above looks something like the following:
foreverLabel: stdin.get( character ); cmp( character, `.' ); je ForIsDone; stdout.put( character ); jmp foreverLabel; ForIsDone:2.11.4 FOR Loops
The FOR loop is a special form of the WHILE loop that repeats the loop body a specific number of times. In HLA, the FOR loop takes the following form:
for( <<Initialization Stmt>>; <<Termination Expression>>; <<inc_Stmt>> ) do << statements >> endfor;This is completely equivalent to the following:
<< Initialization Stmt>>; while( <<Termination Expression>> ) do << statements >> <<inc_Stmt>> endwhile;Traditionally, the FOR loop has been used to process arrays and other objects accessed in sequential numeric order. One normally initializes a loop control variable with the initialization statement and then uses the loop control variable as an index into the array (or other data type), e.g.,
for( mov(0, esi); esi < 7; inc( esi )) do stdout.put( "Array Element = ", SomeArray[ esi*4], nl ); endfor;To convert this to "pure" assembly language, begin by translating the FOR loop into an equivalent WHILE loop:
mov( 0, esi ); while( esi < 7 ) do stdout.put( "Array Element = ", SomeArray[ esi*4], nl ); inc( esi ); endwhile;Now, using the techniques from the section on WHILE loops (see "While Loops" on page 787), translate the code into pure assembly language:
mov( 0, esi ); WhileLp: cmp( esi, 7 ); jnl EndWhileLp; stdout.put( "Array Element = ", SomeArray[ esi*4], nl ); inc( esi ); jmp WhileLp; EndWhileLp:2.11.5 The BREAK and CONTINUE Statements
The HLA BREAK and CONTINUE statements both translate into a single JMP instruction. The BREAK instruction exits the loop that immediately contains the BREAK statement; the CONTINUE statement restarts the loop that immediately contains the CONTINUE statement.
Converting a BREAK statement to "pure" assembly language is very easy. Just emit a JMP instruction that transfers control to the first statement following the ENDxxxx clause of the loop to exit. This is easily accomplished by placing a label after the associated END clause and jumping to that label. The following code fragments demonstrate this technique for the various loops:
// Breaking out of a forever loop: forever <<stmts>> //break; jmp BreakFromForever; <<stmts>> endfor; BreakFromForever: // Breaking out of a FOR loop; for( <<initStmt>>; <<expr>>; <<incStmt>> ) do <<stmts>> //break; jmp BrkFromFor; <<stmts>> endfor; BrkFromFor: // Breaking out of a WHILE loop: while( <<expr>> ) do <<stmts>> //break; jmp BrkFromWhile; <<stmts>> endwhile; BrkFromWhile: // Breaking out of a REPEAT..UNTIL loop: repeat <<stmts>> //break; jmp BrkFromRpt; <<stmts>> until( <<expr>> ); BrkFromRpt:The CONTINUE statement is slightly more difficult to implement that the BREAK statement. The implementation still consists of a single JMP instruction, however the target label doesn't wind up going in the same spot for each of the different loops. The following figures show where the CONTINUE statement transfers control for each of the HLA loops:
Figure 2.2 CONTINUE Destination for the FOREVER Loop
Figure 2.3 CONTINUE Destination and the WHILE Loop
Figure 2.4 CONTINUE Destination and the FOR Loop
Figure 2.5 CONTINUE Destination and the REPEAT..UNTIL Loop
The following code fragments demonstrate how to convert the CONTINUE statement into an appropriate JMP instruction for each of these loop types.
// Conversion of forever loop w/continue // to pure assembly: forever <<stmts>> continue; <<stmts>> endfor; // Converted code: foreverLbl: <<stmts>> jmp foreverLbl; <<stmts>>// Conversion of while loop w/continue // into pure assembly: while( <<expr>> ) do <<stmts>> continue; <<stmts>> endwhile; // Converted code: whlLabel: <<Code to evaluate expr>> Jcc EndOfWhile; // Skip loop on expr failure. <<stmts> jmp whlLabel; // Jump to start of loop on continue. <<stmts>> jmp whlLabel; // Repeat the code. EndOfwhile:// Conversion for a for loop w/continue // into pure assembly: for( <<initStmt>>; <<expr>>; <<incStmt>> ) do <<stmts>> continue; <<stmts>> endfor; // Converted code <<initStmt>> ForLpLbl: <<Code to evaluate expr>> Jcc EndOfFor; // Branch if expression fails. <<stmts>> jmp ContFor; // Branch to <<incStmt>> on continue. <<stmts>> ContFor: <<incStmt>> jmp ForLpLbl; EndOfFor:repeat <<stmts>> continue; <<stmts>> until( <<expr>> ); // Converted Code: RptLpLbl: <<stmts>> jmp ContRpt; // Continue branches to loop termination test. <<stmts>> ContRpt: <<code to test expr>> Jcc RptLpLbl; // Jumps if expression evaluates false.2.11.6 Register Usage and Loops
Given that the 80x86 accesses registers much faster than memory locations, registers are the ideal spot to place loop control variables (especially for small loops). However, there are some problems associated with using registers within a loop. The primary problem with using registers as loop control variables is that registers are a limited resource. Therefore, the following will not work properly:
mov( 8, cx ); loop1: mov( 4, cx ); loop2: <<stmts>> dec( cx ); jnz loop2; dec( cx ); jnz loop1;The intent here, of course, was to create a set of nested loops, that is, one loop inside another. The inner loop (Loop2) should repeat four times for each of the eight executions of the outer loop (Loop1). Unfortunately, both loops use the same register as a loop control variable.. Therefore, this will form an infinite loop since CX will be set to zero at the end of the first loop. Since CX is always zero upon encountering the second DEC instruction, control will always transfer to the LOOP1 label (since decrementing zero produces a non-zero result). The solution here is to save and restore the CX register or to use a different register in place of CX for the outer loop:
mov( 8, cx ); loop1: push( cx ); mov( 4, cx ); loop2: <<stmts>> dec( cx ); jnz loop2; pop( cx ); dec( cx ); jnz loop1;mov( 8, dx ); loop1: mov( 4, cx ); loop2: <<stmts>> dec( cx ); jnz loop2; dec( dx ); jnz loop1;Register corruption is one of the primary sources of bugs in loops in assembly language programs, always keep an eye out for this problem.
1Of course, the HLA Standard Library provides the str.eq routine that compares the strings for you, effectively hiding the loop even in an assembly language program.
2Note that HLA will actually convert most WHILE statements to different 80x86 code than this section presents. The reason for the difference appears a little later in this text when we explore how to write more efficient loop code.
|