10.10 Writing Compile Time Programs


10.10 Writing Compile Time "Programs"

The HLA compile time language provides a powerful facility with which to write "programs" that execute while HLA is compiling your assembly language programs. Although it is possible to write some general- purpose programs using the HLA compile time language, the real purpose of the HLA compile time language is to allow you to write short programs that write other programs. In particular, the primary purpose of the HLA compile time language is to automate the creation of large or complex assembly language sequences. The following subsections provide some simple examples of such compile time programs.

10.10.1 Constructing Data Tables at Compile Time

Earlier, this book suggested that you could write programs to generate large, complex, look-up tables for your assembly language programs (see the discussion of tables in the chapter on advanced arithmetic). That chapter provides examples in HLA but suggests that writing a separate program is unnecessary. This is true; you can generate most look-up tables you'll need using nothing more than the HLA compile time language facilities. Indeed, filling in table entries is one of the principle uses of the HLA compile time language. In this section we will take a look at using the HLA compile time language to construct data tables during compilation.

In the section on generating tables, you saw an example of an HLA program that writes a text file containing a look-up table for the trigonometric sine function. The table contains 360 entries with the index into the table specifying an angle in degrees. Each int32 entry in the table contained the value sin(angle)*1000 where angle is equal to the index into the table. The section on generating tables suggests running this program and then including the text output from that program into the actual program that used the resulting table. You can avoid much of this work by using the compile time language. The HLA program in Listing 10-7 includes a short compile time code fragment that constructs this table of sines directly.

Listing 10-7: Generating a SINE Look-up Table with the Compile Time Language.

start example
 // demoSines.hla // // This program demonstrates how to create a look-up table // of sine values using the HLA compile time language. program demoSines; #include( "stdlib.hhf" ) const     pi :real80 := 3.1415926535897; readonly     sines: int32[ 360 ] :=            [                // The following compile time program generates                // 359 entries (out of 360). For each entry                // it computes the sine of the index into the                // table and multiplies this result by 1000                // in order to get a reasonable integer value.                ?angle := 0;                #while( angle < 359 )                     // Note: HLA's @sin function expects angles                     // in radians. radians = degrees*pi/180.                     // the "int32" function truncates its result,                     // so this function adds 1/2 as a weak attempt                     // to round the value up.                     int32( @sin( angle * pi / 180.0 ) * 1000 + 0.5 ),                     ?angle := angle + 1;                #endwhile                // Here's the 360th entry in the table. This code                // handles the last entry specially because a comma                // does not follow this entry in the table.                int32( @sin( 359 * pi / 180.0 ) * 1000 + 0.5 )           ]; begin demoSines;      // Simple demo program that displays all the values in the table.      for( mov( 0, ebx); ebx<360; inc( ebx )) do           mov( sines[ ebx*4 ], eax );           stdout.put           (                "sin( ",                (type uns32 ebx ),                " )*1000 = ",                (type int32 eax ),                nl           );      endfor; end demoSines; 
end example

Another common use for the compile time language is to build ASCII character look-up tables for use by the xlat instruction at runtime. Common examples include look-up tables for alphabetic case manipulation. The program in Listing 10-8 demonstrates how to construct an upper case conversion table and a lower case conversion table.[10] Note the use of a macro as a compile time procedure to reduce the complexity of the table-generating code:

Listing 10-8: Generating Case Conversion Tables with the Compile Time Language

start example
 // demoCase.hla // // This program demonstrates how to create a look-up table // of alphabetic case conversion values using the HLA // compile time language. program demoCase; #include( "stdlib.hhf" ) const      pi :real80 := 3.1415926535897; // emitCharRange // // This macro emits a set of character entries // for an array of characters. It emits a list // of values (with a comma suffix on each value) // from the starting value up to, but not including, // the ending value. #macro emitCharRange( start, last ): index;      ?index:uns8 := start;      #while( index < last )           char( index ),           ?index := index + 1;      #endwhile #endmacro; readonly      // toUC:      // The entries in this table contain the value of the index      // into the table except for indicies #$61..#$7A (those entries      // whose indicies are the ASCII codes for the lower case      // characters). Those particular table entries contain the      // codes for the corresponding upper case alphabetic characters.      // If you use an ASCII character as an index into this table and      // fetch the specified byte at that location, you will effectively      // translate lower case characters to upper case characters and      // leave all other characters unaffected.      toUC: char[ 256 ] :=            [                // The following compile time program generates                // 255 entries (out of 256). For each entry                // it computes toupper( index ) where index is                // the character whose ASCII code is an index                // into the table.                emitCharRange( 0, uns8('a') )                // Okay, we've generated all the entries up to                // the start of the lower case characters. Output                // upper case characters in place of the lower                // case characters here.                emitCharRange( uns8('A'), uns8('Z') + 1 )                // Okay, emit the nonalphabetic characters                // through to byte code #$FE:                emitCharRange( uns8('z') + 1, $FF )                // Here's the last entry in the table. This code                // handles the last entry specially because a comma                // does not follow this entry in the table.                #$FF           ];      // The following table is very similar to the one above.      // You would use this one, however, to translate upper case      // characters to lower case while leaving everything else alone.      // See the comments in the previous table for more details.      TOlc: char[ 256 ] :=            [                emitCharRange( 0, uns8('A') )                emitCharRange( uns8('a'), uns8('z') + 1 )                emitCharRange( uns8('Z') + 1, $FF )                #$FF            ]; begin demoCase;      for( mov( uns32( ' ' ), eax ); eax <= $FF; inc( eax )) do           mov( toUC[ eax ], bl );           mov( TOlc[ eax ], bh );           stdout.put           (                "toupper( '",                (type char al),                "' ) = '",                (type char bl),                "' tolower( '",                (type char al),                "' ) = '",                (type char bh),                "'",                nl           );      endfor; end demoCase; 
end example

One important thing to note about this sample is the fact that a semicolon does not follow the emitCharRange macro invocations. Macro invocations do not require a closing semicolon. Often, it is legal to go ahead and add one to the end of the macro invocation because HLA is normally very forgiving about having extra semicolons inserted into the code. In this case, however, the extra semicolons are illegal because they would appear between adjacent entries in the TOlc and toUC tables. Keep in mind that macro invocations don't require a semicolon, especially when using macro invocations as compile time procedures.

10.10.2 Unrolling Loops

In the chapter on low level control structures this text points out that you can unravel loops to improve the performance of certain assembly language programs. One problem with unravelling, or unrolling, loops is that you may need to do a lot of extra typing, especially if many iterations are necessary. Fortunately, HLA's compile time language facilities, especially the #while and #for loops, come to the rescue. With a small amount of extra typing plus one copy of the loop body, you can unroll a loop as many times as you please.

If you simply want to repeat the same exact code sequence some number of times, unrolling the code is especially trivial. All you've got to do is wrap an HLA #for..#endfor loop around the sequence and count off a val object the specified number of times. For example, if you wanted to print "Hello World" ten times, you could encode this as follows:

 #for( count := 1 to 10 )      stdout.put( "Hello World", nl ); #endfor 

Although the code above looks very similar to a while (or for) loop you could write in your program, remember the fundamental difference: The preceding code simply consists of ten straight stdout.put calls in the program. Were you to encode this using an HLA for loop, there would be only one call to stdout.put and lots of additional logic to loop back and execute that single call ten times.

Unrolling loops becomes slightly more complicated if any instructions in that loop refer to the value of a loop control variable or another value, which changes with each iteration of the loop. A typical example is a loop that zeros the elements of an integer array:

 mov( 0, eax ); for( mov( 0, ebx ); ebx < 20; inc( ebx )) do      mov( eax, array[ ebx*4 ] ); endfor; 

In this code fragment the loop uses the value of the loop control variable (in EBX) to index into array. Simply copying "mov( eax, array[ ebx*4 ]);" twenty times is not the proper way to unroll this loop. You must substitute an appropriate constant index in the range 0..76 (the corresponding loop indices, times four) in place of "EBX*4" in this example. Correctly unrolling this loop should produce the following code sequence:

      mov( eax, array[ 0*4 ] );      mov( eax, array[ 1*4 ] );      mov( eax, array[ 2*4 ] );      mov( eax, array[ 3*4 ] );      mov( eax, array[ 4*4 ] );      mov( eax, array[ 5*4 ] );      mov( eax, array[ 6*4 ] );      mov( eax, array[ 7*4 ] );      mov( eax, array[ 8*4 ] );      mov( eax, array[ 9*4 ] );      mov( eax, array[ 10*4 ] );      mov( eax, array[ 11*4 ] );      mov( eax, array[ 12*4 ] );      mov( eax, array[ 13*4 ] );      mov( eax, array[ 14*4 ] );      mov( eax, array[ 15*4 ] );      mov( eax, array[ 16*4 ] );      mov( eax, array[ 17*4 ] );      mov( eax, array[ 18*4 ] );      mov( eax, array[ 19*4 ] ); 

You can do this more efficiently using the following compile time code sequence:

 #for( iteration := 0 to 19 )      mov( eax, array[ iteration*4 ] ); #endfor 

If the statements in a loop make use of the loop control variable's value, it is only possible to unroll such loops if those values are known at compile time. You cannot unroll loops when user input (or other run-time information) controls the number of iterations.

[10]Note that on modern processors, using a look-up table is probably not the most efficient way to convert between alphabetic cases. However, this is just an example of filling in the table using the compile time language. The principles are correct, even if the code is not exactly the best it could be.




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