Use backtick code blocks for syntax highlighting

This commit is contained in:
James Gregory 2013-12-31 11:38:48 +11:00
commit f658bfbb40
203 changed files with 26546 additions and 25801 deletions

View file

@ -82,46 +82,48 @@ It's *slow*.
**LISTING 1.1 L1-1.C** **LISTING 1.1 L1-1.C**
/* ```c
* Program to calculate the 16-bit checksum of all bytes in the /*
* specified file. Obtains the bytes one at a time via read(), * Program to calculate the 16-bit checksum of all bytes in the
* letting DOS perform all data buffering. * specified file. Obtains the bytes one at a time via read(),
*/ * letting DOS perform all data buffering.
#include <stdio.h> */
#include <fcntl.h> #include <stdio.h>
#include <fcntl.h>
main(int argc, char *argv[]) { main(int argc, char *argv[]) {
int Handle; int Handle;
unsigned char Byte; unsigned char Byte;
unsigned int Checksum; unsigned int Checksum;
int ReadLength; int ReadLength;
if ( argc != 2 ) { if ( argc != 2 ) {
printf("usage: checksum filename\n"); printf("usage: checksum filename\n");
exit(1); exit(1);
} }
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) { if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
printf("Can't open file: %s\n", argv[1]); printf("Can't open file: %s\n", argv[1]);
exit(1); exit(1);
} }
/* Initialize the checksum accumulator */ /* Initialize the checksum accumulator */
Checksum = 0; Checksum = 0;
/* Add each byte in turn into the checksum accumulator */ /* Add each byte in turn into the checksum accumulator */
while ( (ReadLength = read(Handle, &Byte, sizeof(Byte))) > 0 ) { while ( (ReadLength = read(Handle, &Byte, sizeof(Byte))) > 0 ) {
Checksum += (unsigned int) Byte; Checksum += (unsigned int) Byte;
} }
if ( ReadLength == -1 ) { if ( ReadLength == -1 ) {
printf("Error reading file %s\n", argv[1]); printf("Error reading file %s\n", argv[1]);
exit(1); exit(1);
} }
/* Report the result */ /* Report the result */
printf("The checksum is: %u\n", Checksum); printf("The checksum is: %u\n", Checksum);
exit(0); exit(0);
} }
```
Table 1.1 shows the time taken for Listing 1.1 to generate a checksum of Table 1.1 shows the time taken for Listing 1.1 to generate a checksum of
the WordPerfect version 4.2 thesaurus file, TH.WP (362,293 bytes in the WordPerfect version 4.2 thesaurus file, TH.WP (362,293 bytes in

188
01-03.md
View file

@ -30,105 +30,109 @@ disk caching turned off.
**LISTING 1.2 L1-2.C** **LISTING 1.2 L1-2.C**
/* ```c
* Program to calculate the 16-bit checksum of the stream of bytes /*
* from the specified file. Obtains the bytes one at a time in * Program to calculate the 16-bit checksum of the stream of bytes
* assembler, via direct calls to DOS. * from the specified file. Obtains the bytes one at a time in
*/ * assembler, via direct calls to DOS.
*/
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
main(int argc, char *argv[]) { main(int argc, char *argv[]) {
int Handle; int Handle;
unsigned char Byte; unsigned char Byte;
unsigned int Checksum; unsigned int Checksum;
int ReadLength; int ReadLength;
if ( argc != 2 ) { if ( argc != 2 ) {
printf("usage: checksum filename\n"); printf("usage: checksum filename\n");
exit(1); exit(1);
} }
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) { if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
printf("Can't open file: %s\n", argv[1]); printf("Can't open file: %s\n", argv[1]);
exit(1); exit(1);
} }
if ( !ChecksumFile(Handle, &Checksum) ) { if ( !ChecksumFile(Handle, &Checksum) ) {
printf("Error reading file %s\n", argv[1]); printf("Error reading file %s\n", argv[1]);
exit(1); exit(1);
} }
/* Report the result */ /* Report the result */
printf("The checksum is: %u\n", Checksum); printf("The checksum is: %u\n", Checksum);
exit(0); exit(0);
} }
```
**LISTING 1.3 L1-3.ASM** **LISTING 1.3 L1-3.ASM**
; Assembler subroutine to perform a 16-bit checksum on the file ```nasm
; opened on the passed-in handle. Stores the result in the ; Assembler subroutine to perform a 16-bit checksum on the file
; passed-in checksum variable. Returns 1 for success, 0 for error. ; opened on the passed-in handle. Stores the result in the
; ; passed-in checksum variable. Returns 1 for success, 0 for error.
; Call as: ;
; int ChecksumFile(unsigned int Handle, unsigned int *Checksum); ; Call as:
; ; int ChecksumFile(unsigned int Handle, unsigned int *Checksum);
; where: ;
; Handle = handle # under which file to checksum is open ; where:
; Checksum = pointer to unsigned int variable checksum is ; Handle = handle # under which file to checksum is open
; to be stored in ; Checksum = pointer to unsigned int variable checksum is
; ; to be stored in
; Parameter structure: ;
; ; Parameter structure:
Parms struc ;
dw ? ;pushed BP Parms struc
dw ? ;return address dw ? ;pushed BP
Handle dw ? dw ? ;return address
Checksum dw ? Handle dw ?
Parms ends Checksum dw ?
; Parms ends
.model small ;
.data .model small
TempWord label word .data
TempByte db ? ;each byte read by DOS will be stored here TempWord label word
db 0 ;high byte of TempWord is always 0 TempByte db ? ;each byte read by DOS will be stored here
;for 16-bit adds db 0 ;high byte of TempWord is always 0
; ;for 16-bit adds
.code ;
public _ChecksumFile .code
_ChecksumFile proc near public _ChecksumFile
push bp _ChecksumFile proc near
mov bp,sp push bp
push si ;save C's register variable mov bp,sp
; push si ;save C's register variable
mov bx,[bp+Handle] ;get file handle ;
sub si,si ;zero the checksum ;accumulator mov bx,[bp+Handle] ;get file handle
mov cx,1 ;request one byte on each ;read sub si,si ;zero the checksum ;accumulator
mov dx,offset TempByte ;point DX to the byte in mov cx,1 ;request one byte on each ;read
;which DOS should store mov dx,offset TempByte ;point DX to the byte in
;each byte read ;which DOS should store
ChecksumLoop: ;each byte read
mov ah,3fh ;DOS read file function # ChecksumLoop:
int 21h ;read the byte mov ah,3fh ;DOS read file function #
jcErrorEnd;an error occurred int 21h ;read the byte
and ax,ax ;any bytes read? jcErrorEnd;an error occurred
jz Success ;no-end of file reached-we're done and ax,ax ;any bytes read?
add si,[TempWord] ;add the byte into the jz Success ;no-end of file reached-we're done
;checksum total add si,[TempWord] ;add the byte into the
jmpChecksumLoop ;checksum total
ErrorEnd: jmpChecksumLoop
sub ax,ax ;error ErrorEnd:
jmp short Done sub ax,ax ;error
Success: jmp short Done
mov bx,[bp+Checksum] ;point to the checksum variable Success:
mov [bx],si ;save the new checksum mov bx,[bp+Checksum] ;point to the checksum variable
mov ax,1 ;success mov [bx],si ;save the new checksum
; mov ax,1 ;success
Done: ;
pop si ;restore C's register variable Done:
pop bp pop si ;restore C's register variable
ret pop bp
_ChecksumFileendp ret
end _ChecksumFileendp
end
```
The lesson is clear: Optimization makes code faster, but without proper The lesson is clear: Optimization makes code faster, but without proper
design, optimization just creates fast slow code. design, optimization just creates fast slow code.

View file

@ -20,39 +20,41 @@ libraries do their work. In other words, *know the territory*!
**LISTING 1.4 L1-4.C** **LISTING 1.4 L1-4.C**
/* ```c
* Program to calculate the 16-bit checksum of the stream of bytes /*
* from the specified file. Obtains the bytes one at a time via * Program to calculate the 16-bit checksum of the stream of bytes
* getc(), allowing C to perform data buffering. * from the specified file. Obtains the bytes one at a time via
*/ * getc(), allowing C to perform data buffering.
#include <stdio.h> */
#include <stdio.h>
main(int argc, char *argv[]) { main(int argc, char *argv[]) {
FILE *CheckFile; FILE *CheckFile;
int Byte; int Byte;
unsigned int Checksum; unsigned int Checksum;
if ( argc != 2 ) { if ( argc != 2 ) {
printf("usage: checksum filename\n"); printf("usage: checksum filename\n");
exit(1); exit(1);
} }
if ( (CheckFile = fopen(argv[1], "rb")) == NULL ) { if ( (CheckFile = fopen(argv[1], "rb")) == NULL ) {
printf("Can't open file: %s\n", argv[1]); printf("Can't open file: %s\n", argv[1]);
exit(1); exit(1);
} }
/* Initialize the checksum accumulator */ /* Initialize the checksum accumulator */
Checksum = 0; Checksum = 0;
/* Add each byte in turn into the checksum accumulator */ /* Add each byte in turn into the checksum accumulator */
while ( (Byte = getc(CheckFile)) != EOF ) { while ( (Byte = getc(CheckFile)) != EOF ) {
Checksum += (unsigned int) Byte; Checksum += (unsigned int) Byte;
} }
/* Report the result */ /* Report the result */
printf("The checksum is: %u\n", Checksum); printf("The checksum is: %u\n", Checksum);
exit(0); exit(0);
} }
```
#### Know When It Matters {#Heading10} #### Know When It Matters {#Heading10}

194
01-05.md
View file

@ -30,62 +30,64 @@ uses no assembly at all.
**LISTING 1.5 L1-5.C** **LISTING 1.5 L1-5.C**
/* ```c
* Program to calculate the 16-bit checksum of the stream of bytes /*
* from the specified file. Buffers the bytes internally, rather * Program to calculate the 16-bit checksum of the stream of bytes
* than letting C or DOS do the work. * from the specified file. Buffers the bytes internally, rather
*/ * than letting C or DOS do the work.
#include <stdio.h> */
#include <fcntl.h> #include <stdio.h>
#include <alloc.h> /* alloc.h for Borland, #include <fcntl.h>
malloc.h for Microsoft */ #include <alloc.h> /* alloc.h for Borland,
malloc.h for Microsoft */
#define BUFFER_SIZE 0x8000 /* 32Kb data buffer */ #define BUFFER_SIZE 0x8000 /* 32Kb data buffer */
main(int argc, char *argv[]) { main(int argc, char *argv[]) {
int Handle; int Handle;
unsigned int Checksum; unsigned int Checksum;
unsigned char *WorkingBuffer, *WorkingPtr; unsigned char *WorkingBuffer, *WorkingPtr;
int WorkingLength, LengthCount; int WorkingLength, LengthCount;
if ( argc != 2 ) { if ( argc != 2 ) {
printf("usage: checksum filename\n"); printf("usage: checksum filename\n");
exit(1); exit(1);
} }
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) { if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
printf("Can't open file: %s\n", argv[1]); printf("Can't open file: %s\n", argv[1]);
exit(1); exit(1);
} }
/* Get memory in which to buffer the data */ /* Get memory in which to buffer the data */
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) { if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
printf("Can't get enough memory\n"); printf("Can't get enough memory\n");
exit(1); exit(1);
} }
/* Initialize the checksum accumulator */ /* Initialize the checksum accumulator */
Checksum = 0; Checksum = 0;
/* Process the file in BUFFER_SIZE chunks */ /* Process the file in BUFFER_SIZE chunks */
do { do {
if ( (WorkingLength = read(Handle, WorkingBuffer, if ( (WorkingLength = read(Handle, WorkingBuffer,
BUFFER_SIZE)) == -1 ) { BUFFER_SIZE)) == -1 ) {
printf("Error reading file %s\n", argv[1]); printf("Error reading file %s\n", argv[1]);
exit(1); exit(1);
} }
/* Checksum this chunk */ /* Checksum this chunk */
WorkingPtr = WorkingBuffer; WorkingPtr = WorkingBuffer;
LengthCount = WorkingLength; LengthCount = WorkingLength;
while ( LengthCount-- ) { while ( LengthCount-- ) {
/* Add each byte in turn into the checksum accumulator */ /* Add each byte in turn into the checksum accumulator */
Checksum += (unsigned int) *WorkingPtr++; Checksum += (unsigned int) *WorkingPtr++;
} }
} while ( WorkingLength ); } while ( WorkingLength );
/* Report the result */ /* Report the result */
printf("The checksum is: %u\n", Checksum); printf("The checksum is: %u\n", Checksum);
exit(0); exit(0);
} }
```
That brings us to the fourth reason: avoiding an internal-buffered That brings us to the fourth reason: avoiding an internal-buffered
implementation like Listing 1.5 because of the difficulty of coding such implementation like Listing 1.5 because of the difficulty of coding such
@ -122,56 +124,58 @@ the design has been maxed out.
**LISTING 1.6 L1-6.C** **LISTING 1.6 L1-6.C**
/* ```c
* Program to calculate the 16-bit checksum of the stream of bytes /*
* from the specified file. Buffers the bytes internally, rather * Program to calculate the 16-bit checksum of the stream of bytes
* than letting C or DOS do the work, with the time-critical * from the specified file. Buffers the bytes internally, rather
* portion of the code written in optimized assembler. * than letting C or DOS do the work, with the time-critical
*/ * portion of the code written in optimized assembler.
#include <stdio.h> */
#include <fcntl.h> #include <stdio.h>
#include <alloc.h> /* alloc.h for Borland, #include <fcntl.h>
malloc.h for Microsoft */ #include <alloc.h> /* alloc.h for Borland,
malloc.h for Microsoft */
#define BUFFER_SIZE 0x8000 /* 32K data buffer */ #define BUFFER_SIZE 0x8000 /* 32K data buffer */
main(int argc, char *argv[]) { main(int argc, char *argv[]) {
int Handle; int Handle;
unsigned int Checksum; unsigned int Checksum;
unsigned char *WorkingBuffer; unsigned char *WorkingBuffer;
int WorkingLength; int WorkingLength;
if ( argc != 2 ) { if ( argc != 2 ) {
printf("usage: checksum filename\n"); printf("usage: checksum filename\n");
exit(1); exit(1);
} }
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) { if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
printf("Can't open file: %s\n", argv[1]); printf("Can't open file: %s\n", argv[1]);
exit(1); exit(1);
} }
/* Get memory in which to buffer the data */ /* Get memory in which to buffer the data */
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) { if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
printf("Can't get enough memory\n"); printf("Can't get enough memory\n");
exit(1); exit(1);
} }
/* Initialize the checksum accumulator */ /* Initialize the checksum accumulator */
Checksum = 0; Checksum = 0;
/* Process the file in 32K chunks */ /* Process the file in 32K chunks */
do { do {
if ( (WorkingLength = read(Handle, WorkingBuffer, if ( (WorkingLength = read(Handle, WorkingBuffer,
BUFFER_SIZE)) == -1 ) { BUFFER_SIZE)) == -1 ) {
printf("Error reading file %s\n", argv[1]); printf("Error reading file %s\n", argv[1]);
exit(1); exit(1);
} }
/* Checksum this chunk if there's anything in it */ /* Checksum this chunk if there's anything in it */
if ( WorkingLength ) if ( WorkingLength )
ChecksumChunk(WorkingBuffer, WorkingLength, &Checksum); ChecksumChunk(WorkingBuffer, WorkingLength, &Checksum);
} while ( WorkingLength ); } while ( WorkingLength );
/* Report the result */ /* Report the result */
printf("The checksum is: %u\n", Checksum); printf("The checksum is: %u\n", Checksum);
exit(0); exit(0);
} }
```

100
01-06.md
View file

@ -12,55 +12,57 @@ pages: 018-019
**LISTING 1.7 L1-7.ASM** **LISTING 1.7 L1-7.ASM**
; Assembler subroutine to perform a 16-bit checksum on a block of ```nasm
; bytes 1 to 64K in size. Adds checksum for block into passed-in ; Assembler subroutine to perform a 16-bit checksum on a block of
; checksum. ; bytes 1 to 64K in size. Adds checksum for block into passed-in
; ; checksum.
; Call as: ;
; void ChecksumChunk(unsigned char *Buffer, ; Call as:
; unsigned int BufferLength, unsigned int *Checksum); ; void ChecksumChunk(unsigned char *Buffer,
; ; unsigned int BufferLength, unsigned int *Checksum);
; where: ;
; Buffer = pointer to start of block of bytes to checksum ; where:
; BufferLength = # of bytes to checksum (0 means 64K, not 0) ; Buffer = pointer to start of block of bytes to checksum
; Checksum = pointer to unsigned int variable checksum is ; BufferLength = # of bytes to checksum (0 means 64K, not 0)
;stored in ; Checksum = pointer to unsigned int variable checksum is
; ;stored in
; Parameter structure: ;
; ; Parameter structure:
Parms struc ;
dw ? ;pushed BP Parms struc
dw ? ;return address dw ? ;pushed BP
Buffer dw ? dw ? ;return address
BufferLength dw ? Buffer dw ?
Checksum dw ? BufferLength dw ?
Parmsends Checksum dw ?
; Parmsends
.model small ;
.code .model small
public _ChecksumChunk .code
_ChecksumChunkprocnear public _ChecksumChunk
push bp _ChecksumChunkprocnear
mov bp,sp push bp
push si ;save C's register variable mov bp,sp
; push si ;save C's register variable
cld ;make LODSB increment SI ;
mov si,[bp+Buffer] ;point to buffer cld ;make LODSB increment SI
mov cx,[bp+BufferLength] ;get buffer length mov si,[bp+Buffer] ;point to buffer
mov bx,[bp+Checksum] ;point to checksum variable mov cx,[bp+BufferLength] ;get buffer length
mov dx,[bx] ;get the current checksum mov bx,[bp+Checksum] ;point to checksum variable
sub ah,ah ;so AX will be a 16-bit value after LODSB mov dx,[bx] ;get the current checksum
ChecksumLoop: sub ah,ah ;so AX will be a 16-bit value after LODSB
lodsb ;get the next byte ChecksumLoop:
add dx,ax ;add it into the checksum total lodsb ;get the next byte
loop ChecksumLoop ;continue for all bytes in block add dx,ax ;add it into the checksum total
mov [bx],dx ;save the new checksum loop ChecksumLoop ;continue for all bytes in block
; mov [bx],dx ;save the new checksum
pop si ;restore C's register variable ;
pop bp pop si ;restore C's register variable
ret pop bp
_ChecksumChunkendp ret
end _ChecksumChunkendp
end
```
Note that in Table 1.1, optimization makes little difference except in Note that in Table 1.1, optimization makes little difference except in
the case of Listing 1.5, where the design has been refined considerably. the case of Listing 1.5, where the design has been refined considerably.

View file

@ -45,14 +45,16 @@ bytes.) I examined the subroutine line by line, saving a cycle here and
a cycle there, until the code truly seemed to be optimized. When I was a cycle there, until the code truly seemed to be optimized. When I was
done, the key part of the code looked something like this: done, the key part of the code looked something like this:
LoopTop: ```nasm
lodsb ;get the next byte to extract a bit from LoopTop:
and al,ah ;isolate the bit we want lodsb ;get the next byte to extract a bit from
rol al,cl ;rotate the bit into the desired position and al,ah ;isolate the bit we want
or bl,al ;insert the bit into the final nibble rol al,cl ;rotate the bit into the desired position
dec cx ;the next bit goes 1 place to the right or bl,al ;insert the bit into the final nibble
dec dx ;count down the number of bits dec cx ;the next bit goes 1 place to the right
jnz LoopTop ;process the next bit, if any dec dx ;count down the number of bits
jnz LoopTop ;process the next bit, if any
```
Now, it's hard to write code that's much faster than seven instructions, Now, it's hard to write code that's much faster than seven instructions,
only one of which accesses memory, and most programmers would have only one of which accesses memory, and most programmers would have
@ -68,15 +70,17 @@ total of four separate time-consuming multibit rotations!
I changed the code to the following: I changed the code to the following:
LoopTop: ```nasm
lodsb ;get the next byte to extract a bit from LoopTop:
and al,ah ;isolate the bit we want lodsb ;get the next byte to extract a bit from
or bl,al ;insert the bit into the final nibble and al,ah ;isolate the bit we want
rol bl,1 ;make room for the next bit or bl,al ;insert the bit into the final nibble
dec dx ;count down the number of bits rol bl,1 ;make room for the next bit
jnz LoopTop ;process the next bit, if any dec dx ;count down the number of bits
rol bl,cl ;rotate all four bits into their final jnz LoopTop ;process the next bit, if any
; positions at the same time rol bl,cl ;rotate all four bits into their final
; positions at the same time
```
This moved the costly multibit rotation out of the loop so that it was This moved the costly multibit rotation out of the loop so that it was
performed just once, rather than four times. While the code may not look performed just once, rather than four times. While the code may not look

860
03-02.md
View file

@ -12,442 +12,444 @@ pages: 035-042
**LISTING 3.1 PZTIMER.ASM** **LISTING 3.1 PZTIMER.ASM**
; The precision Zen timer (PZTIMER.ASM) ```nasm
; ; The precision Zen timer (PZTIMER.ASM)
; Uses the 8253 timer to time the performance of code that takes ;
; less than about 54 milliseconds to execute, with a resolution ; Uses the 8253 timer to time the performance of code that takes
; of better than 10 microseconds. ; less than about 54 milliseconds to execute, with a resolution
; ; of better than 10 microseconds.
; By Michael Abrash ;
; ; By Michael Abrash
; Externally callable routines: ;
; ; Externally callable routines:
; ZTimerOn: Starts the Zen timer, with interrupts disabled. ;
; ; ZTimerOn: Starts the Zen timer, with interrupts disabled.
; ZTimerOff: Stops the Zen timer, saves the timer count, ;
; times the overhead code, and restores interrupts to the ; ZTimerOff: Stops the Zen timer, saves the timer count,
; state they were in when ZTimerOn was called. ; times the overhead code, and restores interrupts to the
; ; state they were in when ZTimerOn was called.
; ZTimerReport: Prints the net time that passed between starting ;
; and stopping the timer. ; ZTimerReport: Prints the net time that passed between starting
; ; and stopping the timer.
; Note: If longer than about 54 ms passes between ZTimerOn and ;
; ZTimerOff calls, the timer turns over and the count is ; Note: If longer than about 54 ms passes between ZTimerOn and
; inaccurate. When this happens, an error message is displayed ; ZTimerOff calls, the timer turns over and the count is
; instead of a count. The long-period Zen timer should be used ; inaccurate. When this happens, an error message is displayed
; in such cases. ; instead of a count. The long-period Zen timer should be used
; ; in such cases.
; Note: Interrupts *MUST* be left off between calls to ZTimerOn ;
; and ZTimerOff for accurate timing and for detection of ; Note: Interrupts *MUST* be left off between calls to ZTimerOn
; timer overflow. ; and ZTimerOff for accurate timing and for detection of
; ; timer overflow.
; Note: These routines can introduce slight inaccuracies into the ;
; system clock count for each code section timed even if ; Note: These routines can introduce slight inaccuracies into the
; timer 0 doesn't overflow. If timer 0 does overflow, the ; system clock count for each code section timed even if
; system clock can become slow by virtually any amount of ; timer 0 doesn't overflow. If timer 0 does overflow, the
; time, since the system clock can't advance while the ; system clock can become slow by virtually any amount of
; precison timer is timing. Consequently, it's a good idea ; time, since the system clock can't advance while the
; to reboot at the end of each timing session. (The ; precison timer is timing. Consequently, it's a good idea
; battery-backed clock, if any, is not affected by the Zen ; to reboot at the end of each timing session. (The
; timer.) ; battery-backed clock, if any, is not affected by the Zen
; ; timer.)
; All registers, and all flags except the interrupt flag, are ;
; preserved by all routines. Interrupts are enabled and then disabled ; All registers, and all flags except the interrupt flag, are
; by ZTimerOn, and are restored by ZTimerOff to the state they were ; preserved by all routines. Interrupts are enabled and then disabled
; in when ZTimerOn was called. ; by ZTimerOn, and are restored by ZTimerOff to the state they were
; ; in when ZTimerOn was called.
;
Code segment word public ‘CODE' Code segment word public ‘CODE'
assumecs: Code, ds:nothing assumecs: Code, ds:nothing
public ZTimerOn, ZTimerOff, ZTimerReport public ZTimerOn, ZTimerOff, ZTimerReport
; ;
; Base address of the 8253 timer chip. ; Base address of the 8253 timer chip.
; ;
BASE_8253equ40h BASE_8253equ40h
; ;
; The address of the timer 0 count registers in the 8253. ; The address of the timer 0 count registers in the 8253.
; ;
TIMER_0_8253 equBASE_8253 + 0 TIMER_0_8253 equBASE_8253 + 0
; ;
; The address of the mode register in the 8253. ; The address of the mode register in the 8253.
; ;
MODE_8253 equBASE_8253 + 3 MODE_8253 equBASE_8253 + 3
; ;
; The address of Operation Command Word 3 in the 8259 Programmable ; The address of Operation Command Word 3 in the 8259 Programmable
; Interrupt Controller (PIC) (write only, and writable only when ; Interrupt Controller (PIC) (write only, and writable only when
; bit 4 of the byte written to this address is 0 and bit 3 is 1). ; bit 4 of the byte written to this address is 0 and bit 3 is 1).
; ;
OCW3 equ20h OCW3 equ20h
; ;
; The address of the Interrupt Request register in the 8259 PIC ; The address of the Interrupt Request register in the 8259 PIC
; (read only, and readable only when bit 1 of OCW3 = 1 and bit 0 ; (read only, and readable only when bit 1 of OCW3 = 1 and bit 0
; of OCW3 = 0). ; of OCW3 = 0).
; ;
IRR equ20h IRR equ20h
; ;
; Macro to emulate a POPF instruction in order to fix the bug in some ; Macro to emulate a POPF instruction in order to fix the bug in some
; 80286 chips which allows interrupts to occur during a POPF even when ; 80286 chips which allows interrupts to occur during a POPF even when
; interrupts remain disabled. ; interrupts remain disabled.
; ;
MPOPF macro MPOPF macro
local p1, p2 local p1, p2
jmp short p2 jmp short p2
p1: iret ; jump to pushed address & pop flags p1: iret ; jump to pushed address & pop flags
p2: push cs ; construct far return address to p2: push cs ; construct far return address to
call p1 ; the next instruction call p1 ; the next instruction
endm endm
; ;
; Macro to delay briefly to ensure that enough time has elapsed ; Macro to delay briefly to ensure that enough time has elapsed
; between successive I/O accesses so that the device being accessed ; between successive I/O accesses so that the device being accessed
; can respond to both accesses even on a very fast PC. ; can respond to both accesses even on a very fast PC.
; ;
DELAY macro DELAY macro
jmp $+2 jmp $+2
jmp $+2 jmp $+2
jmp $+2 jmp $+2
endm endm
OriginalFlags db ? ; storage for upper byte of OriginalFlags db ? ; storage for upper byte of
; FLAGS register when ; FLAGS register when
; ZTimerOn called ; ZTimerOn called
TimedCount dw ? ; timer 0 count when the timer TimedCount dw ? ; timer 0 count when the timer
; is stopped ; is stopped
ReferenceCount dw ; number of counts required to ReferenceCount dw ; number of counts required to
; execute timer overhead code ; execute timer overhead code
OverflowFlag db ? ; used to indicate whether the OverflowFlag db ? ; used to indicate whether the
; timer overflowed during the ; timer overflowed during the
; timing interval ; timing interval
; ;
; String printed to report results. ; String printed to report results.
; ;
OutputStr label byte OutputStr label byte
db 0dh, 0ah, ‘Timed count: ‘, 5 dup (?) db 0dh, 0ah, ‘Timed count: ‘, 5 dup (?)
ASCIICountEnd labelbyte ASCIICountEnd labelbyte
db ‘ microseconds', 0dh, 0ah db ‘ microseconds', 0dh, 0ah
db ‘$' db ‘$'
; ;
; String printed to report timer overflow. ; String printed to report timer overflow.
; ;
OverflowStr label byte OverflowStr label byte
db 0dh, 0ah db 0dh, 0ah
db ‘****************************************************' db ‘****************************************************'
db 0dh, 0ah db 0dh, 0ah
db ‘* The timer overflowed, so the interval timed was *' db ‘* The timer overflowed, so the interval timed was *'
db 0dh, 0ah db 0dh, 0ah
db ‘* too long for the precision timer to measure. *' db ‘* too long for the precision timer to measure. *'
db 0dh, 0ah db 0dh, 0ah
db ‘* Please perform the timing test again with the *' db ‘* Please perform the timing test again with the *'
db0dh, 0ah db0dh, 0ah
db ‘* long-period timer. *' db ‘* long-period timer. *'
db 0dh, 0ah db 0dh, 0ah
db ‘****************************************************' db ‘****************************************************'
db 0dh, 0ah db 0dh, 0ah
db ‘$' db ‘$'
; ******************************************************************** ; ********************************************************************
; * Routine called to start timing. * ; * Routine called to start timing. *
; ******************************************************************** ; ********************************************************************
ZTimerOn proc near ZTimerOn proc near
;
; Save the context of the program being timed.
;
push ax
pushf
pop ax ; get flags so we can keep
; interrupts off when leaving
; this routine
mov cs:[OriginalFlags],ah ; remember the state of the
; Interrupt flag
and ah,0fdh ; set pushed interrupt flag
; to 0
push ax
;
; Turn on interrupts, so the timer interrupt can occur if it's
; pending.
;
sti
;
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
; linear counting rather than count-by-two counting. Also
; leaves the 8253 waiting for the initial timer 0 count to
; be loaded.
;
mov al,00110100b ;mode 2
out MODE_8253,al
;
; Set the timer count to 0, so we know we won't get another
; timer interrupt right away.
; Note: this introduces an inaccuracy of up to 54 ms in the system
; clock count each time it is executed.
;
DELAY
sub al,al
out TIMER_0_8253,al ;lsb
DELAY
out TIMER_0_8253,al ;msb
;
; Wait before clearing interrupts to allow the interrupt generated
; when switching from mode 3 to mode 2 to be recognized. The delay
; must be at least 210 ns long to allow time for that interrupt to
; occur. Here, 10 jumps are used for the delay to ensure that the
; delay time will be more than long enough even on a very fast PC.
;
rept 10
jmp $+2
endm
;
; Disable interrupts to get an accurate count.
;
cli
;
; Set the timer count to 0 again to start the timing interval.
;
mov al,00110100b ; set up to load initial
out MODE_8253,al ; timer count
DELAY
sub al,al
out TIMER_0_8253,al ; load count lsb
DELAY
out TIMER_0_8253,al; load count msb
;
; Restore the context and return.
;
MPOPF ; keeps interrupts off
pop ax
ret
ZTimerOn endp
;********************************************************************
;* Routine called to stop timing and get count. *
;********************************************************************
ZTimerOff proc near
;
; Save the context of the program being timed.
;
push ax
push cx
pushf
;
; Latch the count.
;
mov al,00000000b ; latch timer 0
out MODE_8253,al
;
; See if the timer has overflowed by checking the 8259 for a pending
; timer interrupt.
;
mov al,00001010b ; OCW3, set up to read
out OCW3,al; Int errupt Request register
DELAY
ina l,IRR; read Interrupt Request
; register
and al,1 ; set AL to 1 if IRQ0 (the
; timer interrupt) is pending
mov cs:[OverflowFlag],al; store the timer overflow
; status
;
; Allow interrupts to happen again.
;
sti
;
; Read out the count we latched earlier.
;
in al,TIMER_0_8253 ; least significant byte
DELAY
mov ah,al
in al,TIMER_0_8253 ; most significant byte
xchg ah,al
neg ax ; convert from countdown
; remaining to elapsed
; count
mov cs:[TimedCount],ax
; Time a zero-length code fragment, to get a reference for how
; much overhead this routine has. Time it 16 times and average it,
; for accuracy, rounding the result.
;
mov cs:[ReferenceCount],0
mov cx,16
cli ; interrupts off to allow a
; precise reference count
RefLoop:
call ReferenceZTimerOn
call ReferenceZTimerOff
loop RefLoop
sti
add cs:[ReferenceCount],8; total + (0.5 * 16)
mov cl,4
shr cs:[ReferenceCount],cl; (total) / 16 + 0.5
;
; Restore original interrupt state.
;
pop ax ; retrieve flags when called
mov ch,cs:[OriginalFlags] ; get back the original upper
; byte of the FLAGS register
and ch,not 0fdh ; only care about original
; interrupt flag...
and ah,0fdh ; ...keep all other flags in
; their current condition
or ah,ch ; make flags word with original
; interrupt flag
push ax ; prepare flags to be popped
;
; Restore the context of the program being timed and return to it.
;
MPOPF ; restore the flags with the
; original interrupt state
pop cx
pop ax
ret
ZTimerOff endp
;
; Called by ZTimerOff to start timer for overhead measurements.
;
ReferenceZTimerOnproc near
;
; Save the context of the program being timed.
;
push ax
pushf ; interrupts are already off
;
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
; linear counting rather than count-by-two counting.
;
mov al,00110100b ; set up to load
out MODE_8253,al ; initial timer count
DELAY
;
; Set the timer count to 0.
;
sub al,al
out TIMER_0_8253,al; load count lsb
DELAY
out TIMER_0_8253,al; load count msb
;
; Restore the context of the program being timed and return to it.
;
MPOPF
pop ax
ret
ReferenceZTimerOnendp
;
; Called by ZTimerOff to stop timer and add result to ReferenceCount
; for overhead measurements.
;
ReferenceZTimerOff proc near
;
; Save the context of the program being timed.
;
push ax
push cx
pushf
;
; Latch the count and read it.
;
mov al,00000000b ; latch timer 0
out MODE_8253,al
DELAY
in al,TIMER_0_8253 ; lsb
DELAY
mov ah,al
in al,TIMER_0_8253 ; msb
xchg ah,al
neg ax ; convert from countdown
; remaining to amount
; counted down
add cs:[ReferenceCount],ax
;
; Restore the context of the program being timed and return to it.
;
MPOPF
pop cx
pop ax
ret
ReferenceZTimerOff endp
; ********************************************************************
; * Routine called to report timing results. *
; ********************************************************************
ZTimerReport procnear
;
; Save the context of the program being timed.
;
push ax
pushf pushf
pop ax ; get flags so we can keep push ax
; interrupts off when leaving push bx
; this routine push cx
mov cs:[OriginalFlags],ah ; remember the state of the push dx
; Interrupt flag push si
and ah,0fdh ; set pushed interrupt flag push ds
; to 0 ;
push ax push cs ; DOS functions require that DS point
; pop ds ; to text to be displayed on the screen
; Turn on interrupts, so the timer interrupt can occur if it's assume ds :Code
; pending. ;
; ; Check for timer 0 overflow.
sti ;
; cmp [OverflowFlag],0
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause jz PrintGoodCount
; linear counting rather than count-by-two counting. Also mov dx,offset OverflowStr
; leaves the 8253 waiting for the initial timer 0 count to mov ah,9
; be loaded. int 21h
; jmp short EndZTimerReport
mov al,00110100b ;mode 2 ;
out MODE_8253,al ; Convert net count to decimal ASCII in microseconds.
; ;
; Set the timer count to 0, so we know we won't get another PrintGoodCount:
; timer interrupt right away. mov ax,[TimedCount]
; Note: this introduces an inaccuracy of up to 54 ms in the system sub ax,[ReferenceCount]
; clock count each time it is executed. mov si,offset ASCIICountEnd - 1
; ;
DELAY ; Convert count to microseconds by multiplying by .8381.
sub al,al ;
out TIMER_0_8253,al ;lsb mov dx, 8381
DELAY mul dx
out TIMER_0_8253,al ;msb mov bx, 10000
; div bx ;* .8381 = * 8381 / 10000
; Wait before clearing interrupts to allow the interrupt generated ;
; when switching from mode 3 to mode 2 to be recognized. The delay ; Convert time in microseconds to 5 decimal ASCII digits.
; must be at least 210 ns long to allow time for that interrupt to ;
; occur. Here, 10 jumps are used for the delay to ensure that the mov bx, 10
; delay time will be more than long enough even on a very fast PC. mov cx, 5
; CTSLoop:
rept 10 sub dx, dx
jmp $+2 div bx
endm add dl,'0'
; mov [si],dl
; Disable interrupts to get an accurate count. dec si
; loop CTSLoop
cli ;
; ; Print the results.
; Set the timer count to 0 again to start the timing interval. ;
; mov ah, 9
mov al,00110100b ; set up to load initial mov dx, offset OutputStr
out MODE_8253,al ; timer count int 21h
DELAY ;
sub al,al EndZTimerReport:
out TIMER_0_8253,al ; load count lsb pop ds
DELAY pop si
out TIMER_0_8253,al; load count msb pop dx
; pop cx
; Restore the context and return. pop bx
; pop ax
MPOPF ; keeps interrupts off MPOPF
pop ax ret
ret
ZTimerOn endp ZTimerReport endp
;******************************************************************** Code ends
;* Routine called to stop timing and get count. * end
;******************************************************************** ```
ZTimerOff proc near
;
; Save the context of the program being timed.
;
push ax
push cx
pushf
;
; Latch the count.
;
mov al,00000000b ; latch timer 0
out MODE_8253,al
;
; See if the timer has overflowed by checking the 8259 for a pending
; timer interrupt.
;
mov al,00001010b ; OCW3, set up to read
out OCW3,al; Int errupt Request register
DELAY
ina l,IRR; read Interrupt Request
; register
and al,1 ; set AL to 1 if IRQ0 (the
; timer interrupt) is pending
mov cs:[OverflowFlag],al; store the timer overflow
; status
;
; Allow interrupts to happen again.
;
sti
;
; Read out the count we latched earlier.
;
in al,TIMER_0_8253 ; least significant byte
DELAY
mov ah,al
in al,TIMER_0_8253 ; most significant byte
xchg ah,al
neg ax ; convert from countdown
; remaining to elapsed
; count
mov cs:[TimedCount],ax
; Time a zero-length code fragment, to get a reference for how
; much overhead this routine has. Time it 16 times and average it,
; for accuracy, rounding the result.
;
mov cs:[ReferenceCount],0
mov cx,16
cli ; interrupts off to allow a
; precise reference count
RefLoop:
call ReferenceZTimerOn
call ReferenceZTimerOff
loop RefLoop
sti
add cs:[ReferenceCount],8; total + (0.5 * 16)
mov cl,4
shr cs:[ReferenceCount],cl; (total) / 16 + 0.5
;
; Restore original interrupt state.
;
pop ax ; retrieve flags when called
mov ch,cs:[OriginalFlags] ; get back the original upper
; byte of the FLAGS register
and ch,not 0fdh ; only care about original
; interrupt flag...
and ah,0fdh ; ...keep all other flags in
; their current condition
or ah,ch ; make flags word with original
; interrupt flag
push ax ; prepare flags to be popped
;
; Restore the context of the program being timed and return to it.
;
MPOPF ; restore the flags with the
; original interrupt state
pop cx
pop ax
ret
ZTimerOff endp
;
; Called by ZTimerOff to start timer for overhead measurements.
;
ReferenceZTimerOnproc near
;
; Save the context of the program being timed.
;
push ax
pushf ; interrupts are already off
;
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
; linear counting rather than count-by-two counting.
;
mov al,00110100b ; set up to load
out MODE_8253,al ; initial timer count
DELAY
;
; Set the timer count to 0.
;
sub al,al
out TIMER_0_8253,al; load count lsb
DELAY
out TIMER_0_8253,al; load count msb
;
; Restore the context of the program being timed and return to it.
;
MPOPF
pop ax
ret
ReferenceZTimerOnendp
;
; Called by ZTimerOff to stop timer and add result to ReferenceCount
; for overhead measurements.
;
ReferenceZTimerOff proc near
;
; Save the context of the program being timed.
;
push ax
push cx
pushf
;
; Latch the count and read it.
;
mov al,00000000b ; latch timer 0
out MODE_8253,al
DELAY
in al,TIMER_0_8253 ; lsb
DELAY
mov ah,al
in al,TIMER_0_8253 ; msb
xchg ah,al
neg ax ; convert from countdown
; remaining to amount
; counted down
add cs:[ReferenceCount],ax
;
; Restore the context of the program being timed and return to it.
;
MPOPF
pop cx
pop ax
ret
ReferenceZTimerOff endp
; ********************************************************************
; * Routine called to report timing results. *
; ********************************************************************
ZTimerReport procnear
pushf
push ax
push bx
push cx
push dx
push si
push ds
;
push cs ; DOS functions require that DS point
pop ds ; to text to be displayed on the screen
assume ds :Code
;
; Check for timer 0 overflow.
;
cmp [OverflowFlag],0
jz PrintGoodCount
mov dx,offset OverflowStr
mov ah,9
int 21h
jmp short EndZTimerReport
;
; Convert net count to decimal ASCII in microseconds.
;
PrintGoodCount:
mov ax,[TimedCount]
sub ax,[ReferenceCount]
mov si,offset ASCIICountEnd - 1
;
; Convert count to microseconds by multiplying by .8381.
;
mov dx, 8381
mul dx
mov bx, 10000
div bx ;* .8381 = * 8381 / 10000
;
; Convert time in microseconds to 5 decimal ASCII digits.
;
mov bx, 10
mov cx, 5
CTSLoop:
sub dx, dx
div bx
add dl,'0'
mov [si],dl
dec si
loop CTSLoop
;
; Print the results.
;
mov ah, 9
mov dx, offset OutputStr
int 21h
;
EndZTimerReport:
pop ds
pop si
pop dx
pop cx
pop bx
pop ax
MPOPF
ret
ZTimerReport endp
Code ends
end

126
03-05.md
View file

@ -82,43 +82,45 @@ and should contain calls to **ZTimerOn** and **ZTimerOff** .
**LISTING 3.2 PZTEST.ASM** **LISTING 3.2 PZTEST.ASM**
; Program to measure performance of code that takes less than ```nasm
; 54 ms to execute. (PZTEST.ASM) ; Program to measure performance of code that takes less than
; ; 54 ms to execute. (PZTEST.ASM)
; Link with PZTIMER.ASM (Listing 3.1). PZTEST.BAT (Listing 3.4) ;
; can be used to assemble and link both files. Code to be ; Link with PZTIMER.ASM (Listing 3.1). PZTEST.BAT (Listing 3.4)
; measured must be in the file TESTCODE; Listing 3.3 shows ; can be used to assemble and link both files. Code to be
; a sample TESTCODE file. ; measured must be in the file TESTCODE; Listing 3.3 shows
; ; a sample TESTCODE file.
; By Michael Abrash ;
; ; By Michael Abrash
mystack segment para stack ‘STACK' ;
db 512 dup(?) mystack segment para stack ‘STACK'
mystack ends db 512 dup(?)
; mystack ends
Code segment para public ‘CODE' ;
assume cs:Code, ds:Code Code segment para public ‘CODE'
extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near assume cs:Code, ds:Code
Start proc near extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near
push cs Start proc near
pop ds ; set DS to point to the code segment, push cs
; so data as well as code can easily pop ds ; set DS to point to the code segment,
; be included in TESTCODE ; so data as well as code can easily
; ; be included in TESTCODE
include TESTCODE ;code to be measured, including ;
; calls to ZTimerOn and ZTimerOff include TESTCODE ;code to be measured, including
; ; calls to ZTimerOn and ZTimerOff
; Display the results. ;
; ; Display the results.
call ZTimerReport ;
; call ZTimerReport
; Terminate the program. ;
; ; Terminate the program.
mov ah,4ch ;
int 21h mov ah,4ch
Start endp int 21h
Code ends Start endp
end Start Code ends
end Start
```
Listing 3.3 shows some sample code to be timed. This listing measures Listing 3.3 shows some sample code to be timed. This listing measures
the time required to execute 1,000 loads of AL from the memory variable the time required to execute 1,000 loads of AL from the memory variable
@ -130,30 +132,32 @@ after the code in Listing 3.3 has been run.
**LISTING 3.3 LST3-3.ASM** **LISTING 3.3 LST3-3.ASM**
; Test file; ```nasm
; Measures the performance of 1,000 loads of AL from ; Test file;
; memory. (Use by renaming to TESTCODE, which is ; Measures the performance of 1,000 loads of AL from
; included by PZTEST.ASM (Listing 3.2). PZTIME.BAT ; memory. (Use by renaming to TESTCODE, which is
; (Listing 3.4) does this, along with all assembly ; included by PZTEST.ASM (Listing 3.2). PZTIME.BAT
; and linking.) ; (Listing 3.4) does this, along with all assembly
; ; and linking.)
jmp Skip ;jump around defined data ;
; jmp Skip ;jump around defined data
MemVar db ? ;
; MemVar db ?
Skip: ;
; Skip:
; Start timing. ;
; ; Start timing.
call ZTimerOn ;
; call ZTimerOn
rept 1000 ;
mov al,[MemVar] rept 1000
endm mov al,[MemVar]
; endm
; Stop timing. ;
; ; Stop timing.
call ZTimerOff ;
call ZTimerOff
```
It's worth noting that Listing 3.3 begins by jumping around the memory It's worth noting that Listing 3.3 begins by jumping around the memory
variable **MemVar**. This approach lets us avoid reproducing Listing 3.2 variable **MemVar**. This approach lets us avoid reproducing Listing 3.2

132
03-06.md
View file

@ -25,72 +25,76 @@ and "link" with "tlink" in Listing 3.4. The same is true of Listing
**LISTING 3.4 PZTIME.BAT** **LISTING 3.4 PZTIME.BAT**
echo off ```bat
rem echo off
rem *** Listing 3.4 *** rem
rem rem *** Listing 3.4 ***
rem *************************************************************** rem
rem * Batch file PZTIME.BAT, which builds and runs the precision * rem ***************************************************************
rem * Zen timer program PZTEST.EXE to time the code named as the * rem * Batch file PZTIME.BAT, which builds and runs the precision *
rem * command-line parameter. Listing 3.1 must be named * rem * Zen timer program PZTEST.EXE to time the code named as the *
rem * PZTIMER.ASM, and Listing 3.2 must be named PZTEST.ASM. To * rem * command-line parameter. Listing 3.1 must be named *
rem * time the code in LST3-3, you'd type the DOS command: * rem * PZTIMER.ASM, and Listing 3.2 must be named PZTEST.ASM. To *
rem * * rem * time the code in LST3-3, you'd type the DOS command: *
rem * pztime lst3-3 * rem * *
rem * * rem * pztime lst3-3 *
rem * Note that MASM and LINK must be in the current directory or * rem * *
rem * on the current path in order for this batch file to work. * rem * Note that MASM and LINK must be in the current directory or *
rem * * rem * on the current path in order for this batch file to work. *
rem * This batch file can be speeded up by assembling PZTIMER.ASM * rem * *
rem * once, then removing the lines: * rem * This batch file can be speeded up by assembling PZTIMER.ASM *
rem * * rem * once, then removing the lines: *
rem * masm pztimer; * rem * *
rem * if errorlevel 1 goto errorend * rem * masm pztimer; *
rem * * rem * if errorlevel 1 goto errorend *
rem * from this file. * rem * *
rem * * rem * from this file. *
rem * By Michael Abrash * rem * *
rem *************************************************************** rem * By Michael Abrash *
rem rem ***************************************************************
rem Make sure a file to test was specified. rem
rem rem Make sure a file to test was specified.
if not x%1==x goto ckexist rem
echo *************************************************************** if not x%1==x goto ckexist
echo * Please specify a file to test. * echo ***************************************************************
echo *************************************************************** echo * Please specify a file to test. *
goto end echo ***************************************************************
rem goto end
rem Make sure the file exists. rem
rem rem Make sure the file exists.
:ckexist rem
if exist %1 goto docopy :ckexist
echo *************************************************************** if exist %1 goto docopy
echo * The specified file, "%1," doesn't exist. * echo ***************************************************************
echo *************************************************************** echo * The specified file, "%1," doesn't exist. *
goto end echo ***************************************************************
rem goto end
rem copy the file to measure to TESTCODE. rem
rem rem copy the file to measure to TESTCODE.
:docopy rem
copy %1 testcode :docopy
masm pztest; copy %1 testcode
if errorlevel 1 goto errorend masm pztest;
masm pztimer; if errorlevel 1 goto errorend
if errorlevel 1 goto errorend masm pztimer;
link pztest+pztimer; if errorlevel 1 goto errorend
if errorlevel 1 goto errorend link pztest+pztimer;
pztest if errorlevel 1 goto errorend
goto end pztest
:errorend goto end
echo *************************************************************** :errorend
echo * An error occurred while building the precision Zen timer. * echo ***************************************************************
echo *************************************************************** echo * An error occurred while building the precision Zen timer. *
:end echo ***************************************************************
:end
```
Assuming that Listing 3.3 is named LST3-3.ASM and Listing 3.4 is named Assuming that Listing 3.3 is named LST3-3.ASM and Listing 3.4 is named
PZTIME.BAT, the code in Listing 3.3 would be timed with the command: PZTIME.BAT, the code in Listing 3.3 would be timed with the command:
pztime LST3-3.ASM ```sh
pztime LST3-3.ASM
```
which performs all assembly and linking, and reports the execution time which performs all assembly and linking, and reports the execution time
of the code in Listing 3.3. of the code in Listing 3.3.
@ -116,7 +120,9 @@ In order to perform any of the timing tests in this book, enter Listing
and enter Listing 3.4 and name it PZTIME.BAT. Then simply enter the and enter Listing 3.4 and name it PZTIME.BAT. Then simply enter the
listing you wish to run into the file *filename* and enter the command: listing you wish to run into the file *filename* and enter the command:
pztime <filename> ```sh
pztime <filename>
```
In fact, that's exactly how I timed each of the listings in this book. In fact, that's exactly how I timed each of the listings in this book.
Code fragments you write yourself can be timed in just the same way. If Code fragments you write yourself can be timed in just the same way. If

1196
03-07.md

File diff suppressed because it is too large Load diff

118
03-08.md
View file

@ -70,64 +70,66 @@ timing.
**LISTING 3.6 LZTEST.ASM** **LISTING 3.6 LZTEST.ASM**
; Program to measure performance of code that takes longer than ```nasm
; 54 ms to execute. (LZTEST.ASM) ; Program to measure performance of code that takes longer than
; ; 54 ms to execute. (LZTEST.ASM)
; Link with LZTIMER.ASM (Listing 3.5). LZTIME.BAT (Listing 3.7) ;
; can be used to assemble and link both files. Code to be ; Link with LZTIMER.ASM (Listing 3.5). LZTIME.BAT (Listing 3.7)
; measured must be in the file TESTCODE; Listing 3.8 shows ; can be used to assemble and link both files. Code to be
; a sample file (LST3-8.ASM) which should be named TESTCODE. ; measured must be in the file TESTCODE; Listing 3.8 shows
; ; a sample file (LST3-8.ASM) which should be named TESTCODE.
; By Michael Abrash ;
; ; By Michael Abrash
mystack segment para stack ‘STACK' ;
db 512 dup(?) mystack segment para stack ‘STACK'
mystack ends db 512 dup(?)
; mystack ends
Code segment para public ‘CODE' ;
assume cs:Code, ds:Code Code segment para public ‘CODE'
extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near assume cs:Code, ds:Code
Startproc near extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near
push cs Startproc near
pop ds ;point DS to the code segment, push cs
; so data as well as code can easily pop ds ;point DS to the code segment,
; be included in TESTCODE ; so data as well as code can easily
; ; be included in TESTCODE
; Delay for 6-7 seconds, to let the Enter keystroke that started the ;
; program come back up. ; Delay for 6-7 seconds, to let the Enter keystroke that started the
; ; program come back up.
mov ah,2ch ;
int 21h ;get the current time mov ah,2ch
mov bh,dh ;set the current time aside int 21h ;get the current time
DelayLoop: mov bh,dh ;set the current time aside
mov ah,2ch DelayLoop:
push bx ;preserve start time mov ah,2ch
int 21h ;get time push bx ;preserve start time
pop bx ;retrieve start time int 21h ;get time
cmp dh,bh ;is the new seconds count less than pop bx ;retrieve start time
; the start seconds count? cmp dh,bh ;is the new seconds count less than
jnb CheckDelayTime ;no ; the start seconds count?
add dh,60 ;yes, a minute must have turned over, jnb CheckDelayTime ;no
; so add one minute add dh,60 ;yes, a minute must have turned over,
CheckDelayTime: ; so add one minute
sub dh,bh ;get time that's passed CheckDelayTime:
cmp dh,7 ;has it been more than 6 seconds yet? sub dh,bh ;get time that's passed
jb DelayLoop ;not yet cmp dh,7 ;has it been more than 6 seconds yet?
; jb DelayLoop ;not yet
include TESTCODE ;code to be measured, including calls ;
; to ZTimerOn and ZTimerOff include TESTCODE ;code to be measured, including calls
; ; to ZTimerOn and ZTimerOff
; Display the results. ;
; ; Display the results.
call ZTimerReport ;
; call ZTimerReport
; Terminate the program. ;
; ; Terminate the program.
mov ah,4ch ;
int 21h mov ah,4ch
Start endp int 21h
Code ends Start endp
end Start Code ends
end Start
```
As with the precision Zen timer, the program in Listing 3.6 is used by As with the precision Zen timer, the program in Listing 3.6 is used by
naming the file containing the code to be timed TESTCODE, then naming the file containing the code to be timed TESTCODE, then

188
03-09.md
View file

@ -12,67 +12,69 @@ pages: 067-070
**LISTING 3.7 LZTIME.BAT** **LISTING 3.7 LZTIME.BAT**
echo off ```bat
rem echo off
rem *** Listing 3.7 *** rem
rem rem *** Listing 3.7 ***
rem *************************************************************** rem
rem * Batch file LZTIME.BAT, which builds and runs the * rem ***************************************************************
rem * long-period Zen timer program LZTEST.EXE to time the code * rem * Batch file LZTIME.BAT, which builds and runs the *
rem * named as the command-line parameter. Listing 3.5 must be * rem * long-period Zen timer program LZTEST.EXE to time the code *
rem * named LZTIMER.ASM, and Listing 3.6 must be named * rem * named as the command-line parameter. Listing 3.5 must be *
rem * LZTEST.ASM. To time the code in LST3-8, you'd type the * rem * named LZTIMER.ASM, and Listing 3.6 must be named *
rem * DOS command: * rem * LZTEST.ASM. To time the code in LST3-8, you'd type the *
rem * * rem * DOS command: *
rem * lztime lst3-8 * rem * *
rem * * rem * lztime lst3-8 *
rem * Note that MASM and LINK must be in the current directory or * rem * *
rem * on the current path in order for this batch file to work. * rem * Note that MASM and LINK must be in the current directory or *
rem * * rem * on the current path in order for this batch file to work. *
rem * This batch file can be speeded up by assembling LZTIMER.ASM * rem * *
rem * once, then removing the lines: * rem * This batch file can be speeded up by assembling LZTIMER.ASM *
rem * * rem * once, then removing the lines: *
rem * masm lztimer; * rem * *
rem * if errorlevel 1 goto errorend * rem * masm lztimer; *
rem * * rem * if errorlevel 1 goto errorend *
rem * from this file. * rem * *
rem * * rem * from this file. *
rem * By Michael Abrash * rem * *
rem *************************************************************** rem * By Michael Abrash *
rem rem ***************************************************************
rem Make sure a file to test was specified. rem
rem rem Make sure a file to test was specified.
if not x%1==x goto ckexist rem
echo *************************************************************** if not x%1==x goto ckexist
echo * Please specify a file to test. * echo ***************************************************************
echo *************************************************************** echo * Please specify a file to test. *
goto end echo ***************************************************************
rem goto end
rem Make sure the file exists. rem
rem rem Make sure the file exists.
:ckexist rem
if exist %1 goto docopy :ckexist
echo *************************************************************** if exist %1 goto docopy
echo * The specified file, "%1," doesn't exist. * echo ***************************************************************
echo *************************************************************** echo * The specified file, "%1," doesn't exist. *
goto end echo ***************************************************************
rem goto end
rem copy the file to measure to TESTCODE. rem
:docopy rem copy the file to measure to TESTCODE.
copy %1 testcode :docopy
masm lztest; copy %1 testcode
if errorlevel 1 goto errorend masm lztest;
masm lztimer; if errorlevel 1 goto errorend
if errorlevel 1 goto errorend masm lztimer;
link lztest+lztimer; if errorlevel 1 goto errorend
if errorlevel 1 goto errorend link lztest+lztimer;
lztest if errorlevel 1 goto errorend
goto end lztest
:errorend goto end
echo *************************************************************** :errorend
echo * An error occurred while building the long-period Zen timer. * echo ***************************************************************
echo *************************************************************** echo * An error occurred while building the long-period Zen timer. *
:end echo ***************************************************************
:end
```
Listing 3.8 shows sample code that can be timed with the test-bed Listing 3.8 shows sample code that can be timed with the test-bed
program of Listing 3.6. Listing 3.8 measures the time required to program of Listing 3.6. Listing 3.8 measures the time required to
@ -81,38 +83,42 @@ the precision Zen timer to handle on the 8088.
**LISTING 3.8 LST3-8.ASM** **LISTING 3.8 LST3-8.ASM**
; ```nasm
; Measures the performance of 20,000 loads of AL from ;
; memory. (Use by renaming to TESTCODE, which is ; Measures the performance of 20,000 loads of AL from
; included by LZTEST.ASM (Listing 3.6). LZTIME.BAT ; memory. (Use by renaming to TESTCODE, which is
; (Listing 3.7) does this, along with all assembly ; included by LZTEST.ASM (Listing 3.6). LZTIME.BAT
; and linking.) ; (Listing 3.7) does this, along with all assembly
; ; and linking.)
; Note: takes about ten minutes to assemble on a slow PC if ;
;you are using MASM ; Note: takes about ten minutes to assemble on a slow PC if
; ;you are using MASM
jmpSkip;jump around defined data ;
; jmpSkip;jump around defined data
MemVardb? ;
; MemVardb?
Skip: ;
; Skip:
; Start timing. ;
; ; Start timing.
callZTimerOn ;
; callZTimerOn
rept20000 ;
moval,[MemVar] rept20000
endm moval,[MemVar]
; endm
; Stop timing. ;
; ; Stop timing.
callZTimerOff ;
callZTimerOff
```
When LZTIME.BAT is run on a PC with the following command line (assuming When LZTIME.BAT is run on a PC with the following command line (assuming
the code in Listing 3.8 is the file LST3-8.ASM) the code in Listing 3.8 is the file LST3-8.ASM)
lztime lst3-8.asm ```sh
lztime lst3-8.asm
```
the result is 72,544 µs, or about 3.63 µs per load of AL from memory. the result is 72,544 µs, or about 3.63 µs per load of AL from memory.
This is just slightly longer than the time per load of AL measured by This is just slightly longer than the time per load of AL measured by
@ -147,4 +153,6 @@ lines from Listing 3.1 that must be changed. These changes convert the
code to use C-style external label names and the small model C code code to use C-style external label names and the small model C code
segment. (In C++, use the "C" specifier, as in segment. (In C++, use the "C" specifier, as in
extern "C" ZTimerOn(void); ```c
extern "C" ZTimerOn(void);
```

View file

@ -16,11 +16,13 @@ doesn't occur, and the linker can find the routines' C-style names.)
That's all it takes; after doing this, you'll be able to use the Zen That's all it takes; after doing this, you'll be able to use the Zen
timer from C, as, for example, in: timer from C, as, for example, in:
ZTimerOn(): ```c
for (i=0, x=0; i<100; i++) ZTimerOn():
x += i; for (i=0, x=0; i<100; i++)
ZTimerOff(); x += i;
ZTimerReport(); ZTimerOff();
ZTimerReport();
```
(I'm talking about the precision timer here. The long-period (I'm talking about the precision timer here. The long-period
timer—Listing 3.5—requires the same modifications, but to different timer—Listing 3.5—requires the same modifications, but to different
@ -46,12 +48,16 @@ One important safety tip when modifying the Zen timer for use with large
code model C code: Watch out for optimizing assemblers! TASM actually code model C code: Watch out for optimizing assemblers! TASM actually
replaces replaces
call far ptr ReferenceZTimerOn ```nasm
call far ptr ReferenceZTimerOn
```
with with
push cs ```nasm
call near ptr ReferenceZTimerOn push cs
call near ptr ReferenceZTimerOn
```
(and likewise for **ReferenceZTimerOff** ), which works because (and likewise for **ReferenceZTimerOff** ), which works because
**ReferenceZTimerOn** is in the same segment as the calling code. This **ReferenceZTimerOn** is in the same segment as the calling code. This

View file

@ -53,11 +53,15 @@ byte-sized accesses. That's why the official instruction timings
indicate that for code running on an 8088 an additional 4 cycles are indicate that for code running on an 8088 an additional 4 cycles are
required for every word-sized access to a memory operand. For instance, required for every word-sized access to a memory operand. For instance,
mov ax,word ptr [MemVar] ```nasm
mov ax,word ptr [MemVar]
```
takes 4 cycles longer to read the word at address **MemVar** than takes 4 cycles longer to read the word at address **MemVar** than
mov al,byte ptr [MemVar] ```nasm
mov al,byte ptr [MemVar]
```
takes to read the byte at address **MemVar.** (Actually, the difference takes to read the byte at address **MemVar.** (Actually, the difference
between the two isn't very likely to be exactly 4 cycles, for reasons between the two isn't very likely to be exactly 4 cycles, for reasons
@ -72,11 +76,15 @@ prior to adding to it, and one to write the result of the addition back
to the destination operand—and thus incurs not one but two 4-cycle to the destination operand—and thus incurs not one but two 4-cycle
penalties. As a result penalties. As a result
add word ptr [MemVar],ax ```nasm
add word ptr [MemVar],ax
```
takes about 8 cycles longer to execute than: takes about 8 cycles longer to execute than:
add byte ptr [MemVar],al ```nasm
add byte ptr [MemVar],al
```
String instructions can suffer from the 8-bit bus cycle-eater to a String instructions can suffer from the 8-bit bus cycle-eater to a
greater extent than other instructions. Believe it or not, a single greater extent than other instructions. Believe it or not, a single
@ -114,35 +122,39 @@ in all.
**LISTING 4.1 LST4-1.ASM** **LISTING 4.1 LST4-1.ASM**
; Measures the performance of a loop which uses a ```nasm
; byte-sized memory variable as the loop counter. ; Measures the performance of a loop which uses a
; ; byte-sized memory variable as the loop counter.
jmp Skip ;
; jmp Skip
Counter db 100 ;
; Counter db 100
Skip: ;
call ZTimerOn Skip:
LoopTop: call ZTimerOn
dec [Counter] LoopTop:
jnz LoopTop dec [Counter]
call ZTimerOff jnz LoopTop
call ZTimerOff
```
**LISTING 4.2 LST4-2.ASM** **LISTING 4.2 LST4-2.ASM**
; Measures the performance of a loop which uses a ```nasm
; word-sized memory variable as the loop counter. ; Measures the performance of a loop which uses a
; ; word-sized memory variable as the loop counter.
jmp Skip ;
; jmp Skip
Counter dw 100 ;
; Counter dw 100
Skip: ;
call ZTimerOn Skip:
LoopTop: call ZTimerOn
dec [Counter] LoopTop:
jnz LoopTop dec [Counter]
call ZTimerOff jnz LoopTop
call ZTimerOff
```
I'd like to make a brief aside concerning code optimization in the I'd like to make a brief aside concerning code optimization in the
listings in this book. Throughout this book I've modeled the sample code listings in this book. Throughout this book I've modeled the sample code

View file

@ -16,12 +16,16 @@ strive to use byte-sized memory variables whenever possible. That does
manipulate a word-sized memory variable in preference to 1 word-sized manipulate a word-sized memory variable in preference to 1 word-sized
memory access, as, for instance, memory access, as, for instance,
mov dl,byte ptr [MemVar] ```nasm
mov dh,byte ptr [MemVar+1] mov dl,byte ptr [MemVar]
mov dh,byte ptr [MemVar+1]
```
versus: versus:
mov dx,word ptr [MemVar] ```nasm
mov dx,word ptr [MemVar]
```
Recall that every access to a memory byte takes at least 4 cycles; that Recall that every access to a memory byte takes at least 4 cycles; that
limitation is built right into the 8088. The 8088 is also built so that limitation is built right into the 8088. The 8088 is also built so that
@ -48,25 +52,29 @@ the second byte of each word.
**LISTING 4.3 LST4-3.ASM** **LISTING 4.3 LST4-3.ASM**
; Measures the performance of reading 1,000 words ```nasm
; from memory with 1,000 word-sized accesses. ; Measures the performance of reading 1,000 words
; ; from memory with 1,000 word-sized accesses.
sub si,si ;
mov cx,1000 sub si,si
call ZTimerOn mov cx,1000
rep lodsw call ZTimerOn
call ZTimerOff rep lodsw
call ZTimerOff
```
**LISTING 4.4 LST4-4.ASM** **LISTING 4.4 LST4-4.ASM**
; Measures the performance of reading 1000 words ```nasm
; from memory with 2,000 byte-sized accesses. ; Measures the performance of reading 1000 words
; ; from memory with 2,000 byte-sized accesses.
sub si,si ;
mov cx,2000 sub si,si
call ZTimerOn mov cx,2000
rep lodsb call ZTimerOn
call ZTimerOff rep lodsb
call ZTimerOff
```
In short, if you must perform a 16-bit memory access, let the 8088 break In short, if you must perform a 16-bit memory access, let the 8088 break
the access into two byte-sized accesses for you. The 8088 is more the access into two byte-sized accesses for you. The 8088 is more
@ -142,17 +150,21 @@ What makes the prefetch queue cycle-eater tricky is that it's
undocumented and unpredictable. That is, with a word-sized memory undocumented and unpredictable. That is, with a word-sized memory
access, such as access, such as
mov [bx],ax ```nasm
mov [bx],ax
```
it's well-documented that an extra 4 cycles will always be required to it's well-documented that an extra 4 cycles will always be required to
write the upper byte of AX to memory. Not so with the prefetch queue write the upper byte of AX to memory. Not so with the prefetch queue
cycle-eater lurking nearby. For instance, the instructions cycle-eater lurking nearby. For instance, the instructions
shr ax,1 ```nasm
shr ax,1 shr ax,1
shr ax,1 shr ax,1
shr ax,1 shr ax,1
shr ax,1 shr ax,1
shr ax,1
```
should execute in 10 cycles, since each **SHR** takes 2 cycles to should execute in 10 cycles, since each **SHR** takes 2 cycles to
execute, according to Intel's specifications. Those specifications execute, according to Intel's specifications. Those specifications

View file

@ -104,32 +104,36 @@ that the "true" execution time of **SHR** is 8.64 cycles.
**LISTING 4.5 LST4-5.ASM** **LISTING 4.5 LST4-5.ASM**
; Measures the performance of 1,000 SHR instructions ```nasm
; in a row. Since SHR executes in 2 cycles but is ; Measures the performance of 1,000 SHR instructions
; 2 bytes long, the prefetch queue is always empty, ; in a row. Since SHR executes in 2 cycles but is
; and prefetching time determines the overall ; 2 bytes long, the prefetch queue is always empty,
; performance of the code. ; and prefetching time determines the overall
; ; performance of the code.
call ZTimerOn ;
rept 1000 call ZTimerOn
shr ax,1 rept 1000
endm shr ax,1
call ZTimerOff endm
call ZTimerOff
```
**LISTING 4.6 LST4-6.ASM** **LISTING 4.6 LST4-6.ASM**
; Measures the performance of 1,000 MUL/SHR instruction ```nasm
; pairs in a row. The lengthy execution time of MUL ; Measures the performance of 1,000 MUL/SHR instruction
; should keep the prefetch queue from ever emptying. ; pairs in a row. The lengthy execution time of MUL
; ; should keep the prefetch queue from ever emptying.
mov cx,1000 ;
sub ax,ax mov cx,1000
call ZTimerOn sub ax,ax
rept 1000 call ZTimerOn
mul ax rept 1000
shr ax,1 mul ax
endm shr ax,1
call ZTimerOff endm
call ZTimerOff
```
![**Figure 4.3**  *Execution and instruction prefetching sequence for ![**Figure 4.3**  *Execution and instruction prefetching sequence for
Listing 4.5.*](images/04-03.jpg) Listing 4.5.*](images/04-03.jpg)

View file

@ -43,29 +43,33 @@ Listing 4.6.*](images/04-04.jpg)
**LISTING 4.7 LST4-7.ASM** **LISTING 4.7 LST4-7.ASM**
; Measures the performance of repeated MOV AL,0 instructions, ```nasm
; which take 4 cycles each according to Intel's official ; Measures the performance of repeated MOV AL,0 instructions,
; specifications. ; which take 4 cycles each according to Intel's official
; ; specifications.
sub ax,ax ;
call ZTimerOn sub ax,ax
rept 1000 call ZTimerOn
mov al,0 rept 1000
endm mov al,0
call ZTimerOff endm
call ZTimerOff
```
**LISTING 4.8 LST4-8.ASM** **LISTING 4.8 LST4-8.ASM**
; Measures the performance of repeated SUB AL,AL instructions, ```nasm
; which take 3 cycles each according to Intel's official ; Measures the performance of repeated SUB AL,AL instructions,
; specifications. ; which take 3 cycles each according to Intel's official
; ; specifications.
sub ax,ax ;
call ZTimerOn sub ax,ax
rept 1000 call ZTimerOn
sub al,al rept 1000
endm sub al,al
call ZTimerOff endm
call ZTimerOff
```
As you can see, it's easy to be drawn into thinking you're saving cycles As you can see, it's easy to be drawn into thinking you're saving cycles
when you're not. You can only improve the performance of a specific bit when you're not. You can only improve the performance of a specific bit

View file

@ -28,17 +28,19 @@ request a memory access from the Bus Interface Unit.)
**LISTING 4.9 LST4-9.ASM** **LISTING 4.9 LST4-9.ASM**
; Measures the performance of repeated MUL instructions, ```nasm
; which allow the prefetch queue to be full at all times, ; Measures the performance of repeated MUL instructions,
; to demonstrate a case in which DRAM refresh has no impact ; which allow the prefetch queue to be full at all times,
; on code performance. ; to demonstrate a case in which DRAM refresh has no impact
; ; on code performance.
sub ax,ax ;
call ZTimerOn sub ax,ax
rept 1000 call ZTimerOn
mul ax rept 1000
endm mul ax
call ZTimerOff endm
call ZTimerOff
```
Running Listing 4.9, we find that each **MUL** executes in 24.72 µs, or Running Listing 4.9, we find that each **MUL** executes in 24.72 µs, or
exactly 118 cycles. Since that's the shortest time in which **MUL** can exactly 118 cycles. Since that's the shortest time in which **MUL** can
@ -56,15 +58,17 @@ to fetch the instruction bytes.
**LISTING 4.10 LST4-10.ASM** **LISTING 4.10 LST4-10.ASM**
; Measures the performance of repeated SHR instructions, ```nasm
; which empty the prefetch queue, to demonstrate the ; Measures the performance of repeated SHR instructions,
; worst-case impact of DRAM refresh on code performance. ; which empty the prefetch queue, to demonstrate the
; ; worst-case impact of DRAM refresh on code performance.
call ZTimerOn ;
rept 1000 call ZTimerOn
shr ax,1 rept 1000
endm shr ax,1
call ZTimerOff endm
call ZTimerOff
```
Since 4 cycles are required to read each instruction byte, we'd expect Since 4 cycles are required to read each instruction byte, we'd expect
each **SHR** to execute in 8 cycles, or 1.676 µs, if there were no DRAM each **SHR** to execute in 8 cycles, or 1.676 µs, if there were no DRAM

View file

@ -86,34 +86,36 @@ long as we had assumed, but a long time nonetheless.
**LISTING 4.11 LST4-11.ASM** **LISTING 4.11 LST4-11.ASM**
; Times speed of memory access to Enhanced Graphics ```nasm
; Adapter graphics mode display memory at A000:0000. ; Times speed of memory access to Enhanced Graphics
; ; Adapter graphics mode display memory at A000:0000.
mov ax,0010h ;
int 10h; select hi-res EGA graphics mov ax,0010h
; mode 10 hex (AH=0 selects int 10h; select hi-res EGA graphics
; BIOS set mode function, ; mode 10 hex (AH=0 selects
; with AL=mode to select) ; BIOS set mode function,
; ; with AL=mode to select)
mov ax,0a000h ;
mov ds,ax mov ax,0a000h
mov es,ax ;move to & from same segment mov ds,ax
sub si,si ;move to & from same offset mov es,ax ;move to & from same segment
mov di,si sub si,si ;move to & from same offset
mov cx,800h ;move 2K words mov di,si
cld mov cx,800h ;move 2K words
call ZTimerOn cld
rep movsw ;simply read each of the first call ZTimerOn
; 2K words of the destination segment, rep movsw ;simply read each of the first
; writing each byte immediately back ; 2K words of the destination segment,
; to the same address. No memory ; writing each byte immediately back
; locations are actually altered; this ; to the same address. No memory
; is just to measure memory access ; locations are actually altered; this
; times ; is just to measure memory access
call ZTimerOff ; times
; call ZTimerOff
mov ax,0003h ;
int 10h ;return to text mode mov ax,0003h
int 10h ;return to text mode
```
For comparison, let's see how long the same code takes when accessing For comparison, let's see how long the same code takes when accessing
normal system RAM instead of display memory. The code in Listing 4.12, normal system RAM instead of display memory. The code in Listing 4.12,
@ -125,24 +127,26 @@ cycle-eater can *more than double* the execution time of 8088 code!
**LISTING 4.12 LST4-12.ASM** **LISTING 4.12 LST4-12.ASM**
; Times speed of memory access to normal system ```nasm
; memory. ; Times speed of memory access to normal system
; ; memory.
mov ax,ds ;
mov es,ax ;move to & from same segment mov ax,ds
sub si,si ;move to & from same offset mov es,ax ;move to & from same segment
mov di,si sub si,si ;move to & from same offset
mov cx,800h ;move 2K words mov di,si
cld mov cx,800h ;move 2K words
call ZTimerOn cld
rep movsw ;simply read each of the first call ZTimerOn
; 2K words of the destination segment, rep movsw ;simply read each of the first
; writing each byte immediately back ; 2K words of the destination segment,
; to the same address. No memory ; writing each byte immediately back
; locations are actually altered; this ; to the same address. No memory
; is just to measure memory access ; locations are actually altered; this
; times ; is just to measure memory access
call ZTimerOff ; times
call ZTimerOff
```
Bear in mind that we're talking about a worst case here; the impact of Bear in mind that we're talking about a worst case here; the impact of
the display adapter cycle-eater is proportional to the percent of time a the display adapter cycle-eater is proportional to the percent of time a

306
05-03.md
View file

@ -12,168 +12,170 @@ pages: 118-121
**LISTING 5.1 SEARCH.C** **LISTING 5.1 SEARCH.C**
/* Program to search the file specified by the first command-line ```c
* argument for the string specified by the second command-line /* Program to search the file specified by the first command-line
* argument. Performs the search by reading and searching blocks * argument for the string specified by the second command-line
* of size BLOCK_SIZE. */ * argument. Performs the search by reading and searching blocks
* of size BLOCK_SIZE. */
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
#include <string.h> #include <string.h>
#include <alloc.h> /* alloc.h for Borland compilers, #include <alloc.h> /* alloc.h for Borland compilers,
malloc.h for Microsoft compilers */ malloc.h for Microsoft compilers */
#define BLOCK_SIZE 0x4000 /* we'll process the file in 16K blocks */ #define BLOCK_SIZE 0x4000 /* we'll process the file in 16K blocks */
/* Searches the specified number of sequences in the specified /* Searches the specified number of sequences in the specified
buffer for matches to SearchString of SearchStringLength. Note buffer for matches to SearchString of SearchStringLength. Note
that the calling code should already have shortened SearchLength that the calling code should already have shortened SearchLength
if necessary to compensate for the distance from the end of the if necessary to compensate for the distance from the end of the
buffer to the last possible start of a matching sequence in the buffer to the last possible start of a matching sequence in the
buffer. buffer.
*/ */
int SearchForString(unsigned char *Buffer, int SearchLength, int SearchForString(unsigned char *Buffer, int SearchLength,
unsigned char *SearchString, int SearchStringLength) unsigned char *SearchString, int SearchStringLength)
{ {
unsigned char *PotentialMatch; unsigned char *PotentialMatch;
/* Search so long as there are potential-match locations /* Search so long as there are potential-match locations
remaining */ remaining */
while ( SearchLength ) { while ( SearchLength ) {
/* See if the first character of SearchString can be found */ /* See if the first character of SearchString can be found */
if ( (PotentialMatch = if ( (PotentialMatch =
memchr(Buffer, *SearchString, SearchLength)) == NULL ) { memchr(Buffer, *SearchString, SearchLength)) == NULL ) {
break; /* No matches in this buffer */ break; /* No matches in this buffer */
}
/* The first character matches; see if the rest of the string
also matches */
if ( SearchStringLength == 1 ) {
return(1); /* That one matching character was the whole
search string, so we've got a match */
}
else {
/* Check whether the remaining characters match */
if ( !memcmp(PotentialMatch + 1, SearchString + 1,
SearchStringLength - 1) ) {
return(1); /* We've got a match */
} }
/* The first character matches; see if the rest of the string }
also matches */ /* The string doesn't match; keep going by pointing past the
if ( SearchStringLength == 1 ) { potential match location we just rejected */
return(1); /* That one matching character was the whole SearchLength -= PotentialMatch - Buffer + 1;
search string, so we've got a match */ Buffer = PotentialMatch + 1;
} }
else {
/* Check whether the remaining characters match */
if ( !memcmp(PotentialMatch + 1, SearchString + 1,
SearchStringLength - 1) ) {
return(1); /* We've got a match */
}
}
/* The string doesn't match; keep going by pointing past the
potential match location we just rejected */
SearchLength -= PotentialMatch - Buffer + 1;
Buffer = PotentialMatch + 1;
}
return(0); /* No match found */ return(0); /* No match found */
} }
main(int argc, char *argv[]) { main(int argc, char *argv[]) {
int Done; /* Indicates whether search is done */ int Done; /* Indicates whether search is done */
int Handle; /* Handle of file being searched */ int Handle; /* Handle of file being searched */
int WorkingLength; /* Length of current block */ int WorkingLength; /* Length of current block */
int SearchStringLength; /* Length of string to search for */ int SearchStringLength; /* Length of string to search for */
int BlockSearchLength; /* Length to search in current block */ int BlockSearchLength; /* Length to search in current block */
int Found; /* Indicates final search completion int Found; /* Indicates final search completion
status */ status */
int NextLoadCount; /* # of bytes to read into next block, int NextLoadCount; /* # of bytes to read into next block,
accounting for bytes copied from the accounting for bytes copied from the
last block */ last block */
unsigned char *WorkingBlock; /* Block storage buffer */ unsigned char *WorkingBlock; /* Block storage buffer */
unsigned char *SearchString; /* Pointer to the string to search for */ unsigned char *SearchString; /* Pointer to the string to search for */
unsigned char *NextLoadPtr; /* Offset at which to start loading unsigned char *NextLoadPtr; /* Offset at which to start loading
the next block, accounting for the next block, accounting for
bytes copied from the last block */ bytes copied from the last block */
/* Check for the proper number of arguments */ /* Check for the proper number of arguments */
if ( argc != 3 ) { if ( argc != 3 ) {
printf("usage: search filename search-string\n"); printf("usage: search filename search-string\n");
exit(1); exit(1);
} }
/* Try to open the file to be searched */ /* Try to open the file to be searched */
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) { if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
printf("Can't open file: %s\n", argv[1]); printf("Can't open file: %s\n", argv[1]);
exit(1); exit(1);
} }
/* Calculate the length of text to search for */ /* Calculate the length of text to search for */
SearchString = argv[2]; SearchString = argv[2];
SearchStringLength = strlen(SearchString); SearchStringLength = strlen(SearchString);
/* Try to get memory in which to buffer the data */ /* Try to get memory in which to buffer the data */
if ( (WorkingBlock = malloc(BLOCK_SIZE)) == NULL ) { if ( (WorkingBlock = malloc(BLOCK_SIZE)) == NULL ) {
printf("Can't get enough memory\n"); printf("Can't get enough memory\n");
exit(1); exit(1);
} }
/* Load the first block at the start of the buffer, and try to /* Load the first block at the start of the buffer, and try to
fill the entire buffer */ fill the entire buffer */
NextLoadPtr = WorkingBlock; NextLoadPtr = WorkingBlock;
NextLoadCount = BLOCK_SIZE; NextLoadCount = BLOCK_SIZE;
Done = 0; /* Not done with search yet */ Done = 0; /* Not done with search yet */
Found = 0; /* Assume we won't find a match */ Found = 0; /* Assume we won't find a match */
/* Search the file in BLOCK_SIZE chunks */ /* Search the file in BLOCK_SIZE chunks */
do { do {
/* Read in however many bytes are needed to fill out the block /* Read in however many bytes are needed to fill out the block
(accounting for bytes copied over from the last block), or (accounting for bytes copied over from the last block), or
the rest of the bytes in the file, whichever is less */ the rest of the bytes in the file, whichever is less */
if ( (WorkingLength = read(Handle, NextLoadPtr, if ( (WorkingLength = read(Handle, NextLoadPtr,
NextLoadCount)) == -1 ) { NextLoadCount)) == -1 ) {
printf("Error reading file %s\n", argv[1]); printf("Error reading file %s\n", argv[1]);
exit(1); exit(1);
} }
/* If we didn't read all the bytes we requested, we're done /* If we didn't read all the bytes we requested, we're done
after this block, whether we find a match or not */ after this block, whether we find a match or not */
if ( WorkingLength != NextLoadCount ) { if ( WorkingLength != NextLoadCount ) {
Done = 1; Done = 1;
} }
/* Account for any bytes we copied from the end of the last /* Account for any bytes we copied from the end of the last
block in the total length of this block */ block in the total length of this block */
WorkingLength += NextLoadPtr - WorkingBlock; WorkingLength += NextLoadPtr - WorkingBlock;
/* Calculate the number of bytes in this block that could /* Calculate the number of bytes in this block that could
possibly be the start of a matching sequence that lies possibly be the start of a matching sequence that lies
entirely in this block (sequences that run off the end of entirely in this block (sequences that run off the end of
the block will be transferred to the next block and found the block will be transferred to the next block and found
when that block is searched) when that block is searched)
*/ */
if ( (BlockSearchLength = if ( (BlockSearchLength =
WorkingLength - SearchStringLength + 1) <= 0 ) { WorkingLength - SearchStringLength + 1) <= 0 ) {
Done = 1; /* Too few characters in this block for Done = 1; /* Too few characters in this block for
there to be any possible matches, so this there to be any possible matches, so this
is the final block and we're done without is the final block and we're done without
finding a match finding a match
*/ */
} }
else { else {
/* Search this block */ /* Search this block */
if ( SearchForString(WorkingBlock, BlockSearchLength, if ( SearchForString(WorkingBlock, BlockSearchLength,
SearchString, SearchStringLength) ) { SearchString, SearchStringLength) ) {
Found = 1; /* We've found a match */ Found = 1; /* We've found a match */
Done = 1; Done = 1;
} }
else { else {
/* Copy any bytes from the end of the block that start /* Copy any bytes from the end of the block that start
potentially-matching sequences that would run off potentially-matching sequences that would run off
the end of the block over to the next block */ the end of the block over to the next block */
if ( SearchStringLength > 1 ) { if ( SearchStringLength > 1 ) {
memcpy(WorkingBlock, memcpy(WorkingBlock,
WorkingBlock+BLOCK_SIZE - SearchStringLength + 1, WorkingBlock+BLOCK_SIZE - SearchStringLength + 1,
SearchStringLength - 1); SearchStringLength - 1);
} }
/* Set up to load the next bytes from the file after the /* Set up to load the next bytes from the file after the
bytes copied from the end of the current block */ bytes copied from the end of the current block */
NextLoadPtr = WorkingBlock + SearchStringLength - 1; NextLoadPtr = WorkingBlock + SearchStringLength - 1;
NextLoadCount = BLOCK_SIZE - SearchStringLength + 1; NextLoadCount = BLOCK_SIZE - SearchStringLength + 1;
} }
} }
} while ( !Done ); } while ( !Done );
/* Report the results */ /* Report the results */
if ( Found ) { if ( Found ) {
printf("String found\n"); printf("String found\n");
} else { } else {
printf("String not found\n"); printf("String not found\n");
} }
exit(Found); /* Return the found/not found status as the exit(Found); /* Return the found/not found status as the
DOS errorlevel */ DOS errorlevel */
} }
```

View file

@ -120,10 +120,14 @@ For example, suppose you have an array base address in BX and an index
into the array in SI. You could add the two registers together to into the array in SI. You could add the two registers together to
address memory, like this: address memory, like this:
add bx,si ```nasm
mov al,[bx] add bx,si
mov al,[bx]
```
Or you could let the processor do the arithmetic for you in a single Or you could let the processor do the arithmetic for you in a single
instruction: instruction:
mov al,[bx+si] ```nasm
mov al,[bx+si]
```

View file

@ -19,11 +19,13 @@ within a loop, however, it's advantageous on the 8088 CPU to perform the
addition outside the loop, if possible, reducing effective address addition outside the loop, if possible, reducing effective address
calculation time inside the loop, as in the following: calculation time inside the loop, as in the following:
add bx,si ```nasm
LoopTop: add bx,si
mov al,[bx] LoopTop:
inc bx mov al,[bx]
loop LoopTop inc bx
loop LoopTop
```
Here, **MOV AL,[BX]** is two cycles faster than **MOV AL,[BX+SI]**. Here, **MOV AL,[BX]** is two cycles faster than **MOV AL,[BX+SI]**.
@ -73,24 +75,32 @@ source operands.
Imagine that we want to add BX to DI, add two to the result, and store Imagine that we want to add BX to DI, add two to the result, and store
the result in AX. The obvious solution is this: the result in AX. The obvious solution is this:
mov ax,bx ```nasm
add ax,di mov ax,bx
add ax,2 add ax,di
add ax,2
```
(It would be more compact to increment AX twice than to add two to it, (It would be more compact to increment AX twice than to add two to it,
and would probably be faster on an 8088, but that's not what we're after and would probably be faster on an 8088, but that's not what we're after
at the moment.) An elegant alternative solution is simply: at the moment.) An elegant alternative solution is simply:
lea ax,[bx+di+2] ```nasm
lea ax,[bx+di+2]
```
Likewise, either of the following would copy SI plus two to DI Likewise, either of the following would copy SI plus two to DI
mov di,si ```nasm
add di,2 mov di,si
add di,2
```
or: or:
lea di,[si+2] ```nasm
lea di,[si+2]
```
Mind you, the only components **LEA** can add are BX or BP, SI or DI, Mind you, the only components **LEA** can add are BX or BP, SI or DI,
and a constant displacement, so it's not going to replace **ADD** most and a constant displacement, so it's not going to replace **ADD** most
@ -135,14 +145,18 @@ bits, so we can now add up to two 32-bit registers and a constant, *and*
shift (or multiply) one of the registers to some extent—all with a shift (or multiply) one of the registers to some extent—all with a
single instruction. For example, single instruction. For example,
lea edi,TableBase[ecx+edx*4] ```nasm
lea edi,TableBase[ecx+edx*4]
```
replaces all this replaces all this
mov edi,edx ```nasm
shl edi,2 mov edi,edx
add edi,ecx shl edi,2
add edi,offset TableBase add edi,ecx
add edi,offset TableBase
```
when pointing to an entry in a doubly indexed table. when pointing to an entry in a doubly indexed table.
@ -156,15 +170,19 @@ index on the 386, and can be scaled as the index while being used
unchanged as the base. That means that you can, for example, multiply unchanged as the base. That means that you can, for example, multiply
EBX by 5 with: EBX by 5 with:
lea ebx,[ebx+ebx*4] ```nasm
lea ebx,[ebx+ebx*4]
```
Without **LEA** and scaling, multiplication of EBX by 5 would require Without **LEA** and scaling, multiplication of EBX by 5 would require
either a relatively slow **MUL**, along with a set-up instruction or either a relatively slow **MUL**, along with a set-up instruction or
two, or three separate instructions along the lines of the following two, or three separate instructions along the lines of the following
mov edx,ebx ```nasm
shl ebx,2 mov edx,ebx
add ebx,edx shl ebx,2
add ebx,edx
```
and would in either case require the destruction of the contents of and would in either case require the destruction of the contents of
another register. another register.

View file

@ -13,13 +13,17 @@ pages: 139-141
By the way, don't fall victim to the lures of **JCXZ** and do something By the way, don't fall victim to the lures of **JCXZ** and do something
like this: like this:
and cx,ofh ;Isolate the desired field ```nasm
jcxz SkipLoop ;If field is 0, don't bother and cx,ofh ;Isolate the desired field
jcxz SkipLoop ;If field is 0, don't bother
```
The **AND** instruction has already set the Zero flag, so this The **AND** instruction has already set the Zero flag, so this
and cx,0fh ;Isolate the desired field ```nasm
jz SkipLoop ;If field is 0, don't bother and cx,0fh ;Isolate the desired field
jz SkipLoop ;If field is 0, don't bother
```
will do just fine and is faster on all processors. Use **JCXZ** only will do just fine and is faster on all processors. Use **JCXZ** only
when the Zero flag isn't already set to reflect the status of CX. when the Zero flag isn't already set to reflect the status of CX.

170
07-03.md
View file

@ -12,96 +12,98 @@ pages: 141-143
**LISTING 7.1 L7-1.ASM** **LISTING 7.1 L7-1.ASM**
; Program to illustrate searching through a buffer of a specified ```nasm
; length until either a specified byte or a zero byte is ; Program to illustrate searching through a buffer of a specified
; encountered. ; length until either a specified byte or a zero byte is
; A standard loop terminated with LOOP is used. ; encountered.
; A standard loop terminated with LOOP is used.
.model small .model small
.stack 100h .stack 100h
.data .data
; Sample string to search through. ; Sample string to search through.
SampleString labelbyte SampleString labelbyte
db ‘This is a sample string of a long enough length ' db ‘This is a sample string of a long enough length '
db ‘so that raw searching speed can outweigh any ' db ‘so that raw searching speed can outweigh any '
db ‘extra set-up time that may be required.',0 db ‘extra set-up time that may be required.',0
SAMPLE_STRING_LENGTH equ $-SampleString SAMPLE_STRING_LENGTH equ $-SampleString
; User prompt. ; User prompt.
Prompt db ‘Enter character to search for:$' Prompt db ‘Enter character to search for:$'
; Result status messages. ; Result status messages.
ByteFoundMsg db 0dh,0ah ByteFoundMsg db 0dh,0ah
db ‘Specified byte found.',0dh,0ah,‘$' db ‘Specified byte found.',0dh,0ah,‘$'
ZeroByteFoundMsg db 0dh, 0ah ZeroByteFoundMsg db 0dh, 0ah
db ‘Zero byte encountered.',0dh,0ah,‘$' db ‘Zero byte encountered.',0dh,0ah,‘$'
NoByteFoundMsg db 0dh,0ah NoByteFoundMsg db 0dh,0ah
db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$' db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$'
.code .code
Startprocnear Startprocnear
mov ax,@data ;point to standard data segment mov ax,@data ;point to standard data segment
mov ds,ax mov ds,ax
mov dx,offset Prompt mov dx,offset Prompt
mov ah,9 ;DOS print string function mov ah,9 ;DOS print string function
int 21h ;prompt the user int 21h ;prompt the user
mov ah,1 ;DOS get key function mov ah,1 ;DOS get key function
int 21h ;get the key to search for int 21h ;get the key to search for
mov ah,al ;put character to search for in AH mov ah,al ;put character to search for in AH
mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search
mov si,offset SampleString ;point to buffer to search mov si,offset SampleString ;point to buffer to search
call SearchMaxLength ;search the buffer call SearchMaxLength ;search the buffer
mov dx,offset ByteFoundMsg ;assume we found the byte mov dx,offset ByteFoundMsg ;assume we found the byte
jc PrintStatus ;we did find the byte jc PrintStatus ;we did find the byte
;we didn't find the byte, figure out ;we didn't find the byte, figure out
;whether we found a zero byte or ;whether we found a zero byte or
;ran out of buffer ;ran out of buffer
mov dx,offset NoByteFoundMsg mov dx,offset NoByteFoundMsg
;assume we didn't find a zero byte ;assume we didn't find a zero byte
jcxz PrintStatus ;we didn't find a zero byte jcxz PrintStatus ;we didn't find a zero byte
mov dx,offset ZeroByteFoundMsg ;we found a zero byte mov dx,offset ZeroByteFoundMsg ;we found a zero byte
PrintStatus: PrintStatus:
mov ah,9 ;DOS print string function mov ah,9 ;DOS print string function
int 21h ;report status int 21h ;report status
mov ah,4ch ;return to DOS mov ah,4ch ;return to DOS
int 21h int 21h
Startendp Startendp
; Function to search a buffer of a specified length until either a ; Function to search a buffer of a specified length until either a
; specified byte or a zero byte is encountered. ; specified byte or a zero byte is encountered.
; Input: ; Input:
; AH = character to search for ; AH = character to search for
; CX = maximum length to be searched (must be > 0) ; CX = maximum length to be searched (must be > 0)
; DS:SI = pointer to buffer to be searched ; DS:SI = pointer to buffer to be searched
; Output: ; Output:
; CX = 0 if and only if we ran out of bytes without finding ; CX = 0 if and only if we ran out of bytes without finding
; either the desired byte or a zero byte ; either the desired byte or a zero byte
; DS:SI = pointer to searched-for byte if found, otherwise byte ; DS:SI = pointer to searched-for byte if found, otherwise byte
; after zero byte if found, otherwise byte after last ; after zero byte if found, otherwise byte after last
; byte checked if neither searched-for byte nor zero ; byte checked if neither searched-for byte nor zero
; byte is found ; byte is found
; Carry Flag = set if searched-for byte found, reset otherwise ; Carry Flag = set if searched-for byte found, reset otherwise
SearchMaxLengthprocnear SearchMaxLengthprocnear
cld cld
SearchMaxLengthLoop: SearchMaxLengthLoop:
lodsb ;get the next byte lodsb ;get the next byte
cmp al,ah ;is this the byte we want? cmp al,ah ;is this the byte we want?
jz ByteFound ;yes, we're done with success jz ByteFound ;yes, we're done with success
and al,al ;is this the terminating 0 byte? and al,al ;is this the terminating 0 byte?
jz ByteNotFound ;yes, we're done with failure jz ByteNotFound ;yes, we're done with failure
loop SearchMaxLengthLoop ;it's neither, so check the next loop SearchMaxLengthLoop ;it's neither, so check the next
;byte, if any ;byte, if any
ByteNotFound: ByteNotFound:
clc ;return "not found" status clc ;return "not found" status
ret ret
ByteFound: ByteFound:
dec si ;point back to the location at which dec si ;point back to the location at which
;we found the searched-for byte ;we found the searched-for byte
stc ;return "found" status stc ;return "found" status
ret ret
SearchMaxLengthendp SearchMaxLengthendp
end Start end Start
```
### Unrolling Loops {#Heading7} ### Unrolling Loops {#Heading7}

242
07-04.md
View file

@ -12,134 +12,136 @@ pages: 143-145
**LISTING 7.2 L7-2.ASM** **LISTING 7.2 L7-2.ASM**
; Program to illustrate searching through a buffer of a specified ```nasm
; length until a specified zero byte is encountered. ; Program to illustrate searching through a buffer of a specified
; A loop unrolled four times and terminated with LOOP is used. ; length until a specified zero byte is encountered.
; A loop unrolled four times and terminated with LOOP is used.
.model small .model small
.stack 100h .stack 100h
.data .data
; Sample string to search through. ; Sample string to search through.
SampleStringlabelbyte SampleStringlabelbyte
db ‘This is a sample string of a long enough length ' db ‘This is a sample string of a long enough length '
db ‘so that raw searching speed can outweigh any ' db ‘so that raw searching speed can outweigh any '
db ‘extra set-up time that may be required.',0 db ‘extra set-up time that may be required.',0
SAMPLE_STRING_LENGTH equ $-SampleString SAMPLE_STRING_LENGTH equ $-SampleString
; User prompt. ; User prompt.
Prompt db ‘Enter character to search for:$' Prompt db ‘Enter character to search for:$'
; Result status messages. ; Result status messages.
ByteFoundMsg db 0dh,0ah ByteFoundMsg db 0dh,0ah
db ‘Specified byte found.',0dh,0ah,‘$' db ‘Specified byte found.',0dh,0ah,‘$'
ZeroByteFoundMsg db 0dh,0ah ZeroByteFoundMsg db 0dh,0ah
db ‘Zero byte encountered.', 0dh, 0ah, ‘$' db ‘Zero byte encountered.', 0dh, 0ah, ‘$'
NoByteFoundMsg db 0dh,0ah NoByteFoundMsg db 0dh,0ah
db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$' db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$'
; Table of initial, possibly partial loop entry points for ; Table of initial, possibly partial loop entry points for
; SearchMaxLength. ; SearchMaxLength.
SearchMaxLengthEntryTable labelword SearchMaxLengthEntryTable labelword
dw SearchMaxLengthEntry4 dw SearchMaxLengthEntry4
dw SearchMaxLengthEntry1 dw SearchMaxLengthEntry1
dw SearchMaxLengthEntry2 dw SearchMaxLengthEntry2
dw SearchMaxLengthEntry3 dw SearchMaxLengthEntry3
.code .code
Start proc near Start proc near
mov ax,@data ;point to standard data segment mov ax,@data ;point to standard data segment
mov ds,ax mov ds,ax
mov dx,offset Prompt mov dx,offset Prompt
mov ah,9 ;DOS print string function mov ah,9 ;DOS print string function
int 21h ;prompt the user int 21h ;prompt the user
mov ah,1 ;DOS get key function mov ah,1 ;DOS get key function
int 21h ;get the key to search for int 21h ;get the key to search for
mov ah,al ;put character to search for in AH mov ah,al ;put character to search for in AH
mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search
mov si,offset SampleString ;point to buffer to search mov si,offset SampleString ;point to buffer to search
call SearchMaxLength ;search the buffer call SearchMaxLength ;search the buffer
mov dx,offset ByteFoundMsg ;assume we found the byte mov dx,offset ByteFoundMsg ;assume we found the byte
jc PrintStatus ;we did find the byte jc PrintStatus ;we did find the byte
;we didn't find the byte, figure out ;we didn't find the byte, figure out
;whether we found a zero byte or ;whether we found a zero byte or
;ran out of buffer ;ran out of buffer
mov dx,offset NoByteFoundMsg mov dx,offset NoByteFoundMsg
;assume we didn't find a zero byte ;assume we didn't find a zero byte
jcxz PrintStatus ;we didn't find a zero byte jcxz PrintStatus ;we didn't find a zero byte
mov dx,offset ZeroByteFoundMsg ;we found a zero byte mov dx,offset ZeroByteFoundMsg ;we found a zero byte
PrintStatus: PrintStatus:
mov ah,9 ;DOS print string function mov ah,9 ;DOS print string function
int 21h ;report status int 21h ;report status
mov ah,4ch ;return to DOS mov ah,4ch ;return to DOS
int 21h int 21h
Startendp Startendp
; Function to search a buffer of a specified length until either a ; Function to search a buffer of a specified length until either a
; specified byte or a zero byte is encountered. ; specified byte or a zero byte is encountered.
; Input: ; Input:
; AH = character to search for ; AH = character to search for
; CX = maximum length to be searched (must be > 0) ; CX = maximum length to be searched (must be > 0)
; DS:SI = pointer to buffer to be searched ; DS:SI = pointer to buffer to be searched
; Output: ; Output:
; CX = 0 if and only if we ran out of bytes without finding ; CX = 0 if and only if we ran out of bytes without finding
; either the desired byte or a zero byte ; either the desired byte or a zero byte
; DS:SI = pointer to searched-for byte if found, otherwise byte ; DS:SI = pointer to searched-for byte if found, otherwise byte
; after zero byte if found, otherwise byte after last ; after zero byte if found, otherwise byte after last
; byte checked if neither searched-for byte nor zero ; byte checked if neither searched-for byte nor zero
; byte is found ; byte is found
; Carry Flag = set if searched-for byte found, reset otherwise ; Carry Flag = set if searched-for byte found, reset otherwise
SearchMaxLength proc near SearchMaxLength proc near
cld cld
mov bx,cx mov bx,cx
add cx,3 ;calculate the maximum # of passes add cx,3 ;calculate the maximum # of passes
shr cx,1 ;through the loop, which is shr cx,1 ;through the loop, which is
shr cx,1 ;unrolled 4 times shr cx,1 ;unrolled 4 times
and bx,3 ;calculate the index into the entry and bx,3 ;calculate the index into the entry
;point table for the first, ;point table for the first,
;possibly partial loop ;possibly partial loop
shl bx,1 ;prepare for a word-sized look-up shl bx,1 ;prepare for a word-sized look-up
jmp SearchMaxLengthEntryTable[bx] jmp SearchMaxLengthEntryTable[bx]
;branch into the unrolled loop to do ;branch into the unrolled loop to do
;the first, possibly partial loop ;the first, possibly partial loop
SearchMaxLengthLoop: SearchMaxLengthLoop:
SearchMaxLengthEntry4: SearchMaxLengthEntry4:
lodsb ;get the next byte lodsb ;get the next byte
cmp al,ah ;is this the byte we want? cmp al,ah ;is this the byte we want?
jz ByteFound ;yes, we're done with success jz ByteFound ;yes, we're done with success
and al,al ;is this the terminating 0 byte? and al,al ;is this the terminating 0 byte?
jz ByteNotFound ;yes, we're done with failure jz ByteNotFound ;yes, we're done with failure
SearchMaxLengthEntry3: SearchMaxLengthEntry3:
lodsb ;get the next byte lodsb ;get the next byte
cmp al,ah ;is this the byte we want? cmp al,ah ;is this the byte we want?
jz ByteFound ;yes, we're done with success jz ByteFound ;yes, we're done with success
and al,al ;is this the terminating 0 byte? and al,al ;is this the terminating 0 byte?
jz ByteNotFound ;yes, we're done with failure jz ByteNotFound ;yes, we're done with failure
SearchMaxLengthEntry2: SearchMaxLengthEntry2:
lodsb ;get the next byte lodsb ;get the next byte
cmp al,ah ;is this the byte we want? cmp al,ah ;is this the byte we want?
jz ByteFound ;yes, we're done with success jz ByteFound ;yes, we're done with success
and al,al ;is this the terminating 0 byte? and al,al ;is this the terminating 0 byte?
jz ByteNotFound ;yes, we're done with failure jz ByteNotFound ;yes, we're done with failure
SearchMaxLengthEntry1: SearchMaxLengthEntry1:
lodsb ;get the next byte lodsb ;get the next byte
cmp al,ah ;is this the byte we want? cmp al,ah ;is this the byte we want?
jz ByteFound ;yes, we're done with success jz ByteFound ;yes, we're done with success
and al,al ;is this the terminating 0 byte? and al,al ;is this the terminating 0 byte?
jz ByteNotFound ;yes, we're done with failure jz ByteNotFound ;yes, we're done with failure
loop SearchMaxLengthLoop ;it's neither, so check the next loop SearchMaxLengthLoop ;it's neither, so check the next
; four bytes, if any ; four bytes, if any
ByteNotFound: ByteNotFound:
clc ;return "not found" status clc ;return "not found" status
ret ret
ByteFound: ByteFound:
dec si ;point back to the location at which dec si ;point back to the location at which
; we found the searched-for byte ; we found the searched-for byte
stc ;return "found" status stc ;return "found" status
ret ret
SearchMaxLengthendp SearchMaxLengthendp
end Start end Start
```
How much difference? Listing 7.2 runs in 121 µs—40 percent faster than How much difference? Listing 7.2 runs in 121 µs—40 percent faster than
Listing 7.1, even though Listing 7.2 still uses **LOOP** rather than Listing 7.1, even though Listing 7.2 still uses **LOOP** rather than

View file

@ -19,9 +19,11 @@ simple task of setting bit N of AX to 1.
The obvious way to do this is to place N in CL, rotate the bit into The obvious way to do this is to place N in CL, rotate the bit into
position, and OR it with AX, as follows: position, and OR it with AX, as follows:
MOV BX,1 ```nasm
SHL BX,CL MOV BX,1
OR AX,BX SHL BX,CL
OR AX,BX
```
This solution is obvious because it takes good advantage of the special This solution is obvious because it takes good advantage of the special
ability of the x86 family to shift or rotate by the variable number of ability of the x86 family to shift or rotate by the variable number of
@ -31,15 +33,17 @@ bit number in BX, and look the shifted bit up, as shown in Listing 7.3.
**LISTING 7.3 L7-3.ASM** **LISTING 7.3 L7-3.ASM**
SHL BX,1 ;prepare for word sized look up ```nasm
OR AX,ShiftTable[BX] ;look up the bit and OR it in SHL BX,1 ;prepare for word sized look up
: OR AX,ShiftTable[BX] ;look up the bit and OR it in
ShiftTable LABEL WORD :
BIT_PATTERN=0001H ShiftTable LABEL WORD
REPT 16 BIT_PATTERN=0001H
DW BIT_PATTERN REPT 16
BIT_PATTERN=BIT_PATTERN SHL 1 DW BIT_PATTERN
ENDM BIT_PATTERN=BIT_PATTERN SHL 1
ENDM
```
Even though it accesses memory, this approach takes only 20 cycles—more Even though it accesses memory, this approach takes only 20 cycles—more
than twice as fast as the variable shift. Once again, we were able to than twice as fast as the variable shift. Once again, we were able to
@ -55,14 +59,16 @@ code snippet in Listing 7.4.
**LISTING 7.4 L7-4.ASM** **LISTING 7.4 L7-4.ASM**
OR EAX,ShiftTable[EBX*4] ;look up the bit and OR it in ```nasm
: OR EAX,ShiftTable[EBX*4] ;look up the bit and OR it in
ShiftTable LABEL DWORD :
BIT_PATTERN=0001H ShiftTable LABEL DWORD
REPT 32 BIT_PATTERN=0001H
DD BIT_PATTERN REPT 32
BIT_PATTERN=BIT_PATTERN SHL 1 DD BIT_PATTERN
ENDM BIT_PATTERN=BIT_PATTERN SHL 1
ENDM
```
> ![](images/i.jpg) > ![](images/i.jpg)
> Besides illustrating the advantages of local optimization, this example > Besides illustrating the advantages of local optimization, this example
@ -119,30 +125,34 @@ addition to the next.
**LISTING 7.5 L7-5.ASM** **LISTING 7.5 L7-5.ASM**
CLC ;clear the Carry for the initial addition ```nasm
LOOP_TOP: CLC ;clear the Carry for the initial addition
MOV AX,[SI];get next source operand word LOOP_TOP:
ADC [DI],AX;add with Carry to dest operand word MOV AX,[SI];get next source operand word
INC SI ;point to next source operand word ADC [DI],AX;add with Carry to dest operand word
INC SI INC SI ;point to next source operand word
INC DI ;point to next dest operand word INC SI
INC DI INC DI ;point to next dest operand word
LOOP LOOP_TOP INC DI
LOOP LOOP_TOP
```
If **ADD** were used, the Carry flag would have to be saved between If **ADD** were used, the Carry flag would have to be saved between
additions, with code along the lines shown in Listing 7.6. additions, with code along the lines shown in Listing 7.6.
**LISTING 7.6 L7-6.ASM** **LISTING 7.6 L7-6.ASM**
CLC ;clear the carry for the initial addition ```nasm
LOOP_TOP: CLC ;clear the carry for the initial addition
MOV AX,[SI] ;get next source operand word LOOP_TOP:
ADC [DI],AX ;add with carry to dest operand word MOV AX,[SI] ;get next source operand word
LAHF ;set aside the carry flag ADC [DI],AX ;add with carry to dest operand word
ADD SI,2 ;point to next source operand word LAHF ;set aside the carry flag
ADD DI,2 ;point to next dest operand word ADD SI,2 ;point to next source operand word
SAHF ;restore the carry flag ADD DI,2 ;point to next dest operand word
LOOP LOOP_TOP SAHF ;restore the carry flag
LOOP LOOP_TOP
```
It's not that the Listing 7.6 approach is necessarily better or worse; It's not that the Listing 7.6 approach is necessarily better or worse;
that depends on the processor and the situation. The Listing 7.6 that depends on the processor and the situation. The Listing 7.6
@ -182,13 +192,17 @@ an 8088 but are relatively slow instructions on the 486 and Pentium.
There are times when it's a clear liability that **INC** doesn't set the There are times when it's a clear liability that **INC** doesn't set the
Carry flag. For instance Carry flag. For instance
INC AX ```nasm
ADC DX,0 INC AX
ADC DX,0
```
does *not* increment the 32-bit value in DX:AX. To do that, you'd need does *not* increment the 32-bit value in DX:AX. To do that, you'd need
the following: the following:
ADD AX,1 ```nasm
ADC DX,0 ADD AX,1
ADC DX,0
```
As always, pay attention! As always, pay attention!

View file

@ -91,8 +91,10 @@ deal. You've eliminated the trappings of the compiler—the stack frame
and the restricted register usage—but you're still *thinking* like the and the restricted register usage—but you're still *thinking* like the
compiler. Try this: compiler. Try this:
repnz scasw ```nasm
jz Match repnz scasw
jz Match
```
It's a simple example—but, I hope, a convincing one. Stretch your brain It's a simple example—but, I hope, a convincing one. Stretch your brain
when you optimize. when you optimize.

284
08-03.md
View file

@ -12,125 +12,127 @@ pages: 156-160
**LISTING 8.1 L8-1.C** **LISTING 8.1 L8-1.C**
/* Program to search an array spanning a linked list of variable- ```c
sized blocks, for all entries with a specified ID number, /* Program to search an array spanning a linked list of variable-
and return the average of the values of all such entries. Each of sized blocks, for all entries with a specified ID number,
the variable-sized blocks may contain any number of data entries, and return the average of the values of all such entries. Each of
stored as an array of structures within the block. */ the variable-sized blocks may contain any number of data entries,
stored as an array of structures within the block. */
#include <stdio.h> #include <stdio.h>
#ifdef __TURBOC__ #ifdef __TURBOC__
#include <alloc.h> #include <alloc.h>
#else #else
#include <malloc.h> #include <malloc.h>
#endif #endif
void main(void); void main(void);
void exit(int); void exit(int);
unsigned int FindIDAverage(unsigned int, struct BlockHeader *); unsigned int FindIDAverage(unsigned int, struct BlockHeader *);
/* Structure that starts each variable-sized block */ /* Structure that starts each variable-sized block */
struct BlockHeader { struct BlockHeader {
struct BlockHeader *NextBlock; /* Pointer to next block, or NULL struct BlockHeader *NextBlock; /* Pointer to next block, or NULL
if this is the last block in the if this is the last block in the
linked list */ linked list */
unsigned int BlockCount; /* The number of DataElement entries unsigned int BlockCount; /* The number of DataElement entries
in this variable-sized block */ in this variable-sized block */
}; };
/* Structure that contains one element of the array we'll search */ /* Structure that contains one element of the array we'll search */
struct DataElement { struct DataElement {
unsigned int ID; /* ID # for array entry */ unsigned int ID; /* ID # for array entry */
unsigned int Value; /* Value of array entry */ unsigned int Value; /* Value of array entry */
}; };
void main(void) { void main(void) {
int i,j; int i,j;
unsigned int IDToFind; unsigned int IDToFind;
struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer; struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer;
struct DataElement *WorkingDataPointer; struct DataElement *WorkingDataPointer;
struct BlockHeader **LastBlockPointer; struct BlockHeader **LastBlockPointer;
printf("ID # for which to find average: "); printf("ID # for which to find average: ");
scanf("%d",&IDToFind); scanf("%d",&IDToFind);
/* Build an array across 5 blocks, for testing */ /* Build an array across 5 blocks, for testing */
/* Anchor the linked list to BaseArrayBlockPointer */ /* Anchor the linked list to BaseArrayBlockPointer */
LastBlockPointer = &BaseArrayBlockPointer; LastBlockPointer = &BaseArrayBlockPointer;
/* Create 5 blocks of varying sizes */ /* Create 5 blocks of varying sizes */
for (i = 1; i < 6; i++) { for (i = 1; i < 6; i++) {
/* Try to get memory for the next block */ /* Try to get memory for the next block */
if ((WorkingBlockPointer = if ((WorkingBlockPointer =
(struct BlockHeader *) malloc(sizeof(struct BlockHeader) + (struct BlockHeader *) malloc(sizeof(struct BlockHeader) +
sizeof(struct DataElement) * i * 10)) == NULL) { sizeof(struct DataElement) * i * 10)) == NULL) {
exit(1); exit(1);
} }
/* Set the # of data elements in this block */ /* Set the # of data elements in this block */
WorkingBlockPointer->BlockCount = i * 10; WorkingBlockPointer->BlockCount = i * 10;
/* Link the new block into the chain */ /* Link the new block into the chain */
*LastBlockPointer = WorkingBlockPointer; *LastBlockPointer = WorkingBlockPointer;
/* Point to the first data field */ /* Point to the first data field */
WorkingDataPointer = WorkingDataPointer =
(struct DataElement *) ((char *)WorkingBlockPointer + (struct DataElement *) ((char *)WorkingBlockPointer +
sizeof(struct BlockHeader)); sizeof(struct BlockHeader));
/* Fill the data fields with ID numbers and values */ /* Fill the data fields with ID numbers and values */
for (j = 0; j < (i * 10); j++, WorkingDataPointer++) { for (j = 0; j < (i * 10); j++, WorkingDataPointer++) {
WorkingDataPointer->ID = j; WorkingDataPointer->ID = j;
WorkingDataPointer->Value = i * 1000 + j; WorkingDataPointer->Value = i * 1000 + j;
} }
/* Remember where to set link from this block to the next */ /* Remember where to set link from this block to the next */
LastBlockPointer = &WorkingBlockPointer->NextBlock; LastBlockPointer = &WorkingBlockPointer->NextBlock;
} }
/* Set the last block's "next block" pointer to NULL to indicate /* Set the last block's "next block" pointer to NULL to indicate
that there are no more blocks */ that there are no more blocks */
WorkingBlockPointer->NextBlock = NULL; WorkingBlockPointer->NextBlock = NULL;
printf("Average of all elements with ID %d: %u\n", printf("Average of all elements with ID %d: %u\n",
IDToFind, FindIDAverage(IDToFind, BaseArrayBlockPointer)); IDToFind, FindIDAverage(IDToFind, BaseArrayBlockPointer));
exit(0); exit(0);
} }
/* Searches through the array of DataElement entries spanning the /* Searches through the array of DataElement entries spanning the
linked list of variable-sized blocks, starting with the block linked list of variable-sized blocks, starting with the block
pointed to by BlockPointer, for all entries with IDs matching pointed to by BlockPointer, for all entries with IDs matching
SearchedForID, and returns the average value of those entries. If SearchedForID, and returns the average value of those entries. If
no matches are found, zero is returned */ no matches are found, zero is returned */
unsigned int FindIDAverage(unsigned int SearchedForID, unsigned int FindIDAverage(unsigned int SearchedForID,
struct BlockHeader *BlockPointer) struct BlockHeader *BlockPointer)
{ {
struct DataElement *DataPointer; struct DataElement *DataPointer;
unsigned int IDMatchSum; unsigned int IDMatchSum;
unsigned int IDMatchCount; unsigned int IDMatchCount;
unsigned int WorkingBlockCount; unsigned int WorkingBlockCount;
IDMatchCount = IDMatchSum = 0; IDMatchCount = IDMatchSum = 0;
/* Search through all the linked blocks until the last block /* Search through all the linked blocks until the last block
(marked with a NULL pointer to the next block) has been (marked with a NULL pointer to the next block) has been
searched */ searched */
do { do {
/* Point to the first DataElement entry within this block */ /* Point to the first DataElement entry within this block */
DataPointer = DataPointer =
(struct DataElement *) ((char *)BlockPointer + (struct DataElement *) ((char *)BlockPointer +
sizeof(struct BlockHeader)); sizeof(struct BlockHeader));
/* Search all the DataElement entries within this block /* Search all the DataElement entries within this block
and accumulate data from all that match the desired ID */ and accumulate data from all that match the desired ID */
for (WorkingBlockCount=0; for (WorkingBlockCount=0;
WorkingBlockCount<BlockPointer->BlockCount; WorkingBlockCount<BlockPointer->BlockCount;
WorkingBlockCount++, DataPointer++) { WorkingBlockCount++, DataPointer++) {
/* If the ID matches, add in the value and increment the /* If the ID matches, add in the value and increment the
match counter */ match counter */
if (DataPointer->ID == SearchedForID) { if (DataPointer->ID == SearchedForID) {
IDMatchCount++; IDMatchCount++;
IDMatchSum += DataPointer->Value; IDMatchSum += DataPointer->Value;
} }
} }
/* Point to the next block, and continue as long as that pointer /* Point to the next block, and continue as long as that pointer
isn't NULL */ isn't NULL */
} while ((BlockPointer = BlockPointer->NextBlock) != NULL); } while ((BlockPointer = BlockPointer->NextBlock) != NULL);
/* Calculate the average of all matches */ /* Calculate the average of all matches */
if (IDMatchCount == 0) if (IDMatchCount == 0)
return(0); /* Avoid division by 0 */ return(0); /* Avoid division by 0 */
else else
return(IDMatchSum / IDMatchCount); return(IDMatchSum / IDMatchCount);
} }
```
The main body of Listing 8.1 constructs a linked list of memory blocks The main body of Listing 8.1 constructs a linked list of memory blocks
of various sizes and stores an array of structures across those blocks, of various sizes and stores an array of structures across those blocks,
@ -154,32 +156,34 @@ instruction can be used.
**LISTING 8.2 L8-2.COD** **LISTING 8.2 L8-2.COD**
; Code generated by Microsoft C for inner loop of FindIDAverage. ```nasm
;|*** for (WorkingBlockCount=0; ; Code generated by Microsoft C for inner loop of FindIDAverage.
;|*** WorkingBlockCount<BlockPointer->BlockCount; ;|*** for (WorkingBlockCount=0;
;|*** WorkingBlockCount++, DataPointer++) { ;|*** WorkingBlockCount<BlockPointer->BlockCount;
mov WORD PTR [bp-6],0 ;WorkingBlockCount ;|*** WorkingBlockCount++, DataPointer++) {
mov bx,WORD PTR [bp+6] ;BlockPointer mov WORD PTR [bp-6],0 ;WorkingBlockCount
cmp WORD PTR [bx+2],0 mov bx,WORD PTR [bp+6] ;BlockPointer
je $FB264 cmp WORD PTR [bx+2],0
mov cx,WORD PTR [bx+2] je $FB264
add WORD PTR [bp-6],cx ;WorkingBlockCount mov cx,WORD PTR [bx+2]
mov di,WORD PTR [bp-2] ;IDMatchSum add WORD PTR [bp-6],cx ;WorkingBlockCount
mov dx,WORD PTR [bp-4] ;IDMatchCount mov di,WORD PTR [bp-2] ;IDMatchSum
$L20004: mov dx,WORD PTR [bp-4] ;IDMatchCount
;|*** if (DataPointer->ID == SearchedForID) { $L20004:
mov ax,WORD PTR [si] ;|*** if (DataPointer->ID == SearchedForID) {
cmp WORD PTR [bp+4],ax ;SearchedForID mov ax,WORD PTR [si]
jne $I265 cmp WORD PTR [bp+4],ax ;SearchedForID
;|*** IDMatchCount++; jne $I265
inc dx ;|*** IDMatchCount++;
;|*** IDMatchSum += DataPointer->Value; inc dx
add di,WORD PTR [si+2] ;|*** IDMatchSum += DataPointer->Value;
;|*** } add di,WORD PTR [si+2]
;|*** } ;|*** }
$I265: ;|*** }
add si,4 $I265:
loop $L20004 add si,4
mov WORD PTR [bp-2],di ;IDMatchSum loop $L20004
mov WORD PTR [bp-4],dx ;IDMatchCount mov WORD PTR [bp-2],di ;IDMatchSum
$FB264: mov WORD PTR [bp-4],dx ;IDMatchCount
$FB264:
```

294
08-04.md
View file

@ -24,19 +24,21 @@ optimization, isn't it?
**LISTING 8.3 L8-3.ASM** **LISTING 8.3 L8-3.ASM**
; Typically optimized assembly language version of FindIDAverage. ```nasm
SearchedForID equ 4 ;Passed parameter offsets in the ; Typically optimized assembly language version of FindIDAverage.
BlockPointer equ 6 ; stack frame (skip over pushed BP SearchedForID equ 4 ;Passed parameter offsets in the
; and the return address) BlockPointer equ 6 ; stack frame (skip over pushed BP
NextBlock equ 0 ;Field offsets in struct BlockHeader ; and the return address)
BlockCount equ 2 NextBlock equ 0 ;Field offsets in struct BlockHeader
BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader BlockCount equ 2
ID equ 0 ;struct DataElement field offsets BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader
Value equ 2 ID equ 0 ;struct DataElement field offsets
DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement Value equ 2
.model small DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement
.code .model small
public _FindIDAverage .code
public _FindIDAverage
```
| | On 20 MHz 386 | On 10 MHz 286 | | | On 20 MHz 386 | On 10 MHz 286 |
|------------------------------------------------------------|------------------|------------------| |------------------------------------------------------------|------------------|------------------|
@ -47,49 +49,51 @@ optimization, isn't it?
Table: Table 8.1 Execution Times of FindIDAverage. Table: Table 8.1 Execution Times of FindIDAverage.
_FindIDAverage proc near ```nasm
push bp ;Save caller's stack frame _FindIDAverage proc near
mov bp,sp ;Point to our stack frame push bp ;Save caller's stack frame
push di ;Preserve C register variables mov bp,sp ;Point to our stack frame
push si push di ;Preserve C register variables
sub dx,dx ;IDMatchSum = 0 push si
mov bx,dx ;IDMatchCount = 0 sub dx,dx ;IDMatchSum = 0
mov si,[bp+BlockPointer] ;Pointer to first block mov bx,dx ;IDMatchCount = 0
mov ax,[bp+SearchedForID] ;ID we're looking for mov si,[bp+BlockPointer] ;Pointer to first block
; Search through all the linked blocks until the last block mov ax,[bp+SearchedForID] ;ID we're looking for
; (marked with a NULL pointer to the next block) has been searched. ; Search through all the linked blocks until the last block
BlockLoop: ; (marked with a NULL pointer to the next block) has been searched.
; Point to the first DataElement entry within this block. BlockLoop:
lea di,[si+BLOCK_HEADER_SIZE] ; Point to the first DataElement entry within this block.
; Search through all the DataElement entries within this block lea di,[si+BLOCK_HEADER_SIZE]
; and accumulate data from all that match the desired ID. ; Search through all the DataElement entries within this block
mov cx,[si+BlockCount] ; and accumulate data from all that match the desired ID.
jcxz DoNextBlock ;No data in this block mov cx,[si+BlockCount]
IntraBlockLoop: jcxz DoNextBlock ;No data in this block
cmp [di+ID],ax ;Do we have an ID match? IntraBlockLoop:
jnz NoMatch ;No match cmp [di+ID],ax ;Do we have an ID match?
inc bx ;We have a match; IDMatchCount++; jnz NoMatch ;No match
add dx,[di+Value] ;IDMatchSum += DataPointer->Value; inc bx ;We have a match; IDMatchCount++;
NoMatch: add dx,[di+Value] ;IDMatchSum += DataPointer->Value;
add di,DATA_ELEMENT_SIZE ;point to the next element NoMatch:
loop IntraBlockLoop add di,DATA_ELEMENT_SIZE ;point to the next element
; Point to the next block and continue if that pointer isn't NULL. loop IntraBlockLoop
DoNextBlock: ; Point to the next block and continue if that pointer isn't NULL.
mov si,[si+NextBlock] ;Get pointer to the next block DoNextBlock:
and si,si ;Is it a NULL pointer? mov si,[si+NextBlock] ;Get pointer to the next block
jnz BlockLoop ;No, continue and si,si ;Is it a NULL pointer?
; Calculate the average of all matches. jnz BlockLoop ;No, continue
sub ax,ax ;Assume we found no matches ; Calculate the average of all matches.
and bx,bx sub ax,ax ;Assume we found no matches
jz Done ;We didn't find any matches, return 0 and bx,bx
xchg ax,dx ;Prepare for division jz Done ;We didn't find any matches, return 0
div bx ;Return IDMatchSum / IDMatchCount xchg ax,dx ;Prepare for division
Done: pop si ;Restore C register variables div bx ;Return IDMatchSum / IDMatchCount
pop di Done: pop si ;Restore C register variables
pop bp ;Restore caller's stack frame pop di
ret pop bp ;Restore caller's stack frame
_FindIDAverage ENDP ret
end _FindIDAverage ENDP
end
```
Listing 8.4 tosses some sophisticated optimization techniques into the Listing 8.4 tosses some sophisticated optimization techniques into the
mix. The loop is unrolled eight times, eliminating a good deal of mix. The loop is unrolled eight times, eliminating a good deal of
@ -102,91 +106,93 @@ but not a tremendous return for the optimization effort invested.
**LISTING 8.4 L8-4.ASM** **LISTING 8.4 L8-4.ASM**
; Heavily optimized assembly language version of FindIDAverage. ```nasm
; Features an unrolled loop and more efficient pointer use. ; Heavily optimized assembly language version of FindIDAverage.
SearchedForID equ 4 ;Passed parameter offsets in the ; Features an unrolled loop and more efficient pointer use.
BlockPointer equ 6 ; stack frame (skip over pushed BP SearchedForID equ 4 ;Passed parameter offsets in the
; and the return address) BlockPointer equ 6 ; stack frame (skip over pushed BP
NextBlock equ 0 ;Field offsets in struct BlockHeader ; and the return address)
BlockCount equ 2 NextBlock equ 0 ;Field offsets in struct BlockHeader
BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader BlockCount equ 2
ID equ 0 ;struct DataElement field offsets BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader
Value equ 2 ID equ 0 ;struct DataElement field offsets
DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement Value equ 2
.model small DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement
.code .model small
public _FindIDAverage .code
_FindIDAverage proc near public _FindIDAverage
push bp ;Save caller's stack frame _FindIDAverage proc near
mov bp,sp ;Point to our stack frame push bp ;Save caller's stack frame
push di ;Preserve C register variables mov bp,sp ;Point to our stack frame
push si push di ;Preserve C register variables
mov di,ds ;Prepare for SCASW push si
mov es,di mov di,ds ;Prepare for SCASW
cld mov es,di
sub dx,dx ;IDMatchSum = 0 cld
mov bx,dx ;IDMatchCount = 0 sub dx,dx ;IDMatchSum = 0
mov si,[bp+BlockPointer] ;Pointer to first block mov bx,dx ;IDMatchCount = 0
mov ax,[bp+SearchedForID] ;ID we're looking for mov si,[bp+BlockPointer] ;Pointer to first block
; Search through all of the linked blocks until the last block mov ax,[bp+SearchedForID] ;ID we're looking for
; (marked with a NULL pointer to the next block) has been searched. ; Search through all of the linked blocks until the last block
BlockLoop: ; (marked with a NULL pointer to the next block) has been searched.
; Point to the first DataElement entry within this block. BlockLoop:
lea di,[si+BLOCK_HEADER_SIZE] ; Point to the first DataElement entry within this block.
; Search through all the DataElement entries within this block lea di,[si+BLOCK_HEADER_SIZE]
; and accumulate data from all that match the desired ID. ; Search through all the DataElement entries within this block
mov cx,[si+BlockCount] ;Number of elements in this block ; and accumulate data from all that match the desired ID.
jcxz DoNextBlock ;Skip this block if it's empty mov cx,[si+BlockCount] ;Number of elements in this block
mov bp,cx ;***stack frame no longer available*** jcxz DoNextBlock ;Skip this block if it's empty
add cx,7 mov bp,cx ;***stack frame no longer available***
shr cx,1 ;Number of repetitions of the unrolled add cx,7
shr cx,1 ; loop = (BlockCount + 7) / 8 shr cx,1 ;Number of repetitions of the unrolled
shr cx,1 shr cx,1 ; loop = (BlockCount + 7) / 8
and bp,7 ;Generate the entry point for the shr cx,1
shl bp,1 ; first, possibly partial pass through and bp,7 ;Generate the entry point for the
jmp cs:[LoopEntryTable+bp] ; the unrolled loop and shl bp,1 ; first, possibly partial pass through
; vector to that entry point jmp cs:[LoopEntryTable+bp] ; the unrolled loop and
align 2 ; vector to that entry point
LoopEntryTable label word align 2
dw LoopEntry8,LoopEntry1,LoopEntry2,LoopEntry3 LoopEntryTable label word
dw LoopEntry4,LoopEntry5,LoopEntry6,LoopEntry7 dw LoopEntry8,LoopEntry1,LoopEntry2,LoopEntry3
M_IBL macro P1 dw LoopEntry4,LoopEntry5,LoopEntry6,LoopEntry7
local NoMatch M_IBL macro P1
LoopEntry&P1&: local NoMatch
scasw ;Do we have an ID match? LoopEntry&P1&:
jnz NoMatch ;No match scasw ;Do we have an ID match?
;We have a match jnz NoMatch ;No match
inc bx ;IDMatchCount++; ;We have a match
add dx,[di] ;IDMatchSum += DataPointer->Value; inc bx ;IDMatchCount++;
NoMatch: add dx,[di] ;IDMatchSum += DataPointer->Value;
add di,DATA_ELEMENT_SIZE-2 ;point to the next element NoMatch:
; (SCASW advanced 2 bytes already) add di,DATA_ELEMENT_SIZE-2 ;point to the next element
endm ; (SCASW advanced 2 bytes already)
align 2 endm
IntraBlockLoop: align 2
M_IBL 8 IntraBlockLoop:
M_IBL 7 M_IBL 8
M_IBL 6 M_IBL 7
M_IBL 5 M_IBL 6
M_IBL 4 M_IBL 5
M_IBL 3 M_IBL 4
M_IBL 2 M_IBL 3
M_IBL 1 M_IBL 2
loop IntraBlockLoop M_IBL 1
; Point to the next block and continue if that pointer isn't NULL. loop IntraBlockLoop
DoNextBlock: ; Point to the next block and continue if that pointer isn't NULL.
mov si,[si+NextBlock] ;Get pointer to the next block DoNextBlock:
and si,si ;Is it a NULL pointer? mov si,[si+NextBlock] ;Get pointer to the next block
jnz BlockLoop ;No, continue and si,si ;Is it a NULL pointer?
; Calculate the average of all matches. jnz BlockLoop ;No, continue
sub ax,ax ;Assume we found no matches ; Calculate the average of all matches.
and bx,bx sub ax,ax ;Assume we found no matches
jz Done ;We didn't find any matches, return 0 and bx,bx
xchg ax,dx ;Prepare for division jz Done ;We didn't find any matches, return 0
div bx ;Return IDMatchSum / IDMatchCount xchg ax,dx ;Prepare for division
Done: pop si ;Restore C register variables div bx ;Return IDMatchSum / IDMatchCount
pop di Done: pop si ;Restore C register variables
pop bp ;Restore caller's stack frame pop di
ret pop bp ;Restore caller's stack frame
_FindIDAverage ENDP ret
end _FindIDAverage ENDP
end
```

266
08-05.md
View file

@ -20,149 +20,155 @@ merely rearranged.
**LISTING 8.5 L8-5.C** **LISTING 8.5 L8-5.C**
/* Program to search an array spanning a linked list of variable- ```c
sized blocks, for all entries with a specified ID number, /* Program to search an array spanning a linked list of variable-
and return the average of the values of all such entries. Each of sized blocks, for all entries with a specified ID number,
the variable-sized blocks may contain any number of data entries, and return the average of the values of all such entries. Each of
stored in the form of two separate arrays, one for ID numbers and the variable-sized blocks may contain any number of data entries,
one for values. */ stored in the form of two separate arrays, one for ID numbers and
one for values. */
#include <stdio.h> #include <stdio.h>
#ifdef __TURBOC__ #ifdef __TURBOC__
#include <alloc.h> #include <alloc.h>
#else #else
#include <malloc.h> #include <malloc.h>
#endif #endif
void main(void); void main(void);
void exit(int); void exit(int);
extern unsigned int FindIDAverage2(unsigned int, extern unsigned int FindIDAverage2(unsigned int,
struct BlockHeader *); struct BlockHeader *);
```
![**Figure 8.3**  *Linked array storage format (version 2).*](images/08-03.jpg) ![**Figure 8.3**  *Linked array storage format (version 2).*](images/08-03.jpg)
/* Structure that starts each variable-sized block */ ```c
struct BlockHeader { /* Structure that starts each variable-sized block */
struct BlockHeader *NextBlock; /* Pointer to next block, or NULL struct BlockHeader {
if this is the last block in the struct BlockHeader *NextBlock; /* Pointer to next block, or NULL
linked list */ if this is the last block in the
unsigned int BlockCount; /* The number of DataElement entries linked list */
in this variable-sized block */ unsigned int BlockCount; /* The number of DataElement entries
}; in this variable-sized block */
};
void main(void) { void main(void) {
int i,j; int i,j;
unsigned int IDToFind; unsigned int IDToFind;
struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer; struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer;
int *WorkingDataPointer; int *WorkingDataPointer;
struct BlockHeader **LastBlockPointer; struct BlockHeader **LastBlockPointer;
printf("ID # for which to find average: "); printf("ID # for which to find average: ");
scanf("%d",&IDToFind); scanf("%d",&IDToFind);
/* Build an array across 5 blocks, for testing */ /* Build an array across 5 blocks, for testing */
/* Anchor the linked list to BaseArrayBlockPointer */ /* Anchor the linked list to BaseArrayBlockPointer */
LastBlockPointer = &BaseArrayBlockPointer; LastBlockPointer = &BaseArrayBlockPointer;
/* Create 5 blocks of varying sizes */ /* Create 5 blocks of varying sizes */
for (i = 1; i < 6; i++) { for (i = 1; i < 6; i++) {
/* Try to get memory for the next block */ /* Try to get memory for the next block */
if ((WorkingBlockPointer = if ((WorkingBlockPointer =
(struct BlockHeader *) malloc(sizeof(struct BlockHeader) + (struct BlockHeader *) malloc(sizeof(struct BlockHeader) +
sizeof(int) * 2 * i * 10)) == NULL) { sizeof(int) * 2 * i * 10)) == NULL) {
exit(1); exit(1);
} }
/* Set the number of data elements in this block */ /* Set the number of data elements in this block */
WorkingBlockPointer->BlockCount = i * 10; WorkingBlockPointer->BlockCount = i * 10;
/* Link the new block into the chain */ /* Link the new block into the chain */
*LastBlockPointer = WorkingBlockPointer; *LastBlockPointer = WorkingBlockPointer;
/* Point to the first data field */ /* Point to the first data field */
WorkingDataPointer = (int *) ((char *)WorkingBlockPointer + WorkingDataPointer = (int *) ((char *)WorkingBlockPointer +
sizeof(struct BlockHeader)); sizeof(struct BlockHeader));
/* Fill the data fields with ID numbers and values */ /* Fill the data fields with ID numbers and values */
for (j = 0; j < (i * 10); j++, WorkingDataPointer++) { for (j = 0; j < (i * 10); j++, WorkingDataPointer++) {
*WorkingDataPointer = j; *WorkingDataPointer = j;
*(WorkingDataPointer + i * 10) = i * 1000 + j; *(WorkingDataPointer + i * 10) = i * 1000 + j;
} }
/* Remember where to set link from this block to the next */ /* Remember where to set link from this block to the next */
LastBlockPointer = &WorkingBlockPointer->NextBlock; LastBlockPointer = &WorkingBlockPointer->NextBlock;
} }
/* Set the last block's "next block" pointer to NULL to indicate /* Set the last block's "next block" pointer to NULL to indicate
that there are no more blocks */ that there are no more blocks */
WorkingBlockPointer->NextBlock = NULL; WorkingBlockPointer->NextBlock = NULL;
printf("Average of all elements with ID %d: %u\n", printf("Average of all elements with ID %d: %u\n",
IDToFind, FindIDAverage2(IDToFind, BaseArrayBlockPointer)); IDToFind, FindIDAverage2(IDToFind, BaseArrayBlockPointer));
exit(0); exit(0);
} }
```
**LISTING 8.6 L8-6.ASM** **LISTING 8.6 L8-6.ASM**
; Alternative optimized assembly language version of FindIDAverage ```asm
; requires data organized as two arrays within each block rather ; Alternative optimized assembly language version of FindIDAverage
; than as an array of two-value element structures. This allows the ; requires data organized as two arrays within each block rather
; use of REP SCASW for ID searching. ; than as an array of two-value element structures. This allows the
; use of REP SCASW for ID searching.
SearchedForIDequ4 ;Passed parameter offsets in the SearchedForIDequ4 ;Passed parameter offsets in the
BlockPointerequ6 ; stack frame (skip over pushed BP BlockPointerequ6 ; stack frame (skip over pushed BP
; and the return address) ; and the return address)
NextBlockequ0 ;Field offsets in struct BlockHeader NextBlockequ0 ;Field offsets in struct BlockHeader
BlockCountequ2 BlockCountequ2
BLOCK_HEADER_SIZEequ4 ;Number of bytes in struct BlockHeader BLOCK_HEADER_SIZEequ4 ;Number of bytes in struct BlockHeader
.model small .model small
.code .code
public _FindIDAverage2 public _FindIDAverage2
_FindIDAverage2 proc near _FindIDAverage2 proc near
push bp ;Save caller's stack frame push bp ;Save caller's stack frame
mov bp,sp ;Point to our stack frame mov bp,sp ;Point to our stack frame
push di ;Preserve C register variables push di ;Preserve C register variables
push si push si
mov di,ds ;Prepare for SCASW mov di,ds ;Prepare for SCASW
mov es,di mov es,di
cld cld
mov si,[bp+BlockPointer] ;Pointer to first block mov si,[bp+BlockPointer] ;Pointer to first block
mov ax,[bp+SearchedForID] ;ID we're looking for mov ax,[bp+SearchedForID] ;ID we're looking for
sub dx,dx ;IDMatchSum = 0 sub dx,dx ;IDMatchSum = 0
mov bp,dx ;IDMatchCount = 0 mov bp,dx ;IDMatchCount = 0
;***stack frame no longer available*** ;***stack frame no longer available***
; Search through all the linked blocks until the last block ; Search through all the linked blocks until the last block
; (marked with a NULL pointer to the next block) has been searched. ; (marked with a NULL pointer to the next block) has been searched.
BlockLoop: BlockLoop:
; Search through all the DataElement entries within this block ; Search through all the DataElement entries within this block
; and accumulate data from all that match the desired ID. ; and accumulate data from all that match the desired ID.
mov cx,[si+BlockCount] mov cx,[si+BlockCount]
jcxz DoNextBlock;Skip this block if there's no data jcxz DoNextBlock;Skip this block if there's no data
; to search through ; to search through
mov bx,cx ;We'll use BX to point to the mov bx,cx ;We'll use BX to point to the
shl bx,1 ; corresponding value entry in the shl bx,1 ; corresponding value entry in the
; case of an ID match (BX is the ; case of an ID match (BX is the
; length in bytes of the ID array) ; length in bytes of the ID array)
; Point to the first DataElement entry within this block. ; Point to the first DataElement entry within this block.
lea di,[si+BLOCK_HEADER_SIZE] lea di,[si+BLOCK_HEADER_SIZE]
IntraBlockLoop: IntraBlockLoop:
repnz scasw ;Search for the ID repnz scasw ;Search for the ID
jnz DoNextBlock ;No match, the block is done jnz DoNextBlock ;No match, the block is done
inc bp ;We have a match; IDMatchCount++; inc bp ;We have a match; IDMatchCount++;
add dx,[di+bx-2];IDMatchSum += DataPointer->Value; add dx,[di+bx-2];IDMatchSum += DataPointer->Value;
; (SCASW has advanced DI 2 bytes) ; (SCASW has advanced DI 2 bytes)
and cx,cx ;Is there more data to search through? and cx,cx ;Is there more data to search through?
jnz IntraBlockLoop ;yes jnz IntraBlockLoop ;yes
; Point to the next block and continue if that pointer isn't NULL. ; Point to the next block and continue if that pointer isn't NULL.
DoNextBlock: DoNextBlock:
mov si,[si+NextBlock] ;Get pointer to the next block mov si,[si+NextBlock] ;Get pointer to the next block
and si,si ;Is it a NULL pointer? and si,si ;Is it a NULL pointer?
jnz BlockLoop ;No, continue jnz BlockLoop ;No, continue
; Calculate the average of all matches. ; Calculate the average of all matches.
sub ax,ax ;Assume we found no matches sub ax,ax ;Assume we found no matches
and bp,bp and bp,bp
jz Done ;We didn't find any matches, return 0 jz Done ;We didn't find any matches, return 0
xchg ax,dx ;Prepare for division xchg ax,dx ;Prepare for division
div bp ;Return IDMatchSum / IDMatchCount div bp ;Return IDMatchSum / IDMatchCount
Done: pop si ;Restore C register variables Done: pop si ;Restore C register variables
pop di pop di
pop bp ;Restore caller's stack frame pop bp ;Restore caller's stack frame
ret ret
_FindIDAverage2 ENDP _FindIDAverage2 ENDP
end end
```
The whole point of this rearrangement is to allow us to use **REP The whole point of this rearrangement is to allow us to use **REP
SCASW** to search through each block, and that's exactly what SCASW** to search through each block, and that's exactly what

View file

@ -88,13 +88,17 @@ machine word (32 bits in 386 protected mode, 16 bits otherwise), but it
renders **LEA** useless for multiword operations, which use the Carry renders **LEA** useless for multiword operations, which use the Carry
flag to tie together partial results. For example, these instructions flag to tie together partial results. For example, these instructions
ADD EAX,EBX ```nasm
ADC EDX,ECX ADD EAX,EBX
ADC EDX,ECX
```
could *not* be replaced could *not* be replaced
LEA EAX,[EAX+EBX] ```nasm
ADC EDX,ECX LEA EAX,[EAX+EBX]
ADC EDX,ECX
```
because **LEA** doesn't affect the Carry flag. because **LEA** doesn't affect the Carry flag.
@ -103,16 +107,18 @@ performing pointer arithmetic, however. For instance, the following code
uses **LEA** to advance the pointers while adding one 128-bit memory uses **LEA** to advance the pointers while adding one 128-bit memory
variable to another such variable: variable to another such variable:
MOV ECX,4 ;# of 32-bit words to add ```nasm
CLC MOV ECX,4 ;# of 32-bit words to add
;no carry into the initial ADC CLC
ADDLOOP: ;no carry into the initial ADC
ADDLOOP:
MOV EAX,[ESI] ;get the next element of one array MOV EAX,[ESI] ;get the next element of one array
ADC [EDI],EAX ;add it to the other array, with carry ADC [EDI],EAX ;add it to the other array, with carry
LEA ESI,[ESI+4] ;advance one array's pointer LEA ESI,[ESI+4] ;advance one array's pointer
LEA EDI,[EDI+4] ;advance the other array's pointer LEA EDI,[EDI+4] ;advance the other array's pointer
LOOP ADDLOOP LOOP ADDLOOP
```
(Yes, I could use **LODSD** instead of **MOV/LEA**; I'm just (Yes, I could use **LODSD** instead of **MOV/LEA**; I'm just
illustrating a point here. Besides, **LODS** is only 1 cycle faster than illustrating a point here. Besides, **LODS** is only 1 cycle faster than
@ -140,9 +146,11 @@ of my favorites:
John's code for setting AX to its absolute value is: John's code for setting AX to its absolute value is:
CWD ```nasm
XOR AX,DX CWD
SUB AX,DX XOR AX,DX
SUB AX,DX
```
This does nothing when bit 15 of AX is 0 (that is, if AX is positive). This does nothing when bit 15 of AX is 0 (that is, if AX is positive).
When AX is negative, the code "nots" it and adds 1, which is exactly how When AX is negative, the code "nots" it and adds 1, which is exactly how
@ -150,10 +158,12 @@ you perform a two's complement negate. For the case where AX is not
negative, this trick usually beats the stuffing out of the standard negative, this trick usually beats the stuffing out of the standard
absolute value code: absolute value code:
AND AX,AX ;negative? ```nasm
JNS IsPositive ;no AND AX,AX ;negative?
NEG AX ;yes,negate it JNS IsPositive ;no
IsPositive: NEG AX ;yes,negate it
IsPositive:
```
However, John's code is slower on a 486; as you're no doubt coming to However, John's code is slower on a 486; as you're no doubt coming to
realize (and as I'll explain in Chapters 12 and 13), the 486 is an realize (and as I'll explain in Chapters 12 and 13), the 486 is an
@ -162,19 +172,23 @@ optimization world unto itself.
Here's how John copies a block of bytes from DS:SI to ES:DI, moving as Here's how John copies a block of bytes from DS:SI to ES:DI, moving as
much data as possible a word at a time: much data as possible a word at a time:
SHR CX,1 ;word count ```nasm
REP MOVSW ;copy as many words as possible SHR CX,1 ;word count
ADC CX,CX ;CX=1 if copy length was odd, REP MOVSW ;copy as many words as possible
;0 else ADC CX,CX ;CX=1 if copy length was odd,
REP MOVSB ;copy any odd byte ;0 else
REP MOVSB ;copy any odd byte
```
(**ADC CX,CX** can be replaced with **RCL CX,1**; which is faster (**ADC CX,CX** can be replaced with **RCL CX,1**; which is faster
depends on the processor type.) It might be hard to believe that the depends on the processor type.) It might be hard to believe that the
above is faster than this: above is faster than this:
SHR CX,1 ;word count ```nasm
REP MOVSW ;copy as many words as SHR CX,1 ;word count
;possible REP MOVSW ;copy as many words as
JNC CopyDone ;done if even copy length ;possible
MOVSB ;copy the odd byte JNC CopyDone ;done if even copy length
CopyDone: MOVSB ;copy the odd byte
CopyDone:
```

View file

@ -27,34 +27,42 @@ video programmers are undoubtedly familiar with the following code to
multiply AX times 80 (the width in bytes of the bitmap in most PC multiply AX times 80 (the width in bytes of the bitmap in most PC
display modes): display modes):
SHL AX,1 ;*2 ```nasm
SH LAX,1 ;*4 SHL AX,1 ;*2
SH LAX,1 ;*8 SH LAX,1 ;*4
SH LAX,1 ;*16 SH LAX,1 ;*8
MO VBX,AX SH LAX,1 ;*16
SH LAX,1 ;*32 MO VBX,AX
SH LAX,1 ;*64 SH LAX,1 ;*32
ADD AX,BX ;*80 SH LAX,1 ;*64
ADD AX,BX ;*80
```
Using **LEA** on the 386, the above could be reduced to Using **LEA** on the 386, the above could be reduced to
LEA EAX,[EAX*2] ;*2 ```nasm
LEA EAX,[EAX*8] ;*16 LEA EAX,[EAX*2] ;*2
LEA EAX,[EAX+EAX*4] ;*80 LEA EAX,[EAX*8] ;*16
LEA EAX,[EAX+EAX*4] ;*80
```
which still isn't as fast as using a lookup table like which still isn't as fast as using a lookup table like
MOV EAX,MultiplesOf80Table[EAX*4] ```nasm
MOV EAX,MultiplesOf80Table[EAX*4]
```
but is close and takes a great deal less space. but is close and takes a great deal less space.
Of course, on the 386, the shift and add version could also be reduced Of course, on the 386, the shift and add version could also be reduced
to this considerably more efficient code: to this considerably more efficient code:
SH LAX,4 ;*16 ```nasm
MOV BX,AX SH LAX,4 ;*16
SHL AX,2 ;*64 MOV BX,AX
ADD AX,BX ;*80 SHL AX,2 ;*64
ADD AX,BX ;*80
```
#### Speeding Up Multiplication {#Heading5} #### Speeding Up Multiplication {#Heading5}

184
09-03.md
View file

@ -40,94 +40,96 @@ using **REPZ CMPS** to check scanning matches.
**LISTING 9.1 L9-1.ASM** **LISTING 9.1 L9-1.ASM**
; Searches a text buffer for a text string. Uses REPNZ SCASB to sca"n ```nasm
; the buffer for locations that match the first character of the ; Searches a text buffer for a text string. Uses REPNZ SCASB to sca"n
; searched-for string, then uses REPZ CMPS to check fully only those ; the buffer for locations that match the first character of the
; locations that REPNZ SCASB has identified as potential matches. ; searched-for string, then uses REPZ CMPS to check fully only those
; ; locations that REPNZ SCASB has identified as potential matches.
; Adapted from Zen of Assembly Language, by Michael Abrash ;
; ; Adapted from Zen of Assembly Language, by Michael Abrash
; C small model-callable as: ;
; unsigned char * FindString(unsigned char * Buffer, ; C small model-callable as:
; unsigned int BufferLength, unsigned char * SearchString, ; unsigned char * FindString(unsigned char * Buffer,
; unsigned int SearchStringLength); ; unsigned int BufferLength, unsigned char * SearchString,
; ; unsigned int SearchStringLength);
; Returns a pointer to the first match for SearchString in Buffer,or ;
; a NULL pointer if no match is found. Buffer should not start at ; Returns a pointer to the first match for SearchString in Buffer,or
; offset 0 in the data segment to avoid confusing a match at 0 with ; a NULL pointer if no match is found. Buffer should not start at
; no match found. ; offset 0 in the data segment to avoid confusing a match at 0 with
Parmsstruc ; no match found.
dw 2 dup(?) ;pushed BP/return address Parmsstruc
Buffer dw ? ;pointer to buffer to search dw 2 dup(?) ;pushed BP/return address
BufferLength dw ? ;length of buffer to search Buffer dw ? ;pointer to buffer to search
SearchString dw ? ;pointer to string for which to search BufferLength dw ? ;length of buffer to search
SearchStringLength dw ? ;length of string for which to search SearchString dw ? ;pointer to string for which to search
Parmsends SearchStringLength dw ? ;length of string for which to search
.model small Parmsends
.code .model small
public _FindString .code
_FindStringprocnear public _FindString
push bp ;preserve caller's stack frame _FindStringprocnear
mov bp,sp ;point to our stack frame push bp ;preserve caller's stack frame
push si ;preserve caller's register variables mov bp,sp ;point to our stack frame
push di push si ;preserve caller's register variables
cld ;make string instructions increment pointers push di
mov si,[bp+SearchString] ;pointer to string to search for cld ;make string instructions increment pointers
mov bx,[bp+SearchStringLength] ;length of string mov si,[bp+SearchString] ;pointer to string to search for
and bx,bx mov bx,[bp+SearchStringLength] ;length of string
jz FindStringNotFound ;no match if string is 0 length and bx,bx
movd x,[bp+BufferLength] ;length of buffer jz FindStringNotFound ;no match if string is 0 length
sub dx,bx ;difference between buffer and string lengths movd x,[bp+BufferLength] ;length of buffer
jc FindStringNotFound ;no match if search string is sub dx,bx ;difference between buffer and string lengths
; longer than buffer jc FindStringNotFound ;no match if search string is
inc dx ;difference between buffer and search string ; longer than buffer
; lengths, plus 1 (# of possible string start inc dx ;difference between buffer and search string
; locations to check in the buffer) ; lengths, plus 1 (# of possible string start
mov di,ds ; locations to check in the buffer)
mov es,di mov di,ds
mov di,[bp+Buffer] ;point ES:DI to buffer to search thru mov es,di
lodsb ;put the first byte of the search string in AL mov di,[bp+Buffer] ;point ES:DI to buffer to search thru
mov bp,si ;set aside pointer to the second search byte lodsb ;put the first byte of the search string in AL
dec bx ;don't need to compare the first byte of the mov bp,si ;set aside pointer to the second search byte
; string with CMPS; we'll do it with SCAS dec bx ;don't need to compare the first byte of the
FindStringLoop: ; string with CMPS; we'll do it with SCAS
mov cx,dx ;put remaining buffer search length in CX FindStringLoop:
repnz scasb ;scan for the first byte of the string mov cx,dx ;put remaining buffer search length in CX
jnz FindStringNotFound ;not found, so there's no match repnz scasb ;scan for the first byte of the string
;found, so we have a potential match-check the jnz FindStringNotFound ;not found, so there's no match
; rest of this candidate location ;found, so we have a potential match-check the
push di ;remember the address of the next byte to scan ; rest of this candidate location
mov dx,cx ;set aside the remaining length to search in push di ;remember the address of the next byte to scan
; the buffer mov dx,cx ;set aside the remaining length to search in
mov si,bp ;point to the rest of the search string ; the buffer
mov cx,bx ;string length (minus first byte) mov si,bp ;point to the rest of the search string
shr cx,1 ;convert to word for faster search mov cx,bx ;string length (minus first byte)
jnc FindStringWord ;do word search if no odd byte shr cx,1 ;convert to word for faster search
cmpsb ;compare the odd byte jnc FindStringWord ;do word search if no odd byte
jnz FindStringNoMatch ;odd byte doesn't match, so we cmpsb ;compare the odd byte
; haven't found the search string here jnz FindStringNoMatch ;odd byte doesn't match, so we
FindStringWord: ; haven't found the search string here
jcxz FindStringFound ;test whether we've already checked FindStringWord:
; the whole string; if so, this is a match jcxz FindStringFound ;test whether we've already checked
; bytes long; if so, we've found a match ; the whole string; if so, this is a match
repz cmpsw ;check the rest of the string a word at a time ; bytes long; if so, we've found a match
jz FindStringFound ;it's a match repz cmpsw ;check the rest of the string a word at a time
FindStringNoMatch: jz FindStringFound ;it's a match
pop di ;get back pointer to the next byte to scan FindStringNoMatch:
and dx,dx ;is there anything left to check? pop di ;get back pointer to the next byte to scan
jnz FindStringLoop ;yes-check next byte and dx,dx ;is there anything left to check?
FindStringNotFound: jnz FindStringLoop ;yes-check next byte
sub ax,ax ;return a NULL pointer indicating that the FindStringNotFound:
jmp FindStringDone ; string was not found sub ax,ax ;return a NULL pointer indicating that the
FindStringFound: jmp FindStringDone ; string was not found
pop ax ;point to the buffer location at which the FindStringFound:
dec ax ; string was found (earlier we pushed the pop ax ;point to the buffer location at which the
; address of the byte after the start of the dec ax ; string was found (earlier we pushed the
; potential match) ; address of the byte after the start of the
FindStringDone: ; potential match)
pop di ;restore caller's register variables FindStringDone:
pop si pop di ;restore caller's register variables
pop bp ;restore caller's stack frame pop si
ret pop bp ;restore caller's stack frame
_FindStringendp ret
end _FindStringendp
end
```

258
09-04.md
View file

@ -12,134 +12,138 @@ pages: 178-180
**LISTING 9.2 L9-2.ASM** **LISTING 9.2 L9-2.ASM**
; Searches a text buffer for a text string. Uses REPNZ SCASB to scan ```nasm
; the buffer for locations that match a specified character of the ; Searches a text buffer for a text string. Uses REPNZ SCASB to scan
; searched-for string, then uses REPZ CMPS to check fully only those ; the buffer for locations that match a specified character of the
; locations that REPNZ SCASB has identified as potential matches. ; searched-for string, then uses REPZ CMPS to check fully only those
; ; locations that REPNZ SCASB has identified as potential matches.
; C small model-callable as: ;
; unsigned char * FindString(unsigned char * Buffer, ; C small model-callable as:
; unsigned int BufferLength, unsigned char * SearchString, ; unsigned char * FindString(unsigned char * Buffer,
; unsigned int SearchStringLength, ; unsigned int BufferLength, unsigned char * SearchString,
; unsigned int ScanCharOffset); ; unsigned int SearchStringLength,
; ; unsigned int ScanCharOffset);
; Returns a pointer to the first match for SearchString in Buffer,or ;
; a NULL pointer if no match is found. Buffer should not start at ; Returns a pointer to the first match for SearchString in Buffer,or
; offset 0 in the data segment to avoid confusing a match at 0 with ; a NULL pointer if no match is found. Buffer should not start at
; no match found. ; offset 0 in the data segment to avoid confusing a match at 0 with
Parms struc ; no match found.
dw 2 dup(?) ;pushed BP/return address Parms struc
Buffer dw ? ;pointer to buffer to search dw 2 dup(?) ;pushed BP/return address
BufferLength dw ? ;length of buffer to search Buffer dw ? ;pointer to buffer to search
SearchString dw ? ;pointer to string for which to search BufferLength dw ? ;length of buffer to search
SearchStringLength dw ? ;length of string for which to search SearchString dw ? ;pointer to string for which to search
ScanCharOffset dw ? ;offset in string of character for SearchStringLength dw ? ;length of string for which to search
; which to scan ScanCharOffset dw ? ;offset in string of character for
Parmsends ; which to scan
.model small Parmsends
.code .model small
public _FindString .code
_FindStringprocnear public _FindString
push bp ;preserve caller's stack frame _FindStringprocnear
mov bp,sp ;point to our stack frame push bp ;preserve caller's stack frame
push si ;preserve caller's register variables mov bp,sp ;point to our stack frame
push di push si ;preserve caller's register variables
cld ;make string instructions increment pointers push di
mov si,[bp+SearchString] ;pointer to string to search for cld ;make string instructions increment pointers
mov cx,[bp+SearchStringLength] ;length of string mov si,[bp+SearchString] ;pointer to string to search for
jcxz FindStringNotFound ;no match if string is 0 length mov cx,[bp+SearchStringLength] ;length of string
mov dx,[bp+BufferLength] ;length of buffer jcxz FindStringNotFound ;no match if string is 0 length
sub dx,cx ;difference between buffer and search mov dx,[bp+BufferLength] ;length of buffer
; lengths sub dx,cx ;difference between buffer and search
jc FindStringNotFound ;no match if search string is ; lengths
; longer than buffer jc FindStringNotFound ;no match if search string is
inc dx ; difference between buffer and search string ; longer than buffer
; lengths, plus 1 (# of possible string start inc dx ; difference between buffer and search string
; locations to check in the buffer) ; lengths, plus 1 (# of possible string start
mov di,ds ; locations to check in the buffer)
mov es,di mov di,ds
mov di,[bp+Buffer] ;point ES:DI to buffer to search thru mov es,di
mov bx,[bp+ScanCharOffset] ;offset in string of character mov di,[bp+Buffer] ;point ES:DI to buffer to search thru
; on which to scan mov bx,[bp+ScanCharOffset] ;offset in string of character
add di,bx ;point ES:DI to first buffer byte to scan ; on which to scan
mov al,[si+bx] ;put the scan character in AL add di,bx ;point ES:DI to first buffer byte to scan
inc bx ;set BX to the offset back to the start of the mov al,[si+bx] ;put the scan character in AL
; potential full match after a scan match, inc bx ;set BX to the offset back to the start of the
; accounting for the 1-byte overrun of ; potential full match after a scan match,
; REPNZ SCASB ; accounting for the 1-byte overrun of
FindStringLoop: ; REPNZ SCASB
mov cx,dx ;put remaining buffer search length in CX FindStringLoop:
repnz scasb ;scan for the scan byte mov cx,dx ;put remaining buffer search length in CX
jnz FindStringNotFound ;not found, so there's no match repnz scasb ;scan for the scan byte
;found, so we have a potential match-check the jnz FindStringNotFound ;not found, so there's no match
; rest of this candidate location ;found, so we have a potential match-check the
push di ;remember the address of the next byte to scan ; rest of this candidate location
mov dx,cx ;set aside the remaining length to search in push di ;remember the address of the next byte to scan
; the buffer mov dx,cx ;set aside the remaining length to search in
sub di,bx ;point back to the potential start of the ; the buffer
; match in the buffer sub di,bx ;point back to the potential start of the
mov si,[bp+SearchString] ;point to the start of the string ; match in the buffer
mov cx,[bp+SearchStringLength] ;string length mov si,[bp+SearchString] ;point to the start of the string
shr cx,1 ;convert to word for faster search mov cx,[bp+SearchStringLength] ;string length
jnc FindStringWord ;do word search if no odd byte shr cx,1 ;convert to word for faster search
cmpsb ;compare the odd byte jnc FindStringWord ;do word search if no odd byte
jnz FindStringNoMatch ;odd byte doesn't match, so we cmpsb ;compare the odd byte
; haven't found the search string here jnz FindStringNoMatch ;odd byte doesn't match, so we
FindStringWord: ; haven't found the search string here
jcxz FindStringFound ;if the string is only 1 byte long, FindStringWord:
; we've found a match jcxz FindStringFound ;if the string is only 1 byte long,
repz cmpsw ;check the rest of the string a word at a time ; we've found a match
jz FindStringFound ;it's a match repz cmpsw ;check the rest of the string a word at a time
FindStringNoMatch: jz FindStringFound ;it's a match
pop di ;get back pointer to the next byte to scan FindStringNoMatch:
and dx,dx ;is there anything left to check? pop di ;get back pointer to the next byte to scan
jnz FindStringLoop ;yes-check next byte and dx,dx ;is there anything left to check?
FindStringNotFound: jnz FindStringLoop ;yes-check next byte
sub ax,ax ;return a NULL pointer indicating that the FindStringNotFound:
jmp FindStringDone ; string was not found sub ax,ax ;return a NULL pointer indicating that the
FindStringFound: jmp FindStringDone ; string was not found
pop ax ;point to the buffer location at which the FindStringFound:
sub ax,bx ; string was found (earlier we pushed the pop ax ;point to the buffer location at which the
; address of the byte after the scan match) sub ax,bx ; string was found (earlier we pushed the
FindStringDone: ; address of the byte after the scan match)
pop di ;restore caller's register variables FindStringDone:
pop si pop di ;restore caller's register variables
pop bp ;restore caller's stack frame pop si
ret pop bp ;restore caller's stack frame
_FindStringendp ret
end _FindStringendp
end
```
**LISTING 9.3 L9-3.C** **LISTING 9.3 L9-3.C**
/* Program to exercise buffer-search routines in Listings 9.1 & 9.2 */ ```c
#include <stdio.h> /* Program to exercise buffer-search routines in Listings 9.1 & 9.2 */
#include <string.h> #include <stdio.h>
#include <string.h>
#define DISPLAY_LENGTH 40 #define DISPLAY_LENGTH 40
extern unsigned char * FindString(unsigned char *, unsigned int, extern unsigned char * FindString(unsigned char *, unsigned int,
unsigned char *, unsigned int, unsigned int); unsigned char *, unsigned int, unsigned int);
void main(void); void main(void);
static unsigned char TestBuffer[] = "When, in the course of human \ static unsigned char TestBuffer[] = "When, in the course of human \
events, it becomes necessary for one people to dissolve the \ events, it becomes necessary for one people to dissolve the \
political bands which have connected them with another, and to \ political bands which have connected them with another, and to \
assume among the powers of the earth the separate and equal station \ assume among the powers of the earth the separate and equal station \
to which the laws of nature and of nature's God entitle them..."; to which the laws of nature and of nature's God entitle them...";
void main() { void main() {
static unsigned char TestString[] = "equal"; static unsigned char TestString[] = "equal";
unsigned char TempBuffer[DISPLAY_LENGTH+1]; unsigned char TempBuffer[DISPLAY_LENGTH+1];
unsigned char *MatchPtr; unsigned char *MatchPtr;
/* Search for TestString and report the results */ /* Search for TestString and report the results */
if ((MatchPtr = FindString(TestBuffer, if ((MatchPtr = FindString(TestBuffer,
(unsigned int) strlen(TestBuffer), TestString, (unsigned int) strlen(TestBuffer), TestString,
(unsigned int) strlen(TestString), 1)) == NULL) { (unsigned int) strlen(TestString), 1)) == NULL) {
/* TestString wasn't found */ /* TestString wasn't found */
printf("\"%s\" not found\n", TestString); printf("\"%s\" not found\n", TestString);
} else { } else {
/* TestString was found. Zero-terminate TempBuffer; strncpy /* TestString was found. Zero-terminate TempBuffer; strncpy
won't do it if DISPLAY_LENGTH characters are copied */ won't do it if DISPLAY_LENGTH characters are copied */
TempBuffer[DISPLAY_LENGTH] = 0; TempBuffer[DISPLAY_LENGTH] = 0;
printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n", printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n",
TestString, DISPLAY_LENGTH, TestString, DISPLAY_LENGTH,
strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH)); strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH));
} }
} }
```

View file

@ -43,38 +43,39 @@ one.
**LISTING 9.4 L9-4.ASM** **LISTING 9.4 L9-4.ASM**
. ```nasm
;-------------------------------------------------------------------------- ;--------------------------------------------------------------------------
; Sorts an array of ints. C callable (small model). 25 bytes. ; Sorts an array of ints. C callable (small model). 25 bytes.
; void sort( int num, int a[] ); ; void sort( int num, int a[] );
; ;
; Courtesy of David Stafford. ; Courtesy of David Stafford.
;-------------------------------------------------------------------------- ;--------------------------------------------------------------------------
.model small .model small
.code .code
public _sort public _sort
top: mov dx,[bx] ;swap two adjacent integers top: mov dx,[bx] ;swap two adjacent integers
xchg dx,[bx+2] xchg dx,[bx+2]
xchg dx,[bx] xchg dx,[bx]
cmp dx,[bx] ;did we put them in the right order? cmp dx,[bx] ;did we put them in the right order?
jl top ;no, swap them back jl top ;no, swap them back
inc bx ;go to next integer inc bx ;go to next integer
inc bx inc bx
loop top loop top
_sort: pop dx ;get return address (entry point) _sort: pop dx ;get return address (entry point)
pop cx ;get count pop cx ;get count
pop bx ;get pointer pop bx ;get pointer
push bx ;restore pointer push bx ;restore pointer
dec cx ;decrement count dec cx ;decrement count
push cx ;save count push cx ;save count
push dx ;restore return address push dx ;restore return address
jg top ;if cx > 0 jg top ;if cx > 0
ret ret
end end
```
#### Full 32-Bit Division {#Heading8} #### Full 32-Bit Division {#Heading8}

154
09-06.md
View file

@ -12,91 +12,95 @@ pages: 182-185
**LISTING 9.5 L9-5.ASM** **LISTING 9.5 L9-5.ASM**
; Divides an arbitrarily long unsigned dividend by a 16-bit unsigned ```nasm
; divisor. C near-callable as: ; Divides an arbitrarily long unsigned dividend by a 16-bit unsigned
; unsigned int Div(unsigned int * Dividend, ; divisor. C near-callable as:
; int DividendLength, unsigned int Divisor, ; unsigned int Div(unsigned int * Dividend,
; unsigned int * Quotient); ; int DividendLength, unsigned int Divisor,
; ; unsigned int * Quotient);
; Returns the remainder of the division. ;
; ; Returns the remainder of the division.
; Tested with TASM 2. ;
; Tested with TASM 2.
parms struc parms struc
dw 2 dup (?) ;pushed BP & return address dw 2 dup (?) ;pushed BP & return address
Dividend dw ? ;pointer to value to divide, stored in Intel Dividend dw ? ;pointer to value to divide, stored in Intel
; order, with lsb at lowest address, msb at ; order, with lsb at lowest address, msb at
; highest. Must be composed of an integral ; highest. Must be composed of an integral
; number of words ; number of words
DividendLength dw ? ;# of bytes in Dividend. Must be a multiple DividendLength dw ? ;# of bytes in Dividend. Must be a multiple
; of 2 ; of 2
Divisor dw ? ;value by which to divide. Must not be zero, Divisor dw ? ;value by which to divide. Must not be zero,
; or a Divide By Zero interrupt will occur ; or a Divide By Zero interrupt will occur
Quotient dw ? ;pointer to buffer in which to store the Quotient dw ? ;pointer to buffer in which to store the
; result of the division, in Intel order. ; result of the division, in Intel order.
; The quotient returned is of the same ; The quotient returned is of the same
; length as the dividend ; length as the dividend
parmsends parmsends
.model small .model small
.code .code
public _Div public _Div
_Divprocnear _Divprocnear
push bp ;preserve caller's stack frame push bp ;preserve caller's stack frame
mov bp,sp ;point to our stack frame mov bp,sp ;point to our stack frame
push si ;preserve caller's register variables push si ;preserve caller's register variables
push di push di
std ;we're working from msb to lsb std ;we're working from msb to lsb
mov ax,ds mov ax,ds
mov es,ax ;for STOS mov es,ax ;for STOS
mov cx,[bp+DividendLength] mov cx,[bp+DividendLength]
sub cx,2 sub cx,2
mov si,[bp+Dividend] mov si,[bp+Dividend]
add si,cx ;point to the last word of the dividend add si,cx ;point to the last word of the dividend
; (the most significant word) ; (the most significant word)
mov di,[bp+Quotient] mov di,[bp+Quotient]
add di,cx ;point to the last word of the quotient add di,cx ;point to the last word of the quotient
; buffer (the most significant word) ; buffer (the most significant word)
mov bx,[bp+Divisor] mov bx,[bp+Divisor]
shr cx,1 shr cx,1
inc cx ;# of words to process inc cx ;# of words to process
sub dx,dx ;convert initial divisor word to a 32-bit sub dx,dx ;convert initial divisor word to a 32-bit
;value for DIV ;value for DIV
DivLoop: DivLoop:
lod sw ;get next most significant word of divisor lod sw ;get next most significant word of divisor
div bx div bx
sto sw ;save this word of the quotient sto sw ;save this word of the quotient
;DX contains the remainder at this point, ;DX contains the remainder at this point,
; ready to prepend to the next divisor word ; ready to prepend to the next divisor word
loop DivLoop loop DivLoop
mov ax,dx ;return the remainder mov ax,dx ;return the remainder
cld ;restore default Direction flag setting cld ;restore default Direction flag setting
pop di ;restore caller's register variables pop di ;restore caller's register variables
pop si pop si
pop bp ;restore caller's stack frame pop bp ;restore caller's stack frame
ret ret
_Divendp _Divendp
end end
```
**LISTING 9.6 L9-6.C** **LISTING 9.6 L9-6.C**
/* Sample use of Div function to perform division when the result ```c
doesn't fit in 16 bits */ /* Sample use of Div function to perform division when the result
doesn't fit in 16 bits */
#include <stdio.h> #include <stdio.h>
extern unsigned int Div(unsigned int * Dividend, extern unsigned int Div(unsigned int * Dividend,
int DividendLength, unsigned int Divisor, int DividendLength, unsigned int Divisor,
unsigned int * Quotient); unsigned int * Quotient);
main() { main() {
unsigned long m, i = 0x20000001; unsigned long m, i = 0x20000001;
unsigned int k, j = 0x10; unsigned int k, j = 0x10;
k = Div((unsigned int *)&i, sizeof(i), j, (unsigned int *)&m); k = Div((unsigned int *)&i, sizeof(i), j, (unsigned int *)&m);
printf("%lu / %u = %lu r %u\n", i, j, m, k); printf("%lu / %u = %lu r %u\n", i, j, m, k);
} }
```
#### Sweet Spot Revisited {#Heading9} #### Sweet Spot Revisited {#Heading9}

View file

@ -52,11 +52,13 @@ PTR 1000:5**, but you'd be wrong. That won't even assemble. You might
then think to construct in memory a far pointer containing 1000:5, as in then think to construct in memory a far pointer containing 1000:5, as in
the following: the following:
Ptr dd ? ```nasm
: Ptr dd ?
mov word ptr [Ptr],5 :
mov word ptr [Ptr+2],1000h mov word ptr [Ptr],5
jmp [Ptr] mov word ptr [Ptr+2],1000h
jmp [Ptr]
```
That will work, but at a price in performance. On an 8088, **JMP DWORD That will work, but at a price in performance. On an 8088, **JMP DWORD
PTR [*mem*]** (an indirect far jump) takes at least 37 cycles; **JMP PTR [*mem*]** (an indirect far jump) takes at least 37 cycles; **JMP
@ -87,22 +89,24 @@ solution; if you have one, pass it along.
**LISTING 9.7 L9-7.ASM** **LISTING 9.7 L9-7.ASM**
; Program to perform a direct far jump to address 1000:5. ```nasm
; *** Do not run this program! It's just an example of how *** ; Program to perform a direct far jump to address 1000:5.
; *** to build a direct far jump to an absolute address *** ; *** Do not run this program! It's just an example of how ***
; ; *** to build a direct far jump to an absolute address ***
; Tested with TASM 2 and MASM 5. ;
; Tested with TASM 2 and MASM 5.
FarSeg segment at 01000h FarSeg segment at 01000h
org 5 org 5
FarLabel label far FarLabel label far
FarSeg ends FarSeg ends
.model small .model small
.code .code
start: start:
jmp FarLabel jmp FarLabel
end start end start
```
By the way, if you're wondering how I figured this out, I merely applied By the way, if you're wondering how I figured this out, I merely applied
my good friend Dan Illowsky's long-standing rule for dealing with MASM: my good friend Dan Illowsky's long-standing rule for dealing with MASM:
@ -116,12 +120,16 @@ something that does—a rule with plenty of history on its side.
To finish up this chapter, consider these two items. First, in 32-bit To finish up this chapter, consider these two items. First, in 32-bit
protected mode, protected mode,
sub eax,eax ```nasm
inc eax sub eax,eax
inc eax
```
takes 4 cycles to execute, but is only 3 bytes long, while takes 4 cycles to execute, but is only 3 bytes long, while
mov eax,1 ```nasm
mov eax,1
```
takes only 2 cycles to execute, but is 5 bytes long (because native mode takes only 2 cycles to execute, but is 5 bytes long (because native mode
constants are dwords and the **MOV** instruction doesn't sign-extend). constants are dwords and the **MOV** instruction doesn't sign-extend).
@ -129,11 +137,15 @@ Both code fragments are ways to set **EAX** to 1 (although the first
affects the flags and the second doesn't); this is a classic trade-off affects the flags and the second doesn't); this is a classic trade-off
of speed for space. Second, of speed for space. Second,
or ebx,-1 ```nasm
or ebx,-1
```
takes 2 cycles to execute and is 3 bytes long, while takes 2 cycles to execute and is 3 bytes long, while
move bx,-1 ```nasm
move bx,-1
```
takes 2 cycles to execute and is 5 bytes long. Both instructions set takes 2 cycles to execute and is 5 bytes long. Both instructions set
**EBX** to -1; this is a classic trade-off of—gee, it's not a trade-off **EBX** to -1; this is a classic trade-off of—gee, it's not a trade-off

100
10-02.md
View file

@ -64,28 +64,30 @@ default optimization was used. All times measured with the Zen timer
**LISTING 10.1 L10-1.C** **LISTING 10.1 L10-1.C**
/* Finds and returns the greatest common divisor of two positive ```c
integers. Works by trying every integral divisor between the /* Finds and returns the greatest common divisor of two positive
smaller of the two integers and 1, until a divisor that divides integers. Works by trying every integral divisor between the
both integers evenly is found. All C code tested with Microsoft smaller of the two integers and 1, until a divisor that divides
and Borland compilers.*/ both integers evenly is found. All C code tested with Microsoft
and Borland compilers.*/
unsigned int gcd(unsigned int int1, unsigned int int2) { unsigned int gcd(unsigned int int1, unsigned int int2) {
unsigned int temp, trial_divisor; unsigned int temp, trial_divisor;
/* Swap if necessary to make sure that int1 >= int2 */ /* Swap if necessary to make sure that int1 >= int2 */
if (int1 < int2) { if (int1 < int2) {
temp = int1; temp = int1;
int1 = int2; int1 = int2;
int2 = temp; int2 = temp;
} }
/* Now just try every divisor from int2 on down, until a common /* Now just try every divisor from int2 on down, until a common
divisor is found. This can never be an infinite loop because divisor is found. This can never be an infinite loop because
1 divides everything evenly */ 1 divides everything evenly */
for (trial_divisor = int2; ((int1 % trial_divisor) != 0) || for (trial_divisor = int2; ((int1 % trial_divisor) != 0) ||
((int2 % trial_divisor) != 0); trial_divisor—) ((int2 % trial_divisor) != 0); trial_divisor—)
; ;
return(trial_divisor); return(trial_divisor);
} }
```
#### Wasted Breakthroughs {#Heading5} #### Wasted Breakthroughs {#Heading5}
@ -99,32 +101,34 @@ in Listing 10.2.
**LISTING 10.2 L10-2.C** **LISTING 10.2 L10-2.C**
/* Finds and returns the greatest common divisor of two positive ```c
integers. Works by subtracting the smaller integer from the /* Finds and returns the greatest common divisor of two positive
larger integer until either the values match (in which case integers. Works by subtracting the smaller integer from the
that's the gcd), or the larger integer becomes the smaller of larger integer until either the values match (in which case
the two, in which case the two integers swap roles and the that's the gcd), or the larger integer becomes the smaller of
subtraction process continues. */ the two, in which case the two integers swap roles and the
subtraction process continues. */
unsigned int gcd(unsigned int int1, unsigned int int2) { unsigned int gcd(unsigned int int1, unsigned int int2) {
unsigned int temp; unsigned int temp;
/* If the two integers are the same, that's the gcd and we're /* If the two integers are the same, that's the gcd and we're
done */ done */
if (int1 == int2) { if (int1 == int2) {
return(int1); return(int1);
} }
/* Swap if necessary to make sure that int1 >= int2 */ /* Swap if necessary to make sure that int1 >= int2 */
if (int1 < int2) { if (int1 < int2) {
temp = int1; temp = int1;
int1 = int2; int1 = int2;
int2 = temp; int2 = temp;
} }
/* Subtract int2 from int1 until int1 is no longer the larger of /* Subtract int2 from int1 until int1 is no longer the larger of
the two */ the two */
do { do {
int1 -= int2; int1 -= int2;
} while (int1 > int2); } while (int1 > int2);
/* Now recursively call this function to continue the process */ /* Now recursively call this function to continue the process */
return(gcd(int1, int2)); return(gcd(int1, int2));
} }
```

140
10-03.md
View file

@ -45,49 +45,51 @@ Figure 10.3. Listing 10.3 is an implementation of Euclid's algorithm.
**LISTING 10.3 L10-3.C** **LISTING 10.3 L10-3.C**
/* Finds and returns the greatest common divisor of two integers. ```c
Uses Euclid's algorithm: divides the larger integer by the /* Finds and returns the greatest common divisor of two integers.
smaller; if the remainder is 0, the smaller integer is the GCD, Uses Euclid's algorithm: divides the larger integer by the
otherwise the smaller integer becomes the larger integer, the smaller; if the remainder is 0, the smaller integer is the GCD,
remainder becomes the smaller integer, and the process is otherwise the smaller integer becomes the larger integer, the
repeated. */ remainder becomes the smaller integer, and the process is
repeated. */
static unsigned int gcd_recurs(unsigned int, unsigned int); static unsigned int gcd_recurs(unsigned int, unsigned int);
unsigned int gcd(unsigned int int1, unsigned int int2) { unsigned int gcd(unsigned int int1, unsigned int int2) {
unsigned int temp; unsigned int temp;
/* If the two integers are the same, that's the GCD and we're /* If the two integers are the same, that's the GCD and we're
done */ done */
if (int1 == int2) { if (int1 == int2) {
return(int1); return(int1);
} }
/* Swap if necessary to make sure that int1 >= int2 */ /* Swap if necessary to make sure that int1 >= int2 */
if (int1 < int2) { if (int1 < int2) {
temp = int1; temp = int1;
int1 = int2; int1 = int2;
int2 = temp; int2 = temp;
} }
/* Now call the recursive form of the function, which assumes /* Now call the recursive form of the function, which assumes
that the first parameter is the larger of the two */ that the first parameter is the larger of the two */
return(gcd_recurs(int1, int2)); return(gcd_recurs(int1, int2));
} }
static unsigned int gcd_recurs(unsigned int larger_int, static unsigned int gcd_recurs(unsigned int larger_int,
unsigned int smaller_int) unsigned int smaller_int)
{ {
int temp; int temp;
/* If the remainder of larger_int divided by smaller_int is 0, /* If the remainder of larger_int divided by smaller_int is 0,
then smaller_int is the gcd */ then smaller_int is the gcd */
if ((temp = larger_int % smaller_int) == 0) { if ((temp = larger_int % smaller_int) == 0) {
return(smaller_int); return(smaller_int);
} }
/* Make smaller_int the larger integer and the remainder the /* Make smaller_int the larger integer and the remainder the
smaller integer, and call this function recursively to smaller integer, and call this function recursively to
continue the process */ continue the process */
return(gcd_recurs(smaller_int, temp)); return(gcd_recurs(smaller_int, temp));
} }
```
As you can see from Table 10.1, Euclid's algorithm is superior, As you can see from Table 10.1, Euclid's algorithm is superior,
especially for large numbers (and imagine if we were working with large especially for large numbers (and imagine if we were working with large
@ -129,38 +131,40 @@ recursive operations that Listing 10.3 does.
**LISTING 10.4 L10-4.C** **LISTING 10.4 L10-4.C**
/* Finds and returns the greatest common divisor of two integers. ```c
Uses Euclid's algorithm: divides the larger integer by the /* Finds and returns the greatest common divisor of two integers.
smaller; if the remainder is 0, the smaller integer is the GCD, Uses Euclid's algorithm: divides the larger integer by the
otherwise the smaller integer becomes the larger integer, the smaller; if the remainder is 0, the smaller integer is the GCD,
remainder becomes the smaller integer, and the process is otherwise the smaller integer becomes the larger integer, the
repeated. Avoids code recursion. */ remainder becomes the smaller integer, and the process is
repeated. Avoids code recursion. */
unsigned int gcd(unsigned int int1, unsigned int int2) { unsigned int gcd(unsigned int int1, unsigned int int2) {
unsigned int temp; unsigned int temp;
/* Swap if necessary to make sure that int1 >= int2 */ /* Swap if necessary to make sure that int1 >= int2 */
if (int1 < int2) { if (int1 < int2) {
temp = int1; temp = int1;
int1 = int2; int1 = int2;
int2 = temp; int2 = temp;
} }
/* Now loop, dividing int1 by int2 and checking the remainder, /* Now loop, dividing int1 by int2 and checking the remainder,
until the remainder is 0. At each step, if the remainder isn't until the remainder is 0. At each step, if the remainder isn't
0, assign int2 to int1, and the remainder to int2, then 0, assign int2 to int1, and the remainder to int2, then
repeat */ repeat */
for (;;) { for (;;) {
/* If the remainder of int1 divided by int2 is 0, then int2 is /* If the remainder of int1 divided by int2 is 0, then int2 is
the gcd */ the gcd */
if ((temp = int1 % int2) == 0) { if ((temp = int1 % int2) == 0) {
return(int2); return(int2);
} }
/* Make int2 the larger integer and the remainder the /* Make int2 the larger integer and the remainder the
smaller integer, and repeat the process */ smaller integer, and repeat the process */
int1 = int2; int1 = int2;
int2 = temp; int2 = temp;
} }
} }
```
#### Patient Optimization {#Heading7} #### Patient Optimization {#Heading7}

164
10-04.md
View file

@ -12,93 +12,95 @@ pages: 200-203
**LISTING 10.5 L10-5.ASM** **LISTING 10.5 L10-5.ASM**
; Finds and returns the greatest common divisor of two integers. ```nasm
; Uses Euclid's algorithm: divides the larger integer by the ; Finds and returns the greatest common divisor of two integers.
; smaller; if the remainder is 0, the smaller integer is the GCD, ; Uses Euclid's algorithm: divides the larger integer by the
; otherwise the smaller integer becomes the larger integer, the ; smaller; if the remainder is 0, the smaller integer is the GCD,
; remainder becomes the smaller integer, and the process is ; otherwise the smaller integer becomes the larger integer, the
; repeated. Avoids code recursion. ; remainder becomes the smaller integer, and the process is
; ; repeated. Avoids code recursion.
; ;
; ;
; C near-callable as: ;
; unsigned int gcd(unsigned int int1, unsigned int int2); ; C near-callable as:
; unsigned int gcd(unsigned int int1, unsigned int int2);
; Parameter structure: ; Parameter structure:
parms struc parms struc
dw ? ;pushed BP dw ? ;pushed BP
dw ? ;pushed return address dw ? ;pushed return address
int1 dw ? ;integers for which to find int1 dw ? ;integers for which to find
int2 dw ? ; the GCD int2 dw ? ; the GCD
parms ends parms ends
.model small .model small
.code .code
public _gcd public _gcd
align 2 align 2
_gcd proc near _gcd proc near
push bp ;preserve caller's stack frame push bp ;preserve caller's stack frame
mov bp,sp ;set up our stack frame mov bp,sp ;set up our stack frame
push si ;preserve caller's register variables push si ;preserve caller's register variables
push di push di
;Swap if necessary to make sure that int1 >= int2 ;Swap if necessary to make sure that int1 >= int2
mov ax,int1[bp] mov ax,int1[bp]
mov bx,int2[bp] mov bx,int2[bp]
cmp ax,bx ;is int1 >= int2? cmp ax,bx ;is int1 >= int2?
jnb IntsSet ;yes, so we're all set jnb IntsSet ;yes, so we're all set
xchg ax,bx ;no, so swap int1 and int2 xchg ax,bx ;no, so swap int1 and int2
IntsSet: IntsSet:
; Now loop, dividing int1 by int2 and checking the remainder, until ; Now loop, dividing int1 by int2 and checking the remainder, until
; the remainder is 0. At each step, if the remainder isn't 0, assign ; the remainder is 0. At each step, if the remainder isn't 0, assign
; int2 to int1, and the remainder to int2, then repeat. ; int2 to int1, and the remainder to int2, then repeat.
GCDLoop: GCDLoop:
;if the remainder of int1 divided by ;if the remainder of int1 divided by
; int2 is 0, then int2 is the gcd ; int2 is 0, then int2 is the gcd
sub dx,dx ;prepare int1 in DX:AX for division sub dx,dx ;prepare int1 in DX:AX for division
div bx ;int1/int2; remainder is in DX div bx ;int1/int2; remainder is in DX
and dx,dx ;is the remainder zero? and dx,dx ;is the remainder zero?
jz Done ;yes, so int2 (BX) is the gcd jz Done ;yes, so int2 (BX) is the gcd
;no, so move int2 to int1 and the ;no, so move int2 to int1 and the
; remainder to int2, and repeat the ; remainder to int2, and repeat the
; process ; process
mov ax,bx ;int1 = int2; mov ax,bx ;int1 = int2;
mov bx,dx ;int2 = remainder from DIV mov bx,dx ;int2 = remainder from DIV
;—start of loop unrolling; the above is repeated three times— ;—start of loop unrolling; the above is repeated three times—
sub dx,dx ;prepare int1 in DX:AX for division sub dx,dx ;prepare int1 in DX:AX for division
div bx ;int1/int2; remainder is in DX div bx ;int1/int2; remainder is in DX
and dx,dx ;is the remainder zero? and dx,dx ;is the remainder zero?
jz Done ;yes, so int2 (BX) is the gcd jz Done ;yes, so int2 (BX) is the gcd
mov ax,bx ;int1 = int2; mov ax,bx ;int1 = int2;
mov bx,dx ;int2 = remainder from DIV mov bx,dx ;int2 = remainder from DIV
;— ;—
sub dx,dx ;prepare int1 in DX:AX for division sub dx,dx ;prepare int1 in DX:AX for division
div bx ;int1/int2; remainder is in DX div bx ;int1/int2; remainder is in DX
and dx,dx ;is the remainder zero? and dx,dx ;is the remainder zero?
jz Done ;yes, so int2 (BX) is the gcd jz Done ;yes, so int2 (BX) is the gcd
mov ax,bx ;int1 = int2; mov ax,bx ;int1 = int2;
mov bx,dx ;int2 = remainder from DIV mov bx,dx ;int2 = remainder from DIV
;— ;—
sub dx,dx ;prepare int1 in DX:AX for division sub dx,dx ;prepare int1 in DX:AX for division
div bx ;int1/int2; remainder is in DX div bx ;int1/int2; remainder is in DX
and dx,dx ;is the remainder zero? and dx,dx ;is the remainder zero?
jz Done ;yes, so int2 (BX) is the gcd jz Done ;yes, so int2 (BX) is the gcd
mov ax,bx ;int1 = int2; mov ax,bx ;int1 = int2;
mov bx,dx ;int2 = remainder from DIV mov bx,dx ;int2 = remainder from DIV
;—end of loop unrolling— ;—end of loop unrolling—
jmp GCDLoop jmp GCDLoop
align2 align2
Done: Done:
mov ax,bx ;return the GCD mov ax,bx ;return the GCD
pop di ;restore caller's register variables pop di ;restore caller's register variables
pop si pop si
pop bp ;restore caller's stack frame pop bp ;restore caller's stack frame
ret ret
_gcd endp _gcd endp
end end
```
Assembly language optimization is pattern matching on a local scale. Assembly language optimization is pattern matching on a local scale.
Frankly, it's also the sort of boring, brute-force work that people are Frankly, it's also the sort of boring, brute-force work that people are

View file

@ -97,13 +97,17 @@ than plain old registers, and can't be set to arbitrary values. That
means that segments can't be used for temporary storage or as part of a means that segments can't be used for temporary storage or as part of a
fast indivisible 32-bit load from memory, as in fast indivisible 32-bit load from memory, as in
les ax,dword ptr [LongVar] ```nasm
mov dx,es les ax,dword ptr [LongVar]
mov dx,es
```
which loads **LongVar** into DX:AX faster than this: which loads **LongVar** into DX:AX faster than this:
mov ax,word ptr [LongVar] ```nasm
mov dx,word ptr [LongVar+2] mov ax,word ptr [LongVar]
mov dx,word ptr [LongVar+2]
```
Protected mode uses those altered segment registers to offer access to a Protected mode uses those altered segment registers to offer access to a
great deal more memory than real mode: The 286 supports 16 megabytes of great deal more memory than real mode: The 286 supports 16 megabytes of

View file

@ -126,25 +126,27 @@ shortly.)
**LISTING 11.1 L11-1.ASM** **LISTING 11.1 L11-1.ASM**
; ```nasm
; *** Listing 11.1 *** ;
; ; *** Listing 11.1 ***
; Measures the performance of an immediate move to ;
; memory, in order to demonstrate that the prefetch ; Measures the performance of an immediate move to
; queue cycle-eater is alive and well on the AT. ; memory, in order to demonstrate that the prefetch
; ; queue cycle-eater is alive and well on the AT.
jmp Skip ;
; jmp Skip
even ;always make sure word-sized memory ;
; variables are word-aligned! even ;always make sure word-sized memory
WordVar dw 0 ; variables are word-aligned!
; WordVar dw 0
Skip: ;
call ZTimerOn Skip:
rept 1000 call ZTimerOn
mov [WordVar],0 rept 1000
endm mov [WordVar],0
call ZTimerOff endm
call ZTimerOff
```
What does this mean? It means that, practically speaking, the 286 as What does this mean? It means that, practically speaking, the 286 as
used in the AT doesn't have a 16-bit bus. From a performance used in the AT doesn't have a 16-bit bus. From a performance

View file

@ -101,23 +101,25 @@ refresh.
**LISTING 11.2 L11-2.ASM** **LISTING 11.2 L11-2.ASM**
; ```nasm
; *** Listing 11.2 *** ;
; ; *** Listing 11.2 ***
; Measures the performance of accesses to word-sized ;
; variables that start at odd addresses (are not ; Measures the performance of accesses to word-sized
; word-aligned). ; variables that start at odd addresses (are not
; ; word-aligned).
Skip: ;
push ds Skip:
pop es push ds
mov si,1 ;source and destination are the same pop es
mov di,si ; and both are not word-aligned mov si,1 ;source and destination are the same
mov cx,1000 ;move 1000 words mov di,si ; and both are not word-aligned
cld mov cx,1000 ;move 1000 words
call ZTimerOn cld
rep movsw call ZTimerOn
call ZTimerOff rep movsw
call ZTimerOff
```
On the other hand, Listing 11.3, which is exactly the same as Listing On the other hand, Listing 11.3, which is exactly the same as Listing
11.2 save that the memory accesses are word-aligned (start at even 11.2 save that the memory accesses are word-aligned (start at even
@ -128,22 +130,24 @@ we predicted.
**LISTING 11.3 L11-3.ASM** **LISTING 11.3 L11-3.ASM**
; ```nasm
; *** Listing 11.3 *** ;
; ; *** Listing 11.3 ***
; Measures the performance of accesses to word-sized ;
; variables that start at even addresses (are word-aligned). ; Measures the performance of accesses to word-sized
; ; variables that start at even addresses (are word-aligned).
Skip: ;
push ds Skip:
pop es push ds
sub si,si ;source and destination are the same pop es
mov di,si ; and both are word-aligned sub si,si ;source and destination are the same
mov cx,1000 ;move 1000 words mov di,si ; and both are word-aligned
cld mov cx,1000 ;move 1000 words
call ZTimerOn cld
rep movsw call ZTimerOn
call ZTimerOff rep movsw
call ZTimerOff
```
The data alignment cycle-eater has intriguing implications for speeding The data alignment cycle-eater has intriguing implications for speeding
up 286/386 code. The expenditure of a little care and a few bytes to up 286/386 code. The expenditure of a little care and a few bytes to

View file

@ -15,12 +15,13 @@ AT clone to verify the basic functionality of the timer by measuring the
performance of simple instruction sequences. I was cruising along with performance of simple instruction sequences. I was cruising along with
no problems until I timed the following code: no problems until I timed the following code:
```nasm
mov cx,1000 mov cx,1000
call ZTimerOn call ZTimerOn
LoopTop: LoopTop:
loop LoopTop loop LoopTop
call ZTimerOff call ZTimerOff
```
![**Figure 11.2**  *Word-aligned prefetching on the 286.*](images/11-02.jpg) ![**Figure 11.2**  *Word-aligned prefetching on the 286.*](images/11-02.jpg)
@ -39,12 +40,14 @@ resided at the start of the next word-aligned word.
One simple change brought the execution time down to a reasonable 12.5 One simple change brought the execution time down to a reasonable 12.5
cycles per loop: cycles per loop:
mov cx,1000 ```nasm
call ZTimerOn mov cx,1000
even call ZTimerOn
LoopTop: even
loop LoopTop LoopTop:
call ZTimerOff loop LoopTop
call ZTimerOff
```
While word-aligning branch destinations can improve branching While word-aligning branch destinations can improve branching
performance, it's a nuisance and can increase code size a good deal, so performance, it's a nuisance and can increase code size a good deal, so
@ -60,9 +63,11 @@ branch destination.
I recommend that you only go out of your way to word-align the start I recommend that you only go out of your way to word-align the start
offsets of your subroutines, as in: offsets of your subroutines, as in:
even ```nasm
FindChar proc near even
: FindChar proc near
:
```
In my experience, this simple practice is the one form of code alignment In my experience, this simple practice is the one form of code alignment
that consistently provides a reasonable return for bytes and effort that consistently provides a reasonable return for bytes and effort

View file

@ -113,15 +113,17 @@ Theory confirmed.
**LISTING 11.4 L11-4.ASM** **LISTING 11.4 L11-4.ASM**
; ```nasm
; *** Listing 11.4 *** ;
; ; *** Listing 11.4 ***
; Measures the performance of adding an immediate value ;
; to a register, for comparison with Listing 11.5, which ; Measures the performance of adding an immediate value
; adds an immediate value to a memory variable. ; to a register, for comparison with Listing 11.5, which
; ; adds an immediate value to a memory variable.
call ZTimerOn ;
rept 1000 call ZTimerOn
add dx,100h rept 1000
endm add dx,100h
call ZTimerOff endm
call ZTimerOff
```

View file

@ -12,25 +12,27 @@ pages: 224-226
**LISTING 11.5 L11-5.ASM** **LISTING 11.5 L11-5.ASM**
; ```nasm
; *** Listing 11.5 *** ;
; ; *** Listing 11.5 ***
; Measures the performance of adding an immediate value ;
; to a memory variable, for comparison with Listing 11.4, ; Measures the performance of adding an immediate value
; which adds an immediate value to a register. ; to a memory variable, for comparison with Listing 11.4,
; ; which adds an immediate value to a register.
jmp Skip ;
; jmp Skip
even ;always make sure word-sized memory ;
; variables are word-aligned! even ;always make sure word-sized memory
WordVar dw 0 ; variables are word-aligned!
; WordVar dw 0
Skip: ;
call ZTimerOn Skip:
rept 1000 call ZTimerOn
add [WordVar]100h rept 1000
endm add [WordVar]100h
call ZTimerOff endm
call ZTimerOff
```
What's going on? Simply this: Instruction fetching is controlling What's going on? Simply this: Instruction fetching is controlling
overall execution time on *both* processors. Both the 8088 in a PC and overall execution time on *both* processors. Both the 8088 in a PC and

View file

@ -43,22 +43,24 @@ a segment and an offset. We'll also branch backward so that the address
pushed on the stack will point to the instruction we want to continue pushed on the stack will point to the instruction we want to continue
with. The code works out like this: with. The code works out like this:
jmpshort popfskip ```nasm
popfiret: jmpshort popfskip
iret; branches to the instruction after the popfiret:
; call, popping the word below the address iret; branches to the instruction after the
; pushed by CALL into the FLAGS register ; call, popping the word below the address
popfskip: ; pushed by CALL into the FLAGS register
call far ptr popfiret popfskip:
;pushes the segment:offset of the next call far ptr popfiret
; instruction on the stack just above ;pushes the segment:offset of the next
; the flags word, setting things up so ; instruction on the stack just above
; that IRET will branch to the next ; the flags word, setting things up so
; instruction and pop the flags ; that IRET will branch to the next
; When execution reaches the instruction following this comment, ; instruction and pop the flags
; the word that was on top of the stack when JMP SHORT POPFSKIP ; When execution reaches the instruction following this comment,
; was reached has been popped into the FLAGS register, just as ; the word that was on top of the stack when JMP SHORT POPFSKIP
; if a POPF instruction had been executed. ; was reached has been popped into the FLAGS register, just as
; if a POPF instruction had been executed.
```
![**Figure 11.5**  *The operation of IRET.*](images/11-05.jpg) ![**Figure 11.5**  *The operation of IRET.*](images/11-05.jpg)
@ -68,26 +70,30 @@ The **POPF** workaround can best be implemented as a macro; we can also
emulate a far call by pushing CS and performing a near call, thereby emulate a far call by pushing CS and performing a near call, thereby
shrinking the workaround code by 1 byte: shrinking the workaround code by 1 byte:
EMULATE_POPF macro ```nasm
local popfskip, popfiret EMULATE_POPF macro
jmp short popfskip local popfskip, popfiret
popfiret: jmp short popfskip
iret popfiret:
popfskip: iret
push cs popfskip:
call popfiret push cs
endm call popfiret
endm
```
By the way, the flags can be popped much more quickly if you're willing By the way, the flags can be popped much more quickly if you're willing
to alter a register in the process. For example, the following macro to alter a register in the process. For example, the following macro
emulates **POPF** with just one branch, but wipes out AX: emulates **POPF** with just one branch, but wipes out AX:
EMULATE_POPF_TRASH_AX macro ```nasm
push cs EMULATE_POPF_TRASH_AX macro
mov ax,offset $+5 push cs
push ax mov ax,offset $+5
iret push ax
endm iret
endm
```
It's not a perfect substitute for **POPF**, since **POPF** doesn't alter It's not a perfect substitute for **POPF**, since **POPF** doesn't alter
any registers, but it's faster and shorter than **EMULATE\_POPF** when any registers, but it's faster and shorter than **EMULATE\_POPF** when
@ -96,13 +102,15 @@ you can use which is shorter still, alters no registers, and branches
just once. (Of course, this version of **EMULATE\_POPF** won't work on just once. (Of course, this version of **EMULATE\_POPF** won't work on
an 8088.) an 8088.)
.286 ```nasm
: .286
EMULATE_POPFmacro :
pushcs EMULATE_POPFmacro
pushoffset $+4 pushcs
iret pushoffset $+4
endm iret
endm
```
![**Figure 11.6**  *Workaround code for the POPF bug.*](images/11-06.jpg) ![**Figure 11.6**  *Workaround code for the POPF bug.*](images/11-06.jpg)

View file

@ -128,18 +128,22 @@ matter what register is used.) **MOV AX, [BX+DI]** and **MOV CL,
As an example, you might adhere to this rule by replacing the code As an example, you might adhere to this rule by replacing the code
LoopTop: ```nasm
add ax,[bx+si] LoopTop:
add si,2 add ax,[bx+si]
dec cx add si,2
jnz LoopTop dec cx
jnz LoopTop
```
with this with this
add si,bx ```nasm
LoopTop: add si,bx
add ax,[si] LoopTop:
add si,2 add ax,[si]
dec cx add si,2
jnz LoopTop dec cx
sub si,bx jnz LoopTop
sub si,bx
```

View file

@ -58,8 +58,10 @@ isn't known until the instruction starts, and that's exactly the case
when the preceding instruction modifies one of the target instruction's when the preceding instruction modifies one of the target instruction's
addressing registers. For example, in the code addressing registers. For example, in the code
MOV BX,OFFSET MemVar ```nasm
MOV AX,[BX] MOV BX,OFFSET MemVar
MOV AX,[BX]
```
there's no way that the 486 can calculate the address referenced by there's no way that the 486 can calculate the address referenced by
**MOV AX,[BX]** until **MOV BX,OFFSET MemVar** finishes, so pipelining **MOV AX,[BX]** until **MOV BX,OFFSET MemVar** finishes, so pipelining
@ -68,19 +70,23 @@ rearranging your code so that at least one instruction lies between the
loading of the memory pointer and its use. For example, loading of the memory pointer and its use. For example,
postdecrementing, as in the following postdecrementing, as in the following
LoopTop: ```nasm
add ax,[si] LoopTop:
add si,2 add ax,[si]
dec cx add si,2
jnz LoopTop dec cx
jnz LoopTop
```
is faster than preincrementing, as in: is faster than preincrementing, as in:
LoopTop: ```nasm
add si,2 LoopTop:
add ax,[SI] add si,2
dec cx add ax,[SI]
jnz LoopTop dec cx
jnz LoopTop
```
Now that we understand what Intel means by this rule, let me make a very Now that we understand what Intel means by this rule, let me make a very
important comment: My observations indicate that for real-mode code, the important comment: My observations indicate that for real-mode code, the
@ -108,28 +114,34 @@ instructions, but, because some 486 instructions take more than 1 cycle,
the 2 are not always equivalent) before it's used to point to memory, 1 the 2 are not always equivalent) before it's used to point to memory, 1
cycle is lost. Therefore, whereas this code cycle is lost. Therefore, whereas this code
mov bx,offset MemVar ```nasm
mov ax,[bx] mov bx,offset MemVar
inc dx mov ax,[bx]
dec cx inc dx
jnz LoopTop dec cx
jnz LoopTop
```
loses two cycles from interrupting the address calculation pipeline, loses two cycles from interrupting the address calculation pipeline,
this code this code
mov bx,offset MemVar ```nasm
inc dx mov bx,offset MemVar
mov ax,[bx] inc dx
dec cx mov ax,[bx]
jnz LoopTop dec cx
jnz LoopTop
```
loses only one cycle, and this code loses only one cycle, and this code
mov bx,offset MemVar ```nasm
inc dx mov bx,offset MemVar
dec cx inc dx
mov ax,[bx] dec cx
jnz LoopTop mov ax,[bx]
jnz LoopTop
```
loses no cycles at all. Apparently, the 486's addressing calculation loses no cycles at all. Apparently, the 486's addressing calculation
pipeline actually starts 2 cycles ahead, as shown in Figure 12.2. (In pipeline actually starts 2 cycles ahead, as shown in Figure 12.2. (In

View file

@ -41,22 +41,26 @@ pops up only spottily when the stack pointer is involved.
For example, you'd certainly expect a sequence such as For example, you'd certainly expect a sequence such as
: ```nasm
pop ax :
ret pop ax
pop ax ret
et pop ax
: et
:
```
to exhibit the addressing pipeline interruption phenomenon (SP is both to exhibit the addressing pipeline interruption phenomenon (SP is both
destination and addressing register for both instructions, according to destination and addressing register for both instructions, according to
Intel), but this code runs in six cycles per **POP/RET** pair, matching Intel), but this code runs in six cycles per **POP/RET** pair, matching
the official execution times exactly. Likewise, a sequence like the official execution times exactly. Likewise, a sequence like
pop dx ```nasm
pop cx pop dx
pop bx pop cx
pop ax pop bx
pop ax
```
runs in one cycle per instruction, just as it should. runs in one cycle per instruction, just as it should.
@ -65,15 +69,19 @@ destination—for example, to deallocate local variables—and then using
**PUSH**, **POP**, or **RET**, definitely can interrupt the addressing **PUSH**, **POP**, or **RET**, definitely can interrupt the addressing
pipeline. For example pipeline. For example
add sp,10h ```nasm
ret add sp,10h
ret
```
loses two cycles because SP is the explicit destination of one loses two cycles because SP is the explicit destination of one
instruction and then the implied addressing register for the next, and instruction and then the implied addressing register for the next, and
the sequence the sequence
add sp,10h ```nasm
pop ax add sp,10h
pop ax
```
loses two cycles for the same reason. loses two cycles for the same reason.
@ -101,22 +109,26 @@ during the next instruction.
So, for example, it would be a bad idea to do this So, for example, it would be a bad idea to do this
mov ah,o ```nasm
: mov ah,o
mov cx,[MemVar1] :
mov al,[MemVar2] mov cx,[MemVar1]
add cx,ax mov al,[MemVar2]
add cx,ax
```
because AL is loaded by one instruction, then AX is used as the source because AL is loaded by one instruction, then AX is used as the source
register for the next instruction. A cycle can be saved simply by register for the next instruction. A cycle can be saved simply by
rearranging the instructions so that the byte register load isn't rearranging the instructions so that the byte register load isn't
immediately followed by the word register usage, like so: immediately followed by the word register usage, like so:
mov ah,o ```nasm
: mov ah,o
mov al,[MemVar2] :
mov cx,[MemVar1] mov al,[MemVar2]
add cx,ax mov cx,[MemVar1]
add cx,ax
```
Strange as it may seem, this rule is neither arbitrary nor nonsensical. Strange as it may seem, this rule is neither arbitrary nor nonsensical.
Basically, when a byte destination register is part of a word source Basically, when a byte destination register is part of a word source
@ -135,10 +147,12 @@ works.
In case you're curious, there's no such penalty for the typical **XLAT** In case you're curious, there's no such penalty for the typical **XLAT**
sequence like sequence like
mov bx,offset MemTable ```nasm
: mov bx,offset MemTable
mov al,[si] :
xlat mov al,[si]
xlat
```
even though AL must be converted to a word by **XLAT** before it can be even though AL must be converted to a word by **XLAT** before it can be
added to BX and used to address memory. In fact, none of the penalties added to BX and used to address memory. In fact, none of the penalties

105
12-04.md
View file

@ -24,9 +24,11 @@ This, the last of this chapter's rules, is the strangest of the lot. If
any byte register is loaded, and then two cycles later any register is any byte register is loaded, and then two cycles later any register is
used to point to memory, one cycle is lost. So, for example, this code used to point to memory, one cycle is lost. So, for example, this code
mov al,bl ```nasm
mov cx,dx mov al,bl
mov si,[di] mov cx,dx
mov si,[di]
```
takes four rather than the expected three cycles to execute. Note that takes four rather than the expected three cycles to execute. Note that
it is *not* required that the byte register be part of the register used it is *not* required that the byte register be part of the register used
@ -35,17 +37,20 @@ to address memory; any byte register will do the trick.
Worse still, loading byte registers both one and two cycles before a Worse still, loading byte registers both one and two cycles before a
register is used to address memory costs two cycles, as in register is used to address memory costs two cycles, as in
mov bl,al ```nasm
mov cl,3 mov bl,al
mov bx,[si] mov cl,3
mov bx,[si]
```
which takes five rather than three cycles to run. However, there is *no* which takes five rather than three cycles to run. However, there is *no*
penalty if a byte register is loaded one cycle but not two cycles before penalty if a byte register is loaded one cycle but not two cycles before
a register is used to address memory. Therefore, a register is used to address memory. Therefore,
mov cx,3 ```nasm
mov dl,al mov cx,3
mov si,[bx] mov dl,al
mov si,[bx]
```
runs in the expected three cycles. runs in the expected three cycles.
@ -57,9 +62,11 @@ its interaction with the other rules—could lead to considerable
performance loss in seemingly air-tight code. For instance, a casual performance loss in seemingly air-tight code. For instance, a casual
observer would expect the following code to run in 3 cycles: observer would expect the following code to run in 3 cycles:
mov bx,offset MemVar ```nasm
mov cl,al mov bx,offset MemVar
mov ax,[bx] mov cl,al
mov ax,[bx]
```
A more sophisticated programmer would expect to lose one cycle, because A more sophisticated programmer would expect to lose one cycle, because
BX is loaded two cycles before being used to address memory. In fact, BX is loaded two cycles before being used to address memory. In fact,
@ -97,43 +104,47 @@ addressing memory.
**LISTING 12.1 LST12-1.ASM** **LISTING 12.1 LST12-1.ASM**
; Measures the effect of loading a byte register 2 cycles before ```nasm
; using a register to address memory. ; Measures the effect of loading a byte register 2 cycles before
mov bp,2 ;run the test code twice to make sure ; using a register to address memory.
; it's cached mov bp,2 ;run the test code twice to make sure
sub bx,bx ; it's cached
CacheFillLoop: sub bx,bx
call ZTimerOn ;start timing CacheFillLoop:
rept 1000 call ZTimerOn ;start timing
mov dl,cl rept 1000
nop mov dl,cl
mov ax,[bx] nop
endm mov ax,[bx]
call ZTimerOff ;stop timing endm
dec bp call ZTimerOff ;stop timing
jz Done dec bp
jmp CacheFillLoop jz Done
Done: jmp CacheFillLoop
Done:
```
**LISTING 12.2 LST12-2.ASM** **LISTING 12.2 LST12-2.ASM**
; Measures the effect of loading a byte register 1 cycle before ```nasm
; using a register to address memory. ; Measures the effect of loading a byte register 1 cycle before
mov bp,2 ;run the test code twice to make sure ; using a register to address memory.
; it's cached mov bp,2 ;run the test code twice to make sure
sub bx,bx ; it's cached
CacheFillLoop: sub bx,bx
call ZTimerOn ;start timing CacheFillLoop:
rept 1000 call ZTimerOn ;start timing
nop rept 1000
mov dl,cl nop
mov ax,[bx] mov dl,cl
endm mov ax,[bx]
call ZTimerOff ;stop timing endm
dec bp call ZTimerOff ;stop timing
jz Done dec bp
jmp CacheFillLoop jz Done
Done: jmp CacheFillLoop
Done:
```
Note that Listings 12.1 and 12.2 each repeat the timing of the code Note that Listings 12.1 and 12.2 each repeat the timing of the code
under test a second time, to make sure that the instructions are in the under test a second time, to make sure that the instructions are in the

View file

@ -78,10 +78,12 @@ also lines, at a rate of three instructions for every two characters!
**LISTING 13.1 L13-1.ASM** **LISTING 13.1 L13-1.ASM**
mov di,[bp+OFFS] ;get the next pair of characters ```nasm
mov bl,[di] ;get the state value for the pair mov di,[bp+OFFS] ;get the next pair of characters
add dx,[bx+8000h] ;increment word and line count mov bl,[di] ;get the state value for the pair
; appropriately for the pair add dx,[bx+8000h] ;increment word and line count
; appropriately for the pair
```
Listing 13.1 looks as tight as it could be, with just two one-cycle Listing 13.1 looks as tight as it could be, with just two one-cycle
instructions, one two-cycle instruction, and no branches. It *is* tight, instructions, one two-cycle instruction, and no branches. It *is* tight,
@ -113,10 +115,12 @@ the intervening instruction takes two cycles, there's no penalty at all.
**LISTING 13.2 L13-2.ASM** **LISTING 13.2 L13-2.ASM**
mov bl,[di] ;get the state value for the pair ```nasm
mov di,[bp+OFFS] ;get the next pair of characters mov bl,[di] ;get the state value for the pair
add dx,[bx+8000h] ;increment word and line count mov di,[bp+OFFS] ;get the next pair of characters
; appropriately for the pair add dx,[bx+8000h] ;increment word and line count
; appropriately for the pair
```
At this point, Terje had nearly doubled the performance of this code At this point, Terje had nearly doubled the performance of this code
simply by moving one instruction. (Note that swapping the instructions simply by moving one instruction. (Note that swapping the instructions

View file

@ -22,18 +22,22 @@ throughput of two cycles/char."
**LISTING 13.3 L13-3.ASM** **LISTING 13.3 L13-3.ASM**
mov bl,[di] ;get the state value for the pair ```nasm
mov di,[bp+OFFS] ;get the next pair of characters mov bl,[di] ;get the state value for the pair
mov ax,[bx+8000h] ;increment word and line count mov di,[bp+OFFS] ;get the next pair of characters
add dx,ax ; appropriately for the pair mov ax,[bx+8000h] ;increment word and line count
add dx,ax ; appropriately for the pair
```
**LISTING 13.4 L13-4.ASM** **LISTING 13.4 L13-4.ASM**
mov bl,[di] ;get the state value for the pair ```nasm
mov di,[bp+OFFS] ;get the next pair of characters mov bl,[di] ;get the state value for the pair
add dx,ax ;increment word and line count mov di,[bp+OFFS] ;get the next pair of characters
; appropriately for the pair add dx,ax ;increment word and line count
mov ax,[bx+8000h] ;get increments for next time ; appropriately for the pair
mov ax,[bx+8000h] ;get increments for next time
```
I'd like to point out two fairly remarkable things. First, the single I'd like to point out two fairly remarkable things. First, the single
cycle that Terje saved in Listing 13.4 sped up his entire word-counting cycle that Terje saved in Listing 13.4 sped up his entire word-counting
@ -57,9 +61,11 @@ and 16-bit registers are not valid operands.) The obvious use of
first in memory, also called *little endian*) to Motorola format (most first in memory, also called *little endian*) to Motorola format (most
significant byte first in memory, or *big endian*), like so: significant byte first in memory, or *big endian*), like so:
lodsd ```nasm
bswap lodsd
stosd bswap
stosd
```
**BSWAP** can also be useful for reversing the order of pixel bits from **BSWAP** can also be useful for reversing the order of pixel bits from
a bitmap so that they can be rotated 32 bits at a time with an a bitmap so that they can be rotated 32 bits at a time with an
@ -94,17 +100,19 @@ memory, isn't it?
**LISTING 13.5 L13-5.ASM** **LISTING 13.5 L13-5.ASM**
mov cx,[initialskip] ```nasm
shl ecx,16 ;put skip value in upper half of ECX mov cx,[initialskip]
mov cx,100 ;put loop count in CX shl ecx,16 ;put skip value in upper half of ECX
looptop: mov cx,100 ;put loop count in CX
: looptop:
ror ecx,16 ;make skip value word accessible in CX :
add bx,cx ;skip BX ahead ror ecx,16 ;make skip value word accessible in CX
inc cx ;set next skip value add bx,cx ;skip BX ahead
ror ecx,16 ;put loop count in CX inc cx ;set next skip value
dec cx ;count down loop ror ecx,16 ;put loop count in CX
jnz looptop dec cx ;count down loop
jnz looptop
```
Not necessarily. Shifts and rotates are among the worst performing Not necessarily. Shifts and rotates are among the worst performing
instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes

View file

@ -21,17 +21,19 @@ registers into one 32-bit register much more useful.
**LISTING 13.6 L13-6.ASM** **LISTING 13.6 L13-6.ASM**
mov cx,[initialskip] ```nasm
bswap ecx ;put skip value in upper half of ECX mov cx,[initialskip]
mov cx,100 ;put loop count in CX bswap ecx ;put skip value in upper half of ECX
looptop: mov cx,100 ;put loop count in CX
: looptop:
bswap ecx ;make skip value word accessible in CX :
add bx,cx ;skip BX ahead bswap ecx ;make skip value word accessible in CX
inc cx ;set next skip value add bx,cx ;skip BX ahead
bswap ecx ;put loop count in CX inc cx ;set next skip value
dec cx ;count down loop bswap ecx ;put loop count in CX
jnz looptop dec cx ;count down loop
jnz looptop
```
### Pushing and Popping Memory {#Heading5} ### Pushing and Popping Memory {#Heading5}
@ -44,12 +46,16 @@ contrast, loading a memory location into a register takes only one
cycle, and pushing a register takes just 1 more cycle, for a total of cycle, and pushing a register takes just 1 more cycle, for a total of
two cycles. Therefore, two cycles. Therefore,
mov ax,[bx] ```nasm
push ax mov ax,[bx]
push ax
```
is twice as fast as is twice as fast as
push word ptr [bx] ```nasm
push word ptr [bx]
```
and the only cost is that the previous contents of AX are destroyed. and the only cost is that the previous contents of AX are destroyed.
@ -98,9 +104,11 @@ disassembly in a debugger or by having the assembler generate a listing
file. You could then insert the n-bit version of **SHL AX,1** in your file. You could then insert the n-bit version of **SHL AX,1** in your
code as follows: code as follows:
mov ax,1 ```nasm
db 0c1h, 0e0h, 001h mov ax,1
mov dx,ax db 0c1h, 0e0h, 001h
mov dx,ax
```
At the end of this sequence, DX will contain 2, and the fast n-bit At the end of this sequence, DX will contain 2, and the fast n-bit
version of **SHL AX,1** will have executed. If you use this approach, version of **SHL AX,1** will have executed. If you use this approach,

View file

@ -17,7 +17,9 @@ register may serve as the base memory addressing register, and almost
any register may serve as the potentially scaled index register. For any register may serve as the potentially scaled index register. For
example, example,
mov al,BaseTable[ecx+edx*4] ```nasm
mov al,BaseTable[ecx+edx*4]
```
uses a perfectly valid 32-bit address, with the byte accessed being the uses a perfectly valid 32-bit address, with the byte accessed being the
one at the offset in DS pointed to by the sum of EDX times 4 plus the one at the offset in DS pointed to by the sum of EDX times 4 plus the
@ -82,21 +84,25 @@ registers when they're needed, but if you find yourself using them
inside key loops, you should see if it's possible to move the index inside key loops, you should see if it's possible to move the index
calculation outside the loop as, for example, in a loop like this: calculation outside the loop as, for example, in a loop like this:
LoopTop: ```nasm
add ax,DataTable[ebx*2] LoopTop:
inc ebx add ax,DataTable[ebx*2]
dec cx inc ebx
jnz LoopTop dec cx
jnz LoopTop
```
You could change this to the following for greater performance: You could change this to the following for greater performance:
add ebx,ebx ;ebx*2 ```nasm
LoopTop: add ebx,ebx ;ebx*2
add ax,DataTable[ebx] LoopTop:
add ebxX,2 add ax,DataTable[ebx]
dec cx add ebxX,2
jnz LoopTop dec cx
shr ebx,1 ;ebx*2/2 jnz LoopTop
shr ebx,1 ;ebx*2/2
```
I'll end this chapter with two more quirks of 32-bit addressing. First, I'll end this chapter with two more quirks of 32-bit addressing. First,
as with 16-bit addressing, addressing that uses EBP as a base register as with 16-bit addressing, addressing that uses EBP as a base register

256
14-04.md
View file

@ -12,148 +12,152 @@ pages: 268-271
**LISTING 14.1 L14-1.C** **LISTING 14.1 L14-1.C**
/* Searches a buffer for a specified pattern. In case of a mismatch, ```c
uses the value of the mismatched byte to skip across as many /* Searches a buffer for a specified pattern. In case of a mismatch,
potential match locations as possible (partial Boyer-Moore). uses the value of the mismatched byte to skip across as many
Returns start offset of first match searching forward, or NULL if potential match locations as possible (partial Boyer-Moore).
no match is found. Returns start offset of first match searching forward, or NULL if
Tested with Borland C++ in C mode and the small model. */ no match is found.
Tested with Borland C++ in C mode and the small model. */
#include <stdio.h> #include <stdio.h>
unsigned char * FindString(unsigned char * BufferPtr, unsigned char * FindString(unsigned char * BufferPtr,
unsigned int BufferLength, unsigned char * PatternPtr, unsigned int BufferLength, unsigned char * PatternPtr,
unsigned int PatternLength) unsigned int PatternLength)
{ {
unsigned char * WorkingPatternPtr, * WorkingBufferPtr; unsigned char * WorkingPatternPtr, * WorkingBufferPtr;
unsigned int CompCount, SkipTable[256], Skip, DistanceMatched; unsigned int CompCount, SkipTable[256], Skip, DistanceMatched;
int i; int i;
/* Reject if the buffer is too small */ /* Reject if the buffer is too small */
if (BufferLength < PatternLength) return(NULL); if (BufferLength < PatternLength) return(NULL);
/* Return an instant match if the pattern is 0-length */ /* Return an instant match if the pattern is 0-length */
if (PatternLength == 0) return(BufferPtr); if (PatternLength == 0) return(BufferPtr);
/* Create the table of distances by which to skip ahead on /* Create the table of distances by which to skip ahead on
mismatches for every possible byte value */ mismatches for every possible byte value */
/* Initialize all skips to the pattern length; this is the skip /* Initialize all skips to the pattern length; this is the skip
distance for bytes that don't appear in the pattern */ distance for bytes that don't appear in the pattern */
for (i = 0; i < 256; i++) SkipTable[i] = PatternLength; for (i = 0; i < 256; i++) SkipTable[i] = PatternLength;
/*Set the skip values for the bytes that do appear in the pattern /*Set the skip values for the bytes that do appear in the pattern
to the distance from the byte location to the end of the to the distance from the byte location to the end of the
pattern. When there are multiple instances of the same byte, pattern. When there are multiple instances of the same byte,
the rightmost instance's skip value is used. Note that the the rightmost instance's skip value is used. Note that the
rightmost byte of the pattern isn't entered in the skip table; rightmost byte of the pattern isn't entered in the skip table;
if we get that value for a mismatch, we know for sure that the if we get that value for a mismatch, we know for sure that the
right end of the pattern has already passed the mismatch right end of the pattern has already passed the mismatch
location, so this is not a relevant byte for skipping purposes */ location, so this is not a relevant byte for skipping purposes */
for (i = 0; i < (PatternLength - 1); i++) for (i = 0; i < (PatternLength - 1); i++)
SkipTable[PatternPtr[i]] = PatternLength - i - 1; SkipTable[PatternPtr[i]] = PatternLength - i - 1;
/* Point to rightmost byte of the pattern */ /* Point to rightmost byte of the pattern */
PatternPtr += PatternLength - 1; PatternPtr += PatternLength - 1;
/* Point to last (rightmost) byte of the first potential pattern /* Point to last (rightmost) byte of the first potential pattern
match location in the buffer */ match location in the buffer */
BufferPtr += PatternLength - 1; BufferPtr += PatternLength - 1;
/* Count of number of potential pattern match locations in /* Count of number of potential pattern match locations in
buffer */ buffer */
BufferLength -= PatternLength - 1; BufferLength -= PatternLength - 1;
/* Search the buffer */ /* Search the buffer */
while (1) { while (1) {
/* See if we have a match at this buffer location */ /* See if we have a match at this buffer location */
WorkingPatternPtr = PatternPtr; WorkingPatternPtr = PatternPtr;
WorkingBufferPtr = BufferPtr; WorkingBufferPtr = BufferPtr;
CompCount = PatternLength; CompCount = PatternLength;
/* Compare the pattern and the buffer location, searching from /* Compare the pattern and the buffer location, searching from
high memory toward low (right to left) */ high memory toward low (right to left) */
while (*WorkingPatternPtr— == *WorkingBufferPtr—) { while (*WorkingPatternPtr— == *WorkingBufferPtr—) {
/* If we've matched the entire pattern, it's a match */ /* If we've matched the entire pattern, it's a match */
if (-CompCount == 0) if (-CompCount == 0)
/* Return a pointer to the start of the match location */ /* Return a pointer to the start of the match location */
return(BufferPtr - PatternLength + 1); return(BufferPtr - PatternLength + 1);
} }
/* It's a mismatch; let's see what we can learn from it */ /* It's a mismatch; let's see what we can learn from it */
WorkingBufferPtr++; /* point back to the mismatch location */ WorkingBufferPtr++; /* point back to the mismatch location */
/* # of bytes that did match */ /* # of bytes that did match */
DistanceMatched = BufferPtr - WorkingBufferPtr; DistanceMatched = BufferPtr - WorkingBufferPtr;
/*If, based on the mismatch character, we can't even skip ahead /*If, based on the mismatch character, we can't even skip ahead
as far as where we started this particular comparison, then as far as where we started this particular comparison, then
just advance by 1 to the next potential match; otherwise, just advance by 1 to the next potential match; otherwise,
skip ahead from the mismatch location by the skip distance skip ahead from the mismatch location by the skip distance
for the mismatch character */ for the mismatch character */
if (SkipTable[*WorkingBufferPtr] <= DistanceMatched) if (SkipTable[*WorkingBufferPtr] <= DistanceMatched)
Skip = 1; /* skip doesn't do any good, advance by 1 */ Skip = 1; /* skip doesn't do any good, advance by 1 */
else else
/* Use skip value, accounting for distance covered by the /* Use skip value, accounting for distance covered by the
partial match */ partial match */
Skip = SkipTable[*WorkingBufferPtr] - DistanceMatched; Skip = SkipTable[*WorkingBufferPtr] - DistanceMatched;
/* If skipping ahead would exhaust the buffer, we're done /* If skipping ahead would exhaust the buffer, we're done
without a match */ without a match */
if (Skip >= BufferLength) return(NULL); if (Skip >= BufferLength) return(NULL);
/* Skip ahead and perform the next comparison */ /* Skip ahead and perform the next comparison */
BufferLength -= Skip; BufferLength -= Skip;
BufferPtr += Skip; BufferPtr += Skip;
} }
} }
```
**LISTING 14.2 L14-2.C** **LISTING 14.2 L14-2.C**
/* Program to exercise buffer-search routines in Listings 14.1 & 14.3. ```c
(Must be modified to put copy of pattern as sentinel at end of the /* Program to exercise buffer-search routines in Listings 14.1 & 14.3.
search buffer in order to be used with Listing 14.4.) */ (Must be modified to put copy of pattern as sentinel at end of the
search buffer in order to be used with Listing 14.4.) */
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <fcntl.h> #include <fcntl.h>
#define DISPLAY_LENGTH 40 #define DISPLAY_LENGTH 40
#define BUFFER_SIZE 0x8000 #define BUFFER_SIZE 0x8000
extern unsigned char * FindString(unsigned char *, unsigned int, extern unsigned char * FindString(unsigned char *, unsigned int,
unsigned char *, unsigned int); unsigned char *, unsigned int);
void main(void); void main(void);
void main() { void main() {
unsigned char TempBuffer[DISPLAY_LENGTH+1]; unsigned char TempBuffer[DISPLAY_LENGTH+1];
unsigned char Filename[150], Pattern[150], *MatchPtr, *TestBuffer; unsigned char Filename[150], Pattern[150], *MatchPtr, *TestBuffer;
int Handle; int Handle;
unsigned int WorkingLength; unsigned int WorkingLength;
printf("File to search:"); printf("File to search:");
gets(Filename); gets(Filename);
printf("Pattern for which to search:"); printf("Pattern for which to search:");
gets(Pattern); gets(Pattern);
if ( (Handle = open(Filename, O_RDONLY | O_BINARY)) == -1 ) { if ( (Handle = open(Filename, O_RDONLY | O_BINARY)) == -1 ) {
printf("Can't open file: %s\n", Filename); exit(1); printf("Can't open file: %s\n", Filename); exit(1);
} }
/* Get memory in which to buffer the data */ /* Get memory in which to buffer the data */
if ( (TestBuffer=(unsigned char *)malloc(BUFFER_SIZE+1)) == NULL) { if ( (TestBuffer=(unsigned char *)malloc(BUFFER_SIZE+1)) == NULL) {
printf("Can't get enough memory\n"); exit(1); printf("Can't get enough memory\n"); exit(1);
} }
/* Process a BUFFER_SIZE chunk */ /* Process a BUFFER_SIZE chunk */
if ( (int)(WorkingLength = if ( (int)(WorkingLength =
read(Handle, TestBuffer, BUFFER_SIZE)) == -1 ) { read(Handle, TestBuffer, BUFFER_SIZE)) == -1 ) {
printf("Error reading file %s\n", Filename); exit(1); printf("Error reading file %s\n", Filename); exit(1);
} }
TestBuffer[WorkingLength] = 0; /* 0-terminate buffer for printf */ TestBuffer[WorkingLength] = 0; /* 0-terminate buffer for printf */
/* Search for the pattern and report the results */ /* Search for the pattern and report the results */
if ((MatchPtr = FindString(TestBuffer, WorkingLength, Pattern, if ((MatchPtr = FindString(TestBuffer, WorkingLength, Pattern,
(unsigned int) strlen(Pattern))) == NULL) { (unsigned int) strlen(Pattern))) == NULL) {
/* Pattern wasn't found */ /* Pattern wasn't found */
printf("\"%s\" not found\n", Pattern); printf("\"%s\" not found\n", Pattern);
} else { } else {
/* Pattern was found. Zero-terminate TempBuffer; strncpy /* Pattern was found. Zero-terminate TempBuffer; strncpy
won't do it if DISPLAY_LENGTH characters are copied */ won't do it if DISPLAY_LENGTH characters are copied */
TempBuffer[DISPLAY_LENGTH] = 0; TempBuffer[DISPLAY_LENGTH] = 0;
printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n", printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n",
Pattern, DISPLAY_LENGTH, Pattern, DISPLAY_LENGTH,
strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH)); strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH));
} }
exit(0); exit(0);
} }
```
Well, architecture carries a lot of weight, but it sure as heck isn't Well, architecture carries a lot of weight, but it sure as heck isn't
destiny. I had simply fallen into the trap of figuring that the destiny. I had simply fallen into the trap of figuring that the

318
14-05.md
View file

@ -12,163 +12,165 @@ pages: 271-274
**LISTING 14.3 L14-3.ASM** **LISTING 14.3 L14-3.ASM**
; Searches a buffer for a specified pattern. In case of a mismatch, ```nasm
; uses the value of the mismatched byte to skip across as many ; Searches a buffer for a specified pattern. In case of a mismatch,
; potential match locations as possible (partial Boyer-Moore). ; uses the value of the mismatched byte to skip across as many
; Returns start offset of first match searching forward, or NULL if ; potential match locations as possible (partial Boyer-Moore).
; no match is found. ; Returns start offset of first match searching forward, or NULL if
; Tested with TASM. ; no match is found.
; C near-callable as: ; Tested with TASM.
; unsigned char * FindString(unsigned char * BufferPtr, ; C near-callable as:
; unsigned int BufferLength, unsigned char * PatternPtr, ; unsigned char * FindString(unsigned char * BufferPtr,
; unsigned int PatternLength); ; unsigned int BufferLength, unsigned char * PatternPtr,
; unsigned int PatternLength);
parms struc parms struc
dw 2 dup(?) ;pushed BP & return address dw 2 dup(?) ;pushed BP & return address
BufferPtr dw ? ;pointer to buffer to be searched BufferPtr dw ? ;pointer to buffer to be searched
BufferLength dw ? ;# of bytes in buffer to be searched BufferLength dw ? ;# of bytes in buffer to be searched
PatternPtr dw ? ;pointer to pattern for which to search PatternPtr dw ? ;pointer to pattern for which to search
PatternLength dw ? ;length of pattern for which to search PatternLength dw ? ;length of pattern for which to search
parms ends parms ends
.model small .model small
.code .code
public _FindString public _FindString
_FindString proc near _FindString proc near
cld cld
push bp ;preserve caller's stack frame push bp ;preserve caller's stack frame
mov bp,sp ;point to our stack frame mov bp,sp ;point to our stack frame
push si ;preserve caller's register variables push si ;preserve caller's register variables
push di push di
sub sp,256*2 ;allocate space for SkipTable sub sp,256*2 ;allocate space for SkipTable
; Create the table of distances by which to skip ahead on mismatches ; Create the table of distances by which to skip ahead on mismatches
; for every possible byte value. First, initialize all skips to the ; for every possible byte value. First, initialize all skips to the
; pattern length; this is the skip distance for bytes that don't ; pattern length; this is the skip distance for bytes that don't
; appear in the pattern. ; appear in the pattern.
mov ax,[bp+PatternLength] mov ax,[bp+PatternLength]
and ax,ax ;return an instant match if the pattern is and ax,ax ;return an instant match if the pattern is
jz InstantMatch ;0-length jz InstantMatch ;0-length
mov di,ds mov di,ds
mov es,di ;ES=DS=SS mov es,di ;ES=DS=SS
mov di,sp ;point to SkipBuffer mov di,sp ;point to SkipBuffer
mov cx,256 mov cx,256
rep stosw rep stosw
dec ax ;from now on, we only need dec ax ;from now on, we only need
mov [bp+PatternLength],ax ; PatternLength - 1 mov [bp+PatternLength],ax ; PatternLength - 1
; Point to last (rightmost) byte of first potential pattern match ; Point to last (rightmost) byte of first potential pattern match
; location in buffer. ; location in buffer.
add [bp+BufferPtr],ax add [bp+BufferPtr],ax
; Reject if buffer is too small, and set the count of the number of ; Reject if buffer is too small, and set the count of the number of
; potential pattern match locations in the buffer. ; potential pattern match locations in the buffer.
sub [bp+BufferLength],ax sub [bp+BufferLength],ax
jbe NoMatch jbe NoMatch
; Set the skip values for the bytes that do appear in the pattern to ; Set the skip values for the bytes that do appear in the pattern to
; the distance from the byte location to the end of the pattern. ; the distance from the byte location to the end of the pattern.
; When there are multiple instances of the same byte, the rightmost ; When there are multiple instances of the same byte, the rightmost
; instance's skip value is used. Note that the rightmost byte of the ; instance's skip value is used. Note that the rightmost byte of the
; pattern isn't entered in the skip table; if we get that value for ; pattern isn't entered in the skip table; if we get that value for
; a mismatch, we know for sure that the right end of the pattern has ; a mismatch, we know for sure that the right end of the pattern has
; already passed the mismatch location, so this is not a relevant byte ; already passed the mismatch location, so this is not a relevant byte
; for skipping purposes. ; for skipping purposes.
mov si,[bp+PatternPtr] ;point to start of pattern mov si,[bp+PatternPtr] ;point to start of pattern
and ax,ax ;are there any skips to set? and ax,ax ;are there any skips to set?
jz SetSkipDone ;no jz SetSkipDone ;no
mov di,sp ;point to SkipBuffer mov di,sp ;point to SkipBuffer
SetSkipLoop: SetSkipLoop:
sub bx,bx ;prepare for word addressing off byte value sub bx,bx ;prepare for word addressing off byte value
mov bl,[si] ;get the next pattern byte mov bl,[si] ;get the next pattern byte
inc si ;advance the pattern pointer inc si ;advance the pattern pointer
shl bx,1 ;prepare for word lookup shl bx,1 ;prepare for word lookup
mov [di+bx],ax ;set the skip value when this byte value is mov [di+bx],ax ;set the skip value when this byte value is
; the mismatch value in the buffer ; the mismatch value in the buffer
dec ax dec ax
jnz SetSkipLoop jnz SetSkipLoop
SetSkipDone: SetSkipDone:
mov dl,[si] ;DL=rightmost pattern byte from now on mov dl,[si] ;DL=rightmost pattern byte from now on
dec si ;point to next-to-rightmost byte of pattern dec si ;point to next-to-rightmost byte of pattern
mov [bp+PatternPtr],si ; from now on mov [bp+PatternPtr],si ; from now on
; Search the buffer. ; Search the buffer.
std ;for backward REPZ CMPSB std ;for backward REPZ CMPSB
mov di,[bp+BufferPtr] ;point to first search location mov di,[bp+BufferPtr] ;point to first search location
mov cx,[bp+BufferLength] ;# of match locations to check mov cx,[bp+BufferLength] ;# of match locations to check
SearchLoop: SearchLoop:
mov si,sp ;point SI to SkipTable mov si,sp ;point SI to SkipTable
; Skip through until there's a match for the rightmost pattern byte. ; Skip through until there's a match for the rightmost pattern byte.
QuickSearchLoop: QuickSearchLoop:
mov bl,[di] ;rightmost buffer byte at this location mov bl,[di] ;rightmost buffer byte at this location
cmp dl,bl ;does it match the rightmost pattern byte? cmp dl,bl ;does it match the rightmost pattern byte?
jz FullCompare ;yes, so keep going jz FullCompare ;yes, so keep going
sub bh,bh ;convert to a word sub bh,bh ;convert to a word
add bx,bx ;prepare for look-up in SkipTable add bx,bx ;prepare for look-up in SkipTable
mov ax,[si+bx] ;get skip value from skip table for this mov ax,[si+bx] ;get skip value from skip table for this
; mismatch value ; mismatch value
add di,ax ;BufferPtr += Skip; add di,ax ;BufferPtr += Skip;
sub cx,ax ;BufferLength -= Skip; sub cx,ax ;BufferLength -= Skip;
ja QuickSearchLoop ;continue if any buffer left ja QuickSearchLoop ;continue if any buffer left
jmp short NoMatch jmp short NoMatch
; Return a pointer to the start of the buffer (for 0-length pattern). ; Return a pointer to the start of the buffer (for 0-length pattern).
align 2 align 2
InstantMatch: InstantMatch:
mov ax,[bp+BufferPtr] mov ax,[bp+BufferPtr]
jmp short Done jmp short Done
; Compare the pattern and the buffer location, searching from high ; Compare the pattern and the buffer location, searching from high
; memory toward low (right to left). ; memory toward low (right to left).
align 2 align 2
FullCompare: FullCompare:
mov [bp+BufferPtr],di ;save the current state of mov [bp+BufferPtr],di ;save the current state of
mov [bp+BufferLength],cx ; the search mov [bp+BufferLength],cx ; the search
mov cx,[bp+PatternLength] ;# of bytes yet to compare mov cx,[bp+PatternLength] ;# of bytes yet to compare
jcxz Match ;done if only one character jcxz Match ;done if only one character
mov si,[bp+PatternPtr] ;point to next-to-rightmost bytes mov si,[bp+PatternPtr] ;point to next-to-rightmost bytes
dec di ; of buffer location and pattern dec di ; of buffer location and pattern
repz cmpsb ;compare the rest of the pattern repz cmpsb ;compare the rest of the pattern
jz Match ;that's it; we've found a match jz Match ;that's it; we've found a match
; It's a mismatch; let's see what we can learn from it. ; It's a mismatch; let's see what we can learn from it.
inc di ;compensate for 1-byte overrun of REPZ CMPSB; inc di ;compensate for 1-byte overrun of REPZ CMPSB;
; point to mismatch location in buffer ; point to mismatch location in buffer
; # of bytes that did match. ; # of bytes that did match.
mov si,[bp+BufferPtr] mov si,[bp+BufferPtr]
sub si,di sub si,di
; If, based on the mismatch character, we can't even skip ahead as far ; If, based on the mismatch character, we can't even skip ahead as far
; as where we started this particular comparison, then just advance by ; as where we started this particular comparison, then just advance by
; 1 to the next potential match; otherwise, skip ahead from this ; 1 to the next potential match; otherwise, skip ahead from this
; comparison location by the skip distance for the mismatch character, ; comparison location by the skip distance for the mismatch character,
; less the distance covered by the partial match. ; less the distance covered by the partial match.
sub bx,bx ;prepare for word addressing off byte value sub bx,bx ;prepare for word addressing off byte value
mov bl,[di] ;get the value of the mismatch byte in buffer mov bl,[di] ;get the value of the mismatch byte in buffer
add bx,bx ;prepare for word look-up add bx,bx ;prepare for word look-up
add bx,sp ;SP points to SkipTable add bx,sp ;SP points to SkipTable
mov cx,[bx] ;get the skip value for this mismatch mov cx,[bx] ;get the skip value for this mismatch
mov ax,1 ;assume we'll just advance to the next mov ax,1 ;assume we'll just advance to the next
; potential match location ; potential match location
sub cx,si ;is the skip far enough to be worth taking? sub cx,si ;is the skip far enough to be worth taking?
jna MoveAhead ;no, go with the default advance of 1 jna MoveAhead ;no, go with the default advance of 1
mov ax,cx ;yes; this is the distance to skip ahead from mov ax,cx ;yes; this is the distance to skip ahead from
; the last potential match location checked ; the last potential match location checked
MoveAhead: MoveAhead:
; Skip ahead and perform the next comparison, if there's any buffer ; Skip ahead and perform the next comparison, if there's any buffer
; left to check. ; left to check.
mov di,[bp+BufferPtr] mov di,[bp+BufferPtr]
add di,ax ;BufferPtr += Skip; add di,ax ;BufferPtr += Skip;
mov cx,[bp+BufferLength] mov cx,[bp+BufferLength]
sub cx,ax ;BufferLength -= Skip; sub cx,ax ;BufferLength -= Skip;
ja SearchLoop ;continue if any buffer left ja SearchLoop ;continue if any buffer left
; Return a NULL pointer for no match. ; Return a NULL pointer for no match.
align 2 align 2
NoMatch: NoMatch:
sub ax,ax sub ax,ax
jmp short Done jmp short Done
; Return start of match in buffer (BufferPtr - (PatternLength - 1)). ; Return start of match in buffer (BufferPtr - (PatternLength - 1)).
align 2 align 2
Match: Match:
mov ax,[bp+BufferPtr] mov ax,[bp+BufferPtr]
sub ax,[bp+PatternLength] sub ax,[bp+PatternLength]
Done: Done:
cld ;restore default direction flag cld ;restore default direction flag
add sp,256*2 ;deallocate space for SkipTable add sp,256*2 ;deallocate space for SkipTable
pop di ;restore caller's register variables pop di ;restore caller's register variables
pop si pop si
pop bp ;restore caller's stack frame pop bp ;restore caller's stack frame
ret ret
_FindString endp _FindString endp
end end
```

290
14-06.md
View file

@ -35,152 +35,154 @@ about 60 percent faster than Listing 14.3.
**LISTING 14.4 L14-4.ASM** **LISTING 14.4 L14-4.ASM**
; Searches a buffer for a specified pattern. In case of a mismatch, ```nasm
; uses the value of the mismatched byte to skip across as many ; Searches a buffer for a specified pattern. In case of a mismatch,
; potential match locations as possible (partial Boyer-Moore). ; uses the value of the mismatched byte to skip across as many
; Returns start offset of first match searching forward, or NULL if ; potential match locations as possible (partial Boyer-Moore).
; no match is found. ; Returns start offset of first match searching forward, or NULL if
; Requires that the pattern be no longer than 255 bytes, and that ; no match is found.
; there be a match for the pattern somewhere in the buffer (ie., a ; Requires that the pattern be no longer than 255 bytes, and that
; copy of the pattern should be placed as a sentinel at the end of ; there be a match for the pattern somewhere in the buffer (ie., a
; the buffer if the pattern isn't already known to be in the buffer). ; copy of the pattern should be placed as a sentinel at the end of
; Tested with TASM. ; the buffer if the pattern isn't already known to be in the buffer).
; C near-callable as: ; Tested with TASM.
; unsigned char * FindString(unsigned char * BufferPtr, ; C near-callable as:
; unsigned int BufferLength, unsigned char * PatternPtr, ; unsigned char * FindString(unsigned char * BufferPtr,
; unsigned int PatternLength); ; unsigned int BufferLength, unsigned char * PatternPtr,
; unsigned int PatternLength);
parms struc parms struc
dw 2 dup(?) ;pushed BP & return address dw 2 dup(?) ;pushed BP & return address
BufferPtr dw ? ;pointer to buffer to be searched BufferPtr dw ? ;pointer to buffer to be searched
BufferLength dw ? ;# of bytes in buffer to be searched BufferLength dw ? ;# of bytes in buffer to be searched
; (not used, actually) ; (not used, actually)
PatternPtr dw ? ;pointer to pattern for which to search PatternPtr dw ? ;pointer to pattern for which to search
; (pattern *MUST* exist in the buffer) ; (pattern *MUST* exist in the buffer)
PatternLength dw ? ;length of pattern for which to search (must PatternLength dw ? ;length of pattern for which to search (must
; be <= 255) ; be <= 255)
parms ends parms ends
.model small .model small
.code .code
public _FindString public _FindString
_FindString proc near _FindString proc near
cld cld
push bp ;preserve caller's stack frame push bp ;preserve caller's stack frame
mov bp,sp ;point to our stack frame mov bp,sp ;point to our stack frame
push si ;preserve caller's register variables push si ;preserve caller's register variables
push di push di
sub sp,256 ;allocate space for SkipTable sub sp,256 ;allocate space for SkipTable
; Create the table of distances by which to skip ahead on mismatches ; Create the table of distances by which to skip ahead on mismatches
; for every possible byte value. First, initialize all skips to the ; for every possible byte value. First, initialize all skips to the
; pattern length; this is the skip distance for bytes that don't ; pattern length; this is the skip distance for bytes that don't
; appear in the pattern. ; appear in the pattern.
mov di,ds mov di,ds
mov es,di ;ES=DS=SS mov es,di ;ES=DS=SS
mov di,sp ;point to SkipBuffer mov di,sp ;point to SkipBuffer
mov al,byte ptr [bp+PatternLength] mov al,byte ptr [bp+PatternLength]
and al,al ;return an instant match if the pattern is and al,al ;return an instant match if the pattern is
jz InstantMatch ; 0-length jz InstantMatch ; 0-length
mov ah,al mov ah,al
mov cx,256/2 mov cx,256/2
rep stosw rep stosw
mov ax,[bp+PatternLength] mov ax,[bp+PatternLength]
dec ax ;from now on, we only need dec ax ;from now on, we only need
mov [bp+PatternLength],ax ; PatternLength - 1 mov [bp+PatternLength],ax ; PatternLength - 1
; Point to rightmost byte of first potential pattern match location ; Point to rightmost byte of first potential pattern match location
; in buffer. ; in buffer.
add [bp+BufferPtr],ax add [bp+BufferPtr],ax
; Set the skip values for the bytes that do appear in the pattern to ; Set the skip values for the bytes that do appear in the pattern to
; the distance from the byte location to the end of the pattern. ; the distance from the byte location to the end of the pattern.
mov si,[bp+PatternPtr] ;point to start of pattern mov si,[bp+PatternPtr] ;point to start of pattern
and ax,ax ;are there any skips to set? and ax,ax ;are there any skips to set?
jz SetSkipDone ;no jz SetSkipDone ;no
mov di,sp ;point to SkipBuffer mov di,sp ;point to SkipBuffer
sub bx,bx ;prepare for word addressing off byte value sub bx,bx ;prepare for word addressing off byte value
SetSkipLoop: SetSkipLoop:
mov bl,[si] ;get the next pattern byte mov bl,[si] ;get the next pattern byte
inc si ;advance the pattern pointer inc si ;advance the pattern pointer
mov [di+bx],al ;set the skip value when this byte value is mov [di+bx],al ;set the skip value when this byte value is
;the mismatch value in the buffer ;the mismatch value in the buffer
dec ax dec ax
jnz SetSkipLoop jnz SetSkipLoop
SetSkipDone: SetSkipDone:
mov dl,[si] ;DL=rightmost pattern byte from now on mov dl,[si] ;DL=rightmost pattern byte from now on
dec si ;point to next-to-rightmost byte of pattern dec si ;point to next-to-rightmost byte of pattern
mov [bp+PatternPtr],si ; from now on mov [bp+PatternPtr],si ; from now on
; Search the buffer. ; Search the buffer.
std ;for backward REPZ CMPSB std ;for backward REPZ CMPSB
mov di,[bp+BufferPtr] ;point to the first search location mov di,[bp+BufferPtr] ;point to the first search location
mov bx,sp ;point to SkipTable for XLAT mov bx,sp ;point to SkipTable for XLAT
SearchLoop: SearchLoop:
sub ah,ah ;used to convert AL to a word sub ah,ah ;used to convert AL to a word
; Skip through until there's a match for the first pattern byte. ; Skip through until there's a match for the first pattern byte.
QuickSearchLoop: QuickSearchLoop:
; See if we have a match at the first buffer location. ; See if we have a match at the first buffer location.
REPT 8 ;unroll loop 8 times to reduce branching REPT 8 ;unroll loop 8 times to reduce branching
mov al,[di] ;next buffer byte mov al,[di] ;next buffer byte
cmp dl,al ;does it match the pattern? cmp dl,al ;does it match the pattern?
jz FullCompare ;yes, so keep going jz FullCompare ;yes, so keep going
xlat ;no, look up the skip value for this mismatch xlat ;no, look up the skip value for this mismatch
add di,ax ;BufferPtr += Skip; add di,ax ;BufferPtr += Skip;
ENDM ENDM
jmp QuickSearchLoop jmp QuickSearchLoop
; Return a pointer to the start of the buffer (for 0-length pattern). ; Return a pointer to the start of the buffer (for 0-length pattern).
align 2 align 2
InstantMatch: InstantMatch:
mov ax,[bp+BufferPtr] mov ax,[bp+BufferPtr]
jmp short Done jmp short Done
; Compare the pattern and the buffer location, searching from high ; Compare the pattern and the buffer location, searching from high
; memory toward low (right to left). ; memory toward low (right to left).
align 2 align 2
FullCompare: FullCompare:
mov [bp+BufferPtr],di ;save the current buffer location mov [bp+BufferPtr],di ;save the current buffer location
mov cx,[bp+PatternLength] ;# of bytes yet to compare mov cx,[bp+PatternLength] ;# of bytes yet to compare
jcxz Match ;done if there was only one character jcxz Match ;done if there was only one character
dec di ;point to next destination byte to compare (SI dec di ;point to next destination byte to compare (SI
; points to next-to-rightmost source byte) ; points to next-to-rightmost source byte)
repz cmpsb ;compare the rest of the pattern repz cmpsb ;compare the rest of the pattern
jz Match ;that's it; we've found a match jz Match ;that's it; we've found a match
; It's a mismatch; let's see what we can learn from it. ; It's a mismatch; let's see what we can learn from it.
inc di ;compensate for 1-byte overrun of REPZ CMPSB; inc di ;compensate for 1-byte overrun of REPZ CMPSB;
; point to mismatch location in buffer ; point to mismatch location in buffer
; # of bytes that did match. ; # of bytes that did match.
mov si,[bp+BufferPtr] mov si,[bp+BufferPtr]
sub si,di sub si,di
; If, based on the mismatch character, we can't even skip ahead as far ; If, based on the mismatch character, we can't even skip ahead as far
; as where we started this particular comparison, then just advance by ; as where we started this particular comparison, then just advance by
; 1 to the next potential match; otherwise, skip ahead from this ; 1 to the next potential match; otherwise, skip ahead from this
; comparison location by the skip distance for the mismatch character, ; comparison location by the skip distance for the mismatch character,
; less the distance covered by the partial match. ; less the distance covered by the partial match.
mov al,[di] ;get the value of the mismatch byte in buffer mov al,[di] ;get the value of the mismatch byte in buffer
xlat ;get the skip value for this mismatch xlat ;get the skip value for this mismatch
mov cx,1 ;assume we'll just advance to the next mov cx,1 ;assume we'll just advance to the next
; potential match location ; potential match location
sub ax,si ;is the skip far enough to be worth taking? sub ax,si ;is the skip far enough to be worth taking?
jna MoveAhead ;no, go with the default advance of 1 jna MoveAhead ;no, go with the default advance of 1
mov cx,ax ;yes, this is the distance to skip ahead from mov cx,ax ;yes, this is the distance to skip ahead from
;the last potential match location checked ;the last potential match location checked
MoveAhead: MoveAhead:
; Skip ahead and perform the next comparison. ; Skip ahead and perform the next comparison.
mov di,[bp+BufferPtr] mov di,[bp+BufferPtr]
add di,cx ;BufferPtr += Skip; add di,cx ;BufferPtr += Skip;
mov si,[bp+PatternPtr] ;point to the next-to-rightmost mov si,[bp+PatternPtr] ;point to the next-to-rightmost
; pattern byte ; pattern byte
jmp SearchLoop jmp SearchLoop
; Return start of match in buffer (BufferPtr - (PatternLength - 1)). ; Return start of match in buffer (BufferPtr - (PatternLength - 1)).
align 2 align 2
Match: Match:
mov ax,[bp+BufferPtr] mov ax,[bp+BufferPtr]
sub ax,[bp+PatternLength] sub ax,[bp+PatternLength]
Done: Done:
cld ;restore default direction flag cld ;restore default direction flag
add sp,256 ;deallocate space for SkipTable add sp,256 ;deallocate space for SkipTable
pop di ;restore caller's register variables pop di ;restore caller's register variables
pop si pop si
pop bp ;restore caller's stack frame pop bp ;restore caller's stack frame
ret ret
_FindString endp _FindString endp
end end
```
Note that Table 14.1 includes the time required to build the skip table Note that Table 14.1 includes the time required to build the skip table
each time **FindString** is called. This time could be eliminated for each time **FindString** is called. This time could be eliminated for

134
15-02.md
View file

@ -12,57 +12,63 @@ pages: 284-287
**LISTING 15.1 L15-1.C** **LISTING 15.1 L15-1.C**
/* Deletes the node in a linked list that follows the indicated node. ```c
Assumes list is headed by a dummy node, so no special testing for /* Deletes the node in a linked list that follows the indicated node.
the head-of-list pointer is required. Returns the same pointer Assumes list is headed by a dummy node, so no special testing for
that was passed in. */ the head-of-list pointer is required. Returns the same pointer
that was passed in. */
#include "llist.h" #include "llist.h"
struct LinkNode *DeleteNodeAfter(struct LinkNode *NodeToDeleteAfter) struct LinkNode *DeleteNodeAfter(struct LinkNode *NodeToDeleteAfter)
{ {
NodeToDeleteAfter->NextNode = NodeToDeleteAfter->NextNode =
NodeToDeleteAfter->NextNode->NextNode; NodeToDeleteAfter->NextNode->NextNode;
return(NodeToDeleteAfter); return(NodeToDeleteAfter);
} }
```
**LISTING 15.2 LLIST.H** **LISTING 15.2 LLIST.H**
/* Linked list header file. */ ```c
#define MAX_TEXT_LENGTH 100 /* longest allowed Text field */ /* Linked list header file. */
#define SENTINEL 32767 /* largest possible Value field */ #define MAX_TEXT_LENGTH 100 /* longest allowed Text field */
#define SENTINEL 32767 /* largest possible Value field */
struct LinkNode { struct LinkNode {
struct LinkNode *NextNode; struct LinkNode *NextNode;
int Value; int Value;
char Text[MAX_TEXT_LENGTH+1]; char Text[MAX_TEXT_LENGTH+1];
/* Any number of additional data fields may by present */ /* Any number of additional data fields may by present */
}; };
struct LinkNode *DeleteNodeAfter(struct LinkNode *); struct LinkNode *DeleteNodeAfter(struct LinkNode *);
struct LinkNode *FindNodeBeforeValue(struct LinkNode *, int); struct LinkNode *FindNodeBeforeValue(struct LinkNode *, int);
struct LinkNode *InitLinkedList(void); struct LinkNode *InitLinkedList(void);
struct LinkNode *InsertNodeSorted(struct LinkNode *, struct LinkNode *InsertNodeSorted(struct LinkNode *,
struct LinkNode *); struct LinkNode *);
```
**LISTING 15.3 L15-3.C** **LISTING 15.3 L15-3.C**
/* Deletes the node in the specified linked list that follows the ```c
indicated node. List is headed by a head-of-list pointer; if the /* Deletes the node in the specified linked list that follows the
pointer to the node to delete after points to the head-of-list indicated node. List is headed by a head-of-list pointer; if the
pointer, special handling is performed. */ pointer to the node to delete after points to the head-of-list
#include "llist.h" pointer, special handling is performed. */
struct LinkNode *DeleteNodeAfter(struct LinkNode **HeadOfListPtr, #include "llist.h"
struct LinkNode *NodeToDeleteAfter) struct LinkNode *DeleteNodeAfter(struct LinkNode **HeadOfListPtr,
{ struct LinkNode *NodeToDeleteAfter)
/* Handle specially if the node to delete after is actually the {
head of the list (delete the first element in the list) */ /* Handle specially if the node to delete after is actually the
if (NodeToDeleteAfter == (struct LinkNode *)HeadOfListPtr) { head of the list (delete the first element in the list) */
*HeadOfListPtr = (*HeadOfListPtr)->NextNode; if (NodeToDeleteAfter == (struct LinkNode *)HeadOfListPtr) {
} else { *HeadOfListPtr = (*HeadOfListPtr)->NextNode;
NodeToDeleteAfter->NextNode = } else {
NodeToDeleteAfter->NextNode->NextNode; NodeToDeleteAfter->NextNode =
} NodeToDeleteAfter->NextNode->NextNode;
return(NodeToDeleteAfter); }
} return(NodeToDeleteAfter);
}
```
However, it is true that if you're going to store a variety of types of However, it is true that if you're going to store a variety of types of
structures in your linked lists, you should start each node with the structures in your linked lists, you should start each node with the
@ -123,28 +129,30 @@ value has to perform two tests in the inner loop, as shown in Listing
**LISTING 15.4 L15-4.C** **LISTING 15.4 L15-4.C**
/* Finds the first node in a linked list with a value field greater ```c
than or equal to a key value, and returns a pointer to the node /* Finds the first node in a linked list with a value field greater
preceding that node (to facilitate insertion and deletion), or a than or equal to a key value, and returns a pointer to the node
NULL pointer if no such value was found. Assumes the list is preceding that node (to facilitate insertion and deletion), or a
terminated with a tail node pointing to itself as the next node. */ NULL pointer if no such value was found. Assumes the list is
#include <stdio.h> terminated with a tail node pointing to itself as the next node. */
#include "llist.h" #include <stdio.h>
struct LinkNode *FindNodeBeforeValueNotLess( #include "llist.h"
struct LinkNode *HeadOfListNode, int SearchValue) struct LinkNode *FindNodeBeforeValueNotLess(
{ struct LinkNode *HeadOfListNode, int SearchValue)
struct LinkNode *NodePtr = HeadOfListNode; {
struct LinkNode *NodePtr = HeadOfListNode;
while ( (NodePtr->NextNode->NextNode != NodePtr->NextNode) && while ( (NodePtr->NextNode->NextNode != NodePtr->NextNode) &&
(NodePtr->NextNode->Value < SearchValue) ) (NodePtr->NextNode->Value < SearchValue) )
NodePtr = NodePtr->NextNode; NodePtr = NodePtr->NextNode;
if (NodePtr->NextNode->NextNode == NodePtr->NextNode) if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
return(NULL); /* we found the sentinel; failed search */ return(NULL); /* we found the sentinel; failed search */
else else
return(NodePtr); /* success; return pointer to node preceding return(NodePtr); /* success; return pointer to node preceding
node that was >= */ node that was >= */
} }
```
Suppose, however, that we make the tail node a *sentinel* by giving it a Suppose, however, that we make the tail node a *sentinel* by giving it a
value that is guaranteed to terminate the search, as shown in Figure value that is guaranteed to terminate the search, as shown in Figure

166
15-03.md
View file

@ -12,27 +12,29 @@ pages: 287-290
**LISTING 15.5 L15-5.C** **LISTING 15.5 L15-5.C**
/* Finds the first node in a value-sorted linked list that ```c
has a Value field greater than or equal to a key value, and /* Finds the first node in a value-sorted linked list that
returns a pointer to the node preceding that node (to facilitate has a Value field greater than or equal to a key value, and
insertion and deletion), or a NULL pointer if no such value was returns a pointer to the node preceding that node (to facilitate
found. Assumes the list is terminated with a sentinel tail node insertion and deletion), or a NULL pointer if no such value was
containing the largest possible Value field setting and pointing found. Assumes the list is terminated with a sentinel tail node
to itself as the next node. */ containing the largest possible Value field setting and pointing
#include <stdio.h> to itself as the next node. */
#include "llist.h" #include <stdio.h>
struct LinkNode *FindNodeBeforeValueNotLess( #include "llist.h"
struct LinkNode *HeadOfListNode, int SearchValue) struct LinkNode *FindNodeBeforeValueNotLess(
{ struct LinkNode *HeadOfListNode, int SearchValue)
struct LinkNode *NodePtr = HeadOfListNode; {
while (NodePtr->NextNode->Value < SearchValue) struct LinkNode *NodePtr = HeadOfListNode;
NodePtr = NodePtr->NextNode; while (NodePtr->NextNode->Value < SearchValue)
if (NodePtr->NextNode->NextNode == NodePtr->NextNode) NodePtr = NodePtr->NextNode;
return(NULL); /* we found the sentinel; failed search */ if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
else return(NULL); /* we found the sentinel; failed search */
return(NodePtr); /* success; return pointer to node preceding else
node that was >= */ return(NodePtr); /* success; return pointer to node preceding
} node that was >= */
}
```
![**Figure 15.4**  *List terminated by a sentinel.*](images/15-04.jpg) ![**Figure 15.4**  *List terminated by a sentinel.*](images/15-04.jpg)
@ -80,68 +82,70 @@ before you write a single line of code.
**LISTING 15.6 L15-6.C** **LISTING 15.6 L15-6.C**
/* Suite of functions for maintaining a linked list sorted by ```c
ascending order of the Value field. The list is circular; that /* Suite of functions for maintaining a linked list sorted by
is,it has a dummy node as both the head and the tail of the list. ascending order of the Value field. The list is circular; that
The dummy node is a sentinel, containing the largest possible is,it has a dummy node as both the head and the tail of the list.
Value field setting. Tested with Borland C++ in C mode. */ The dummy node is a sentinel, containing the largest possible
#include <stdlib.h> Value field setting. Tested with Borland C++ in C mode. */
#include <stdio.h> #include <stdlib.h>
#include <string.h> #include <stdio.h>
#include "llist.h" #include <string.h>
/* Initializes an empty linked list of LinkNode structures, #include "llist.h"
consisting of a single head/tail/sentinel node, and returns a /* Initializes an empty linked list of LinkNode structures,
pointer to the list. Returns NULL for failure. */ consisting of a single head/tail/sentinel node, and returns a
struct LinkNode *InitLinkedList() pointer to the list. Returns NULL for failure. */
{ struct LinkNode *InitLinkedList()
struct LinkNode *Sentinel; {
struct LinkNode *Sentinel;
if ((Sentinel = malloc(sizeof(struct LinkNode))) == NULL) if ((Sentinel = malloc(sizeof(struct LinkNode))) == NULL)
return(NULL); return(NULL);
Sentinel->NextNode = Sentinel; Sentinel->NextNode = Sentinel;
Sentinel->Value = SENTINEL; Sentinel->Value = SENTINEL;
strcpy(Sentinel->Text, "*** sentinel ***"); strcpy(Sentinel->Text, "*** sentinel ***");
return(Sentinel); return(Sentinel);
} }
/* Finds the first node in a value-sorted linked list with a value /* Finds the first node in a value-sorted linked list with a value
field equal to a key value, and returns a pointer to the node field equal to a key value, and returns a pointer to the node
preceding that node (to facilitate insertion and deletion), or a preceding that node (to facilitate insertion and deletion), or a
NULL pointer if no value was found. Assumes list is terminated NULL pointer if no value was found. Assumes list is terminated
with a sentinel node containing the largest possible value. */ with a sentinel node containing the largest possible value. */
struct LinkNode *FindNodeBeforeValue(struct LinkNode *HeadOfListNode, struct LinkNode *FindNodeBeforeValue(struct LinkNode *HeadOfListNode,
int SearchValue) int SearchValue)
{ {
struct LinkNode *NodePtr = HeadOfListNode; struct LinkNode *NodePtr = HeadOfListNode;
while (NodePtr->NextNode->Value < SearchValue) while (NodePtr->NextNode->Value < SearchValue)
NodePtr = NodePtr->NextNode; NodePtr = NodePtr->NextNode;
if (NodePtr->NextNode->Value == SearchValue) { if (NodePtr->NextNode->Value == SearchValue) {
/* Found the search value; success unless we found the /* Found the search value; success unless we found the
sentinel (can happen only if SearchValue == SENTINEL) */ sentinel (can happen only if SearchValue == SENTINEL) */
if (NodePtr->NextNode == HeadOfListNode) { if (NodePtr->NextNode == HeadOfListNode) {
return(NULL); /* failure; we found the sentinel */ return(NULL); /* failure; we found the sentinel */
} else { } else {
return(NodePtr); /* success; return pointer to node return(NodePtr); /* success; return pointer to node
preceding the node that was equal */ preceding the node that was equal */
} }
} else { } else {
return(NULL); /* No match; return failure status */ return(NULL); /* No match; return failure status */
} }
} }
/* Inserts the specified node into a value-sorted linked list, such /* Inserts the specified node into a value-sorted linked list, such
that value-sorting is maintained. Returns a pointer to the node that value-sorting is maintained. Returns a pointer to the node
after which the new node is inserted. */ after which the new node is inserted. */
struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode, struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode,
struct LinkNode *NodeToInsert) struct LinkNode *NodeToInsert)
{ {
struct LinkNode *NodePtr = HeadOfListNode; struct LinkNode *NodePtr = HeadOfListNode;
int SearchValue = NodeToInsert->Value; int SearchValue = NodeToInsert->Value;
while (NodePtr->NextNode->Value < SearchValue) while (NodePtr->NextNode->Value < SearchValue)
NodePtr = NodePtr->NextNode; NodePtr = NodePtr->NextNode;
NodeToInsert->NextNode = NodePtr->NextNode; NodeToInsert->NextNode = NodePtr->NextNode;
NodePtr->NextNode = NodeToInsert; NodePtr->NextNode = NodeToInsert;
return(NodePtr); return(NodePtr);
} }
```

324
15-04.md
View file

@ -12,149 +12,153 @@ pages: 290-293
**LISTING 15.7 L15-7.ASM** **LISTING 15.7 L15-7.ASM**
; C near-callable assembly function for inserting a new node in a ```nasm
; linked list sorted by ascending order of the Value field. The list ; C near-callable assembly function for inserting a new node in a
; is circular; that is, it has a dummy node as both the head and the ; linked list sorted by ascending order of the Value field. The list
; tail of the list. The dummy node is a sentinel, containing the ; is circular; that is, it has a dummy node as both the head and the
; largest possible Value field setting. Tested with TASM. ; tail of the list. The dummy node is a sentinel, containing the
MAX_TEXT_LENGTH equ 100 ;longest allowed Text field ; largest possible Value field setting. Tested with TASM.
SENTINEL equ 32767 ;largest possible Value field MAX_TEXT_LENGTH equ 100 ;longest allowed Text field
LinkNode struc SENTINEL equ 32767 ;largest possible Value field
NextNode dw ? LinkNode struc
Value dw ? NextNode dw ?
Text db MAX_TEXT_LENGTH+1 dup(?) Value dw ?
;*** Any number of additional data fields may by present *** Text db MAX_TEXT_LENGTH+1 dup(?)
LinkNode ends ;*** Any number of additional data fields may by present ***
LinkNode ends
.model small .model small
.code .code
; Inserts the specified node into a ascending-value-sorted linked ; Inserts the specified node into a ascending-value-sorted linked
; list, such that value-sorting is maintained. Returns a pointer to ; list, such that value-sorting is maintained. Returns a pointer to
; the node after which the new node is inserted. ; the node after which the new node is inserted.
; C near-callable as: ; C near-callable as:
; struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode, ; struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode,
; struct LinkNode *NodeToInsert) ; struct LinkNode *NodeToInsert)
parms struc parms struc
dw 2 dup (?) ;pushed return address & BP dw 2 dup (?) ;pushed return address & BP
HeadOfListNode dw ? ;pointer to head node of list HeadOfListNode dw ? ;pointer to head node of list
NodeToInsert dw ? ;pointer to node to insert NodeToInsert dw ? ;pointer to node to insert
parms ends parms ends
public _InsertNodeSorted public _InsertNodeSorted
_InsertNodeSorted proc near _InsertNodeSorted proc near
push bp push bp
mov bp,sp ;point to stack frame mov bp,sp ;point to stack frame
push si ;preserve register vars push si ;preserve register vars
push di push di
mov si,[bp].NodeToInsert ;point to node to insert mov si,[bp].NodeToInsert ;point to node to insert
mov ax,[si].Value ;search value mov ax,[si].Value ;search value
mov di,[bp].HeadOfListNode ;point to linked list in mov di,[bp].HeadOfListNode ;point to linked list in
; which to insert ; which to insert
SearchLoop: SearchLoop:
mov bx,di ;advance to the next node mov bx,di ;advance to the next node
mov di,[bx].NextNode ;point to following node mov di,[bx].NextNode ;point to following node
cmp [di].Value,ax ;is the following node's cmp [di].Value,ax ;is the following node's
; value less than the value ; value less than the value
; from the node to insert? ; from the node to insert?
jl SearchLoop ;yes, so continue searching jl SearchLoop ;yes, so continue searching
;no, so we have found our ;no, so we have found our
; insert point ; insert point
mov ax,[bx].NextNode ;link the new node between mov ax,[bx].NextNode ;link the new node between
mov [si].NextNode,ax ; the current node and the mov [si].NextNode,ax ; the current node and the
mov [bx].NextNode,si ; following node mov [bx].NextNode,si ; following node
mov ax,bx ;return pointer to node mov ax,bx ;return pointer to node
; after which we inserted ; after which we inserted
pop di ;restore register vars pop di ;restore register vars
pop si pop si
pop bp pop bp
ret ret
_InsertNodeSorted endp _InsertNodeSorted endp
end end
```
**LISTING 15.8 L15-8.C** **LISTING 15.8 L15-8.C**
/* Sample linked list program. Tested with Borland C++. */ ```c
#include <stdlib.h> /* Sample linked list program. Tested with Borland C++. */
#include <stdio.h> #include <stdlib.h>
#include <conio.h> #include <stdio.h>
#include <ctype.h> #include <conio.h>
#include <string.h> #include <ctype.h>
#include "llist.h" #include <string.h>
#include "llist.h"
void main() void main()
{ int Done = 0, Char, TempValue; { int Done = 0, Char, TempValue;
struct LinkNode *TempPtr, *ListPtr, *TempPtr2; struct LinkNode *TempPtr, *ListPtr, *TempPtr2;
char TempBuffer[MAX_TEXT_LENGTH+3]; char TempBuffer[MAX_TEXT_LENGTH+3];
if ((ListPtr = InitLinkedList()) == NULL) { if ((ListPtr = InitLinkedList()) == NULL) {
printf("Out of memory\n"); printf("Out of memory\n");
exit(1); exit(1);
} }
while (!Done) { while (!Done) {
printf("\nA=add; D=delete; F=find; L=list all; Q=quit\n>"); printf("\nA=add; D=delete; F=find; L=list all; Q=quit\n>");
Char = toupper(getche()); Char = toupper(getche());
printf("\n"); printf("\n");
switch (Char) { switch (Char) {
case 'A': /* add a node */ case 'A': /* add a node */
if ((TempPtr = malloc(sizeof(struct LinkNode))) == NULL) if ((TempPtr = malloc(sizeof(struct LinkNode))) == NULL)
{ {
printf("Out of memory\n ); printf("Out of memory\n );
exit(1); exit(1);
} }
printf("Node value: "); printf("Node value: ");
scanf("%d", &TempPtr->Value); scanf("%d", &TempPtr->Value);
if ((FindNodeBeforeValue(ListPtr,TempPtr->Value))!=NULL) if ((FindNodeBeforeValue(ListPtr,TempPtr->Value))!=NULL)
{ printf("*** value already in list; try again ***\n"); { printf("*** value already in list; try again ***\n");
free(TempPtr); free(TempPtr);
} else {printf("Node text: "); } else {printf("Node text: ");
TempBuffer[0] = MAX_TEXT_LENGTH; TempBuffer[0] = MAX_TEXT_LENGTH;
cgets(TempBuffer); cgets(TempBuffer);
strcpy(TempPtr->Text, &TempBuffer[2]); strcpy(TempPtr->Text, &TempBuffer[2]);
InsertNodeSorted(ListPtr, TempPtr); InsertNodeSorted(ListPtr, TempPtr);
printf("\n"); printf("\n");
} }
break; break;
case 'D': /* delete a node */ case 'D': /* delete a node */
printf("Value field of node to delete: "); printf("Value field of node to delete: ");
scanf("%d", &TempValue); scanf("%d", &TempValue);
if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue)) if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue))
!= NULL) { != NULL) {
TempPtr2 = TempPtr->NextNode; /* -> node to delete */ TempPtr2 = TempPtr->NextNode; /* -> node to delete */
DeleteNodeAfter(TempPtr); /* delete it */ DeleteNodeAfter(TempPtr); /* delete it */
free(TempPtr2); /* free its memory */ free(TempPtr2); /* free its memory */
} else { } else {
printf("*** no such value field in list ***\n") printf("*** no such value field in list ***\n")
break; break;
case 'F': /* find a node */ case 'F': /* find a node */
printf("Value field of node to find: "); printf("Value field of node to find: ");
scanf("%d", &TempValue); scanf("%d", &TempValue);
if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue)) if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue))
!= NULL) != NULL)
printf("Value: %d\nText: %s\n", printf("Value: %d\nText: %s\n",
TempPtr->NextNode->Value, TempPtr->NextNode->Text); TempPtr->NextNode->Value, TempPtr->NextNode->Text);
else else
printf("*** no such value field in list ***\n"); printf("*** no such value field in list ***\n");
break; break;
case 'L': /* list all nodes */ case 'L': /* list all nodes */
TempPtr = ListPtr->NextNode; /* point to first node */ TempPtr = ListPtr->NextNode; /* point to first node */
if (TempPtr == ListPtr) { /* empty if at sentinel */ if (TempPtr == ListPtr) { /* empty if at sentinel */
printf("*** List is empty ***\n"); printf("*** List is empty ***\n");
} else { } else {
do {printf("Value: %d\n Text: %s\n", TempPtr->Value, do {printf("Value: %d\n Text: %s\n", TempPtr->Value,
TempPtr->Text); TempPtr->Text);
TempPtr = TempPtr->NextNode; TempPtr = TempPtr->NextNode;
} while (TempPtr != ListPtr); } while (TempPtr != ListPtr);
} }
break; break;
case 'Q': case 'Q':
Done = 1; Done = 1;
break; break;
default: default:
break; break;
} }
} }
} }
```
### Hi/Lo in 24 Bytes {#Heading6} ### Hi/Lo in 24 Bytes {#Heading6}
@ -182,33 +186,35 @@ reads closely enough.
**LISTING 15.9 L15-9.ASM** **LISTING 15.9 L15-9.ASM**
; Find the greatest or smallest unsigned int. ```nasm
; C callable (small model); 24 bytes. ; Find the greatest or smallest unsigned int.
; By David Stafford. ; C callable (small model); 24 bytes.
; unsigned hi( int num, unsigned a[] ); ; By David Stafford.
; unsigned lo( int num, unsigned a[] ); ; unsigned hi( int num, unsigned a[] );
; unsigned lo( int num, unsigned a[] );
public _hi, _lo public _hi, _lo
_hi: db 0b9h ;mov cx,immediate _hi: db 0b9h ;mov cx,immediate
_lo: xor cx,cx _lo: xor cx,cx
pop ax ;get return address pop ax ;get return address
pop dx ;get count pop dx ;get count
pop bx ;get pointer pop bx ;get pointer
push bx ;restore pointer push bx ;restore pointer
push dx ;restore count push dx ;restore count
push ax ;restore return address push ax ;restore return address
save: mov ax,[bx] save: mov ax,[bx]
top: cmp ax,[bx] top: cmp ax,[bx]
jcxz around jcxz around
cmc cmc
around: ja save around: ja save
inc bx inc bx
inc bx inc bx
dec dx dec dx
jnz top jnz top
ret ret
```
Before I end this chapter, let me say that I get a lot of feedback from Before I end this chapter, let me say that I get a lot of feedback from
my readers, and it's much appreciated. Keep those cards, letters, and my readers, and it's much appreciated. Keep those cards, letters, and

128
16-01.md
View file

@ -98,75 +98,77 @@ timed from a RAM disk on a 20 MHz 386.
**LISTING 16.1 L16-1.C** **LISTING 16.1 L16-1.C**
/* Word-counting program. Tested with Borland C++ in C ```c
compilation mode and the small model. */ /* Word-counting program. Tested with Borland C++ in C
compilation mode and the small model. */
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys\stat.h> #include <sys\stat.h>
#include <stdlib.h> #include <stdlib.h>
#include <io.h> #include <io.h>
#define B UFFER_SIZE 0x8000 /* largest chunk of file worked #define B UFFER_SIZE 0x8000 /* largest chunk of file worked
with at any one time */ with at any one time */
int main(int, char **); int main(int, char **);
int main(int argc, char **argv) { int main(int argc, char **argv) {
int Handle; int Handle;
unsigned int BlockSize; unsigned int BlockSize;
long FileSize; long FileSize;
unsigned long WordCount = 0; unsigned long WordCount = 0;
char *Buffer, CharFlag = 0, PredCharFlag, *BufferPtr, Ch; char *Buffer, CharFlag = 0, PredCharFlag, *BufferPtr, Ch;
if (argc != 2) { if (argc != 2) {
printf("usage: wc <filename>\n"); printf("usage: wc <filename>\n");
exit(1); exit(1);
} }
if ((Buffer = malloc(BUFFER_SIZE)) == NULL) { if ((Buffer = malloc(BUFFER_SIZE)) == NULL) {
printf("Can't allocate adequate memory\n"); printf("Can't allocate adequate memory\n");
exit(1); exit(1);
} }
if ((Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1) { if ((Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1) {
printf("Can't open file %s\n", argv[1]); printf("Can't open file %s\n", argv[1]);
exit(1); exit(1);
} }
if ((FileSize = filelength(Handle)) == -1) { if ((FileSize = filelength(Handle)) == -1) {
printf("Error sizing file %s\n", argv[1]); printf("Error sizing file %s\n", argv[1]);
exit(1); exit(1);
} }
/* Process the file in chunks */ /* Process the file in chunks */
while (FileSize > 0) { while (FileSize > 0) {
/* Get the next chunk */ /* Get the next chunk */
FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE)); FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE));
if (read(Handle, Buffer, BlockSize) == -1) { if (read(Handle, Buffer, BlockSize) == -1) {
printf("Error reading file %s\n", argv[1]); printf("Error reading file %s\n", argv[1]);
exit(1); exit(1);
} }
/* Count words in the chunk */ /* Count words in the chunk */
BufferPtr = Buffer; BufferPtr = Buffer;
do { do {
PredCharFlag = CharFlag; PredCharFlag = CharFlag;
Ch = *BufferPtr++ & 0x7F; /* strip high bit, which some Ch = *BufferPtr++ & 0x7F; /* strip high bit, which some
word processors set as an word processors set as an
internal flag */ internal flag */
CharFlag = ((Ch >= ‘a') && (Ch <= ‘z')) || CharFlag = ((Ch >= ‘a') && (Ch <= ‘z')) ||
((Ch >= ‘A') && (Ch <= ‘Z')) || ((Ch >= ‘A') && (Ch <= ‘Z')) ||
((Ch >= ‘0') && (Ch <= ‘9')) || ((Ch >= ‘0') && (Ch <= ‘9')) ||
(Ch == ‘\''); (Ch == ‘\'');
if ((!CharFlag) && PredCharFlag) { if ((!CharFlag) && PredCharFlag) {
WordCo u nt++; WordCo u nt++;
} }
} while (—BlockSize); } while (—BlockSize);
} }
/* Catch the last word, if any */ /* Catch the last word, if any */
if (CharFlag) { if (CharFlag) {
WordCount++; WordCount++;
} }
printf("\nTotal words in file: %lu\n", WordCount); printf("\nTotal words in file: %lu\n", WordCount);
return(0); return(0);
} }
```

252
16-02.md
View file

@ -22,147 +22,151 @@ generates.
**LISTING 16.2 L16-2.C** **LISTING 16.2 L16-2.C**
/* Word-counting program incorporating assembly language. Tested ```c
with Borland C++ in C compilation mode & the small model. */ /* Word-counting program incorporating assembly language. Tested
with Borland C++ in C compilation mode & the small model. */
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys\stat.h> #include <sys\stat.h>
#include <stdlib.h> #include <stdlib.h>
#include <io.h> #include <io.h>
#define BUFFER_SIZE 0x8000 /* largest chunk of file worked #define BUFFER_SIZE 0x8000 /* largest chunk of file worked
with at any one time */ with at any one time */
int main(int, char **); int main(int, char **);
void ScanBuffer(char *, unsigned int, char *, unsigned long *); void ScanBuffer(char *, unsigned int, char *, unsigned long *);
int main(int argc, char **argv) { int main(int argc, char **argv) {
int Handle; int Handle;
unsigned int BlockSize; unsigned int BlockSize;
long FileSize; long FileSize;
unsigned long WordCount = 0; unsigned long WordCount = 0;
char *Buffer, CharFlag = 0; char *Buffer, CharFlag = 0;
if (argc != 2) { if (argc != 2) {
printf("usage: wc <filename>\n"); printf("usage: wc <filename>\n");
exit(1); exit(1);
} }
if ((Buffer = malloc(BUFFER_SIZE)) == NULL) { if ((Buffer = malloc(BUFFER_SIZE)) == NULL) {
printf("Can't allocate adequate memory\n"); printf("Can't allocate adequate memory\n");
exit(1); exit(1);
} }
if ((Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1) { if ((Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1) {
printf("Can't open file %s\n", argv[1]); printf("Can't open file %s\n", argv[1]);
exit(1); exit(1);
} }
if ((FileSize = filelength(Handle)) == -1) { if ((FileSize = filelength(Handle)) == -1) {
printf("Error sizing file %s\n", argv[1]); printf("Error sizing file %s\n", argv[1]);
exit(1); exit(1);
} }
CharFlag = 0; CharFlag = 0;
while (FileSize > 0) { while (FileSize > 0) {
FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE)); FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE));
if (read(Handle, Buffer, BlockSize) == -1) { if (read(Handle, Buffer, BlockSize) == -1) {
printf("Error reading file %s\n", argv[1]); printf("Error reading file %s\n", argv[1]);
exit(1); exit(1);
} }
ScanBuffer(Buffer, BlockSize, &CharFlag, &WordCount); ScanBuffer(Buffer, BlockSize, &CharFlag, &WordCount);
} }
/* Catch the last word, if any */ /* Catch the last word, if any */
if (CharFlag) { if (CharFlag) {
WordCount++; WordCount++;
} }
printf("\nTotal words in file: %lu\n", WordCount); printf("\nTotal words in file: %lu\n", WordCount);
return(0); return(0);
} }
```
**LISTING 16.3 L16-3.ASM** **LISTING 16.3 L16-3.ASM**
; Assembly subroutine for Listing 16.2. Scans through Buffer, of ```nasm
; length BufferLength, counting words and updating WordCount as ; Assembly subroutine for Listing 16.2. Scans through Buffer, of
; appropriate. BufferLength must be > 0. *CharFlag and *WordCount ; length BufferLength, counting words and updating WordCount as
; should equal 0 on the first call. Tested with TASM. ; appropriate. BufferLength must be > 0. *CharFlag and *WordCount
; C near-callable as: ; should equal 0 on the first call. Tested with TASM.
; void ScanBuffer(char *Buffer, unsigned int BufferLength, ; C near-callable as:
; char *CharFlag, unsigned long *WordCount); ; void ScanBuffer(char *Buffer, unsigned int BufferLength,
; char *CharFlag, unsigned long *WordCount);
parms struc parms struc
dw 2 dup(?) ;pushed return address & BP dw 2 dup(?) ;pushed return address & BP
Buffer dw ? ;buffer to scan Buffer dw ? ;buffer to scan
BufferLength dw ? ;length of buffer to scan BufferLength dw ? ;length of buffer to scan
CharFlag dw ? ;pointer to flag for state of last CharFlag dw ? ;pointer to flag for state of last
; char processed on entry (0 on ; char processed on entry (0 on
; initial call). Updated on exit ; initial call). Updated on exit
WordCount dw ? ;pointer to 32-bit count of words WordCount dw ? ;pointer to 32-bit count of words
; found (0 on initial call) ; found (0 on initial call)
parms ends parms ends
.model small .model small
.code .code
public _ScanBuffer public _ScanBuffer
_ScanBuffer proc near _ScanBuffer proc near
push bp ;preserve caller's stack frame push bp ;preserve caller's stack frame
mov bp,sp ;set up local stack frame mov bp,sp ;set up local stack frame
push si ;preserve caller's register vars push si ;preserve caller's register vars
push di push di
mov si,[bp+Buffer] ;point to buffer to scan mov si,[bp+Buffer] ;point to buffer to scan
mov bx,[bp+WordCount] mov bx,[bp+WordCount]
mov cx,[bx] ;get current 32-bit word count mov cx,[bx] ;get current 32-bit word count
mov dx,[bx+2] mov dx,[bx+2]
mov bx,[bp+CharFlag] mov bx,[bp+CharFlag]
mov bl,[bx] ;get current CharFlag mov bl,[bx] ;get current CharFlag
mov di,[bp+BufferLength];get # of bytes to scan mov di,[bp+BufferLength];get # of bytes to scan
ScanLoop: ScanLoop:
mov bh,bl ;PredCharFlag = CharFlag; mov bh,bl ;PredCharFlag = CharFlag;
lodsb ;Ch = *BufferPtr++ & 0x7F; lodsb ;Ch = *BufferPtr++ & 0x7F;
and al,7fh ;strip high bit for word processors and al,7fh ;strip high bit for word processors
; that set it as an internal flag ; that set it as an internal flag
mov bl,1 ;assume this is a char; CharFlag = 1; mov bl,1 ;assume this is a char; CharFlag = 1;
cmp al,‘a' ;it is a char if between a and z cmp al,‘a' ;it is a char if between a and z
jb CheckAZ jb CheckAZ
cmp al,‘z' cmp al,‘z'
jna IsAChar jna IsAChar
CheckAZ: CheckAZ:
cmp al,‘A' ;it is a char if between A and Z cmp al,‘A' ;it is a char if between A and Z
jb Check09 jb Check09
cmp al,‘Z' cmp al,‘Z'
jna IsAChar jna IsAChar
Check09: Check09:
cmp al,‘0' ;it is a char if between 0 and 9 cmp al,‘0' ;it is a char if between 0 and 9
jb CheckApostrophe jb CheckApostrophe
cmp al,‘9' cmp al,‘9'
jna IsAChar jna IsAChar
CheckApostrophe: CheckApostrophe:
cmp al,27h ;it is a char if an apostrophe cmp al,27h ;it is a char if an apostrophe
jz IsAChar jz IsAChar
sub bl,bl ;not a char; CharFlag = 0; sub bl,bl ;not a char; CharFlag = 0;
and bh,bh and bh,bh
jz ScanLoopBottom ;if ((!CharFlag) && PredCharFlag) { jz ScanLoopBottom ;if ((!CharFlag) && PredCharFlag) {
add cx,1 ; (WordCount)++; add cx,1 ; (WordCount)++;
adc dx,0 ;} adc dx,0 ;}
IsAChar: IsAChar:
ScanLoopBottom: ScanLoopBottom:
dec di ;} while (—BufferLength); dec di ;} while (—BufferLength);
jnz ScanLoop jnz ScanLoop
mov si,[bp+CharFlag] mov si,[bp+CharFlag]
mov [si],bl ;set new CharFlag mov [si],bl ;set new CharFlag
mov bx,[bp+WordCount] mov bx,[bp+WordCount]
mov [bx],cx ;set new word count mov [bx],cx ;set new word count
mov [bx+2],dx mov [bx+2],dx
pop di ;restore caller's register vars pop di ;restore caller's register vars
pop si pop si
pop bp ;restore caller's stack frame pop bp ;restore caller's stack frame
ret ret
_ScanBuffer endp _ScanBuffer endp
end end
```
#### Which Way to Go from Here? {#Heading4} #### Which Way to Go from Here? {#Heading4}

171
16-03.md
View file

@ -12,97 +12,98 @@ pages: 303-305
**LISTING 16.4 L16-4.ASM** **LISTING 16.4 L16-4.ASM**
; Assembly subroutine for Listing 16.2. Scans through Buffer, of ```nasm
; length BufferLength, counting words and updating WordCount as ; Assembly subroutine for Listing 16.2. Scans through Buffer, of
; appropriate, using a lookup table-based approach. BufferLength ; length BufferLength, counting words and updating WordCount as
; must be > 0. *CharFlag and *WordCount should equal 0 on the ; appropriate, using a lookup table-based approach. BufferLength
; first call. Tested with TASM. ; must be > 0. *CharFlag and *WordCount should equal 0 on the
; C near-callable as: ; first call. Tested with TASM.
; void ScanBuffer(char *Buffer, unsigned int BufferLength, ; C near-callable as:
; char *CharFlag, unsigned long *WordCount); ; void ScanBuffer(char *Buffer, unsigned int BufferLength,
; char *CharFlag, unsigned long *WordCount);
parms struc parms struc
dw 2 dup(?) ;pushed return address & BP dw 2 dup(?) ;pushed return address & BP
Buffer dw ? ;buffer to scan Buffer dw ? ;buffer to scan
BufferLength dw ? ;length of buffer to scan BufferLength dw ? ;length of buffer to scan
CharFlag dw ? ;pointer to flag for state of last CharFlag dw ? ;pointer to flag for state of last
;char processed on entry (0 on ;char processed on entry (0 on
;initial call). Updated on exit ;initial call). Updated on exit
WordCount dw ? ;pointer to 32-bit count of words WordCount dw ? ;pointer to 32-bit count of words
; found (0 on initial call) ; found (0 on initial call)
parms ends parms ends
.model small .model small
.data .data
; Table of char/not statuses for byte values 0-255 (128-255 are ; Table of char/not statuses for byte values 0-255 (128-255 are
; duplicates of 0-127 to effectively mask off bit 7, which some ; duplicates of 0-127 to effectively mask off bit 7, which some
; word processors set as an internal flag). ; word processors set as an internal flag).
CharStatusTable label byte CharStatusTable label byte
REPT 2 REPT 2
db 39 dup(0) db 39 dup(0)
db 1 ;apostrophe db 1 ;apostrophe
db 8 dup(0) db 8 dup(0)
db 10 dup(1) ;0-9 db 10 dup(1) ;0-9
db 7 dup(0) db 7 dup(0)
db 26 dup(1) ;A-Z db 26 dup(1) ;A-Z
db 6 dup(0) db 6 dup(0)
db 26 dup(1) ;a-z db 26 dup(1) ;a-z
db 5 dup(0) db 5 dup(0)
ENDM ENDM
.code .code
public _ScanBuffer public _ScanBuffer
_ScanBuffer proc near _ScanBuffer proc near
push bp ;preserve caller's stack frame push bp ;preserve caller's stack frame
mov bp,sp ;set up local stack frame mov bp,sp ;set up local stack frame
push si ;preserve caller's register vars push si ;preserve caller's register vars
push di push di
mov si,[bp+Buffer] ;point to buffer to scan mov si,[bp+Buffer] ;point to buffer to scan
mov bx,[bp+WordCount] mov bx,[bp+WordCount]
mov di,[bx] ;get current 32-bit word count mov di,[bx] ;get current 32-bit word count
mov dx,[bx+2] mov dx,[bx+2]
mov bx,[bp+CharFlag] mov bx,[bp+CharFlag]
mov al,[bx] ;get current CharFlag mov al,[bx] ;get current CharFlag
mov cx,[bp+BufferLength] ;get # of bytes to scan mov cx,[bp+BufferLength] ;get # of bytes to scan
mov bx,offset CharStatusTable mov bx,offset CharStatusTable
ScanLoop: ScanLoop:
and al,al ;ZF=0 if last byte was a char, and al,al ;ZF=0 if last byte was a char,
; ZF=1 if not ; ZF=1 if not
lodsb ;get the next byte lodsb ;get the next byte
;***doesn't change flags*** ;***doesn't change flags***
xlat ;look up its char/not status xlat ;look up its char/not status
;***doesn't change flags*** ;***doesn't change flags***
jz ScanLoopBottom ;don't count a word if last byte was jz ScanLoopBottom ;don't count a word if last byte was
; not a character ; not a character
and al,al ;last byte was a character; is the and al,al ;last byte was a character; is the
; current byte a character? ; current byte a character?
jz CountWord ;no, so count a word jz CountWord ;no, so count a word
ScanLoopBottom: ScanLoopBottom:
dec cx ;count down buffer length dec cx ;count down buffer length
jnz ScanLoop jnz ScanLoop
Done: Done:
mov si,[bp+CharFlag] mov si,[bp+CharFlag]
mov [si],al ;set new CharFlag mov [si],al ;set new CharFlag
mov bx,[bp+WordCount] mov bx,[bp+WordCount]
mov [bx],di ;set new word count mov [bx],di ;set new word count
mov [bx+2],dx mov [bx+2],dx
pop di ;restore caller's register vars pop di ;restore caller's register vars
pop si pop si
pop bp ;restore caller's stack frame pop bp ;restore caller's stack frame
ret ret
align 2
CountWord:
add di,1 ;increment the word count
adc dx,0
dec cx ;count down buffer length
jnz ScanLoop
jmp Done
_ScanBuffer endp
end
align 2
CountWord:
add di,1 ;increment the word count
adc dx,0
dec cx ;count down buffer length
jnz ScanLoop
jmp Done
_ScanBuffer endp
end
```
Listing 16.4 features several interesting tricks. First, it uses Listing 16.4 features several interesting tricks. First, it uses
**LODSB** and **XLAT** in succession, a very neat way to get a **LODSB** and **XLAT** in succession, a very neat way to get a

View file

@ -40,9 +40,9 @@ over Terje's 386 native-mode code, and found the critical inner loop,
which was indeed as tight as one could imagine, consisting of just a few which was indeed as tight as one could imagine, consisting of just a few
386 native-mode instructions. However, one of the instructions was this: 386 native-mode instructions. However, one of the instructions was this:
```nasm
CMP DH,[EBX+EAX] CMP DH,[EBX+EAX]
```
Harmless enough, save for two things. First, EBX happened to be zero at Harmless enough, save for two things. First, EBX happened to be zero at
this point (a leftover from an earlier version of the code, as it turned this point (a leftover from an earlier version of the code, as it turned

322
16-05.md
View file

@ -36,181 +36,183 @@ Table: Table 16.2 The top four word-counting entries.
**LISTING 16.5 QSCAN3.ASM** **LISTING 16.5 QSCAN3.ASM**
; QSCAN3.ASM ```nasm
; David Stafford ; QSCAN3.ASM
; David Stafford
COMMENT $ COMMENT $
How it works How it works
—————— ——————
The idea is to go through the buffer fetching each letter-pair (words The idea is to go through the buffer fetching each letter-pair (words
rather than bytes). The carry flag indicates whether we are rather than bytes). The carry flag indicates whether we are
currently in a (text) word or not. The letter-pair fetched from the currently in a (text) word or not. The letter-pair fetched from the
buffer is converted to a 16-bit address by shifting it left one bit buffer is converted to a 16-bit address by shifting it left one bit
(losing the high bit of the second character) and putting the carry (losing the high bit of the second character) and putting the carry
flag in the low bit. The high bit of the count register is set to flag in the low bit. The high bit of the count register is set to
1. Then the count register is added to the byte found at the given 1. Then the count register is added to the byte found at the given
address in a large (64K, naturally) table. The byte at the given address in a large (64K, naturally) table. The byte at the given
address will contain a 1 in the high bit if the last character of the address will contain a 1 in the high bit if the last character of the
letter-pair is a word-letter (alphanumeric or apostrophe). This will letter-pair is a word-letter (alphanumeric or apostrophe). This will
set the carry flag since the high bit of the count register is also a set the carry flag since the high bit of the count register is also a
1. The low bit of the byte found at the given address will be one if 1. The low bit of the byte found at the given address will be one if
the second character of the previous letter-pair was a word-letter the second character of the previous letter-pair was a word-letter
and the first character of this letter-pair is not a word-letter. It and the first character of this letter-pair is not a word-letter. It
will also be 1 if the first character of this letter-pair is a will also be 1 if the first character of this letter-pair is a
word-letter but the second character is not. This process is word-letter but the second character is not. This process is
repeated. Finally, the carry flag is saved to indicate the final repeated. Finally, the carry flag is saved to indicate the final
in-a-word/not-in-a-word status. The count register is masked to in-a-word/not-in-a-word status. The count register is masked to
remove the high bit and the count of words remains in the count remove the high bit and the count of words remains in the count
register. register.
Sound complicated? You're right! But it's fast! Sound complicated? You're right! But it's fast!
The beauty of this method is that no jumps are required, the The beauty of this method is that no jumps are required, the
operations are fast, it requires only one table and the process can operations are fast, it requires only one table and the process can
be repeated (unrolled) many times. QSCAN3 can read 256 bytes without be repeated (unrolled) many times. QSCAN3 can read 256 bytes without
jumping. jumping.
COMMEND $ COMMEND $
.model small .model small
.code .code
Test1 macro x,y ;9 or 10 bytes Test1 macro x,y ;9 or 10 bytes
Addr&x: mov di,[bp+y] ;3 or 4 bytes Addr&x: mov di,[bp+y] ;3 or 4 bytes
adc di,di adc di,di
or ax,si or ax,si
add al,[di] add al,[di]
endm endm
Test2 macro x,y ;7 or 8 bytes Test2 macro x,y ;7 or 8 bytes
Addr&x: mov di,[bp+y] ;3 or 4 bytes Addr&x: mov di,[bp+y] ;3 or 4 bytes
adc di,di adc di,di
add ah,[di] add ah,[di]
endm endm
Scan = 128 ;scan 256 bytes at a time Scan = 128 ;scan 256 bytes at a time
Buffer = 4 ;parms Buffer = 4 ;parms
BufferLength = 6 BufferLength = 6
CharFlag = 8 CharFlag = 8
WordCount = 10 WordCount = 10
public _ScanBuffer public _ScanBuffer
_ScanBuffer proc near _ScanBuffer proc near
push bp push bp
mov bp,sp mov bp,sp
push si push si
push di push di
xor cx,cx xor cx,cx
mov si,[bp+Buffer] ;si = text buffer mov si,[bp+Buffer] ;si = text buffer
mov ax,[bp+BufferLength] ;dx = length in bytes mov ax,[bp+BufferLength] ;dx = length in bytes
shr ax,1 ;dx = length in words shr ax,1 ;dx = length in words
jnz NormalBuf jnz NormalBuf
OneByteBuf: OneByteBuf:
mov ax,seg WordTable mov ax,seg WordTable
mov es,ax mov es,ax
mov di,[bp+CharFlag] mov di,[bp+CharFlag]
mov bh,[di] ;bh = old CharFlag mov bh,[di] ;bh = old CharFlag
mov bl,[si] ;bl = character mov bl,[si] ;bl = character
add bh,‘A'-1 ;make bh into character add bh,‘A'-1 ;make bh into character
add bx,bx ;prepare to index add bx,bx ;prepare to index
mov al,es:[bx] mov al,es:[bx]
cbw ;get hi bit in ah (then bh) cbw ;get hi bit in ah (then bh)
shr al,1 ;get low bit shr al,1 ;get low bit
adc cx,cx ;cx = 0 or 1 adc cx,cx ;cx = 0 or 1
xchg ax,bx xchg ax,bx
jmp CleanUp jmp CleanUp
NormalBuf: NormalBuf:
push bp ;(1) push bp ;(1)
pushf ;(2) pushf ;(2)
cwd ;dx = 0 cwd ;dx = 0
mov cl,Scan mov cl,Scan
div cx div cx
or dx,dx ;remainder? or dx,dx ;remainder?
jz StartAtTheTop ;nope, do the whole banana jz StartAtTheTop ;nope, do the whole banana
sub cx,dx sub cx,dx
sub si,cx ;adjust buf pointer sub si,cx ;adjust buf pointer
sub si,cx sub si,cx
inc ax ;adjust for partial read inc ax ;adjust for partial read
StartAtTheTop: mov bx,dx ;get index for start... StartAtTheTop: mov bx,dx ;get index for start...
shl bx,1 shl bx,1
mov di,LoopEntry[bx] ;...address in di mov di,LoopEntry[bx] ;...address in di
xchg dx,ax ;dx is the loop counter xchg dx,ax ;dx is the loop counter
xor cx,cx ;total word count xor cx,cx ;total word count
mov bx,[bp+CharFlag] mov bx,[bp+CharFlag]
mov bl,[bx] ;bl = old CharFlag mov bl,[bx] ;bl = old CharFlag
mov bp,seg WordTable mov bp,seg WordTable
mov ds,bp mov ds,bp
mov bp,si ;scan buffer with bp mov bp,si ;scan buffer with bp
mov si,8080h ;hi bits mov si,8080h ;hi bits
mov ax,si ;init local word counter mov ax,si ;init local word counter
shr bl,1 ;carry = old CharFlag shr bl,1 ;carry = old CharFlag
jmp di jmp di
align 2 align 2
Top: add bx,bx ;restore carry Top: add bx,bx ;restore carry
n = 0 n = 0
rept Scan/2 rept Scan/2
Test1 %n,%n*2 Test1 %n,%n*2
Test2 %n+1,%n*2+2 Test2 %n+1,%n*2+2
n = n+2 n = n+2
endm endm
EndCount: EndCount:
sbb bx,bx ;save carry sbb bx,bx ;save carry
if Scan ge 128 ;because al+ah may equal 128! if Scan ge 128 ;because al+ah may equal 128!
or ax,si or ax,si
add al,ah add al,ah
mov ah,0 mov ah,0
else else
add al,ah add al,ah
and ax,7fh ;mask and ax,7fh ;mask
endif endif
add cx,ax ;update word count add cx,ax ;update word count
mov ax,si mov ax,si
add bp,Scan*2 add bp,Scan*2
dec dx ;any left? dec dx ;any left?
jng Quit jng Quit
jmp Top jmp Top
Quit: popf ;(2) even or odd buffer? Quit: popf ;(2) even or odd buffer?
jnc ItsEven jnc ItsEven
clc clc
Test1 Odd,-1 Test1 Odd,-1
sbb bx,bx ;save carry sbb bx,bx ;save carry
shr ax,1 shr ax,1
adc cx,0 adc cx,0
ItsEven: ItsEven:
push ss ;restore ds push ss ;restore ds
pop ds pop ds
pop bp ;(1) pop bp ;(1)
CleanUp: CleanUp:
mov si,[bp+WordCount] mov si,[bp+WordCount]
add [si],cx add [si],cx
adc word ptr [si+2],0 adc word ptr [si+2],0
and bh,1 ;save only the carry flag and bh,1 ;save only the carry flag
mov si,[bp+CharFlag] mov si,[bp+CharFlag]
mov [si],bh mov [si],bh
pop di pop di
pop si pop si
pop bp pop bp
ret ret
_ScanBuffer endp _ScanBuffer endp
.data .data
Address macro X Address macro X
dw Addr&X dw Addr&X
endm endm
LoopEntry label word LoopEntry label word
n = Scan n = Scan
REPT Scan REPT Scan
Address %n MOD Scan Address %n MOD Scan
n = n - 1 n = n - 1
ENDM ENDM
.fardata WordTable .fardata WordTable
include qscan3.inc ;built by MAKETAB include qscan3.inc ;built by MAKETAB
end end
```

210
16-07.md
View file

@ -12,110 +12,112 @@ pages: 313-316
**Listing 16.6 OPT2.ASM** **Listing 16.6 OPT2.ASM**
; ```nasm
; Opt2 Final optimization word count ;
; Written by Michael Abrash ; Opt2 Final optimization word count
; Modified by Willem Clements ; Written by Michael Abrash
; C/ Moncayo 5, Laurel de la Reina ; Modified by Willem Clements
; 18140 La Zubia ; C/ Moncayo 5, Laurel de la Reina
; Granada, Spain ; 18140 La Zubia
; Tel 34-58-890398 ; Granada, Spain
; Fax 34-58-224102 ; Tel 34-58-890398
; ; Fax 34-58-224102
parms struc ;
dw 2 dup(?) parms struc
buffer dw ? dw 2 dup(?)
bufferlength dw ? buffer dw ?
charflag dw ? bufferlength dw ?
wordcount dw ? charflag dw ?
parms ends wordcount dw ?
.model small parms ends
.data .model small
charstatustable label byte .data
rept 2 charstatustable label byte
db 39 dup(0) rept 2
db 1 db 39 dup(0)
db 8 dup(0) db 1
db 10 dup(1) db 8 dup(0)
db 7 dup(0) db 10 dup(1)
db 26 dup(1) db 7 dup(0)
db 6 dup(0) db 26 dup(1)
db 26 dup(1) db 6 dup(0)
db 5 dup(0) db 26 dup(1)
endm db 5 dup(0)
.code endm
public _ScanBuffer .code
_ScanBuffer proc near public _ScanBuffer
push bp _ScanBuffer proc near
mov bp,sp push bp
push si mov bp,sp
push di push si
mov si,[bp+buffer] push di
mov bx,[bp+charflag] mov si,[bp+buffer]
mov al,[bx] mov bx,[bp+charflag]
mov cx,[bp+bufferlength] mov al,[bx]
mov bx,offset charstatustable mov cx,[bp+bufferlength]
xor di,di ; set wordcount to zero mov bx,offset charstatustable
shr cx,1 ; change count to wordcount xor di,di ; set wordcount to zero
jc oddentry ; odd number of bytes to process shr cx,1 ; change count to wordcount
cmp al,01h ; check if last one is char jc oddentry ; odd number of bytes to process
jne scanloop4 ; if not so, search for char cmp al,01h ; check if last one is char
jmp scanloop1 ; if so, search for zero jne scanloop4 ; if not so, search for char
oddentry: xchg al,ah ; last one in ah jmp scanloop1 ; if so, search for zero
lodsb ; get first byte oddentry: xchg al,ah ; last one in ah
inc cx lodsb ; get first byte
cmp ah,01h ; check if last one was char inc cx
jne scanloop5 ; if not so, search for char cmp ah,01h ; check if last one was char
jmp scanloop2 ; if so, search for zero jne scanloop5 ; if not so, search for char
; jmp scanloop2 ; if so, search for zero
; locate the end of a word ;
scanloop1: lodsw ; get two chars ; locate the end of a word
xlat ; translate first scanloop1: lodsw ; get two chars
xchg al,ah ; first in ah xlat ; translate first
scanloop2: xlat ; translate second xchg al,ah ; first in ah
dec cx ; count down scanloop2: xlat ; translate second
jz done1 ; no more bytes left dec cx ; count down
cmp ax,0101h ; check if two chars jz done1 ; no more bytes left
je scanloop1 ; go for next two bytes cmp ax,0101h ; check if two chars
inc di ; increase wordcount je scanloop1 ; go for next two bytes
cmp al,01h ; check if new word started inc di ; increase wordcount
je scanloop1 ; locate end of word cmp al,01h ; check if new word started
; je scanloop1 ; locate end of word
; locate the begin of a word ;
scanloop4: lodsw ; get two chars ; locate the begin of a word
xlat ; translate first scanloop4: lodsw ; get two chars
xchg al,ah ; first in ah xlat ; translate first
scanloop5: xlat ; translate second xchg al,ah ; first in ah
dec cx ; count down scanloop5: xlat ; translate second
jz done2 ; no more bytes left dec cx ; count down
cmp ax,0 ; check if word started jz done2 ; no more bytes left
je scanloop4 ; if not, locate begin cmp ax,0 ; check if word started
cmp al,01h ; check one-letter word je scanloop4 ; if not, locate begin
je scanloop1 ; if not, locate end of word cmp al,01h ; check one-letter word
inc di ; increase wordcount je scanloop1 ; if not, locate end of word
jmp scanloop4 ; locate begin of next word inc di ; increase wordcount
done1: cmp ax,0101h ; check if end-of-word jmp scanloop4 ; locate begin of next word
je done ; if not, we have finished done1: cmp ax,0101h ; check if end-of-word
inc di ; increase wordcount je done ; if not, we have finished
jmp done inc di ; increase wordcount
done2: cmp ax,0100h ; check for one-letter word jmp done
jne done ; if not, we have finished done2: cmp ax,0100h ; check for one-letter word
inc di ; increase wordcount jne done ; if not, we have finished
done: mov si,[bp+charflag] inc di ; increase wordcount
mov [si],al done: mov si,[bp+charflag]
mov bx,[bp+wordcount] mov [si],al
mov ax,[bx] mov bx,[bp+wordcount]
mov dx,[bx+2] mov ax,[bx]
add di,ax mov dx,[bx+2]
adc dx,0 add di,ax
mov [bx],di adc dx,0
mov [bx+2],dx mov [bx],di
pop di mov [bx+2],dx
pop si pop di
pop bp pop si
ret pop bp
_ScanBuffer endp ret
end _ScanBuffer endp
end
```
### Level 2: A New Perspective {#Heading11} ### Level 2: A New Perspective {#Heading11}

View file

@ -12,20 +12,21 @@ pages: 316-319
**Listing 16.7 L16-7.ASM** **Listing 16.7 L16-7.ASM**
ScanLoop: ```nasm
lodsw ;get the next 2 bytes (AL = first, AH = 2nd) ScanLoop:
xlat ;look up first's char/not status lodsw ;get the next 2 bytes (AL = first, AH = 2nd)
xor dl,al ;see if there's a new char/not status xlat ;look up first's char/not status
add di,dx ;we add 1 for each char/not transition xor dl,al ;see if there's a new char/not status
mov dl,al add di,dx ;we add 1 for each char/not transition
mov al,ah ;look at the second byte mov dl,al
xlat ;look up its char/not status mov al,ah ;look at the second byte
xor dl,al ;see if there's a new char/not status xlat ;look up its char/not status
add di,dx ;we add 1 for each char/not transition xor dl,al ;see if there's a new char/not status
mov dl,al add di,dx ;we add 1 for each char/not transition
dec dx mov dl,al
jnz ScanLoop dec dx
jnz ScanLoop
```
John later divides the transition count by two to get the word count. John later divides the transition count by two to get the word count.
(Food for thought: It's also possible to use **CMP** and **ADC** to (Food for thought: It's also possible to use **CMP** and **ADC** to
@ -66,39 +67,40 @@ report a "location counter overflow" warning; ignore it.)
**LISTING 16.8 MAKETAB.C** **LISTING 16.8 MAKETAB.C**
// MAKETAB.C — Build QSCAN3.INC for QSCAN3.ASM ```c
// MAKETAB.C — Build QSCAN3.INC for QSCAN3.ASM
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
#define ChType( c ) (((c) & 0x7f) == ‘\'' || isalnum((c) & 0x7f)) #define ChType( c ) (((c) & 0x7f) == ‘\'' || isalnum((c) & 0x7f))
int NoCarry[ 4 ] = { 0, 0x80, 1, 0x80 }; int NoCarry[ 4 ] = { 0, 0x80, 1, 0x80 };
int Carry[ 4 ] = { 1, 0x81, 1, 0x80 }; int Carry[ 4 ] = { 1, 0x81, 1, 0x80 };
void main( void ) void main( void )
{ {
int ahChar, alChar, i; int ahChar, alChar, i;
FILE *t = fopen( "QSCAN3.INC", "wt" ); FILE *t = fopen( "QSCAN3.INC", "wt" );
printf( "Building table. Please wait..." ); printf( "Building table. Please wait..." );
for( ahChar = 0; ahChar < 128; ahChar++ ) for( ahChar = 0; ahChar < 128; ahChar++ )
{ {
for( alChar = 0; alChar < 256; alChar++ ) for( alChar = 0; alChar < 256; alChar++ )
{ {
i = ChType( alChar ) * 2 + ChType( ahChar ); i = ChType( alChar ) * 2 + ChType( ahChar );
if( alChar % 8 == 0 ) fprintf( t, "\ndb %02Xh", NoCarry[ i ] ); if( alChar % 8 == 0 ) fprintf( t, "\ndb %02Xh", NoCarry[ i ] );
else fprintf( t, ",%02Xh", NoCarry[ i ] ); else fprintf( t, ",%02Xh", NoCarry[ i ] );
fprintf( t, ",%02Xh", Carry[ i ] ); fprintf( t, ",%02Xh", Carry[ i ] );
} }
} }
fclose( t );
}
fclose( t );
}
```
David's approach is simplicity itself, although his implementation David's approach is simplicity itself, although his implementation
arguably is not. Consider any three sequential bytes in the buffer. arguably is not. Consider any three sequential bytes in the buffer.

446
17-02.md
View file

@ -12,256 +12,260 @@ pages: 325-329
**LISTING 17.1 L17-1.CPP** **LISTING 17.1 L17-1.CPP**
/* C++ Game of Life implementation for any mode for which mode set ```cpp
and draw pixel functions can be provided. /* C++ Game of Life implementation for any mode for which mode set
Tested with Borland C++ in the small model. */ and draw pixel functions can be provided.
#include <stdlib.h> Tested with Borland C++ in the small model. */
#include <stdio.h> #include <stdlib.h>
#include <iostream.h> #include <stdio.h>
#include <conio.h> #include <iostream.h>
#include <time.h> #include <conio.h>
#include <dos.h> #include <time.h>
#include <bios.h> #include <dos.h>
#include <mem.h> #include <bios.h>
#include <mem.h>
#define ON_COLOR 15 // on-cell pixel color #define ON_COLOR 15 // on-cell pixel color
#define OFF_COLOR 0 // off-cell pixel color #define OFF_COLOR 0 // off-cell pixel color
#define MSG_LINE 10 // row for text messages #define MSG_LINE 10 // row for text messages
#define GENERATION_LINE 12 // row for generation # display #define GENERATION_LINE 12 // row for generation # display
#define LIMIT_18_HZ 1 // set 1 for maximum frame rate = 18Hz #define LIMIT_18_HZ 1 // set 1 for maximum frame rate = 18Hz
#define WRAP_EDGES 1 // set to 0 to disable wrapping around #define WRAP_EDGES 1 // set to 0 to disable wrapping around
// at cell map edges // at cell map edges
class cellmap { class cellmap {
private: private:
unsigned char *cells; unsigned char *cells;
unsigned int width; unsigned int width;
unsigned int width_in_bytes; unsigned int width_in_bytes;
unsigned int height; unsigned int height;
unsigned int length_in_bytes; unsigned int length_in_bytes;
public: public:
cellmap(unsigned int h, unsigned int v); cellmap(unsigned int h, unsigned int v);
~cellmap(void); ~cellmap(void);
void copy_cells(cellmap &sourcemap); void copy_cells(cellmap &sourcemap);
void set_cell(unsigned int x, unsigned int y); void set_cell(unsigned int x, unsigned int y);
void clear_cell(unsigned int x, unsigned int y); void clear_cell(unsigned int x, unsigned int y);
int cell_state(int x, int y); int cell_state(int x, int y);
void next_generation(cellmap& dest_map); void next_generation(cellmap& dest_map);
}; };
extern void enter_display_mode(void); extern void enter_display_mode(void);
extern void exit_display_mode(void); extern void exit_display_mode(void);
extern void draw_pixel(unsigned int X, unsigned int Y, extern void draw_pixel(unsigned int X, unsigned int Y,
unsigned int Color); unsigned int Color);
extern void show_text(int x, int y, char *text); extern void show_text(int x, int y, char *text);
/* Controls the size of the cell map. Must be within the capabilities /* Controls the size of the cell map. Must be within the capabilities
of the display mode, and must be limited to leave room for text of the display mode, and must be limited to leave room for text
display at right. */ display at right. */
unsigned int cellmap_width = 96; unsigned int cellmap_width = 96;
unsigned int cellmap_height = 96; unsigned int cellmap_height = 96;
/* Width & height in pixels of each cell as displayed on screen. */ /* Width & height in pixels of each cell as displayed on screen. */
unsigned int magnifier = 2; unsigned int magnifier = 2;
void main() void main()
{ {
unsigned int init_length, x, y, seed; unsigned int init_length, x, y, seed;
unsigned long generation = 0; unsigned long generation = 0;
char gen_text[80]; char gen_text[80];
long bios_time, start_bios_time; long bios_time, start_bios_time;
cellmap current_map(cellmap_height, cellmap_width); cellmap current_map(cellmap_height, cellmap_width);
cellmap next_map(cellmap_height, cellmap_width); cellmap next_map(cellmap_height, cellmap_width);
// Get the seed; seed randomly if 0 entered // Get the seed; seed randomly if 0 entered
cout << "Seed (0 for random seed): "; cout << "Seed (0 for random seed): ";
cin >> seed; cin >> seed;
if (seed == 0) seed = (unsigned) time(NULL); if (seed == 0) seed = (unsigned) time(NULL);
// Randomly initialize the initial cell map // Randomly initialize the initial cell map
cout << "Initializing..."; cout << "Initializing...";
srand(seed); srand(seed);
init_length = (cellmap_height * cellmap_width) / 2; init_length = (cellmap_height * cellmap_width) / 2;
do { do {
x = random(cellmap_width); x = random(cellmap_width);
y = random(cellmap_height); y = random(cellmap_height);
next_map.set_cell(x, y); next_map.set_cell(x, y);
} while (—init_length); } while (—init_length);
current_map.copy_cells(next_map); // put init map in current_map current_map.copy_cells(next_map); // put init map in current_map
enter_display_mode(); enter_display_mode();
// Keep recalculating and redisplaying generations until a key // Keep recalculating and redisplaying generations until a key
// is pressed // is pressed
show_text(0, MSG_LINE, "Generation: "); show_text(0, MSG_LINE, "Generation: ");
start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time); start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time);
do { do {
generation++; generation++;
sprintf(gen_text, "%10lu", generation); sprintf(gen_text, "%10lu", generation);
show_text(1, GENERATION_LINE, gen_text); show_text(1, GENERATION_LINE, gen_text);
// Recalculate and draw the next generation // Recalculate and draw the next generation
current_map.next_generation(next_map); current_map.next_generation(next_map);
// Make current_map current again // Make current_map current again
current_map.copy_cells(next_map); current_map.copy_cells(next_map);
#if LIMIT_18_HZ #if LIMIT_18_HZ
// Limit to a maximum of 18.2 frames per second,for visibility // Limit to a maximum of 18.2 frames per second,for visibility
do { do {
_bios_timeofday(_TIME_GETCLOCK, &bios_time); _bios_timeofday(_TIME_GETCLOCK, &bios_time);
} while (start_bios_time == bios_time); } while (start_bios_time == bios_time);
start_bios_time = bios_time; start_bios_time = bios_time;
#endif #endif
} while (!kbhit()); } while (!kbhit());
getch(); // clear keypress getch(); // clear keypress
exit_display_mode(); exit_display_mode();
cout << "Total generations: " << generation << "\nSeed: " << cout << "Total generations: " << generation << "\nSeed: " <<
seed << "\n"; seed << "\n";
} }
/* cellmap constructor. */ /* cellmap constructor. */
cellmap::cellmap(unsigned int h, unsigned int w) cellmap::cellmap(unsigned int h, unsigned int w)
{ {
width = w; width = w;
width_in_bytes = (w + 7) / 8; width_in_bytes = (w + 7) / 8;
height = h; height = h;
length_in_bytes = width_in_bytes * h; length_in_bytes = width_in_bytes * h;
cells = new unsigned char[length_in_bytes]; // cell storage cells = new unsigned char[length_in_bytes]; // cell storage
memset(cells, 0, length_in_bytes); // clear all cells, to start memset(cells, 0, length_in_bytes); // clear all cells, to start
} }
/* cellmap destructor. */ /* cellmap destructor. */
cellmap::~cellmap(void) cellmap::~cellmap(void)
{ {
delete[] cells; delete[] cells;
} }
/* Copies one cellmap's cells to another cellmap. Both cellmaps are /* Copies one cellmap's cells to another cellmap. Both cellmaps are
assumed to be the same size. */ assumed to be the same size. */
void cellmap::copy_cells(cellmap &sourcemap) void cellmap::copy_cells(cellmap &sourcemap)
{ {
memcpy(cells, sourcemap.cells, length_in_bytes); memcpy(cells, sourcemap.cells, length_in_bytes);
} }
/* Turns cell on. */ /* Turns cell on. */
void cellmap::set_cell(unsigned int x, unsigned int y) void cellmap::set_cell(unsigned int x, unsigned int y)
{ {
unsigned char *cell_ptr = unsigned char *cell_ptr =
cells + (y * width_in_bytes) + (x / 8); cells + (y * width_in_bytes) + (x / 8);
*(cell_ptr) |= 0x80 >> (x & 0x07); *(cell_ptr) |= 0x80 >> (x & 0x07);
} }
/* Turns cell off. */ /* Turns cell off. */
void cellmap::clear_cell(unsigned int x, unsigned int y) void cellmap::clear_cell(unsigned int x, unsigned int y)
{ {
unsigned char *cell_ptr = unsigned char *cell_ptr =
cells + (y * width_in_bytes) + (x / 8); cells + (y * width_in_bytes) + (x / 8);
*(cell_ptr) &= ~(0x80 >> (x & 0x07)); *(cell_ptr) &= ~(0x80 >> (x & 0x07));
} }
/* Returns cell state (1=on or 0=off), optionally wrapping at the /* Returns cell state (1=on or 0=off), optionally wrapping at the
borders around to the opposite edge. */ borders around to the opposite edge. */
int cellmap::cell_state(int x, int y) int cellmap::cell_state(int x, int y)
{ {
unsigned char *cell_ptr; unsigned char *cell_ptr;
#if WRAP_EDGES #if WRAP_EDGES
while (x < 0) x += width; // wrap, if necessary while (x < 0) x += width; // wrap, if necessary
while (x >= width) x -= width; while (x >= width) x -= width;
while (y < 0) y += height; while (y < 0) y += height;
while (y >= height) y -= height; while (y >= height) y -= height;
#else #else
if ((x < 0) || (x >= width) || (y < 0) || (y >= height)) if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
return 0; // return 0 for off edges if no wrapping return 0; // return 0 for off edges if no wrapping
#endif #endif
cell_ptr = cells + (y * width_in_bytes) + (x / 8); cell_ptr = cells + (y * width_in_bytes) + (x / 8);
return (*cell_ptr & (0x80 >> (x & 0x07))) ? 1 : 0; return (*cell_ptr & (0x80 >> (x & 0x07))) ? 1 : 0;
} }
/* Calculates the next generation of a cellmap and stores it in /* Calculates the next generation of a cellmap and stores it in
next_map. */ next_map. */
void cellmap::next_generation(cellmap& next_map) void cellmap::next_generation(cellmap& next_map)
{ {
unsigned int x, y, neighbor_count; unsigned int x, y, neighbor_count;
for (y=0; y<height; y++) { for (y=0; y<height; y++) {
for (x=0; x<width; x++) { for (x=0; x<width; x++) {
// Figure out how many neighbors this cell has // Figure out how many neighbors this cell has
neighbor_count = cell_state(x-1, y-1) + cell_state(x, y-1) + neighbor_count = cell_state(x-1, y-1) + cell_state(x, y-1) +
cell_state(x+1, y-1) + cell_state(x-1, y) + cell_state(x+1, y-1) + cell_state(x-1, y) +
cell_state(x+1, y) + cell_state(x-1, y+1) + cell_state(x+1, y) + cell_state(x-1, y+1) +
cell_state(x, y+1) + cell_state(x+1, y+1); cell_state(x, y+1) + cell_state(x+1, y+1);
if (cell_state(x, y) == 1) { if (cell_state(x, y) == 1) {
// The cell is on; does it stay on? // The cell is on; does it stay on?
if ((neighbor_count != 2) && (neighbor_count != 3)) { if ((neighbor_count != 2) && (neighbor_count != 3)) {
next_map.clear_cell(x, y); // turn it off next_map.clear_cell(x, y); // turn it off
draw_pixel(x, y, OFF_COLOR); draw_pixel(x, y, OFF_COLOR);
} }
} else { } else {
// The cell is off; does it turn on? // The cell is off; does it turn on?
if (neighbor_count == 3) { if (neighbor_count == 3) {
next_map.set_cell(x, y); // turn it on next_map.set_cell(x, y); // turn it on
draw_pixel(x, y, ON_COLOR); draw_pixel(x, y, ON_COLOR);
} }
} }
} }
} }
} }
```
**LISTING 17.2 L17-2.CPP** **LISTING 17.2 L17-2.CPP**
/* VGA mode 13h functions for Game of Life. ```cpp
Tested with Borland C++. */ /* VGA mode 13h functions for Game of Life.
#include <stdio.h> Tested with Borland C++. */
#include <conio.h> #include <stdio.h>
#include <dos.h> #include <conio.h>
#include <dos.h>
#define TEXT_X_OFFSET 27 #define TEXT_X_OFFSET 27
#define SCREEN_WIDTH_IN_BYTES 320 #define SCREEN_WIDTH_IN_BYTES 320
/* Width & height in pixels of each cell. */ /* Width & height in pixels of each cell. */
extern unsigned int magnifier; extern unsigned int magnifier;
/* Mode 13h draw pixel function. Pixels are of width & height /* Mode 13h draw pixel function. Pixels are of width & height
specified by magnifier. */ specified by magnifier. */
void draw_pixel(unsigned int x, unsigned int y, unsigned int color) void draw_pixel(unsigned int x, unsigned int y, unsigned int color)
{ {
#define SCREEN_SEGMENT 0xA000 #define SCREEN_SEGMENT 0xA000
unsigned char far *screen_ptr; unsigned char far *screen_ptr;
int i, j; int i, j;
FP_SEG(screen_ptr) = SCREEN_SEGMENT; FP_SEG(screen_ptr) = SCREEN_SEGMENT;
FP_OFF(screen_ptr) = FP_OFF(screen_ptr) =
y * magnifier * SCREEN_WIDTH_IN_BYTES + x * magnifier; y * magnifier * SCREEN_WIDTH_IN_BYTES + x * magnifier;
for (i=0; i<magnifier; i++) { for (i=0; i<magnifier; i++) {
for (j=0; j<magnifier; j++) { for (j=0; j<magnifier; j++) {
*(screen_ptr+j) = color; *(screen_ptr+j) = color;
} }
screen_ptr += SCREEN_WIDTH_IN_BYTES; screen_ptr += SCREEN_WIDTH_IN_BYTES;
} }
} }
/* Mode 13h mode-set function. */ /* Mode 13h mode-set function. */
void enter_display_mode() void enter_display_mode()
{ {
union REGS regset; union REGS regset;
regset.x.ax = 0x0013; regset.x.ax = 0x0013;
int86(0x10, &regset, &regset); int86(0x10, &regset, &regset);
} }
/* Text mode mode-set function. */ /* Text mode mode-set function. */
void exit_display_mode() void exit_display_mode()
{ {
union REGS regset; union REGS regset;
regset.x.ax = 0x0003; regset.x.ax = 0x0003;
int86(0x10, &regset, &regset); int86(0x10, &regset, &regset);
} }
/* Text display function. Offsets text to non-graphics area of /* Text display function. Offsets text to non-graphics area of
screen. */ screen. */
void show_text(int x, int y, char *text) void show_text(int x, int y, char *text)
{ {
gotoxy(TEXT_X_OFFSET + x, y); gotoxy(TEXT_X_OFFSET + x, y);
puts(text); puts(text);
} }
```

290
17-04.md
View file

@ -48,164 +48,166 @@ improvement.
**LISTING 17.3 L17-3.CPP** **LISTING 17.3 L17-3.CPP**
/* cellmap class definition, constructor, copy_cells(), set_cell(), ```cpp
clear_cell(), cell_state(), count_neighbors(), and /* cellmap class definition, constructor, copy_cells(), set_cell(),
next_generation() for fast, hard-wired neighbor count approach. clear_cell(), cell_state(), count_neighbors(), and
Otherwise, the same as Listing 17.1 */ next_generation() for fast, hard-wired neighbor count approach.
Otherwise, the same as Listing 17.1 */
class cellmap { class cellmap {
private: private:
unsigned char *cells; unsigned char *cells;
unsigned int width; unsigned int width;
unsigned int width_in_bytes; unsigned int width_in_bytes;
unsigned int height; unsigned int height;
unsigned int length_in_bytes; unsigned int length_in_bytes;
public: public:
cellmap(unsigned int h, unsigned int v); cellmap(unsigned int h, unsigned int v);
~cellmap(void); ~cellmap(void);
void copy_cells(cellmap &sourcemap); void copy_cells(cellmap &sourcemap);
void set_cell(unsigned int x, unsigned int y); void set_cell(unsigned int x, unsigned int y);
void clear_cell(unsigned int x, unsigned int y); void clear_cell(unsigned int x, unsigned int y);
int cell_state(int x, int y); int cell_state(int x, int y);
int count_neighbors(int x, int y); int count_neighbors(int x, int y);
void next_generation(cellmap& dest_map); void next_generation(cellmap& dest_map);
}; };
/* cellmap constructor. Pads around cell storage area with 1 extra /* cellmap constructor. Pads around cell storage area with 1 extra
byte, used for handling edge wrapping. */ byte, used for handling edge wrapping. */
cellmap::cellmap(unsigned int h, unsigned int w) cellmap::cellmap(unsigned int h, unsigned int w)
{ {
width = w; width = w;
width_in_bytes = ((w + 7) / 8) + 2; // pad each side with width_in_bytes = ((w + 7) / 8) + 2; // pad each side with
// 1 extra byte // 1 extra byte
height = h; height = h;
length_in_bytes = width_in_bytes * (h + 2); // pad top/bottom length_in_bytes = width_in_bytes * (h + 2); // pad top/bottom
// with 1 extra byte // with 1 extra byte
cells = new unsigned char[length_in_bytes]; // cell storage cells = new unsigned char[length_in_bytes]; // cell storage
memset(cells, 0, length_in_bytes); // clear all cells, to start memset(cells, 0, length_in_bytes); // clear all cells, to start
} }
/* Copies one cellmap's cells to another cellmap. If wrapping is /* Copies one cellmap's cells to another cellmap. If wrapping is
enabled, copies edge (wrap) bytes into opposite padding bytes in enabled, copies edge (wrap) bytes into opposite padding bytes in
source first, so that the padding bytes off each edge have the source first, so that the padding bytes off each edge have the
same values as would be found by wrapping around to the opposite same values as would be found by wrapping around to the opposite
edge. Both cellmaps are assumed to be the same size. */ edge. Both cellmaps are assumed to be the same size. */
void cellmap::copy_cells(cellmap &sourcemap) void cellmap::copy_cells(cellmap &sourcemap)
{ {
unsigned char *cell_ptr; unsigned char *cell_ptr;
int i; int i;
#if WRAP_EDGES #if WRAP_EDGES
// Copy left and right edges into padding bytes on right and left // Copy left and right edges into padding bytes on right and left
cell_ptr = sourcemap.cells + width_in_bytes; cell_ptr = sourcemap.cells + width_in_bytes;
for (i=0; i<height; i++) { for (i=0; i<height; i++) {
*cell_ptr = *(cell_ptr + width_in_bytes - 2); *cell_ptr = *(cell_ptr + width_in_bytes - 2);
*(cell_ptr + width_in_bytes - 1) = *(cell_ptr + 1); *(cell_ptr + width_in_bytes - 1) = *(cell_ptr + 1);
cell_ptr += width_in_bytes; cell_ptr += width_in_bytes;
} }
// Copy top and bottom edges into padding bytes on bottom and top // Copy top and bottom edges into padding bytes on bottom and top
memcpy(sourcemap.cells, sourcemap.cells + length_in_bytes - memcpy(sourcemap.cells, sourcemap.cells + length_in_bytes -
(width_in_bytes * 2), width_in_bytes); (width_in_bytes * 2), width_in_bytes);
memcpy(sourcemap.cells + length_in_bytes - width_in_bytes, memcpy(sourcemap.cells + length_in_bytes - width_in_bytes,
sourcemap.cells + width_in_bytes, width_in_bytes); sourcemap.cells + width_in_bytes, width_in_bytes);
#endif #endif
// Copy all cells to the destination // Copy all cells to the destination
memcpy(cells, sourcemap.cells, length_in_bytes); memcpy(cells, sourcemap.cells, length_in_bytes);
} }
/* Turns cell on. x and y are offset by 1 byte down and to the right, to compensate for the /* Turns cell on. x and y are offset by 1 byte down and to the right, to compensate for the
padding bytes around the cellmap. */ padding bytes around the cellmap. */
void cellmap::set_cell(unsigned int x, unsigned int y) void cellmap::set_cell(unsigned int x, unsigned int y)
{ {
unsigned char *cell_ptr = unsigned char *cell_ptr =
cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1); cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1);
*(cell_ptr) |= 0x80 >> (x & 0x07); *(cell_ptr) |= 0x80 >> (x & 0x07);
} }
/* Turns cell off. x and y are offset by 1 byte down and to the right, /* Turns cell off. x and y are offset by 1 byte down and to the right,
to compensate for the padding bytes around the cell map. */ to compensate for the padding bytes around the cell map. */
void cellmap::clear_cell(unsigned int x, unsigned int y) void cellmap::clear_cell(unsigned int x, unsigned int y)
{ {
unsigned char *cell_ptr = unsigned char *cell_ptr =
cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1); cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1);
*(cell_ptr) &= ~(0x80 >> (x & 0x07)); *(cell_ptr) &= ~(0x80 >> (x & 0x07));
} }
/* Returns cell state (1=on or 0=off). x and y are offset by 1 byte /* Returns cell state (1=on or 0=off). x and y are offset by 1 byte
down and to the right, to down and to the right, to
compensate for the padding bytes around compensate for the padding bytes around
the cell map. */ the cell map. */
int cellmap::cell_state(int x, int y) int cellmap::cell_state(int x, int y)
{ {
unsigned char *cell_ptr = unsigned char *cell_ptr =
cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1); cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1);
return (*cell_ptr & (0x80 >> (x & 0x07))) ? 1 : 0; return (*cell_ptr & (0x80 >> (x & 0x07))) ? 1 : 0;
} }
/* Counts the number of neighboring on-cells for specified cell. */ /* Counts the number of neighboring on-cells for specified cell. */
int cellmap::count_neighbors(int x, int y) int cellmap::count_neighbors(int x, int y)
{ {
unsigned char *cell_ptr, mask; unsigned char *cell_ptr, mask;
unsigned int neighbor_count; unsigned int neighbor_count;
// Point to upper left neighbor // Point to upper left neighbor
cell_ptr = cells + ((y * width_in_bytes) + ((x + 7) / 8)); cell_ptr = cells + ((y * width_in_bytes) + ((x + 7) / 8));
mask = 0x80 >> ((x - 1) & 0x07); mask = 0x80 >> ((x - 1) & 0x07);
// Count upper left neighbor // Count upper left neighbor
neighbor_count = (*cell_ptr & mask) ? 1 : 0; neighbor_count = (*cell_ptr & mask) ? 1 : 0;
// Count left neighbor // Count left neighbor
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++; if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
// Count lower left neighbor // Count lower left neighbor
if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++; if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++;
// Point to upper neighbor // Point to upper neighbor
if ((mask >>= 1) == 0) { if ((mask >>= 1) == 0) {
mask = 0x80; mask = 0x80;
cell_ptr++; cell_ptr++;
} }
// Count upper neighbor // Count upper neighbor
if ((*cell_ptr & mask)) neighbor_count++; if ((*cell_ptr & mask)) neighbor_count++;
// Count lower neighbor // Count lower neighbor
if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++; if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++;
// Point to upper right neighbor // Point to upper right neighbor
if ((mask >>= 1) == 0) { if ((mask >>= 1) == 0) {
mask = 0x80; mask = 0x80;
cell_ptr++; cell_ptr++;
} }
// Count upper right neighbor // Count upper right neighbor
if ((*cell_ptr & mask)) neighbor_count++; if ((*cell_ptr & mask)) neighbor_count++;
// Count right neighbor // Count right neighbor
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++; if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
// Count lower right neighbor // Count lower right neighbor
if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++; if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++;
return neighbor_count; return neighbor_count;
} }
/* Calculates the next generation of current_map and stores it in /* Calculates the next generation of current_map and stores it in
next_map. */ next_map. */
void cellmap::next_generation(cellmap& next_map) void cellmap::next_generation(cellmap& next_map)
{ {
unsigned int x, y, neighbor_count; unsigned int x, y, neighbor_count;
for (y=0; y<height; y++) { for (y=0; y<height; y++) {
for (x=0; x<width; x++) { for (x=0; x<width; x++) {
neighbor_count = count_neighbors(x, y); neighbor_count = count_neighbors(x, y);
if (cell_state(x, y) == 1) { if (cell_state(x, y) == 1) {
if ((neighbor_count != 2) && (neighbor_count != 3)) { if ((neighbor_count != 2) && (neighbor_count != 3)) {
next_map.clear_cell(x, y); // turn it off next_map.clear_cell(x, y); // turn it off
draw_pixel(x, y, OFF_COLOR); draw_pixel(x, y, OFF_COLOR);
} }
} else { } else {
if (neighbor_count == 3) { if (neighbor_count == 3) {
next_map.set_cell(x, y); // turn it on next_map.set_cell(x, y); // turn it on
draw_pixel(x, y, ON_COLOR); draw_pixel(x, y, ON_COLOR);
} }
} }
} }
} }
} }
```

158
17-05.md
View file

@ -51,86 +51,88 @@ Listing 17.3.
**LISTING 17.4 L17-4.CPP** **LISTING 17.4 L17-4.CPP**
/* next_generation(), implemented using fast, all-in-one hard-wired ```cpp
neighbor count/update/draw function. Otherwise, the same as /* next_generation(), implemented using fast, all-in-one hard-wired
Listing 17.3. */ neighbor count/update/draw function. Otherwise, the same as
Listing 17.3. */
/* Calculates the next generation of current_map and stores it in /* Calculates the next generation of current_map and stores it in
next_map. */ next_map. */
void cellmap::next_generation(cellmap& next_map) void cellmap::next_generation(cellmap& next_map)
{ {
unsigned int x, y, neighbor_count; unsigned int x, y, neighbor_count;
unsigned int width_in_bytesX2 = width_in_bytes << 1; unsigned int width_in_bytesX2 = width_in_bytes << 1;
unsigned char *cell_ptr, *current_cell_ptr, mask, current_mask; unsigned char *cell_ptr, *current_cell_ptr, mask, current_mask;
unsigned char *base_cell_ptr, *row_cell_ptr, base_mask; unsigned char *base_cell_ptr, *row_cell_ptr, base_mask;
unsigned char *dest_cell_ptr = next_map.cells; unsigned char *dest_cell_ptr = next_map.cells;
// Process all cells in the current cellmap // Process all cells in the current cellmap
row_cell_ptr = cells; // point to upper left neighbor of row_cell_ptr = cells; // point to upper left neighbor of
// first cell in cell map // first cell in cell map
for (y=0; y<height; y++) { // repeat for each row of cells for (y=0; y<height; y++) { // repeat for each row of cells
// Cell pointer and cell bit mask for first cell in row // Cell pointer and cell bit mask for first cell in row
base_cell_ptr = row_cell_ptr; // to access upper left neighbor base_cell_ptr = row_cell_ptr; // to access upper left neighbor
base_mask = 0x01; // of first cell in row base_mask = 0x01; // of first cell in row
for (x=0; x<width; x++) { // repeat for each cell in row for (x=0; x<width; x++) { // repeat for each cell in row
// First, count neighbors // First, count neighbors
// Point to upper left neighbor of current cell // Point to upper left neighbor of current cell
cell_ptr = base_cell_ptr; // pointer and bit mask for cell_ptr = base_cell_ptr; // pointer and bit mask for
mask = base_mask; // upper left neighbor mask = base_mask; // upper left neighbor
// Count upper left neighbor // Count upper left neighbor
neighbor_count = (*cell_ptr & mask) ? 1 : 0; neighbor_count = (*cell_ptr & mask) ? 1 : 0;
// Count left neighbor // Count left neighbor
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++; if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
// Count lower left neighbor // Count lower left neighbor
if ((*(cell_ptr + width_in_bytesX2) & mask)) if ((*(cell_ptr + width_in_bytesX2) & mask))
neighbor_count++; neighbor_count++;
// Point to upper neighbor // Point to upper neighbor
if ((mask >>= 1) == 0) { if ((mask >>= 1) == 0) {
mask = 0x80; mask = 0x80;
cell_ptr++; cell_ptr++;
} }
// Remember where to find the current cell // Remember where to find the current cell
current_cell_ptr = cell_ptr + width_in_bytes; current_cell_ptr = cell_ptr + width_in_bytes;
current_mask = mask; current_mask = mask;
// Count upper neighbor // Count upper neighbor
if ((*cell_ptr & mask)) neighbor_count++; if ((*cell_ptr & mask)) neighbor_count++;
// Count lower neighbor // Count lower neighbor
if ((*(cell_ptr + width_in_bytesX2) & mask)) if ((*(cell_ptr + width_in_bytesX2) & mask))
neighbor_count++; neighbor_count++;
// Point to upper right neighbor // Point to upper right neighbor
if ((mask >>= 1) == 0) { if ((mask >>= 1) == 0) {
mask = 0x80; mask = 0x80;
cell_ptr++; cell_ptr++;
} }
// Count upper right neighbor // Count upper right neighbor
if ((*cell_ptr & mask)) neighbor_count++; if ((*cell_ptr & mask)) neighbor_count++;
// Count right neighbor // Count right neighbor
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++; if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
// Count lower right neighbor // Count lower right neighbor
if ((*(cell_ptr + width_in_bytesX2) & mask)) if ((*(cell_ptr + width_in_bytesX2) & mask))
neighbor_count++; neighbor_count++;
if (*current_cell_ptr & current_mask) { if (*current_cell_ptr & current_mask) {
if ((neighbor_count != 2) && (neighbor_count != 3)) { if ((neighbor_count != 2) && (neighbor_count != 3)) {
*(dest_cell_ptr + (current_cell_ptr - cells)) &= *(dest_cell_ptr + (current_cell_ptr - cells)) &=
~current_mask; // turn off cell ~current_mask; // turn off cell
draw_pixel(x, y, OFF_COLOR); draw_pixel(x, y, OFF_COLOR);
} }
} else { } else {
if (neighbor_count == 3) { if (neighbor_count == 3) {
*(dest_cell_ptr + (current_cell_ptr - cells)) |= *(dest_cell_ptr + (current_cell_ptr - cells)) |=
current_mask; // turn on cell current_mask; // turn on cell
draw_pixel(x, y, ON_COLOR); draw_pixel(x, y, ON_COLOR);
} }
} }
// Advance to the next cell on row // Advance to the next cell on row
if ((base_mask >>= 1) == 0) { if ((base_mask >>= 1) == 0) {
base_mask = 0x80; base_mask = 0x80;
base_cell_ptr++; // advance to the next cell byte base_cell_ptr++; // advance to the next cell byte
} }
} }
row_cell_ptr += width_in_bytes; // point to start of next row row_cell_ptr += width_in_bytes; // point to start of next row
} }
} }
```
Listing 17.4 and Listing 17.3 are functionally the same; the only Listing 17.4 and Listing 17.3 are functionally the same; the only
difference lies in how **next\_generation()** is implemented. (Only difference lies in how **next\_generation()** is implemented. (Only

492
17-07.md
View file

@ -12,276 +12,278 @@ pages: 340-345
**LISTING 17.5 L17-5.CPP** **LISTING 17.5 L17-5.CPP**
/* C++ Game of Life implementation for any mode for which mode set ```cpp
and draw pixel functions can be provided. The cellmap stores the /* C++ Game of Life implementation for any mode for which mode set
neighbor count for each cell as well as the state of each cell; and draw pixel functions can be provided. The cellmap stores the
this allows very fast next-state determination. Edges always wrap neighbor count for each cell as well as the state of each cell;
in this implementation. this allows very fast next-state determination. Edges always wrap
Tested with Borland C++. To run, link with Listing 17.2 in this implementation.
in the large model. */ Tested with Borland C++. To run, link with Listing 17.2
#include <stdlib.h> in the large model. */
#include <stdio.h> #include <stdlib.h>
#include <iostream.h> #include <stdio.h>
#include <conio.h> #include <iostream.h>
#include <time.h> #include <conio.h>
#include <dos.h> #include <time.h>
#include <bios.h> #include <dos.h>
#include <mem.h> #include <bios.h>
#include <mem.h>
#define ON_COLOR 15 // on-cell pixel color #define ON_COLOR 15 // on-cell pixel color
#define OFF_COLOR 0 // off-cell pixel color #define OFF_COLOR 0 // off-cell pixel color
#define MSG_LINE 10 // row for text messages #define MSG_LINE 10 // row for text messages
#define GENERATION_LINE 12 // row for generation # display #define GENERATION_LINE 12 // row for generation # display
#define LIMIT_18_HZ 0 // set 1 to to maximum frame rate = 18Hz #define LIMIT_18_HZ 0 // set 1 to to maximum frame rate = 18Hz
class cellmap { class cellmap {
private: private:
unsigned char *cells; unsigned char *cells;
unsigned char *temp_cells; unsigned char *temp_cells;
unsigned int width; unsigned int width;
unsigned int height; unsigned int height;
unsigned int length_in_bytes; unsigned int length_in_bytes;
public: public:
cellmap(unsigned int h, unsigned int v); cellmap(unsigned int h, unsigned int v);
~cellmap(void); ~cellmap(void);
void set_cell(unsigned int x, unsigned int y); void set_cell(unsigned int x, unsigned int y);
void clear_cell(unsigned int x, unsigned int y); void clear_cell(unsigned int x, unsigned int y);
int cell_state(int x, int y); int cell_state(int x, int y);
int count_neighbors(int x, int y); int count_neighbors(int x, int y);
void next_generation(void); void next_generation(void);
void init(void); void init(void);
}; };
extern void enter_display_mode(void); extern void enter_display_mode(void);
extern void exit_display_mode(void); extern void exit_display_mode(void);
extern void draw_pixel(unsigned int X, unsigned int Y, extern void draw_pixel(unsigned int X, unsigned int Y,
unsigned int Color); unsigned int Color);
extern void show_text(int x, int y, char *text); extern void show_text(int x, int y, char *text);
/* Controls the size of the cell map. Must be within the capabilities /* Controls the size of the cell map. Must be within the capabilities
of the display mode, and must be limited to leave room for text of the display mode, and must be limited to leave room for text
display at right. */ display at right. */
unsigned int cellmap_width = 96; unsigned int cellmap_width = 96;
unsigned int cellmap_height = 96; unsigned int cellmap_height = 96;
/* Width & height in pixels of each cell. */ /* Width & height in pixels of each cell. */
unsigned int magnifier = 2; unsigned int magnifier = 2;
/* Randomizing seed */ /* Randomizing seed */
unsigned int seed; unsigned int seed;
void main() void main()
{ {
unsigned long generation = 0; unsigned long generation = 0;
char gen_text[80]; char gen_text[80];
long bios_time, start_bios_time; long bios_time, start_bios_time;
cellmap current_map(cellmap_height, cellmap_width); cellmap current_map(cellmap_height, cellmap_width);
current_map.init(); // randomly initialize cell map current_map.init(); // randomly initialize cell map
enter_display_mode(); enter_display_mode();
// Keep recalculating and redisplaying generations until any key // Keep recalculating and redisplaying generations until any key
// is pressed // is pressed
show_text(0, MSG_LINE, "Generation: "); show_text(0, MSG_LINE, "Generation: ");
start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time); start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time);
do { do {
generation++; generation++;
sprintf(gen_text, "%10lu", generation); sprintf(gen_text, "%10lu", generation);
show_text(1, GENERATION_LINE, gen_text); show_text(1, GENERATION_LINE, gen_text);
// Recalculate and draw the next generation // Recalculate and draw the next generation
current_map.next_generation(); current_map.next_generation();
#if LIMIT_18_HZ #if LIMIT_18_HZ
// Limit to a maximum of 18.2 frames per second, for visibility // Limit to a maximum of 18.2 frames per second, for visibility
do { do {
_bios_timeofday(_TIME_GETCLOCK, &bios_time); _bios_timeofday(_TIME_GETCLOCK, &bios_time);
} while (start_bios_time == bios_time); } while (start_bios_time == bios_time);
start_bios_time = bios_time; start_bios_time = bios_time;
#endif #endif
} while (!kbhit()); } while (!kbhit());
getch(); // clear keypress getch(); // clear keypress
exit_display_mode(); exit_display_mode();
cout << "Total generations: " << generation << "\nSeed: " << cout << "Total generations: " << generation << "\nSeed: " <<
seed << "\n"; seed << "\n";
} }
/* cellmap constructor. */ /* cellmap constructor. */
cellmap::cellmap(unsigned int h, unsigned int w) cellmap::cellmap(unsigned int h, unsigned int w)
{ {
width = w; width = w;
height = h; height = h;
length_in_bytes = w * h; length_in_bytes = w * h;
cells = new unsigned char[length_in_bytes]; // cell storage cells = new unsigned char[length_in_bytes]; // cell storage
temp_cells = new unsigned char[length_in_bytes]; // temp cell storage temp_cells = new unsigned char[length_in_bytes]; // temp cell storage
if ( (cells == NULL) || (temp_cells == NULL) ) { if ( (cells == NULL) || (temp_cells == NULL) ) {
printf("Out of memory\n"); printf("Out of memory\n");
exit(1); exit(1);
} }
memset(cells, 0, length_in_bytes); // clear all cells, to start memset(cells, 0, length_in_bytes); // clear all cells, to start
} }
/* cellmap destructor. */ /* cellmap destructor. */
cellmap::~cellmap(void) cellmap::~cellmap(void)
{ {
delete[] cells; delete[] cells;
delete[] temp_cells; delete[] temp_cells;
} }
/* Turns an off-cell on, incrementing the on-neighbor count for the /* Turns an off-cell on, incrementing the on-neighbor count for the
eight neighboring cells. */ eight neighboring cells. */
void cellmap::set_cell(unsigned int x, unsigned int y) void cellmap::set_cell(unsigned int x, unsigned int y)
{ {
unsigned int w = width, h = height; unsigned int w = width, h = height;
int xoleft, xoright, yoabove, yobelow; int xoleft, xoright, yoabove, yobelow;
unsigned char *cell_ptr = cells + (y * w) + x; unsigned char *cell_ptr = cells + (y * w) + x;
// Calculate the offsets to the eight neighboring cells, // Calculate the offsets to the eight neighboring cells,
// accounting for wrapping around at the edges of the cell map // accounting for wrapping around at the edges of the cell map
if (x == 0) if (x == 0)
xoleft = w - 1; xoleft = w - 1;
else else
xoleft = -1; xoleft = -1;
if (y == 0) if (y == 0)
yoabove = length_in_bytes - w; yoabove = length_in_bytes - w;
else else
yoabove = -w; yoabove = -w;
if (x == (w - 1)) if (x == (w - 1))
xoright = -(w - 1); xoright = -(w - 1);
else else
xoright = 1; xoright = 1;
if (y == (h - 1)) if (y == (h - 1))
yobelow = -(length_in_bytes - w); yobelow = -(length_in_bytes - w);
else else
yobelow = w; yobelow = w;
*(cell_ptr) |= 0x01; *(cell_ptr) |= 0x01;
*(cell_ptr + yoabove + xoleft) += 2; *(cell_ptr + yoabove + xoleft) += 2;
*(cell_ptr + yoabove) += 2; *(cell_ptr + yoabove) += 2;
*(cell_ptr + yoabove + xoright) += 2; *(cell_ptr + yoabove + xoright) += 2;
*(cell_ptr + xoleft) += 2; *(cell_ptr + xoleft) += 2;
*(cell_ptr + xoright) += 2; *(cell_ptr + xoright) += 2;
*(cell_ptr + yobelow + xoleft) += 2; *(cell_ptr + yobelow + xoleft) += 2;
*(cell_ptr + yobelow) += 2; *(cell_ptr + yobelow) += 2;
*(cell_ptr + yobelow + xoright) += 2; *(cell_ptr + yobelow + xoright) += 2;
} }
/* Turns an on-cell off, decrementing the on-neighbor count for the /* Turns an on-cell off, decrementing the on-neighbor count for the
eight neighboring cells. */ eight neighboring cells. */
void cellmap::clear_cell(unsigned int x, unsigned int y) void cellmap::clear_cell(unsigned int x, unsigned int y)
{ {
unsigned int w = width, h = height; unsigned int w = width, h = height;
int xoleft, xoright, yoabove, yobelow; int xoleft, xoright, yoabove, yobelow;
unsigned char *cell_ptr = cells + (y * w) + x; unsigned char *cell_ptr = cells + (y * w) + x;
// Calculate the offsets to the eight neighboring cells, // Calculate the offsets to the eight neighboring cells,
// accounting for wrapping around at the edges of the cell map // accounting for wrapping around at the edges of the cell map
if (x == 0) if (x == 0)
xoleft = w - 1; xoleft = w - 1;
else else
xoleft = -1; xoleft = -1;
if (y == 0) if (y == 0)
yoabove = length_in_bytes - w; yoabove = length_in_bytes - w;
else else
yoabove = -w; yoabove = -w;
if (x == (w - 1)) if (x == (w - 1))
xoright = -(w - 1); xoright = -(w - 1);
else else
xoright = 1; xoright = 1;
if (y == (h - 1)) if (y == (h - 1))
yobelow = -(length_in_bytes - w); yobelow = -(length_in_bytes - w);
else else
yobelow = w; yobelow = w;
*(cell_ptr) &= ~0x01; *(cell_ptr) &= ~0x01;
*(cell_ptr + yoabove + xoleft) -= 2; *(cell_ptr + yoabove + xoleft) -= 2;
*(cell_ptr + yoabove ) -= 2; *(cell_ptr + yoabove ) -= 2;
*(cell_ptr + yoabove + xoright) -= 2; *(cell_ptr + yoabove + xoright) -= 2;
*(cell_ptr + xoleft) -= 2; *(cell_ptr + xoleft) -= 2;
*(cell_ptr + xoright) -= 2; *(cell_ptr + xoright) -= 2;
*(cell_ptr + yobelow + xoleft) -= 2; *(cell_ptr + yobelow + xoleft) -= 2;
*(cell_ptr + yobelow) -= 2; *(cell_ptr + yobelow) -= 2;
*(cell_ptr + yobelow + xoright) -= 2; *(cell_ptr + yobelow + xoright) -= 2;
} }
/* Returns cell state (1=on or 0=off). */ /* Returns cell state (1=on or 0=off). */
int cellmap::cell_state(int x, int y) int cellmap::cell_state(int x, int y)
{ {
unsigned char *cell_ptr; unsigned char *cell_ptr;
cell_ptr = cells + (y * width) + x; cell_ptr = cells + (y * width) + x;
return *cell_ptr & 0x01; return *cell_ptr & 0x01;
} }
/* Calculates and displays the next generation of current_map */ /* Calculates and displays the next generation of current_map */
void cellmap::next_generation() void cellmap::next_generation()
{ {
unsigned int x, y, count; unsigned int x, y, count;
unsigned int h = height, w = width; unsigned int h = height, w = width;
unsigned char *cell_ptr, *row_cell_ptr; unsigned char *cell_ptr, *row_cell_ptr;
// Copy to temp map, so we can have an unaltered version from // Copy to temp map, so we can have an unaltered version from
// which to work // which to work
memcpy(temp_cells, cells, length_in_bytes); memcpy(temp_cells, cells, length_in_bytes);
// Process all cells in the current cell map // Process all cells in the current cell map
cell_ptr = temp_cells; // first cell in cell map cell_ptr = temp_cells; // first cell in cell map
for (y=0; y<h; y++) { // repeat for each row of cells for (y=0; y<h; y++) { // repeat for each row of cells
// Process all cells in the current row of the cell map // Process all cells in the current row of the cell map
x = 0; x = 0;
do { // repeat for each cell in row do { // repeat for each cell in row
// Zip quickly through as many off-cells with no // Zip quickly through as many off-cells with no
// neighbors as possible // neighbors as possible
while (*cell_ptr == 0) { while (*cell_ptr == 0) {
cell_ptr++; // advance to the next cell cell_ptr++; // advance to the next cell
if (++x >= w) goto RowDone; if (++x >= w) goto RowDone;
} }
// Found a cell that's either on or has on-neighbors, // Found a cell that's either on or has on-neighbors,
// so see if its state needs to be changed // so see if its state needs to be changed
count = *cell_ptr >> 1; // # of neighboring on-cells count = *cell_ptr >> 1; // # of neighboring on-cells
if (*cell_ptr & 0x01) { if (*cell_ptr & 0x01) {
// Cell is on; turn it off if it doesn't have // Cell is on; turn it off if it doesn't have
// 2 or 3 neighbors // 2 or 3 neighbors
if ((count != 2) && (count != 3)) { if ((count != 2) && (count != 3)) {
clear_cell(x, y); clear_cell(x, y);
draw_pixel(x, y, OFF_COLOR); draw_pixel(x, y, OFF_COLOR);
} }
} else { } else {
// Cell is off; turn it on if it has exactly 3 neighbors // Cell is off; turn it on if it has exactly 3 neighbors
if (count == 3) { if (count == 3) {
set_cell(x, y); set_cell(x, y);
draw_pixel(x, y, ON_COLOR); draw_pixel(x, y, ON_COLOR);
} }
} }
// Advance to the next cell // Advance to the next cell
cell_ptr++; // advance to the next cell byte cell_ptr++; // advance to the next cell byte
} while (++x < w); } while (++x < w);
RowDone: RowDone:
} }
} }
/* Randomly initializes the cellmap to about 50% on-pixels. */ /* Randomly initializes the cellmap to about 50% on-pixels. */
void cellmap::init() void cellmap::init()
{ {
unsigned int x, y, init_length; unsigned int x, y, init_length;
// Get the seed; seed randomly if 0 entered // Get the seed; seed randomly if 0 entered
cout << "Seed (0 for random seed): "; cout << "Seed (0 for random seed): ";
cin >> seed; cin >> seed;
if (seed == 0) seed = (unsigned) time(NULL); if (seed == 0) seed = (unsigned) time(NULL);
// Randomly initialize the initial cell map to 50% on-pixels // Randomly initialize the initial cell map to 50% on-pixels
// (actually generally fewer, because some coordinates will be // (actually generally fewer, because some coordinates will be
// randomly selected more than once) // randomly selected more than once)
cout << "Initializing..."; cout << "Initializing...";
srand(seed); srand(seed);
init_length = (height * width) / 2; init_length = (height * width) / 2;
do { do {
x = random(width); x = random(width);
y = random(height); y = random(height);
if (cell_state(x, y) == 0) { if (cell_state(x, y) == 0) {
set_cell(x, y); set_cell(x, y);
} }
} while (—init_length); } while (—init_length);
} }
```

928
18-03.md
View file

@ -12,503 +12,507 @@ pages: 352-361
**LISTING 18.1 BUILD.BAT** **LISTING 18.1 BUILD.BAT**
bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 lcomp.c ```bat
lcomp > qlife.asm bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 lcomp.c
tasmx /mx /kh30000 qlife lcomp > qlife.asm
bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 qlife.obj main.c video.c tasmx /mx /kh30000 qlife
bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 qlife.obj main.c video.c
```
**LISTING 18.2 LCOMP.C** **LISTING 18.2 LCOMP.C**
// LCOMP.C ```c
// // LCOMP.C
// Life compiler, ver 1.3 //
// // Life compiler, ver 1.3
// David Stafford //
// // David Stafford
//
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "life.h" #include "life.h"
#define LIST_LIMIT (46 * 138) // when we need to use es: #define LIST_LIMIT (46 * 138) // when we need to use es:
int Old, New, Edge, Label; int Old, New, Edge, Label;
char Buf[ 20 ]; char Buf[ 20 ];
void Next1( void ) void Next1( void )
{
char *Seg = "";
if( WIDTH * HEIGHT > LIST_LIMIT ) Seg = "es:";
printf( "mov bp,%s[si]\n", Seg );
printf( "add si,2\n" );
printf( "mov dh,[bp+1]\n" );
printf( "and dh,0FEh\n" );
printf( "jmp dx\n" );
}
void Next2( void )
{
printf( "mov bp,es:[si]\n" );
printf( "add si,2\n" );
printf( "mov dh,[bp+1]\n" );
printf( "or dh,1\n" );
printf( "jmp dx\n" );
}
void BuildMaps( void )
{
unsigned short i, j, Size, x = 0, y, N1, N2, N3, C1, C2, C3;
printf( "_DATA segment ‘DATA'\nalign 2\n" );
printf( "public _CellMap\n" );
printf( "_CellMap label word\n" );
for( j = 0; j < HEIGHT; j++ )
{
for( i = 0; i < WIDTH; i++ )
{ {
char *Seg = ""; if( i == 0 || i == WIDTH-1 || j == 0 || j == HEIGHT-1 )
if( WIDTH * HEIGHT > LIST_LIMIT ) Seg = "es:";
printf( "mov bp,%s[si]\n", Seg );
printf( "add si,2\n" );
printf( "mov dh,[bp+1]\n" );
printf( "and dh,0FEh\n" );
printf( "jmp dx\n" );
}
void Next2( void )
{
printf( "mov bp,es:[si]\n" );
printf( "add si,2\n" );
printf( "mov dh,[bp+1]\n" );
printf( "or dh,1\n" );
printf( "jmp dx\n" );
}
void BuildMaps( void )
{
unsigned short i, j, Size, x = 0, y, N1, N2, N3, C1, C2, C3;
printf( "_DATA segment ‘DATA'\nalign 2\n" );
printf( "public _CellMap\n" );
printf( "_CellMap label word\n" );
for( j = 0; j < HEIGHT; j++ )
{ {
for( i = 0; i < WIDTH; i++ ) printf( "dw 8000h\n" );
{
if( i == 0 || i == WIDTH-1 || j == 0 || j == HEIGHT-1 )
{
printf( "dw 8000h\n" );
}
else
{
printf( "dw 0\n" );
}
}
}
printf( "ChangeCell dw 0\n" );
printf( "_RowColMap label word\n" );
for( j = 0; j < HEIGHT; j++ )
{
for( i = 0; i < WIDTH; i++ )
{
printf( "dw 0%02x%02xh\n", j, i * 3 );
}
}
if( WIDTH * HEIGHT > LIST_LIMIT )
{
printf( "Change1 dw offset _CHANGE:_ChangeList1\n" );
printf( "Change2 dw offset _CHANGE:_ChangeList2\n" );
printf( "ends\n\n" );
printf( "_CHANGE segment para public ‘FAR_DATA'\n" );
} }
else else
{ {
printf( "Change1 dw offset DGROUP:_ChangeList1\n" ); printf( "dw 0\n" );
printf( "Change2 dw offset DGROUP:_ChangeList2\n" );
} }
Size = WIDTH * HEIGHT + 1;
printf( "public _ChangeList1\n_ChangeList1 label word\n" );
printf( "dw %d dup (offset DGROUP:ChangeCell)\n", Size );
printf( "public _ChangeList2\n_ChangeList2 label word\n" );
printf( "dw %d dup (offset DGROUP:ChangeCell)\n", Size );
printf( "ends\n\n" );
printf( "_LDMAP segment para public ‘FAR_DATA'\n" );
do
{
// Current cell states
C1 = (x & 0x0800) >> 11;
C2 = (x & 0x0400) >> 10;
C3 = (x & 0x0200) >> 9;
// Neighbor counts
N1 = (x & 0x01C0) >> 6;
N2 = (x & 0x0038) >> 3;
N3 = (x & 0x0007);
y = x & 0x8FFF; // Preserve all but the next generation states
if( C1 && ((N1 + C2 == 2) || (N1 + C2 == 3)) )
{
y |= 0x4000;
}
if( !C1 && (N1 + C2 == 3) )
{
y |= 0x4000;
}
if( C2 && ((N2 + C1 + C3 == 2) || (N2 + C1 + C3 == 3)) )
{
y |= 0x2000;
}
if( !C2 && (N2 + C1 + C3 == 3) )
{
y |= 0x2000;
}
if( C3 && ((N3 + C2 == 2) || (N3 + C2 == 3)) )
{
y |= 0x1000;
}
if( !C3 && (N3 + C2 == 3) )
{
y |= 0x1000;
}
printf( "db 0%02xh\n", y >> 8 );
}
while( ++x != 0 );
printf( "ends\n\n" );
} }
}
void GetUpAndDown( void ) printf( "ChangeCell dw 0\n" );
printf( "_RowColMap label word\n" );
for( j = 0; j < HEIGHT; j++ )
{
for( i = 0; i < WIDTH; i++ )
{ {
printf( "mov ax,[bp+_RowColMap-_CellMap]\n" ); printf( "dw 0%02x%02xh\n", j, i * 3 );
printf( "or ah,ah\n" );
printf( "mov dx,%d\n", DOWN );
printf( "mov cx,%d\n", WRAPUP );
printf( "jz short D%d\n", Label );
printf( "cmp ah,%d\n", HEIGHT - 1 );
printf( "mov cx,%d\n", UP );
printf( "jb short D%d\n", Label );
printf( "mov dx,%d\n", WRAPDOWN );
printf( "D%d:\n", Label );
} }
}
void FirstPass( void ) if( WIDTH * HEIGHT > LIST_LIMIT )
{
printf( "Change1 dw offset _CHANGE:_ChangeList1\n" );
printf( "Change2 dw offset _CHANGE:_ChangeList2\n" );
printf( "ends\n\n" );
printf( "_CHANGE segment para public ‘FAR_DATA'\n" );
}
else
{
printf( "Change1 dw offset DGROUP:_ChangeList1\n" );
printf( "Change2 dw offset DGROUP:_ChangeList2\n" );
}
Size = WIDTH * HEIGHT + 1;
printf( "public _ChangeList1\n_ChangeList1 label word\n" );
printf( "dw %d dup (offset DGROUP:ChangeCell)\n", Size );
printf( "public _ChangeList2\n_ChangeList2 label word\n" );
printf( "dw %d dup (offset DGROUP:ChangeCell)\n", Size );
printf( "ends\n\n" );
printf( "_LDMAP segment para public ‘FAR_DATA'\n" );
do
{
// Current cell states
C1 = (x & 0x0800) >> 11;
C2 = (x & 0x0400) >> 10;
C3 = (x & 0x0200) >> 9;
// Neighbor counts
N1 = (x & 0x01C0) >> 6;
N2 = (x & 0x0038) >> 3;
N3 = (x & 0x0007);
y = x & 0x8FFF; // Preserve all but the next generation states
if( C1 && ((N1 + C2 == 2) || (N1 + C2 == 3)) )
{ {
char *Op; y |= 0x4000;
unsigned short UpDown = 0;
printf( "org 0%02x00h\n", (Edge << 7) + (New << 4) + (Old << 1) );
// reset cell
printf( "xor byte ptr [bp+1],0%02xh\n", (New ^ Old) << 1 );
// get the screen address and update the display
#ifndef NODRAW
printf( "mov al,160\n" );
printf( "mov bx,[bp+_RowColMap-_CellMap]\n" );
printf( "mul bh\n" );
printf( "add ax,ax\n" );
printf( "mov bh,0\n" );
printf( "add bx,ax\n" ); // bx = screen offset
if( ((New ^ Old) & 6) == 6 )
{
printf( "mov word ptr fs:[bx],0%02x%02xh\n",
(New & 2) ? 15 : 0,
(New & 4) ? 15 : 0 );
if( (New ^ Old) & 1 )
{
printf( "mov byte ptr fs:[bx+2],%s\n",
(New & 1) ? "15" : "dl" );
}
}
else
{
if( ((New ^ Old) & 3) == 3 )
{
printf( "mov word ptr fs:[bx+1],0%02x%02xh\n",
(New & 1) ? 15 : 0,
(New & 2) ? 15 : 0 );
}
else
{
if( (New ^ Old) & 2 )
{
printf( "mov byte ptr fs:[bx+1],%s\n",
(New & 2) ? "15" : "dl" );
}
if( (New ^ Old) & 1 )
{
printf( "mov byte ptr fs:[bx+2],%s\n",
(New & 1) ? "15" : "dl" );
}
}
if( (New ^ Old) & 4 )
{
printf( "mov byte ptr fs:[bx],%s\n",
(New & 4) ? "15" : "dl" );
}
}
#endif
if( (New ^ Old) & 4 ) UpDown += (New & 4) ? 0x48 : -0x48;
if( (New ^ Old) & 2 ) UpDown += (New & 2) ? 0x49 : -0x49;
if( (New ^ Old) & 1 ) UpDown += (New & 1) ? 0x09 : -0x09;
if( Edge )
{
GetUpAndDown(); // ah = row, al = col, cx = up, dx = down
if( (New ^ Old) & 4 )
{
printf( "mov di,%d\n", WRAPLEFT ); // di = left
printf( "cmp al,0\n" );
printf( "je short L%d\n", Label );
printf( "mov di,%d\n", LEFT );
printf( "L%d:\n", Label );
if( New & 4 ) Op = "inc";
else Op = "dec";
printf( "%s word ptr [bp+di]\n", Op );
printf( "add di,cx\n" );
printf( "%s word ptr [bp+di]\n", Op );
printf( "sub di,cx\n" );
printf( "add di,dx\n" );
printf( "%s word ptr [bp+di]\n", Op );
}
if( (New ^ Old) & 1 )
{
printf( "mov di,%d\n", WRAPRIGHT ); // di = right
printf( "cmp al,%d\n", (WIDTH - 1) * 3 );
printf( "je short R%d\n", Label );
printf( "mov di,%d\n", RIGHT );
printf( "R%d:\n", Label );
if( New & 1 ) Op = "add";
else Op = "sub";
printf( "%s word ptr [bp+di],40h\n", Op );
printf( "add di,cx\n" );
printf( "%s word ptr [bp+di],40h\n", Op );
printf( "sub di,cx\n" );
printf( "add di,dx\n" );
printf( "%s word ptr [bp+di],40h\n", Op );
}
printf( "mov di,cx\n" );
printf( "add word ptr [bp+di],%d\n", UpDown );
printf( "mov di,dx\n" );
printf( "add word ptr [bp+di],%d\n", UpDown );
printf( "mov dl,0\n" );
}
else
{
if( (New ^ Old) & 4 )
{
if( New & 4 ) Op = "inc";
else Op = "dec";
printf( "%s byte ptr [bp+%d]\n", Op, LEFT );
printf( "%s byte ptr [bp+%d]\n", Op, UPPERLEFT );
printf( "%s byte ptr [bp+%d]\n", Op, LOWERLEFT );
}
if( (New ^ Old) & 1 )
{
if( New & 1 ) Op = "add";
else Op = "sub";
printf( "%s word ptr [bp+%d],40h\n", Op, RIGHT );
printf( "%s word ptr [bp+%d],40h\n", Op, UPPERRIGHT );
printf( "%s word ptr [bp+%d],40h\n", Op, LOWERRIGHT );
}
if( abs( UpDown ) > 1 )
{
printf( "add word ptr [bp+%d],%d\n", UP, UpDown );
printf( "add word ptr [bp+%d],%d\n", DOWN, UpDown );
}
else
{
if( UpDown == 1 ) Op = "inc";
else Op = "dec";
printf( "%s byte ptr [bp+%d]\n", Op, UP );
printf( "%s byte ptr [bp+%d]\n", Op, DOWN );
}
}
Next1();
} }
void Test( char *Offset, char *Str ) if( !C1 && (N1 + C2 == 3) )
{ {
printf( "mov bx,[bp+%s]\n", Offset ); y |= 0x4000;
printf( "cmp bh,[bx]\n" );
printf( "jnz short FIX_%s%d\n", Str, Label );
printf( "%s%d:\n", Str, Label );
} }
void Fix( char *Offset, char *Str, int JumpBack ) if( C2 && ((N2 + C1 + C3 == 2) || (N2 + C1 + C3 == 3)) )
{ {
printf( "FIX_%s%d:\n", Str, Label ); y |= 0x2000;
printf( "mov bh,[bx]\n" );
printf( "mov [bp+%s],bx\n", Offset );
if( *Offset != ‘0' ) printf( "lea ax,[bp+%s]\n", Offset );
else printf( "mov ax,bp\n" );
printf( "stosw\n" );
if( JumpBack ) printf( "jmp short %s%d\n", Str, Label );
} }
void SecondPass( void ) if( !C2 && (N2 + C1 + C3 == 3) )
{ {
printf( "org 0%02x00h\n", y |= 0x2000;
(Edge << 7) + (New << 4) + (Old << 1) + 1 );
if( Edge )
{
// finished with second pass
if( New == 7 && Old == 0 )
{
printf( "cmp bp,offset DGROUP:ChangeCell\n" );
printf( "jne short NotEnd\n" );
printf( "mov word ptr es:[di],offset DGROUP:ChangeCell\n" );
printf( "pop di si bp ds\n" );
printf( "mov ChangeCell,0\n" );
printf( "retf\n" );
printf( "NotEnd:\n" );
}
GetUpAndDown(); // ah = row, al = col, cx = up, dx = down
printf( "push si\n" );
printf( "mov si,%d\n", WRAPLEFT ); // si = left
printf( "cmp al,0\n" );
printf( "je short L%d\n", Label );
printf( "mov si,%d\n", LEFT );
printf( "L%d:\n", Label );
Test( "si", "LEFT" );
printf( "add si,cx\n" );
Test( "si", "UPPERLEFT" );
printf( "sub si,cx\n" );
printf( "add si,dx\n" );
Test( "si", "LOWERLEFT" );
printf( "mov si,cx\n" );
Test( "si", "UP" );
printf( "mov si,dx\n" );
Test( "si", "DOWN" );
printf( "cmp byte ptr [bp+_RowColMap-_CellMap],%d\n",
(WIDTH - 1) * 3 );
printf( "mov si,%d\n", WRAPRIGHT ); // si = right
printf( "je short R%d\n", Label );
printf( "mov si,%d\n", RIGHT );
printf( "R%d:\n", Label );
Test( "si", "RIGHT" );
printf( "add si,cx\n" );
Test( "si", "UPPERRIGHT" );
printf( "sub si,cx\n" );
printf( "add si,dx\n" );
Test( "si", "LOWERRIGHT" );
}
else
{
Test( itoa( LEFT, Buf, 10 ), "LEFT" );
Test( itoa( UPPERLEFT, Buf, 10 ), "UPPERLEFT" );
Test( itoa( LOWERLEFT, Buf, 10 ), "LOWERLEFT" );
Test( itoa( UP, Buf, 10 ), "UP" );
Test( itoa( DOWN, Buf, 10 ), "DOWN" );
Test( itoa( RIGHT, Buf, 10 ), "RIGHT" );
Test( itoa( UPPERRIGHT, Buf, 10 ), "UPPERRIGHT" );
Test( itoa( LOWERRIGHT, Buf, 10 ), "LOWERRIGHT" );
}
if( New == Old ) Test( "0", "CENTER" );
if( Edge ) printf( "pop si\n" "mov dl,0\n" );
Next2();
if( Edge )
{
Fix( "si", "LEFT", 1 );
Fix( "si", "UPPERLEFT", 1 );
Fix( "si", "LOWERLEFT", 1 );
Fix( "si", "UP", 1 );
Fix( "si", "DOWN", 1 );
Fix( "si", "RIGHT", 1 );
Fix( "si", "UPPERRIGHT", 1 );
Fix( "si", "LOWERRIGHT", New == Old );
}
else
{
Fix( itoa( LEFT, Buf, 10 ), "LEFT", 1 );
Fix( itoa( UPPERLEFT, Buf, 10 ), "UPPERLEFT", 1 );
Fix( itoa( LOWERLEFT, Buf, 10 ), "LOWERLEFT", 1 );
Fix( itoa( UP, Buf, 10 ), "UP", 1 );
Fix( itoa( DOWN, Buf, 10 ), "DOWN", 1 );
Fix( itoa( RIGHT, Buf, 10 ), "RIGHT", 1 );
Fix( itoa( UPPERRIGHT, Buf, 10 ), "UPPERRIGHT", 1 );
Fix( itoa( LOWERRIGHT, Buf, 10 ), "LOWERRIGHT", New == Old );
}
if( New == Old ) Fix( "0", "CENTER", 0 );
if( Edge ) printf( "pop si\n" "mov dl,0\n" );
Next2();
} }
void main( void ) if( C3 && ((N3 + C2 == 2) || (N3 + C2 == 3)) )
{ {
char *Seg = "ds"; y |= 0x1000;
}
BuildMaps(); if( !C3 && (N3 + C2 == 3) )
{
y |= 0x1000;
}
printf( "DGROUP group _DATA\n" ); printf( "db 0%02xh\n", y >> 8 );
printf( "LIFE segment ‘CODE'\n" ); }
printf( "assume cs:LIFE,ds:DGROUP,ss:DGROUP,es:NOTHING\n" ); while( ++x != 0 );
printf( ".386C\n" "public _NextGen\n\n" );
for( Edge = 0; Edge <= 1; Edge++ ) printf( "ends\n\n" );
}
void GetUpAndDown( void )
{
printf( "mov ax,[bp+_RowColMap-_CellMap]\n" );
printf( "or ah,ah\n" );
printf( "mov dx,%d\n", DOWN );
printf( "mov cx,%d\n", WRAPUP );
printf( "jz short D%d\n", Label );
printf( "cmp ah,%d\n", HEIGHT - 1 );
printf( "mov cx,%d\n", UP );
printf( "jb short D%d\n", Label );
printf( "mov dx,%d\n", WRAPDOWN );
printf( "D%d:\n", Label );
}
void FirstPass( void )
{
char *Op;
unsigned short UpDown = 0;
printf( "org 0%02x00h\n", (Edge << 7) + (New << 4) + (Old << 1) );
// reset cell
printf( "xor byte ptr [bp+1],0%02xh\n", (New ^ Old) << 1 );
// get the screen address and update the display
#ifndef NODRAW
printf( "mov al,160\n" );
printf( "mov bx,[bp+_RowColMap-_CellMap]\n" );
printf( "mul bh\n" );
printf( "add ax,ax\n" );
printf( "mov bh,0\n" );
printf( "add bx,ax\n" ); // bx = screen offset
if( ((New ^ Old) & 6) == 6 )
{
printf( "mov word ptr fs:[bx],0%02x%02xh\n",
(New & 2) ? 15 : 0,
(New & 4) ? 15 : 0 );
if( (New ^ Old) & 1 )
{
printf( "mov byte ptr fs:[bx+2],%s\n",
(New & 1) ? "15" : "dl" );
}
}
else
{
if( ((New ^ Old) & 3) == 3 )
{
printf( "mov word ptr fs:[bx+1],0%02x%02xh\n",
(New & 1) ? 15 : 0,
(New & 2) ? 15 : 0 );
}
else
{
if( (New ^ Old) & 2 )
{ {
for( New = 0; New < 8; New++ ) printf( "mov byte ptr fs:[bx+1],%s\n",
{ (New & 2) ? "15" : "dl" );
for( Old = 0; Old < 8; Old++ )
{
if( New != Old ) FirstPass(); Label++;
SecondPass(); Label++;
}
}
} }
// finished with first pass if( (New ^ Old) & 1 )
printf( "org 0\n" ); {
printf( "mov si,Change1\n" ); printf( "mov byte ptr fs:[bx+2],%s\n",
printf( "mov di,Change2\n" ); (New & 1) ? "15" : "dl" );
printf( "mov Change1,di\n" ); }
printf( "mov Change2,si\n" );
printf( "mov ChangeCell,0F000h\n" );
printf( "mov ax,seg _LDMAP\n" );
printf( "mov ds,ax\n" );
Next2();
// entry point
printf( "_NextGen: push ds bp si di\n" "cld\n" );
if( WIDTH * HEIGHT > LIST_LIMIT ) Seg = "seg _CHANGE";
printf( "mov ax,%s\n", Seg );
printf( "mov es,ax\n" );
#ifndef NODRAW
printf( "mov ax,0A000h\n" );
printf( "mov fs,ax\n" );
#endif
printf( "mov si,Change1\n" );
printf( "mov dl,0\n" );
Next1();
printf( "LIFE ends\nend\n" );
} }
if( (New ^ Old) & 4 )
{
printf( "mov byte ptr fs:[bx],%s\n",
(New & 4) ? "15" : "dl" );
}
}
#endif
if( (New ^ Old) & 4 ) UpDown += (New & 4) ? 0x48 : -0x48;
if( (New ^ Old) & 2 ) UpDown += (New & 2) ? 0x49 : -0x49;
if( (New ^ Old) & 1 ) UpDown += (New & 1) ? 0x09 : -0x09;
if( Edge )
{
GetUpAndDown(); // ah = row, al = col, cx = up, dx = down
if( (New ^ Old) & 4 )
{
printf( "mov di,%d\n", WRAPLEFT ); // di = left
printf( "cmp al,0\n" );
printf( "je short L%d\n", Label );
printf( "mov di,%d\n", LEFT );
printf( "L%d:\n", Label );
if( New & 4 ) Op = "inc";
else Op = "dec";
printf( "%s word ptr [bp+di]\n", Op );
printf( "add di,cx\n" );
printf( "%s word ptr [bp+di]\n", Op );
printf( "sub di,cx\n" );
printf( "add di,dx\n" );
printf( "%s word ptr [bp+di]\n", Op );
}
if( (New ^ Old) & 1 )
{
printf( "mov di,%d\n", WRAPRIGHT ); // di = right
printf( "cmp al,%d\n", (WIDTH - 1) * 3 );
printf( "je short R%d\n", Label );
printf( "mov di,%d\n", RIGHT );
printf( "R%d:\n", Label );
if( New & 1 ) Op = "add";
else Op = "sub";
printf( "%s word ptr [bp+di],40h\n", Op );
printf( "add di,cx\n" );
printf( "%s word ptr [bp+di],40h\n", Op );
printf( "sub di,cx\n" );
printf( "add di,dx\n" );
printf( "%s word ptr [bp+di],40h\n", Op );
}
printf( "mov di,cx\n" );
printf( "add word ptr [bp+di],%d\n", UpDown );
printf( "mov di,dx\n" );
printf( "add word ptr [bp+di],%d\n", UpDown );
printf( "mov dl,0\n" );
}
else
{
if( (New ^ Old) & 4 )
{
if( New & 4 ) Op = "inc";
else Op = "dec";
printf( "%s byte ptr [bp+%d]\n", Op, LEFT );
printf( "%s byte ptr [bp+%d]\n", Op, UPPERLEFT );
printf( "%s byte ptr [bp+%d]\n", Op, LOWERLEFT );
}
if( (New ^ Old) & 1 )
{
if( New & 1 ) Op = "add";
else Op = "sub";
printf( "%s word ptr [bp+%d],40h\n", Op, RIGHT );
printf( "%s word ptr [bp+%d],40h\n", Op, UPPERRIGHT );
printf( "%s word ptr [bp+%d],40h\n", Op, LOWERRIGHT );
}
if( abs( UpDown ) > 1 )
{
printf( "add word ptr [bp+%d],%d\n", UP, UpDown );
printf( "add word ptr [bp+%d],%d\n", DOWN, UpDown );
}
else
{
if( UpDown == 1 ) Op = "inc";
else Op = "dec";
printf( "%s byte ptr [bp+%d]\n", Op, UP );
printf( "%s byte ptr [bp+%d]\n", Op, DOWN );
}
}
Next1();
}
void Test( char *Offset, char *Str )
{
printf( "mov bx,[bp+%s]\n", Offset );
printf( "cmp bh,[bx]\n" );
printf( "jnz short FIX_%s%d\n", Str, Label );
printf( "%s%d:\n", Str, Label );
}
void Fix( char *Offset, char *Str, int JumpBack )
{
printf( "FIX_%s%d:\n", Str, Label );
printf( "mov bh,[bx]\n" );
printf( "mov [bp+%s],bx\n", Offset );
if( *Offset != ‘0' ) printf( "lea ax,[bp+%s]\n", Offset );
else printf( "mov ax,bp\n" );
printf( "stosw\n" );
if( JumpBack ) printf( "jmp short %s%d\n", Str, Label );
}
void SecondPass( void )
{
printf( "org 0%02x00h\n",
(Edge << 7) + (New << 4) + (Old << 1) + 1 );
if( Edge )
{
// finished with second pass
if( New == 7 && Old == 0 )
{
printf( "cmp bp,offset DGROUP:ChangeCell\n" );
printf( "jne short NotEnd\n" );
printf( "mov word ptr es:[di],offset DGROUP:ChangeCell\n" );
printf( "pop di si bp ds\n" );
printf( "mov ChangeCell,0\n" );
printf( "retf\n" );
printf( "NotEnd:\n" );
}
GetUpAndDown(); // ah = row, al = col, cx = up, dx = down
printf( "push si\n" );
printf( "mov si,%d\n", WRAPLEFT ); // si = left
printf( "cmp al,0\n" );
printf( "je short L%d\n", Label );
printf( "mov si,%d\n", LEFT );
printf( "L%d:\n", Label );
Test( "si", "LEFT" );
printf( "add si,cx\n" );
Test( "si", "UPPERLEFT" );
printf( "sub si,cx\n" );
printf( "add si,dx\n" );
Test( "si", "LOWERLEFT" );
printf( "mov si,cx\n" );
Test( "si", "UP" );
printf( "mov si,dx\n" );
Test( "si", "DOWN" );
printf( "cmp byte ptr [bp+_RowColMap-_CellMap],%d\n",
(WIDTH - 1) * 3 );
printf( "mov si,%d\n", WRAPRIGHT ); // si = right
printf( "je short R%d\n", Label );
printf( "mov si,%d\n", RIGHT );
printf( "R%d:\n", Label );
Test( "si", "RIGHT" );
printf( "add si,cx\n" );
Test( "si", "UPPERRIGHT" );
printf( "sub si,cx\n" );
printf( "add si,dx\n" );
Test( "si", "LOWERRIGHT" );
}
else
{
Test( itoa( LEFT, Buf, 10 ), "LEFT" );
Test( itoa( UPPERLEFT, Buf, 10 ), "UPPERLEFT" );
Test( itoa( LOWERLEFT, Buf, 10 ), "LOWERLEFT" );
Test( itoa( UP, Buf, 10 ), "UP" );
Test( itoa( DOWN, Buf, 10 ), "DOWN" );
Test( itoa( RIGHT, Buf, 10 ), "RIGHT" );
Test( itoa( UPPERRIGHT, Buf, 10 ), "UPPERRIGHT" );
Test( itoa( LOWERRIGHT, Buf, 10 ), "LOWERRIGHT" );
}
if( New == Old ) Test( "0", "CENTER" );
if( Edge ) printf( "pop si\n" "mov dl,0\n" );
Next2();
if( Edge )
{
Fix( "si", "LEFT", 1 );
Fix( "si", "UPPERLEFT", 1 );
Fix( "si", "LOWERLEFT", 1 );
Fix( "si", "UP", 1 );
Fix( "si", "DOWN", 1 );
Fix( "si", "RIGHT", 1 );
Fix( "si", "UPPERRIGHT", 1 );
Fix( "si", "LOWERRIGHT", New == Old );
}
else
{
Fix( itoa( LEFT, Buf, 10 ), "LEFT", 1 );
Fix( itoa( UPPERLEFT, Buf, 10 ), "UPPERLEFT", 1 );
Fix( itoa( LOWERLEFT, Buf, 10 ), "LOWERLEFT", 1 );
Fix( itoa( UP, Buf, 10 ), "UP", 1 );
Fix( itoa( DOWN, Buf, 10 ), "DOWN", 1 );
Fix( itoa( RIGHT, Buf, 10 ), "RIGHT", 1 );
Fix( itoa( UPPERRIGHT, Buf, 10 ), "UPPERRIGHT", 1 );
Fix( itoa( LOWERRIGHT, Buf, 10 ), "LOWERRIGHT", New == Old );
}
if( New == Old ) Fix( "0", "CENTER", 0 );
if( Edge ) printf( "pop si\n" "mov dl,0\n" );
Next2();
}
void main( void )
{
char *Seg = "ds";
BuildMaps();
printf( "DGROUP group _DATA\n" );
printf( "LIFE segment ‘CODE'\n" );
printf( "assume cs:LIFE,ds:DGROUP,ss:DGROUP,es:NOTHING\n" );
printf( ".386C\n" "public _NextGen\n\n" );
for( Edge = 0; Edge <= 1; Edge++ )
{
for( New = 0; New < 8; New++ )
{
for( Old = 0; Old < 8; Old++ )
{
if( New != Old ) FirstPass(); Label++;
SecondPass(); Label++;
}
}
}
// finished with first pass
printf( "org 0\n" );
printf( "mov si,Change1\n" );
printf( "mov di,Change2\n" );
printf( "mov Change1,di\n" );
printf( "mov Change2,si\n" );
printf( "mov ChangeCell,0F000h\n" );
printf( "mov ax,seg _LDMAP\n" );
printf( "mov ds,ax\n" );
Next2();
// entry point
printf( "_NextGen: push ds bp si di\n" "cld\n" );
if( WIDTH * HEIGHT > LIST_LIMIT ) Seg = "seg _CHANGE";
printf( "mov ax,%s\n", Seg );
printf( "mov es,ax\n" );
#ifndef NODRAW
printf( "mov ax,0A000h\n" );
printf( "mov fs,ax\n" );
#endif
printf( "mov si,Change1\n" );
printf( "mov dl,0\n" );
Next1();
printf( "LIFE ends\nend\n" );
}
```

244
18-04.md
View file

@ -12,155 +12,161 @@ pages: 361-365
**LISTING 18.3 MAIN.C** **LISTING 18.3 MAIN.C**
// MAIN.C ```c
// // MAIN.C
// David Stafford //
// // David Stafford
//
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <conio.h> #include <conio.h>
#include <time.h> #include <time.h>
#include <bios.h> #include <bios.h>
#include "life.h" #include "life.h"
// functions in VIDEO.C // functions in VIDEO.C
void enter_display_mode( void ); void enter_display_mode( void );
void exit_display_mode( void ); void exit_display_mode( void );
void show_text( int x, int y, char *text ); void show_text( int x, int y, char *text );
void InitCellmap( void ) void InitCellmap( void )
{
unsigned int i, j, t, x, y, init;
for( init = (HEIGHT * WIDTH * 3) / 2; init; init— )
{
x = random( WIDTH * 3 );
y = random( HEIGHT );
CellMap[ (y * WIDTH) + x / 3 ] |= 0x1000 << (2 - (x % 3));
}
for( i = j = 0; i < WIDTH * HEIGHT; i++ )
{
if( CellMap[ i ] & 0x7000 )
{ {
unsigned int i, j, t, x, y, init; ChangeList1[ j++ ] = (short)&CellMap[ i ];
for( init = (HEIGHT * WIDTH * 3) / 2; init; init— )
{
x = random( WIDTH * 3 );
y = random( HEIGHT );
CellMap[ (y * WIDTH) + x / 3 ] |= 0x1000 << (2 - (x % 3));
}
for( i = j = 0; i < WIDTH * HEIGHT; i++ )
{
if( CellMap[ i ] & 0x7000 )
{
ChangeList1[ j++ ] = (short)&CellMap[ i ];
}
}
NextGen(); // Set cell states, prime the pump.
} }
}
void main( void ) NextGen(); // Set cell states, prime the pump.
{ }
unsigned long generation = 0;
char gen_text[ 80 ];
long start_time, end_time;
unsigned int seed;
printf( "Seed (0 for random seed): " ); void main( void )
scanf( "%d", &seed ); {
if( seed == 0 ) seed = (unsigned) time(NULL); unsigned long generation = 0;
srand( seed ); char gen_text[ 80 ];
long start_time, end_time;
unsigned int seed;
#ifndef NODRAW printf( "Seed (0 for random seed): " );
enter_display_mode(); scanf( "%d", &seed );
show_text( 0, 10, "Generation:" ); if( seed == 0 ) seed = (unsigned) time(NULL);
#endif srand( seed );
InitCellmap(); // randomly initialize cell map #ifndef NODRAW
enter_display_mode();
show_text( 0, 10, "Generation:" );
#endif
_bios_timeofday( _TIME_GETCLOCK, &start_time ); InitCellmap(); // randomly initialize cell map
do _bios_timeofday( _TIME_GETCLOCK, &start_time );
{
NextGen();
generation++;
#ifndef NOCOUNTER do
sprintf( gen_text, "%10lu", generation ); {
show_text( 0, 12, gen_text ); NextGen();
#endif generation++;
}
#ifdef GEN
while( generation < GEN );
#else
while( !kbhit() );
#endif
_bios_timeofday( _TIME_GETCLOCK, &end_time ); #ifndef NOCOUNTER
end_time -= start_time; sprintf( gen_text, "%10lu", generation );
show_text( 0, 12, gen_text );
#endif
}
#ifdef GEN
while( generation < GEN );
#else
while( !kbhit() );
#endif
#ifndef NODRAW _bios_timeofday( _TIME_GETCLOCK, &end_time );
getch(); // clear keypress end_time -= start_time;
exit_display_mode();
#endif
printf( "Total generations: %ld\nSeed: %u\n", generation, seed ); #ifndef NODRAW
printf( "%ld ticks\n", end_time ); getch(); // clear keypress
printf( "Time: %f generations/second\n", exit_display_mode();
(double)generation / (double)end_time * 18.2 ); #endif
}
printf( "Total generations: %ld\nSeed: %u\n", generation, seed );
printf( "%ld ticks\n", end_time );
printf( "Time: %f generations/second\n",
(double)generation / (double)end_time * 18.2 );
}
```
**LISTING 18.4 VIDEO.C** **LISTING 18.4 VIDEO.C**
/* VGA mode 13h functions for Game of Life. ```c
Tested with Borland C++. */ /* VGA mode 13h functions for Game of Life.
#include <stdio.h> Tested with Borland C++. */
#include <conio.h> #include <stdio.h>
#include <dos.h> #include <conio.h>
#include <dos.h>
#define TEXT_X_OFFSET 28 #define TEXT_X_OFFSET 28
#define SCREEN_WIDTH_IN_BYTES 320 #define SCREEN_WIDTH_IN_BYTES 320
#define SCREEN_SEGMENT 0xA000 #define SCREEN_SEGMENT 0xA000
/* Mode 13h mode-set function. */ /* Mode 13h mode-set function. */
void enter_display_mode() void enter_display_mode()
{ {
union REGS regset; union REGS regset;
regset.x.ax = 0x0013; regset.x.ax = 0x0013;
int86(0x10, &regset, &regset); int86(0x10, &regset, &regset);
} }
/* Text mode mode-set function. */ /* Text mode mode-set function. */
void exit_display_mode() void exit_display_mode()
{ {
union REGS regset; union REGS regset;
regset.x.ax = 0x0003; regset.x.ax = 0x0003;
int86(0x10, &regset, &regset); int86(0x10, &regset, &regset);
} }
/* Text display function. Offsets text to non-graphics area of /* Text display function. Offsets text to non-graphics area of
screen. */ screen. */
void show_text(int x, int y, char *text) void show_text(int x, int y, char *text)
{ {
gotoxy(TEXT_X_OFFSET + x, y); gotoxy(TEXT_X_OFFSET + x, y);
puts(text); puts(text);
} }
```
**LISTING 18.5 LIFE.H** **LISTING 18.5 LIFE.H**
void far NextGen( void ); ```c
void far NextGen( void );
extern unsigned short CellMap[]; extern unsigned short CellMap[];
extern unsigned short far ChangeList1[]; extern unsigned short far ChangeList1[];
#define LEFT (-2) #define LEFT (-2)
#define RIGHT (+2) #define RIGHT (+2)
#define UP (WIDTH * LEFT) #define UP (WIDTH * LEFT)
#define DOWN (WIDTH * RIGHT) #define DOWN (WIDTH * RIGHT)
#define UPPERLEFT (UP + LEFT) #define UPPERLEFT (UP + LEFT)
#define UPPERRIGHT (UP + RIGHT) #define UPPERRIGHT (UP + RIGHT)
#define LOWERLEFT (DOWN + LEFT) #define LOWERLEFT (DOWN + LEFT)
#define LOWERRIGHT (DOWN + RIGHT) #define LOWERRIGHT (DOWN + RIGHT)
#define WRAPLEFT (RIGHT * (WIDTH - 1)) #define WRAPLEFT (RIGHT * (WIDTH - 1))
#define WRAPRIGHT (LEFT * (WIDTH - 1)) #define WRAPRIGHT (LEFT * (WIDTH - 1))
#define WRAPUP (DOWN * (HEIGHT - 1)) #define WRAPUP (DOWN * (HEIGHT - 1))
#define WRAPDOWN (UP * (HEIGHT - 1)) #define WRAPDOWN (UP * (HEIGHT - 1))
```
### Keeping Track of Change with a Change List {#Heading5} ### Keeping Track of Change with a Change List {#Heading5}

View file

@ -103,9 +103,11 @@ and branching to the routine at that address. As with everything in this
amazing program, this represents the least possible work to accomplish amazing program, this represents the least possible work to accomplish
the desired result—just three instructions: the desired result—just three instructions:
mov dh,[bp+1] ```nasm
or dh,1 mov dh,[bp+1]
jmp dx or dh,1
jmp dx
```
These suffice to select the proper, minimum-work code to process the These suffice to select the proper, minimum-work code to process the
next cell triplet that has changed, and all potentially affected next cell triplet that has changed, and all potentially affected

View file

@ -102,13 +102,15 @@ pipelines mean that an AGI can now slow down execution of an instruction
that's *three* instructions away from the AGI (because four instructions that's *three* instructions away from the AGI (because four instructions
can execute in two cycles). So, for example, the code sequence can execute in two cycles). So, for example, the code sequence
add edx,4 ;U-pipe cycle 1 ```nasm
mov ecx,[ebx] ;V-pipe cycle 1 add edx,4 ;U-pipe cycle 1
add ebx,4 ;U-pipe cycle 2 mov ecx,[ebx] ;V-pipe cycle 1
mov [edx],ecx ;V-pipe cycle 3 add ebx,4 ;U-pipe cycle 2
; due to AGI mov [edx],ecx ;V-pipe cycle 3
; (would have been ; due to AGI
; V-pipe cycle 2) ; (would have been
; V-pipe cycle 2)
```
takes three cycles rather than the two cycles it should take, because takes three cycles rather than the two cycles it should take, because
EDX was modified on cycle 1 and an attempt was made to use it on cycle EDX was modified on cycle 1 and an attempt was made to use it on cycle
@ -116,10 +118,12 @@ two, before the AGI had time to clear—even though there are two
instructions between the instructions that are actually involved in the instructions between the instructions that are actually involved in the
AGI. Rearranging the code like AGI. Rearranging the code like
mov ecx,[ebx] ;U-pipe cycle 1 ```nasm
add ebx,4 ;V-pipe cycle 1 mov ecx,[ebx] ;U-pipe cycle 1
mov [edx+4],ecx ;U-pipe cycle 2 add ebx,4 ;V-pipe cycle 1
add edx,4 ;V-pipe cycle 2 mov [edx+4],ecx ;U-pipe cycle 2
add edx,4 ;V-pipe cycle 2
```
makes it functionally identical, but cuts the cycles to 2—a 50 percent makes it functionally identical, but cuts the cycles to 2—a 50 percent
improvement. Clearly, avoiding AGIs becomes a much more challenging and improvement. Clearly, avoiding AGIs becomes a much more challenging and

View file

@ -38,9 +38,11 @@ word-aligned, dword operands should be dword-aligned, and qword operands
(double-precision variables) should be qword-aligned. Spanning a dword (double-precision variables) should be qword-aligned. Spanning a dword
boundary, as in boundary, as in
mov ebx,3 ```nasm
: mov ebx,3
mov eax,[ebx] :
mov eax,[ebx]
```
costs three cycles. On the other hand, as noted above, branch targets costs three cycles. On the other hand, as noted above, branch targets
can now span cache lines with impunity, so on the Pentium there's no can now span cache lines with impunity, so on the Pentium there's no

134
20-02.md
View file

@ -40,43 +40,45 @@ waits until the first instruction is done, then executes in the U-pipe,
possibly pairing with the next instruction in line if all pairing possibly pairing with the next instruction in line if all pairing
conditions are met. conditions are met.
MOV reg,reg (1 cycle) ```nasm
mem,reg (1 cycle) MOV reg,reg (1 cycle)
reg,mem (1 cycle) mem,reg (1 cycle)
reg,immediate (1 cycle) reg,mem (1 cycle)
mem,immediate (1 cycle)† reg,immediate (1 cycle)
mem,immediate (1 cycle)†
AND/OR/XOR/ADD/SUB reg,reg (1 cycle) AND/OR/XOR/ADD/SUB reg,reg (1 cycle)
mem,reg (3 cycles) mem,reg (3 cycles)
reg,mem (2 cycles) reg,mem (2 cycles)
reg,immediate (1 cycle) reg,immediate (1 cycle)
mem,immediate (3 cycles)† mem,immediate (3 cycles)†
INC/DEC reg (1 cycle) INC/DEC reg (1 cycle)
mem (3 cycles) mem (3 cycles)
CMP reg,reg (1 cycle) CMP reg,reg (1 cycle)
mem,reg (2 cycles) mem,reg (2 cycles)
reg,mem (2 cycles) reg,mem (2 cycles)
reg,immediate (1 cycle) reg,immediate (1 cycle)
mem,immediate (2 cycles)† mem,immediate (2 cycles)†
TEST reg,reg (1 cycle) TEST reg,reg (1 cycle)
EAX,immediate (1 cycle) EAX,immediate (1 cycle)
PUSH/POP reg (1 cycle) PUSH/POP reg (1 cycle)
immediate (1 cycle) immediate (1 cycle)
LEA reg,mem (1 cycle) LEA reg,mem (1 cycle)
JCC near (1 cycle if predicted correctly; JCC near (1 cycle if predicted correctly;
5 cycles otherwise in V-pipe, 5 cycles otherwise in V-pipe,
4 cycles otherwise in U-pipe) 4 cycles otherwise in U-pipe)
JMP/CALL near (1 cycle if predicted correctly; JMP/CALL near (1 cycle if predicted correctly;
3 cycles otherwise) 3 cycles otherwise)
```
† Can't execute in V-pipe if address contains a displacement † Can't execute in V-pipe if address contains a displacement
**Table 20.1 Instructions that can execute in the V-pipe.** **Table 20.1 Instructions that can execute in the V-pipe.**
@ -100,41 +102,43 @@ each of 2 cycles, for 2\*0.5 = 1 cycle total execution time), as shown
in Figure 20.3—a full cycle *faster* than **PUSH [*mem*]**, which takes in Figure 20.3—a full cycle *faster* than **PUSH [*mem*]**, which takes
2 cycles. 2 cycles.
MOV reg,reg (1 cycle) ```nasm
mem,reg (1 cycle) MOV reg,reg (1 cycle)
reg,mem (1 cycle) mem,reg (1 cycle)
reg,immediate (1 cycle) reg,mem (1 cycle)
mem,immediate (1 cycle)† reg,immediate (1 cycle)
mem,immediate (1 cycle)†
AND/OR/XOR/ADD/SUB/ADC/SBB reg,reg (1 cycle) AND/OR/XOR/ADD/SUB/ADC/SBB reg,reg (1 cycle)
mem,reg (3 cycles) mem,reg (3 cycles)
reg,mem (2 cycles) reg,mem (2 cycles)
reg,immediate (1 cycle) reg,immediate (1 cycle)
mem,immediate (3 cycles)† mem,immediate (3 cycles)†
INC/DEC reg (1 cycle) INC/DEC reg (1 cycle)
mem (3 cycles) mem (3 cycles)
CMP reg,reg (1 cycle) CMP reg,reg (1 cycle)
mem,reg (2 cycles) mem,reg (2 cycles)
reg,mem (2 cycles) reg,mem (2 cycles)
reg,immediate (1 cycle) reg,immediate (1 cycle)
mem,immediate (2 cycles)† mem,immediate (2 cycles)†
TEST reg,reg (1 cycle) TEST reg,reg (1 cycle)
EAX,immediate (1 cycle) EAX,immediate (1 cycle)
PUSH/POP reg (1 cycle) PUSH/POP reg (1 cycle)
immediate (1 cycle) immediate (1 cycle)
LEA reg,mem (1 cycle) LEA reg,mem (1 cycle)
SHL/SHR/SAL/SAR reg,immediate (1 cycle)†† SHL/SHR/SAL/SAR reg,immediate (1 cycle)††
ROL/ROR/RCL/RCR reg,1 (1 cycle) ROL/ROR/RCL/RCR reg,1 (1 cycle)
```
† Can't pair if address contains a displacement † Can't pair if address contains a displacement\
†† Includes shift-by-1 forms of instructions †† Includes shift-by-1 forms of instructions
**Table 20.2 Instructions that, when executed in the U-pipe, allow **Table 20.2 Instructions that, when executed in the U-pipe, allow
V-pipe-executable instructions to execute simultaneously (pair) in the V-pipe-executable instructions to execute simultaneously (pair) in the
@ -155,25 +159,33 @@ One downside of this "RISCification" (turning complex instructions into
simple, RISC-like ones) of Pentium-optimized code is that it makes for simple, RISC-like ones) of Pentium-optimized code is that it makes for
substantially larger code. For example, substantially larger code. For example,
push dword ptr [esi] ```nasm
push dword ptr [esi]
```
is one byte smaller than this sequence: is one byte smaller than this sequence:
mov eax,[esi] ```nasm
push eax mov eax,[esi]
push eax
```
![**Figure 20.3**  *Pushing a value from memory effectively in one ![**Figure 20.3**  *Pushing a value from memory effectively in one
cycle.*](images/20-03.jpg) cycle.*](images/20-03.jpg)
A more telling example is the following A more telling example is the following
add [MemVar],eax ```nasm
add [MemVar],eax
```
versus the equivalent: versus the equivalent:
mov edx,[MemVar] ```nasm
add edx,eax mov edx,[MemVar]
mov [MemVar],edx add edx,eax
mov [MemVar],edx
```
The single complex instruction takes 3 cycles and is 6 bytes long; with The single complex instruction takes 3 cycles and is 6 bytes long; with
proper sequencing, interleaving the simple instructions with other proper sequencing, interleaving the simple instructions with other

View file

@ -79,23 +79,29 @@ the same values for address
bits 2, 3, and 4 (fall in the same bank) in tight loops, and you should bits 2, 3, and 4 (fall in the same bank) in tight loops, and you should
also avoid sequences like also avoid sequences like
mov bl,[esi] ```nasm
mov bh,[esi+1] mov bl,[esi]
mov bh,[esi+1]
```
because both operands will generally be in the same bank. An alternative because both operands will generally be in the same bank. An alternative
is to place another instruction between the two instructions that access is to place another instruction between the two instructions that access
the same bank, as in this sequence: the same bank, as in this sequence:
mov bl,[esi] ```nasm
mov edi,edx mov bl,[esi]
mov bh,[esi+1] mov edi,edx
mov bh,[esi+1]
```
By the way, the reason a code sequence that takes two instructions to By the way, the reason a code sequence that takes two instructions to
load a single word is attractive in a 32-bit segment is because it takes load a single word is attractive in a 32-bit segment is because it takes
only one cycle when the two instructions can be paired with other only one cycle when the two instructions can be paired with other
instructions; by contrast, the obvious way of loading BX instructions; by contrast, the obvious way of loading BX
mov bx,[esi] ```nasm
mov bx,[esi]
```
takes 1.5 to two cycles because the size prefix can't pair, as described takes 1.5 to two cycles because the size prefix can't pair, as described
below. This is yet another example of how different Pentium optimization below. This is yet another example of how different Pentium optimization
@ -116,5 +122,7 @@ but that turns out to not always be the case. Two two-cycle instructions
execute in two cycles, so it's okay to pair two instructions such as execute in two cycles, so it's okay to pair two instructions such as
these: these:
add esi,[SourceSkip] ;U-pipe cycles 1 and 2 ```nasm
add edi,[DestinationSkip] ;V-pipe cycles 1 and 2 add esi,[SourceSkip] ;U-pipe cycles 1 and 2
add edi,[DestinationSkip] ;V-pipe cycles 1 and 2
```

View file

@ -41,13 +41,17 @@ it's the only way to get both pipes running at capacity.
You may well ask why it's necessary to interleave operations, as is done You may well ask why it's necessary to interleave operations, as is done
in Figure 20.7. It seems simpler just to turn in Figure 20.7. It seems simpler just to turn
and [ebx],al ```nasm
and [ebx],al
```
into into
mov dl,[ebx] ```nasm
and dl,al mov dl,[ebx]
mov [ebx],dl and dl,al
mov [ebx],dl
```
and be done with it. The problem here is one of dependency. Before the and be done with it. The problem here is one of dependency. Before the
Pentium can execute **AND DL,AL,**, it must first know what is in DL, Pentium can execute **AND DL,AL,**, it must first know what is in DL,

View file

@ -65,7 +65,9 @@ areas of AGIs and register dependencies.
AGIs are *Address Generation Interlocks*, a fancy way of saying that if AGIs are *Address Generation Interlocks*, a fancy way of saying that if
a register is used to address memory, as is EBX in this instruction a register is used to address memory, as is EBX in this instruction
mov [ebx],eax ```nasm
mov [ebx],eax
```
and the value of the register is not set far enough ahead for the and the value of the register is not set far enough ahead for the
Pentium to perform the addressing calculations before the instruction Pentium to perform the addressing calculations before the instruction
@ -101,14 +103,16 @@ As an example of a sort of AGI that's new to the Pentium, consider the
following test for a NULL pointer, followed by the use of the pointer if following test for a NULL pointer, followed by the use of the pointer if
it's not NULL: it's not NULL:
push ebx ;U-pipe cycle 1 ```nasm
mov ebx,[Ptr] ;V-pipe cycle 1 push ebx ;U-pipe cycle 1
and ebx,ebx ;U-pipe cycle 2 mov ebx,[Ptr] ;V-pipe cycle 1
jz short IsNull ;V-pipe cycle 2 and ebx,ebx ;U-pipe cycle 2
mov eax,[ebx] ;U-pipe cycle 3 AGI stall jz short IsNull ;V-pipe cycle 2
mov edx,[ebp-8] ;V-pipe cycle 3 lockstep idle mov eax,[ebx] ;U-pipe cycle 3 AGI stall
;U-pipe cycle 4 mov eax,[ebx] mov edx,[ebp-8] ;V-pipe cycle 3 lockstep idle
;V-pipe cycle 4 mov edx,[ebp-8] ;U-pipe cycle 4 mov eax,[ebx]
;V-pipe cycle 4 mov edx,[ebp-8]
```
This commonplace code loses a U-pipe cycle to the AGI caused by **AND This commonplace code loses a U-pipe cycle to the AGI caused by **AND
EBX,EBX**, followed by the attempt two instructions later to use EBX to EBX,EBX**, followed by the attempt two instructions later to use EBX to

108
21-02.md
View file

@ -15,16 +15,22 @@ stack pointer. Implicit modifiers of ESP, such as **PUSH** and **POP**,
are special-cased so you don't have to worry about AGIs. However, if you are special-cased so you don't have to worry about AGIs. However, if you
explicitly modify ESP with this instruction explicitly modify ESP with this instruction
sub esp,100h ```nasm
sub esp,100h
```
for example, or with the popular for example, or with the popular
mov esp,ebp ```nasm
mov esp,ebp
```
you can then get AGIs if you attempt to use ESP to address memory, you can then get AGIs if you attempt to use ESP to address memory,
either explicitly with instructions like this one either explicitly with instructions like this one
moveax,[esp+20h] ```nasm
moveax,[esp+20h]
```
or via **PUSH**, **POP**, or other instructions that implicitly use ESP or via **PUSH**, **POP**, or other instructions that implicitly use ESP
as an addressing register. as an addressing register.
@ -32,25 +38,31 @@ as an addressing register.
On the 486, any instruction that had both a constant value and an On the 486, any instruction that had both a constant value and an
addressing displacement, such as addressing displacement, such as
mov dword ptr [ebp+16],1 ```nasm
mov dword ptr [ebp+16],1
```
suffered a 1-cycle penalty, taking a total of 2 cycles. Such suffered a 1-cycle penalty, taking a total of 2 cycles. Such
instructions take only one cycle on the Pentium, but they cannot pair, instructions take only one cycle on the Pentium, but they cannot pair,
so they're still the most expensive sort of **MOV**. Knowing this can so they're still the most expensive sort of **MOV**. Knowing this can
speed up something as simple as zeroing two memory variables, as in speed up something as simple as zeroing two memory variables, as in
sub eax,eax ;U-pipe 1 ```nasm
;any V-pipe pairable sub eax,eax ;U-pipe 1
; instruction can go here, ;any V-pipe pairable
; or SUB could be in V-pipe ; instruction can go here,
mov [MemVar1],eax ;U-pipe 2 ; or SUB could be in V-pipe
mov [MemVar2],eax ;V-pipe 2 mov [MemVar1],eax ;U-pipe 2
mov [MemVar2],eax ;V-pipe 2
```
which should never be slower and should potentially be 0.5 cycles which should never be slower and should potentially be 0.5 cycles
faster, and six bytes smaller than this sequence: faster, and six bytes smaller than this sequence:
mov [MemVar1],0 ;U-pipe 1 ```nasm
mov [MemVar2],0 ;U-pipe 2 mov [MemVar1],0 ;U-pipe 1
mov [MemVar2],0 ;U-pipe 2
```
Note, however, that my experiments thus far indicate that the two writes Note, however, that my experiments thus far indicate that the two writes
in the first case don't actually pair (possibly because the memory in the first case don't actually pair (possibly because the memory
@ -66,10 +78,12 @@ optimization: register contention. The basic premise here is simple: You
can't use the same register in two inherently sequential ways in a can't use the same register in two inherently sequential ways in a
single cycle. For example, you can't execute single cycle. For example, you can't execute
inc eax ;U-pipe cycle 1 ```nasm
;V-pipe idle cycle 1 inc eax ;U-pipe cycle 1
; due to dependency ;V-pipe idle cycle 1
and ebx,eax ;U-pipe cycle 2 ; due to dependency
and ebx,eax ;U-pipe cycle 2
```
in a single cycle; **AND EBX,EAX** can't execute until the value in EAX in a single cycle; **AND EBX,EAX** can't execute until the value in EAX
is known, and that can't happen until **INC EAX** is done. Consequently, is known, and that can't happen until **INC EAX** is done. Consequently,
@ -86,10 +100,12 @@ to write to the same register on the same cycle. While that may not seem
like a particularly useful operation in general, it can happen when like a particularly useful operation in general, it can happen when
subregisters are being set, as in the following subregisters are being set, as in the following
sub eax,eax ;U-pipe cycle 1 ```nasm
;V-pipe idle cycle 1 sub eax,eax ;U-pipe cycle 1
; due to register contention ;V-pipe idle cycle 1
mov al,[Var] ;U-pipe cycle 2 ; due to register contention
mov al,[Var] ;U-pipe cycle 2
```
where an attempt is made to set both EAX and its AL subregister on the where an attempt is made to set both EAX and its AL subregister on the
same cycle. Write-after-write contention implies that the two same cycle. Write-after-write contention implies that the two
@ -103,8 +119,10 @@ Intel has special-cased some very useful exceptions to register
contention. Happily, write-after-read operations do *not* cause contention. Happily, write-after-read operations do *not* cause
contention. Such operations, as in contention. Such operations, as in
mov eax,edx ;U-pipe cycle 1 ```nasm
sub edx,edxX ;V-pipe cycle 1 mov eax,edx ;U-pipe cycle 1
sub edx,edxX ;V-pipe cycle 1
```
are free of charge. are free of charge.
@ -117,12 +135,14 @@ register contention on ESP—but not AGIs—with instructions that use ESP
explicitly, such as **MOV EAX,[ESP+4]**.) Without this special case, the explicitly, such as **MOV EAX,[ESP+4]**.) Without this special case, the
following sequence would hardly use the V-pipe at all: following sequence would hardly use the V-pipe at all:
mov eax,[MemVar] ;U-pipe cycle 1 ```nasm
push esi ;V-pipe cycle 1 mov eax,[MemVar] ;U-pipe cycle 1
push eax ;U-pipe cycle 2 push esi ;V-pipe cycle 1
push edi ;V-pipe cycle 2 push eax ;U-pipe cycle 2
push ebx ;U-pipe cycle 3 push edi ;V-pipe cycle 2
call FooTilde ;V-pipe cycle 3 push ebx ;U-pipe cycle 3
call FooTilde ;V-pipe cycle 3
```
But in fact, all the instructions pair, even though ESP is modified five But in fact, all the instructions pair, even though ESP is modified five
times in the space of six instructions. times in the space of six instructions.
@ -133,11 +153,13 @@ pair only in the V-pipe: branches. Any near call or conditional or
unconditional near jump can execute in the V-pipe paired with any unconditional near jump can execute in the V-pipe paired with any
pairable U-pipe instruction, as illustrated by this sequence: pairable U-pipe instruction, as illustrated by this sequence:
LoopTop: ```nasm
mov [esi],eax ;U-pipe cycle 1 LoopTop:
add esi,4 ;V-pipe cycle 1 mov [esi],eax ;U-pipe cycle 1
dec ecx ;U-pipe cycle 2 add esi,4 ;V-pipe cycle 1
jnz LoopTop ;V-pipe cycle 2 dec ecx ;U-pipe cycle 2
jnz LoopTop ;V-pipe cycle 2
```
Branches can't pair in the U-pipe; a branch that executes in the U-pipe Branches can't pair in the U-pipe; a branch that executes in the U-pipe
runs alone, with the V-pipe idle. If a call or jump is correctly runs alone, with the V-pipe idle. If a call or jump is correctly
@ -159,12 +181,14 @@ instruction, the other instructions will go through different pipes than
previously, and cause the loop as a whole to take 50 percent longer, previously, and cause the loop as a whole to take 50 percent longer,
even though we only added 25 percent more cycles: even though we only added 25 percent more cycles:
LoopTop: ```nasm
inc edx ;U-pipe cycle 1 LoopTop:
mov [esi],eax ;V-pipe cycle 1 inc edx ;U-pipe cycle 1
add esi,4 ;U-pipe cycle 2 mov [esi],eax ;V-pipe cycle 1
dec ecx ;V-pipe cycle 2 add esi,4 ;U-pipe cycle 2
jnz LoopTop ;U-pipe cycle 3 dec ecx ;V-pipe cycle 2
;V-pipe idle cycle 3 jnz LoopTop ;U-pipe cycle 3
; because JNZ can't ;V-pipe idle cycle 3
; pair in the U-pipe ; because JNZ can't
; pair in the U-pipe
```

View file

@ -40,34 +40,36 @@ right?
**LISTING 21.1 L21-1.ASM** **LISTING 21.1 L21-1.ASM**
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer ```nasm
; starting at ESI, of length ECX words. ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
; Returns checksum in AX. ; starting at ESI, of length ECX words.
; ECX and ESI destroyed. ; Returns checksum in AX.
; All cycle counts assume 32-bit protected mode. ; ECX and ESI destroyed.
; Assumes buffer length > 0. ; All cycle counts assume 32-bit protected mode.
; Note that timing indicates that the pipe sequence and ; Assumes buffer length > 0.
; cycle counts shown (based on documented execution rules) ; Note that timing indicates that the pipe sequence and
; differ from the actual execution sequence and cycle counts; ; cycle counts shown (based on documented execution rules)
; this loop has been measured to execute in 5 cycles; apparently, ; differ from the actual execution sequence and cycle counts;
; the 1st half of ADD somehow pairs with the prefix byte, or the ; this loop has been measured to execute in 5 cycles; apparently,
; refix byte gets executed ahead of time. ; the 1st half of ADD somehow pairs with the prefix byte, or the
; refix byte gets executed ahead of time.
sub ax,ax ;initialize the checksum sub ax,ax ;initialize the checksum
ckloop: ckloop:
add ax,[esi] ;cycle 1 U-pipe prefix byte add ax,[esi] ;cycle 1 U-pipe prefix byte
;cycle 1 V-pipe idle (no pairing w/prefix) ;cycle 1 V-pipe idle (no pairing w/prefix)
;cycle 2 U-pipe 1st half of ADD ;cycle 2 U-pipe 1st half of ADD
;cycle 2 V-pipe idle (register contention) ;cycle 2 V-pipe idle (register contention)
;cycle 3 U-pipe 2nd half of ADD ;cycle 3 U-pipe 2nd half of ADD
;cycle 3 V-pipe idle (register contention) ;cycle 3 V-pipe idle (register contention)
adc ax,0 ;cycle 4 U-pipe prefix byte adc ax,0 ;cycle 4 U-pipe prefix byte
;cycle 4 V-pipe idle (no pairing w/prefix) ;cycle 4 V-pipe idle (no pairing w/prefix)
;cycle 5 U-pipe ADC AX,0 ;cycle 5 U-pipe ADC AX,0
add esi,2 ;cycle 5 V-pipe add esi,2 ;cycle 5 V-pipe
dec ecx ;cycle 6 U-pipe dec ecx ;cycle 6 U-pipe
jnz ckloop ;cycle 6 V-pipe jnz ckloop ;cycle 6 V-pipe
```
Wrong, wrong, wrong! As detailed in Listing 21.1, this loop should take Wrong, wrong, wrong! As detailed in Listing 21.1, this loop should take
6 cycles per checksummed word in 32-bit protected mode, a ridiculously 6 cycles per checksummed word in 32-bit protected mode, a ridiculously

134
21-04.md
View file

@ -27,32 +27,34 @@ same cache data bank, as discussed in the last chapter).
**LISTING 21.2 L21-2.ASM** **LISTING 21.2 L21-2.ASM**
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer ```nasm
; starting at ESI, of length ECX words. ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
; Returns checksum in AX. ; starting at ESI, of length ECX words.
; High word of EAX, DX, ECX and ESI destroyed. ; Returns checksum in AX.
; All cycle counts assume 32-bit protected mode. ; High word of EAX, DX, ECX and ESI destroyed.
; Assumes buffer length > 0. ; All cycle counts assume 32-bit protected mode.
; Assumes buffer length > 0.
sub eax,eax ;initialize the checksum sub eax,eax ;initialize the checksum
mov dx,[esi] ;first word to checksum mov dx,[esi] ;first word to checksum
dec ecx ;we'll do 1 checksum outside the loop dec ecx ;we'll do 1 checksum outside the loop
jz short ckloopend ;only 1 checksum to do jz short ckloopend ;only 1 checksum to do
add esi,2 ;point to the next word to checksum add esi,2 ;point to the next word to checksum
ckloop: ckloop:
add al,dl ;cycle 1 U-pipe add al,dl ;cycle 1 U-pipe
mov dl,[esi] ;cycle 1 V-pipe mov dl,[esi] ;cycle 1 V-pipe
adc ah,dh ;cycle 2 U-pipe adc ah,dh ;cycle 2 U-pipe
mov dh,[esi+1] ;cycle 2 V-pipe mov dh,[esi+1] ;cycle 2 V-pipe
adc eax,0 ;cycle 3 U-pipe adc eax,0 ;cycle 3 U-pipe
add esi,2 ;cycle 3 V-pipe add esi,2 ;cycle 3 V-pipe
dec ecx ;cycle 4 U-pipe dec ecx ;cycle 4 U-pipe
jnz ckloop ;cycle 4 V-pipe jnz ckloop ;cycle 4 V-pipe
ckloopend: ckloopend:
add ax,dx ;checksum the last word add ax,dx ;checksum the last word
adc eax,0 adc eax,0
```
Listing 21.3 is a more sophisticated attempt to speed up the checksum Listing 21.3 is a more sophisticated attempt to speed up the checksum
calculation. Here we see a hallmark of Pentium optimization: two calculation. Here we see a hallmark of Pentium optimization: two
@ -68,52 +70,54 @@ placement of **ADD ESI,4** to avoid an AGI.
**LISTING 21.3 L21-3.ASM** **LISTING 21.3 L21-3.ASM**
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer ```nasm
; starting at ESI, of length ECX words. ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
; Returns checksum in AX. ; starting at ESI, of length ECX words.
; High word of EAX, BX, EDX, ECX and ESI destroyed. ; Returns checksum in AX.
; All cycle counts assume 32-bit protected mode. ; High word of EAX, BX, EDX, ECX and ESI destroyed.
; Assumes buffer length > 0. ; All cycle counts assume 32-bit protected mode.
; Assumes buffer length > 0.
sub eax,eax ;initialize the checksum sub eax,eax ;initialize the checksum
sub edx,edx ;prepare for later ORing sub edx,edx ;prepare for later ORing
shr ecx,1 ;we'll do two words per loop shr ecx,1 ;we'll do two words per loop
jnc short ckloopsetup ;even number of words jnc short ckloopsetup ;even number of words
mov ax,[esi] ;do the odd word mov ax,[esi] ;do the odd word
jz short ckloopdone ;no more words to checksum jz short ckloopdone ;no more words to checksum
add esi,2 ;point to the next word add esi,2 ;point to the next word
ckloopsetup: ckloopsetup:
mov dx,[esi] ;load most of 1st word to mov dx,[esi] ;load most of 1st word to
mov bl,[esi+2] ; checksum (last byte loaded in loop) mov bl,[esi+2] ; checksum (last byte loaded in loop)
dec ecx ;any more dwords to checksum? dec ecx ;any more dwords to checksum?
jz short ckloopend ;no jz short ckloopend ;no
ckloop: ckloop:
mov bh,[esi+3] ;cycle 1 U-pipe mov bh,[esi+3] ;cycle 1 U-pipe
add esi,4 ;cycle 1 V-pipe add esi,4 ;cycle 1 V-pipe
shl ebx,16 ;cycle 2 U-pipe shl ebx,16 ;cycle 2 U-pipe
;cycle 2 V-pipe idle ;cycle 2 V-pipe idle
; (register contention) ; (register contention)
or ebx,edx ;cycle 3 U-pipe or ebx,edx ;cycle 3 U-pipe
mov dl,[esi] ;cycle 3 V-pipe mov dl,[esi] ;cycle 3 V-pipe
add eax,ebx ;cycle 4 U-pipe add eax,ebx ;cycle 4 U-pipe
mov bl,[esi+2] ;cycle 4 V-pipe mov bl,[esi+2] ;cycle 4 V-pipe
adc eax,0 ;cycle 5 U-pipe adc eax,0 ;cycle 5 U-pipe
mov dh,[esi+1] ;cycle 5 V-pipe mov dh,[esi+1] ;cycle 5 V-pipe
dec ecx ;cycle 6 U-pipe dec ecx ;cycle 6 U-pipe
jnz ckloop ;cycle 6 V-pipe jnz ckloop ;cycle 6 V-pipe
ckloopend: ckloopend:
mov bh,[esi+3] ;checksum the last dword mov bh,[esi+3] ;checksum the last dword
add ax,dx add ax,dx
adc ax,bx adc ax,bx
adc ax,0 adc ax,0
mov edx,eax ;compress the 32-bit checksum mov edx,eax ;compress the 32-bit checksum
shr edx,16 ; into a 16-bit checksum shr edx,16 ; into a 16-bit checksum
add ax,dx add ax,dx
adc eax,0 adc eax,0
ckloopdone: ckloopdone:
```
The checksum loop in Listing 21.3 takes longer than the loop in Listing The checksum loop in Listing 21.3 takes longer than the loop in Listing
21.2, at 6 cycles versus 4 cycles for Listing 21.2—but Listing 21.3 does 21.2, at 6 cycles versus 4 cycles for Listing 21.2—but Listing 21.3 does

132
21-05.md
View file

@ -12,36 +12,38 @@ pages: 409-411
**LISTING 21.4 L21-4.ASM** **LISTING 21.4 L21-4.ASM**
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer ```nasm
; starting at ESI, of length ECX words. ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
; Returns checksum in AX. ; starting at ESI, of length ECX words.
; High word of EAX, ECX, EDX, and ESI destroyed. ; Returns checksum in AX.
; All cycle counts assume 32-bit protected mode. ; High word of EAX, ECX, EDX, and ESI destroyed.
; Assumes buffer starts on a dword boundary, is a dword multiple ; All cycle counts assume 32-bit protected mode.
; in length, and length > 0. ; Assumes buffer starts on a dword boundary, is a dword multiple
; in length, and length > 0.
sub eax,eax ;initialize the checksum sub eax,eax ;initialize the checksum
shr ecx,1 ;we'll do two words per loop shr ecx,1 ;we'll do two words per loop
mov edx,[esi] ;preload the first dword mov edx,[esi] ;preload the first dword
add esi,4 ;point to the next dword add esi,4 ;point to the next dword
dec ecx ;we'll do 1 checksum outside the loop dec ecx ;we'll do 1 checksum outside the loop
jz short ckloopend ;only 1 checksum to do jz short ckloopend ;only 1 checksum to do
ckloop: ckloop:
add eax,edx ;cycle 1 U-pipe add eax,edx ;cycle 1 U-pipe
mov edx,[esi] ;cycle 1 V-pipe mov edx,[esi] ;cycle 1 V-pipe
adc eax,0 ;cycle 2 U-pipe adc eax,0 ;cycle 2 U-pipe
add esi,4 ;cycle 2 V-pipe add esi,4 ;cycle 2 V-pipe
dec ecx ;cycle 3 U-pipe dec ecx ;cycle 3 U-pipe
jnz ckloop ;cycle 3 V-pipe jnz ckloop ;cycle 3 V-pipe
ckloopend: ckloopend:
add eax,edx ;checksum the last dword add eax,edx ;checksum the last dword
adc eax,0 adc eax,0
mov edx,eax ;compress the 32-bit checksum mov edx,eax ;compress the 32-bit checksum
shr edx,16 ; into a 16-bit checksum shr edx,16 ; into a 16-bit checksum
add ax,dx add ax,dx
adc eax,0 adc eax,0
```
Listing 21.5 improves upon Listing 21.4 by processing 2 dwords per loop, Listing 21.5 improves upon Listing 21.4 by processing 2 dwords per loop,
thereby bringing the time per checksummed word down to exactly 1 cycle. thereby bringing the time per checksummed word down to exactly 1 cycle.
@ -53,46 +55,48 @@ more registers.
**LISTING 21.5 L21-5.ASM** **LISTING 21.5 L21-5.ASM**
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer ```nasm
; starting at ESI, of length ECX words. ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
; Returns checksum in AX. ; starting at ESI, of length ECX words.
; High word of EAX, EBX, ECX, EDX, and ESI destroyed. ; Returns checksum in AX.
; All cycle counts assume 32-bit protected mode. ; High word of EAX, EBX, ECX, EDX, and ESI destroyed.
; Assumes buffer starts on a dword boundary, is a dword multiple ; All cycle counts assume 32-bit protected mode.
; in length, and length > 0. ; Assumes buffer starts on a dword boundary, is a dword multiple
; in length, and length > 0.
sub eax,eax ;initialize the checksum sub eax,eax ;initialize the checksum
shr ecx,2 ;we'll do two dwords per loop shr ecx,2 ;we'll do two dwords per loop
jnc short noodddword ;is there an odd dword in buffer? jnc short noodddword ;is there an odd dword in buffer?
mov eax,[esi] ;checksum the odd dword mov eax,[esi] ;checksum the odd dword
jz short ckloopdone ;no, done jz short ckloopdone ;no, done
add esi,4 ;point to the next dword add esi,4 ;point to the next dword
noodddword: noodddword:
mov edx,[esi] ;preload the first dword mov edx,[esi] ;preload the first dword
mov ebx,[esi+4] ;preload the second dword mov ebx,[esi+4] ;preload the second dword
dec ecx ;we'll do 1 checksum outside the loop dec ecx ;we'll do 1 checksum outside the loop
jz short ckloopend ;only 1 checksum to do jz short ckloopend ;only 1 checksum to do
add esi,8 ;point to the next dword add esi,8 ;point to the next dword
ckloop: ckloop:
add eax,edx ;cycle 1 U-pipe add eax,edx ;cycle 1 U-pipe
mov edx,[esi] ;cycle 1 V-pipe mov edx,[esi] ;cycle 1 V-pipe
adc eax,ebx ;cycle 2 U-pipe adc eax,ebx ;cycle 2 U-pipe
mov ebx,[esi+4] ;cycle 2 V-pipe mov ebx,[esi+4] ;cycle 2 V-pipe
adc eax,0 ;cycle 3 U-pipe adc eax,0 ;cycle 3 U-pipe
add esi,8 ;cycle 3 V-pipe add esi,8 ;cycle 3 V-pipe
dec ecx ;cycle 4 U-pipe dec ecx ;cycle 4 U-pipe
jnz ckloop ;cycle 4 V-pipe jnz ckloop ;cycle 4 V-pipe
ckloopend: ckloopend:
add eax,edx ;checksum the last two dwords add eax,edx ;checksum the last two dwords
adc eax,ebx adc eax,ebx
adc eax,0 adc eax,0
ckloopdone: ckloopdone:
mov edx,eax ;compress the 32-bit checksum mov edx,eax ;compress the 32-bit checksum
shr edx,16 ; into a 16-bit checksum shr edx,16 ; into a 16-bit checksum
add ax,dx add ax,dx
adc eax,0 adc eax,0
```
Listing 21.5 is undeniably intricate code, and not the sort of thing one Listing 21.5 is undeniably intricate code, and not the sort of thing one
would choose to write as a matter of course. On the other hand, it's would choose to write as a matter of course. On the other hand, it's

View file

@ -55,40 +55,42 @@ are mine.
**LISTING 22.1 L22-1.ASM** **LISTING 22.1 L22-1.ASM**
OnStack struc ;data that's stored on the stack after PUSH BP ```nasm
OldBP dw ? ;caller's BP OnStack struc ;data that's stored on the stack after PUSH BP
RetAddr dw ? ;return address OldBP dw ? ;caller's BP
Filler dw ? ;character to fill the buffer with RetAddr dw ? ;return address
Attrib dw ? ;attribute to fill the buffer with Filler dw ? ;character to fill the buffer with
BufSize dw ? ;number of character/attribute pairs to fill Attrib dw ? ;attribute to fill the buffer with
BufOfs dw ? ;buffer offset BufSize dw ? ;number of character/attribute pairs to fill
BufSeg dw ? ;buffer segment BufOfs dw ? ;buffer offset
EndMrk db ? ;marker for the end of the stack frame BufSeg dw ? ;buffer segment
OnStack ends EndMrk db ? ;marker for the end of the stack frame
; OnStack ends
ClearS proc near ;
push bp ;save caller's BP ClearS proc near
mov bp,sp ;point to stack frame push bp ;save caller's BP
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null mov bp,sp ;point to stack frame
jne Start ; pointer is passed cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
cmp word ptr [bp].BufOfs,0 jne Start ; pointer is passed
je Bye cmp word ptr [bp].BufOfs,0
Start: cld ;make STOSW count up je Bye
mov ax,[bp].Attrib ;load AX with attribute parameter Start: cld ;make STOSW count up
and ax,0ff00h ;prepare for merging with fill char mov ax,[bp].Attrib ;load AX with attribute parameter
mov bx,[bp].Filler ;load BX with fill char and ax,0ff00h ;prepare for merging with fill char
and bx,0ffh ;prepare for merging with attribute mov bx,[bp].Filler ;load BX with fill char
or ax,bx ;combine attribute and fill char and bx,0ffh ;prepare for merging with attribute
mov bx,[bp].BufOfs ;load DI with target buffer offset or ax,bx ;combine attribute and fill char
mov di,bx mov bx,[bp].BufOfs ;load DI with target buffer offset
mov bx,[bp].BufSeg ;load ES with target buffer segment mov di,bx
mov es,bx mov bx,[bp].BufSeg ;load ES with target buffer segment
mov cx,[bp].BufSize ;load CX with buffer size mov es,bx
rep stosw ;fill the buffer mov cx,[bp].BufSize ;load CX with buffer size
Bye:mov sp,bp ;restore original stack pointer rep stosw ;fill the buffer
pop bp ; and caller's BP Bye:mov sp,bp ;restore original stack pointer
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack pop bp ; and caller's BP
ClearS endp ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp
```
The first thing you'll notice about Listing 22.1 is that **ClearS** uses The first thing you'll notice about Listing 22.1 is that **ClearS** uses
a **REP STOSW** instruction. That means that we're not going to improve a **REP STOSW** instruction. That means that we're not going to improve

View file

@ -12,27 +12,29 @@ pages: 417-418
**LISTING 22.2 L22-2.ASM** **LISTING 22.2 L22-2.ASM**
ClearS proc near ```nasm
push bp ;save caller's BP ClearS proc near
mov bp,sp ;point to stack frame push bp ;save caller's BP
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null mov bp,sp ;point to stack frame
jne Start ; pointer is passed cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
cmp word ptr [bp].BufOfs,0 jne Start ; pointer is passed
je Bye cmp word ptr [bp].BufOfs,0
Start: cld ;make STOSW count up je Bye
mov ax,[bp].Attrib ;load AX with attribute parameter Start: cld ;make STOSW count up
and ax,0ff00h ;prepare for merging with fill char mov ax,[bp].Attrib ;load AX with attribute parameter
mov bx,[bp].Filler ;load BX with fill char and ax,0ff00h ;prepare for merging with fill char
and bx,0ffh ;prepare for merging with attribute mov bx,[bp].Filler ;load BX with fill char
or ax,bx ;combine attribute and fill char and bx,0ffh ;prepare for merging with attribute
mov di,[bp].BufOfs ;load DI with target buffer offset or ax,bx ;combine attribute and fill char
mov es,[bp].BufSeg ;load ES with target buffer segment mov di,[bp].BufOfs ;load DI with target buffer offset
mov cx,[bp].BufSize ;load CX with buffer size mov es,[bp].BufSeg ;load ES with target buffer segment
rep stosw ;fill the buffer mov cx,[bp].BufSize ;load CX with buffer size
Bye: rep stosw ;fill the buffer
pop bp ;restore caller's BP Bye:
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack pop bp ;restore caller's BP
ClearS endp ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp
```
(The **OnStack** structure definition doesn't change in any of our (The **OnStack** structure definition doesn't change in any of our
examples, so I'm not going clutter up this chapter by reproducing it for examples, so I'm not going clutter up this chapter by reproducing it for
@ -47,27 +49,29 @@ loading ES and DI as shown in Listing 22.3.
**LISTING 22.3 L22-3.ASM** **LISTING 22.3 L22-3.ASM**
ClearS proc near ```nasm
push bp ;save caller's BP ClearS proc near
mov bp,sp ;point to stack frame push bp ;save caller's BP
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null mov bp,sp ;point to stack frame
jne Start ; pointer is passed cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
cmp word ptr [bp].BufOfs,0 jne Start ; pointer is passed
je Bye cmp word ptr [bp].BufOfs,0
Start: cld ;make STOSW count up je Bye
mov ax,[bp].Attrib ;load AX with attribute parameter Start: cld ;make STOSW count up
and ax,0ff00h ;prepare for merging with fill char mov ax,[bp].Attrib ;load AX with attribute parameter
mov bx,[bp].Filler ;load BX with fill char and ax,0ff00h ;prepare for merging with fill char
and bx,0ffh ;prepare for merging with attribute mov bx,[bp].Filler ;load BX with fill char
or ax,bx ;combine attribute and fill char and bx,0ffh ;prepare for merging with attribute
les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer or ax,bx ;combine attribute and fill char
;segment:offset les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer
mov cx,[bp].BufSize ;load CX with buffer size ;segment:offset
rep stosw ;fill the buffer mov cx,[bp].BufSize ;load CX with buffer size
Bye: rep stosw ;fill the buffer
pop bp ;restore caller's BP Bye:
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack pop bp ;restore caller's BP
ClearS endp ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp
```
That's good for another three bytes. We're down to 43 bytes, and That's good for another three bytes. We're down to 43 bytes, and
counting. counting.

104
22-03.md
View file

@ -12,23 +12,25 @@ pages: 419-420
**LISTING 22.5 L22-5.ASM** **LISTING 22.5 L22-5.ASM**
ClearS proc near ```nasm
push bp ;save caller's BP ClearS proc near
mov bp,sp ;point to stack frame push bp ;save caller's BP
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null mov bp,sp ;point to stack frame
jne Start ; pointer is passed cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
cmp word ptr [bp].BufOfs,0 jne Start ; pointer is passed
je Bye cmp word ptr [bp].BufOfs,0
Start: cld ;make STOSW count up je Bye
mov ah,byte ptr [bp].Attrib[1];load AH with attribute Start: cld ;make STOSW count up
mov al,byte ptr [bp].Filler ;load AL with fill char mov ah,byte ptr [bp].Attrib[1];load AH with attribute
les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer segment:offset mov al,byte ptr [bp].Filler ;load AL with fill char
mov cx,[bp].BufSize ;load CX with buffer size les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer segment:offset
rep stosw ;fill the buffer mov cx,[bp].BufSize ;load CX with buffer size
Bye: rep stosw ;fill the buffer
pop bp ;restore caller's BP Bye:
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack pop bp ;restore caller's BP
ClearS endp ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp
```
(We could get rid of yet another instruction by having the calling code (We could get rid of yet another instruction by having the calling code
pack both the attribute and the fill value into the same word, but pack both the attribute and the fill value into the same word, but
@ -44,22 +46,24 @@ shown in Listing 22.6.
**LISTING 22.6 L22-6.ASM** **LISTING 22.6 L22-6.ASM**
ClearS proc near ```nasm
push bp ;save caller's BP ClearS proc near
mov bp,sp ;point to stack frame push bp ;save caller's BP
les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer;segment:offset mov bp,sp ;point to stack frame
mov ax,es ;put segment where we can test it les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer;segment:offset
or ax,di ;is it a null pointer? mov ax,es ;put segment where we can test it
je Bye ;yes, so we're done or ax,di ;is it a null pointer?
Start: cld ;make STOSW count up je Bye ;yes, so we're done
mov ah,byte ptr [bp].Attrib[1];load AH with attribute Start: cld ;make STOSW count up
mov al,byte ptr [bp].Filler ;load AL with fill char mov ah,byte ptr [bp].Attrib[1];load AH with attribute
mov cx,[bp].BufSize ;load CX with buffer size mov al,byte ptr [bp].Filler ;load AL with fill char
rep stosw ;fill the buffer mov cx,[bp].BufSize ;load CX with buffer size
Bye: rep stosw ;fill the buffer
pop bp ;restore caller's BP Bye:
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack pop bp ;restore caller's BP
ClearS endp ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp
```
Well. Now we're down to 28 bytes, having reduced the size of this Well. Now we're down to 28 bytes, having reduced the size of this
subroutine by nearly 50 percent. Only 13 instructions remain. subroutine by nearly 50 percent. Only 13 instructions remain.
@ -103,22 +107,24 @@ With that problem dealt with, Listing 22.7 shows the Zenned version of
**LISTING 22.7 L22-7.ASM** **LISTING 22.7 L22-7.ASM**
ClearS procnear ```nasm
pop dx ;get the return address ClearS procnear
pop ax ;put fill char into AL pop dx ;get the return address
pop bx ;get the attribute pop ax ;put fill char into AL
mov ah,bh ;put attribute into AH pop bx ;get the attribute
pop cx ;get the buffer size mov ah,bh ;put attribute into AH
pop di ;get the offset of the buffer origin pop cx ;get the buffer size
pop es ;get the segment of the buffer origin pop di ;get the offset of the buffer origin
mov bx,es ;put the segment where we can test it pop es ;get the segment of the buffer origin
or bx,di ;null pointer? mov bx,es ;put the segment where we can test it
je Bye ;yes, so we're done or bx,di ;null pointer?
cld ;make STOSW count up je Bye ;yes, so we're done
rep stosw ;do the string store cld ;make STOSW count up
Bye: rep stosw ;do the string store
jmp dx ;return to the calling code Bye:
ClearS endp jmp dx ;return to the calling code
ClearS endp
```
At long last, we're down to the bare metal. This version of **ClearS** At long last, we're down to the bare metal. This version of **ClearS**
is just 19 bytes long. That's just 37 percent as long as the original is just 19 bytes long. That's just 37 percent as long as the original

1104
23-03.md

File diff suppressed because it is too large Load diff

504
24-02.md
View file

@ -12,254 +12,256 @@ pages: 453-458
**LISTING 24.1 L24-1.ASM** **LISTING 24.1 L24-1.ASM**
; Program to illustrate operation of ALUs and latches of the VGA's ```nasm
; Graphics Controller. Draws a variety of patterns against ; Program to illustrate operation of ALUs and latches of the VGA's
; a horizontally striped background, using each of the 4 available ; Graphics Controller. Draws a variety of patterns against
; logical functions (data unmodified, AND, OR, XOR) in turn to combine ; a horizontally striped background, using each of the 4 available
; the images with the background. ; logical functions (data unmodified, AND, OR, XOR) in turn to combine
; By Michael Abrash. ; the images with the background.
; ; By Michael Abrash.
stack segment para stack ‘STACK' ;
db 512 dup(?) stack segment para stack ‘STACK'
stack ends db 512 dup(?)
; stack ends
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment ;
SCREEN_HEIGHT equ 350 VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
SCREEN_WIDTH_IN_BYTES equ 80 SCREEN_HEIGHT equ 350
DEMO_AREA_HEIGHT equ 336 ;# of scan lines in area SCREEN_WIDTH_IN_BYTES equ 80
; logical function operation DEMO_AREA_HEIGHT equ 336 ;# of scan lines in area
; is demonstrated in ; logical function operation
DEMO_AREA_WIDTH_IN_BYTES equ 40 ;width in bytes of area ; is demonstrated in
; logical function operation DEMO_AREA_WIDTH_IN_BYTES equ 40 ;width in bytes of area
; is demonstrated in ; logical function operation
VERTICAL_BOX_WIDTH_IN_BYTES equ 10 ;width in bytes of the box used to ; is demonstrated in
; demonstrate each logical function VERTICAL_BOX_WIDTH_IN_BYTES equ 10 ;width in bytes of the box used to
; ; demonstrate each logical function
; VGA register equates. ;
; ; VGA register equates.
GC_INDEX equ 3ceh ;GC index register ;
GC_ROTATE equ 3 ;GC data rotate/logical function GC_INDEX equ 3ceh ;GC index register
; register index GC_ROTATE equ 3 ;GC data rotate/logical function
GC_MODE equ 5 ;GC mode register index ; register index
; GC_MODE equ 5 ;GC mode register index
dseg segment para common ‘DATA' ;
; dseg segment para common ‘DATA'
; String used to label logical functions. ;
; ; String used to label logical functions.
LabelString label byte ;
db ‘UNMODIFIED AND OR XOR ' LabelString label byte
LABEL_STRING_LENGTH equ $-LabelString db ‘UNMODIFIED AND OR XOR '
; LABEL_STRING_LENGTH equ $-LabelString
; Strings used to label fill patterns. ;
; ; Strings used to label fill patterns.
FillPatternFF db ‘Fill Pattern: 0FFh' ;
FILL_PATTERN_FF_LENGTH equ $ - FillPatternFF FillPatternFF db ‘Fill Pattern: 0FFh'
FillPattern00 db ‘Fill Pattern: 000h' FILL_PATTERN_FF_LENGTH equ $ - FillPatternFF
FILL_PATTERN_00_LENGTH equ $ - FillPattern00 FillPattern00 db ‘Fill Pattern: 000h'
FillPatternVert db ‘Fill Pattern: Vertical Bar' FILL_PATTERN_00_LENGTH equ $ - FillPattern00
FILL_PATTERN_VERT_LENGTH equ $ - FillPatternVert FillPatternVert db ‘Fill Pattern: Vertical Bar'
FillPatternHorz db ‘Fill Pattern: Horizontal Bar' FILL_PATTERN_VERT_LENGTH equ $ - FillPatternVert
FILL_PATTERN_HORZ_LENGTH equ $ - FillPatternHorz FillPatternHorz db ‘Fill Pattern: Horizontal Bar'
; FILL_PATTERN_HORZ_LENGTH equ $ - FillPatternHorz
dseg ends ;
; dseg ends
; Macro to set indexed register INDEX of GC chip to SETTING. ;
; ; Macro to set indexed register INDEX of GC chip to SETTING.
SETGC macro INDEX, SETTING ;
mov dx,GC_INDEX SETGC macro INDEX, SETTING
mov ax,(SETTING SHL 8) OR INDEX mov dx,GC_INDEX
out dx,ax mov ax,(SETTING SHL 8) OR INDEX
endm out dx,ax
; endm
; ;
; Macro to call BIOS write string function to display text string ;
; TEXT_STRING, of length TEXT_LENGTH, at location ROW,COLUMN. ; Macro to call BIOS write string function to display text string
; ; TEXT_STRING, of length TEXT_LENGTH, at location ROW,COLUMN.
TEXT_UP macro TEXT_STRING, TEXT_LENGTH, ROW, COLUMN ;
mov ah,13h ;BIOS write string function TEXT_UP macro TEXT_STRING, TEXT_LENGTH, ROW, COLUMN
mov bp,offset TEXT_STRING ;ES:BP points to string mov ah,13h ;BIOS write string function
mov cx,TEXT_LENGTH mov bp,offset TEXT_STRING ;ES:BP points to string
mov dx,(ROW SHL 8) OR COLUMN ;position mov cx,TEXT_LENGTH
sub al,al ;string is chars only, cursor not moved mov dx,(ROW SHL 8) OR COLUMN ;position
mov bl,7 ;text attribute is white (light gray) sub al,al ;string is chars only, cursor not moved
int 10h mov bl,7 ;text attribute is white (light gray)
endm int 10h
; endm
cseg segment para public ‘CODE' ;
assume cs:cseg, ds:dseg cseg segment para public ‘CODE'
start proc near assume cs:cseg, ds:dseg
mov ax,dseg start proc near
mov ds,ax mov ax,dseg
; mov ds,ax
; Select 640x350 graphics mode. ;
; ; Select 640x350 graphics mode.
mov ax,010h ;
int 10h mov ax,010h
; int 10h
; ES points to VGA memory. ;
; ; ES points to VGA memory.
mov ax,VGA_VIDEO_SEGMENT ;
mov es,ax mov ax,VGA_VIDEO_SEGMENT
; mov es,ax
; Draw background of horizontal bars. ;
; ; Draw background of horizontal bars.
mov dx,SCREEN_HEIGHT/4 ;
;# of bars to draw (each 4 pixels high) mov dx,SCREEN_HEIGHT/4
sub di,di ;start at offset 0 in display memory ;# of bars to draw (each 4 pixels high)
mov ax,0ffffh ;fill pattern for light areas of bars sub di,di ;start at offset 0 in display memory
mov bx,DEMO_AREA_WIDTH_IN_BYTES / 2 ;length of each bar mov ax,0ffffh ;fill pattern for light areas of bars
mov si,SCREEN_WIDTH_IN_BYTES - DEMO_AREA_WIDTH_IN_BYTES mov bx,DEMO_AREA_WIDTH_IN_BYTES / 2 ;length of each bar
mov bp,(SCREEN_WIDTH_IN_BYTES * 3) - DEMO_AREA_WIDTH_IN_BYTES mov si,SCREEN_WIDTH_IN_BYTES - DEMO_AREA_WIDTH_IN_BYTES
BackgroundLoop: mov bp,(SCREEN_WIDTH_IN_BYTES * 3) - DEMO_AREA_WIDTH_IN_BYTES
mov cx,bx ;length of bar BackgroundLoop:
rep stosw ;draw top half of bar mov cx,bx ;length of bar
add di,si ;point to start of bottom half of bar rep stosw ;draw top half of bar
mov cx,bx ;length of bar add di,si ;point to start of bottom half of bar
rep stosw ;draw bottom half of bar mov cx,bx ;length of bar
add di,bp ;point to start of top of next bar rep stosw ;draw bottom half of bar
dec dx add di,bp ;point to start of top of next bar
jnz BackgroundLoop dec dx
; jnz BackgroundLoop
; Draw vertical boxes filled with a variety of fill patterns ;
; using each of the 4 logical functions in turn. ; Draw vertical boxes filled with a variety of fill patterns
; ; using each of the 4 logical functions in turn.
SETGC GC_ROTATE, 0 ;select data unmodified ;
; logical function... SETGC GC_ROTATE, 0 ;select data unmodified
mov di,0 ; logical function...
call DrawVerticalBox ;...and draw box mov di,0
; call DrawVerticalBox ;...and draw box
SETGC GC_ROTATE, 08h ;select AND logical function... ;
mov di,10 SETGC GC_ROTATE, 08h ;select AND logical function...
call DrawVerticalBox ;...and draw box mov di,10
; call DrawVerticalBox ;...and draw box
SETGC GC_ROTATE, 10h ;select OR logical function... ;
mov di,20 SETGC GC_ROTATE, 10h ;select OR logical function...
call DrawVerticalBox ;...and draw box mov di,20
; call DrawVerticalBox ;...and draw box
SETGC GC_ROTATE, 18h ;select XOR logical function... ;
mov di,30 SETGC GC_ROTATE, 18h ;select XOR logical function...
call DrawVerticalBox ;...and draw box mov di,30
; call DrawVerticalBox ;...and draw box
; Reset the logical function to data unmodified, the default state. ;
; ; Reset the logical function to data unmodified, the default state.
SETGC GC_ROTATE, 0 ;
; SETGC GC_ROTATE, 0
; Label the screen. ;
; ; Label the screen.
push ds ;
pop es ;strings we'll display are passed to BIOS push ds
; by pointing ES:BP to them pop es ;strings we'll display are passed to BIOS
; ; by pointing ES:BP to them
; Label the logical functions, using the VGA BIOS's ;
; write string function. ; Label the logical functions, using the VGA BIOS's
; ; write string function.
TEXT_UP LabelString, LABEL_STRING_LENGTH, 24, 0 ;
; TEXT_UP LabelString, LABEL_STRING_LENGTH, 24, 0
; Label the fill patterns, using the VGA BIOS's ;
; write string function. ; Label the fill patterns, using the VGA BIOS's
; ; write string function.
TEXT_UP FillPatternFF, FILL_PATTERN_FF_LENGTH, 3, 42 ;
TEXT_UP FillPattern00, FILL_PATTERN_00_LENGTH, 9, 42 TEXT_UP FillPatternFF, FILL_PATTERN_FF_LENGTH, 3, 42
TEXT_UP FillPatternVert, FILL_PATTERN_VERT_LENGTH, 15, 42 TEXT_UP FillPattern00, FILL_PATTERN_00_LENGTH, 9, 42
TEXT_UP FillPatternHorz, FILL_PATTERN_HORZ_LENGTH, 21, 42 TEXT_UP FillPatternVert, FILL_PATTERN_VERT_LENGTH, 15, 42
; TEXT_UP FillPatternHorz, FILL_PATTERN_HORZ_LENGTH, 21, 42
; Wait until a key's been hit to reset screen mode & exit. ;
; ; Wait until a key's been hit to reset screen mode & exit.
WaitForKey: ;
mov ah,1 WaitForKey:
int 16h mov ah,1
jz WaitForKey int 16h
; jz WaitForKey
; Finished. Clear key, reset screen mode and exit. ;
; ; Finished. Clear key, reset screen mode and exit.
Done: ;
mov ah,0 ;clear key that we just detected Done:
int 16h mov ah,0 ;clear key that we just detected
; int 16h
mov ax,3 ;reset to text mode ;
int 10h mov ax,3 ;reset to text mode
; int 10h
mov ah,4ch ;exit to DOS ;
int 21h mov ah,4ch ;exit to DOS
; int 21h
start endp ;
; start endp
; Subroutine to draw a box 80x336 in size, using currently selected ;
; logical function, with upper left corner at the display memory offset ; Subroutine to draw a box 80x336 in size, using currently selected
; in DI. Box is filled with four patterns. Top quarter of area is ; logical function, with upper left corner at the display memory offset
; filled with 0FFh (solid) pattern, next quarter is filled with 00h ; in DI. Box is filled with four patterns. Top quarter of area is
; (empty) pattern, next quarter is filled with 33h (double pixel wide ; filled with 0FFh (solid) pattern, next quarter is filled with 00h
; vertical bar) pattern, and bottom quarter is filled with double pixel ; (empty) pattern, next quarter is filled with 33h (double pixel wide
; high horizontal bar pattern. ; vertical bar) pattern, and bottom quarter is filled with double pixel
; ; high horizontal bar pattern.
; Macro to draw a column of the specified width in bytes, one-quarter ;
; of the height of the box, with the specified fill pattern. ; Macro to draw a column of the specified width in bytes, one-quarter
; ; of the height of the box, with the specified fill pattern.
DRAW_BOX_QUARTER macro FILL, WIDTH ;
local RowLoop, ColumnLoop DRAW_BOX_QUARTER macro FILL, WIDTH
mov al,FILL ;fill pattern local RowLoop, ColumnLoop
mov dx,DEMO_AREA_HEIGHT / 4 ;1/4 of the full box height mov al,FILL ;fill pattern
RowLoop: mov dx,DEMO_AREA_HEIGHT / 4 ;1/4 of the full box height
mov cx,WIDTH RowLoop:
ColumnLoop: mov cx,WIDTH
mov ah,es:[di] ;load display memory contents into ColumnLoop:
; GC latches (we don't actually care mov ah,es:[di] ;load display memory contents into
; about value read into AH) ; GC latches (we don't actually care
stosb ;write pattern, which is logically ; about value read into AH)
; combined with latch contents for each stosb ;write pattern, which is logically
; plane and then written to display ; combined with latch contents for each
; memory ; plane and then written to display
loop ColumnLoop ; memory
add di,SCREEN_WIDTH_IN_BYTES - WIDTH loop ColumnLoop
;point to start of next line down in box add di,SCREEN_WIDTH_IN_BYTES - WIDTH
dec dx ;point to start of next line down in box
jnz RowLoop dec dx
endm jnz RowLoop
; endm
DrawVerticalBox proc near ;
DRAW_BOX_QUARTER 0ffh, VERTICAL_BOX_WIDTH_IN_BYTES DrawVerticalBox proc near
;first fill pattern: solid fill DRAW_BOX_QUARTER 0ffh, VERTICAL_BOX_WIDTH_IN_BYTES
DRAW_BOX_QUARTER 0, VERTICAL_BOX_WIDTH_IN_BYTES ;first fill pattern: solid fill
;second fill pattern: empty fill DRAW_BOX_QUARTER 0, VERTICAL_BOX_WIDTH_IN_BYTES
DRAW_BOX_QUARTER 033h, VERTICAL_BOX_WIDTH_IN_BYTES ;second fill pattern: empty fill
;third fill pattern: double-pixel DRAW_BOX_QUARTER 033h, VERTICAL_BOX_WIDTH_IN_BYTES
; wide vertical bars ;third fill pattern: double-pixel
mov dx,DEMO_AREA_HEIGHT / 4 / 4 ; wide vertical bars
;fourth fill pattern: horizontal bars in mov dx,DEMO_AREA_HEIGHT / 4 / 4
; sets of 4 scan lines ;fourth fill pattern: horizontal bars in
sub ax,ax ; sets of 4 scan lines
mov si,VERTICAL_BOX_WIDTH_IN_BYTES ;width of fill area sub ax,ax
HorzBarLoop: mov si,VERTICAL_BOX_WIDTH_IN_BYTES ;width of fill area
dec ax ;0ffh fill (smaller to do word than byte DEC) HorzBarLoop:
mov cx,si ;width to fill dec ax ;0ffh fill (smaller to do word than byte DEC)
HBLoop1: mov cx,si ;width to fill
mov bl,es:[di] ;load latches (don't care about value) HBLoop1:
stosb ;write solid pattern, through ALUs mov bl,es:[di] ;load latches (don't care about value)
loop HBLoop1 stosb ;write solid pattern, through ALUs
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES loop HBLoop1
mov cx,si ;width to fill add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
HBLoop2: mov cx,si ;width to fill
mov bl,es:[di] ;load latches HBLoop2:
stosb ;write solid pattern, through ALUs mov bl,es:[di] ;load latches
loop HBLoop2 stosb ;write solid pattern, through ALUs
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES loop HBLoop2
inc ax ;0 fill (smaller to do word than byte DEC) add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
mov cx,si ;width to fill inc ax ;0 fill (smaller to do word than byte DEC)
HBLoop3: mov cx,si ;width to fill
mov bl,es:[di] ;load latches HBLoop3:
stosb ;write empty pattern, through ALUs mov bl,es:[di] ;load latches
loop HBLoop3 stosb ;write empty pattern, through ALUs
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES loop HBLoop3
mov cx,si ;width to fill add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
HBLoop4: mov cx,si ;width to fill
mov bl,es:[di] ;load latches HBLoop4:
stosb ;write empty pattern, through ALUs mov bl,es:[di] ;load latches
loop HBLoop4 stosb ;write empty pattern, through ALUs
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES loop HBLoop4
dec dx add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
jnz HorzBarLoop dec dx
; jnz HorzBarLoop
ret ;
DrawVerticalBox endp ret
cseg ends DrawVerticalBox endp
end start cseg ends
end start
```

View file

@ -90,16 +90,19 @@ program code and a few cycles of execution time. DX is being loaded with
a word value that's composed of two independent immediate byte values. a word value that's composed of two independent immediate byte values.
The obvious way to implement this would be with The obvious way to implement this would be with
MOV DL,VALUE1 ```nasm
MOV DH,VALUE2 MOV DL,VALUE1
MOV DH,VALUE2
```
which requires four instruction bytes. By shifting the value destined which requires four instruction bytes. By shifting the value destined
for the high byte into the high byte with MASM's shift-left operator, for the high byte into the high byte with MASM's shift-left operator,
**SHL** (\*100H would work also), and then logically combining the **SHL** (\*100H would work also), and then logically combining the
values with MASM's **OR** operator (or the **ADD** operator), both values with MASM's **OR** operator (or the **ADD** operator), both
halves of DX can be loaded with a single instruction, as in halves of DX can be loaded with a single instruction, as in
MOV DX,(VALUE2 SHL 8) OR VALUE1 ```nasm
MOV DX,(VALUE2 SHL 8) OR VALUE1
```
which takes only three bytes and is faster, being a single instruction. which takes only three bytes and is faster, being a single instruction.
(Note, though, that in 32-bit protected mode, there's a size and (Note, though, that in 32-bit protected mode, there's a size and

460
25-02.md
View file

@ -12,236 +12,238 @@ pages: 466-470
**LISTING 25.1 L25-1.ASM** **LISTING 25.1 L25-1.ASM**
; Program to illustrate operation of data rotate and bit mask ```nasm
; features of Graphics Controller. Draws 8x8 character at ; Program to illustrate operation of data rotate and bit mask
; specified location, using VGA's 8x8 ROM font. Designed ; features of Graphics Controller. Draws 8x8 character at
; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h. ; specified location, using VGA's 8x8 ROM font. Designed
; By Michael Abrash. ; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h.
; ; By Michael Abrash.
stack segment para stack ‘STACK' ;
db 512 dup(?) stack segment para stack ‘STACK'
stack ends db 512 dup(?)
; stack ends
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment ;
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
; FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char
; VGA register equates. ;
; ; VGA register equates.
GC_INDEX equ 3ceh ;GC index register ;
GC_ROTATE equ 3 ;GC data rotate/logical function GC_INDEX equ 3ceh ;GC index register
; register index GC_ROTATE equ 3 ;GC data rotate/logical function
GC_BIT_MASK equ 8 ;GC bit mask register index ; register index
; GC_BIT_MASK equ 8 ;GC bit mask register index
dseg segment para common ‘DATA' ;
TEST_TEXT_ROW equ 69 ;row to display test text at dseg segment para common ‘DATA'
TEST_TEXT_COL equ 17 ;column to display test text at TEST_TEXT_ROW equ 69 ;row to display test text at
TEST_TEXT_WIDTH equ 8 ;width of a character in pixels TEST_TEXT_COL equ 17 ;column to display test text at
TEST_TEXT_WIDTH equ 8 ;width of a character in pixels
TestString label byte TestString label byte
db ‘Hello, world!',0 ;test string to print. db ‘Hello, world!',0 ;test string to print.
FontPointer dd ? ;font offset FontPointer dd ? ;font offset
dseg ends dseg ends
; ;
; Macro to set indexed register INDEX of GC chip to SETTING. ; Macro to set indexed register INDEX of GC chip to SETTING.
; ;
SETGC macro INDEX, SETTING SETGC macro INDEX, SETTING
mov dx,GC_INDEX mov dx,GC_INDEX
mov ax,(SETTING SHL 8) OR INDEX mov ax,(SETTING SHL 8) OR INDEX
out dx,ax out dx,ax
endm endm
; ;
cseg segment para public ‘CODE' cseg segment para public ‘CODE'
assume cs:cseg, ds:dseg assume cs:cseg, ds:dseg
start proc near start proc near
mov ax,dseg mov ax,dseg
mov ds,ax mov ds,ax
; ;
; Select 640x480 graphics mode. ; Select 640x480 graphics mode.
; ;
mov ax,012h mov ax,012h
int 10h int 10h
; ;
; Set driver to use the 8x8 font. ; Set driver to use the 8x8 font.
; ;
mov ah,11h ;VGA BIOS character generator function, mov ah,11h ;VGA BIOS character generator function,
mov al,30h ; return info subfunction mov al,30h ; return info subfunction
mov bh,3;get 8x8 font pointer mov bh,3;get 8x8 font pointer
int 10h int 10h
call SelectFont call SelectFont
; ;
; Print the test string. ; Print the test string.
; ;
mov si,offset TestString mov si,offset TestString
mov bx,TEST_TEXT_ROW mov bx,TEST_TEXT_ROW
mov cx,TEST_TEXT_COL mov cx,TEST_TEXT_COL
StringOutLoop: StringOutLoop:
lodsb lodsb
and al,al and al,al
jz StringOutDone jz StringOutDone
call DrawChar call DrawChar
add cx,TEST_TEXT_WIDTH add cx,TEST_TEXT_WIDTH
jmp StringOutLoop jmp StringOutLoop
StringOutDone: StringOutDone:
; ;
; Reset the data rotate and bit mask registers. ; Reset the data rotate and bit mask registers.
; ;
SETGC GC_ROTATE, 0 SETGC GC_ROTATE, 0
SETGC GC_BIT_MASK, 0ffh SETGC GC_BIT_MASK, 0ffh
; ;
; Wait for a keystroke. ; Wait for a keystroke.
; ;
mov ah,1 mov ah,1
int 21h int 21h
; ;
; Return to text mode. ; Return to text mode.
; ;
mov ax,03h mov ax,03h
int 10h int 10h
; ;
; Exit to DOS. ; Exit to DOS.
; ;
mov ah,4ch mov ah,4ch
int 21h int 21h
Start endp Start endp
; ;
; Subroutine to draw a text character in a linear graphics mode ; Subroutine to draw a text character in a linear graphics mode
; (0Dh, 0Eh, 0Fh, 010h, 012h). ; (0Dh, 0Eh, 0Fh, 010h, 012h).
; Font used should be pointed to by FontPointer. ; Font used should be pointed to by FontPointer.
; ;
; Input: ; Input:
; AL = character to draw ; AL = character to draw
; BX = row to draw text character at ; BX = row to draw text character at
; CX = column to draw text character at ; CX = column to draw text character at
; ;
; Forces ALU function to "move". ; Forces ALU function to "move".
; ;
DrawChar proc near DrawChar proc near
push ax push ax
push bx push bx
push cx push cx
push dx push dx
push si push si
push di push di
push bp push bp
push ds push ds
; ;
; Set DS:SI to point to font and ES to point to display memory. ; Set DS:SI to point to font and ES to point to display memory.
; ;
lds si,[FontPointer] ;point to font lds si,[FontPointer] ;point to font
mov dx,VGA_VIDEO_SEGMENT mov dx,VGA_VIDEO_SEGMENT
mov es,dx ;point to display memory mov es,dx ;point to display memory
; ;
; Calculate screen address of byte character starts in. ; Calculate screen address of byte character starts in.
; ;
push ds ;point to BIOS data segment push ds ;point to BIOS data segment
sub dx,dx sub dx,dx
mov ds,dx mov ds,dx
xchg ax,bx xchg ax,bx
mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS
; screen width ; screen width
pop ds pop ds
mul di ;calculate offset of start of row mul di ;calculate offset of start of row
push di ;set aside screen width push di ;set aside screen width
mov di,cx ;set aside the column mov di,cx ;set aside the column
and cl,0111b ;keep only the column in-byte address and cl,0111b ;keep only the column in-byte address
shr di,1 shr di,1
shr di,1 shr di,1
shr di,1 ;divide column by 8 to make a byte address shr di,1 ;divide column by 8 to make a byte address
add di,ax ;and point to byte add di,ax ;and point to byte
; ;
; Calculate font address of character. ; Calculate font address of character.
; ;
sub bh,bh sub bh,bh
shl bx,1 ;assumes 8 bytes per character; use shl bx,1 ;assumes 8 bytes per character; use
shl bx,1 ; a multiply otherwise shl bx,1 ; a multiply otherwise
shl bx,1 ;offset in font of character shl bx,1 ;offset in font of character
add si,bx ;offset in font segment of character add si,bx ;offset in font segment of character
; ;
; Set up the GC rotation. ; Set up the GC rotation.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,GC_ROTATE mov al,GC_ROTATE
mov ah,cl mov ah,cl
out dx,ax out dx,ax
; ;
; Set up BH as bit mask for left half, ; Set up BH as bit mask for left half,
; BL as rotation for right half. ; BL as rotation for right half.
; ;
mov bx,0ffffh mov bx,0ffffh
shr bh,cl shr bh,cl
neg cl neg cl
add cl,8 add cl,8
shl bl,cl shl bl,cl
; ;
; Draw the character, left half first, then right half in the ; Draw the character, left half first, then right half in the
; succeeding byte, using the data rotation to position the character ; succeeding byte, using the data rotation to position the character
; across the byte boundary and then using the bit mask to get the ; across the byte boundary and then using the bit mask to get the
; proper portion of the character into each byte. ; proper portion of the character into each byte.
; Does not check for case where character is byte-aligned and ; Does not check for case where character is byte-aligned and
; no rotation and only one write is required. ; no rotation and only one write is required.
; ;
mov bp,FONT_CHARACTER_SIZE mov bp,FONT_CHARACTER_SIZE
mov dx,GC_INDEX mov dx,GC_INDEX
pop cx ;get back screen width pop cx ;get back screen width
dec cx dec cx
dec cx ; -2 because do two bytes for each char dec cx ; -2 because do two bytes for each char
CharacterLoop: CharacterLoop:
; ;
; Set the bit mask for the left half of the character. ; Set the bit mask for the left half of the character.
; ;
mov al,GC_BIT_MASK mov al,GC_BIT_MASK
mov ah,bh mov ah,bh
out dx,ax out dx,ax
; ;
; Get the next character byte & write it to display memory. ; Get the next character byte & write it to display memory.
; (Left half of character.) ; (Left half of character.)
; ;
mov al,[si] ;get character byte mov al,[si] ;get character byte
mov ah,es:[di] ;load latches mov ah,es:[di] ;load latches
stosb ;write character byte stosb ;write character byte
; ;
; Set the bit mask for the right half of the character. ; Set the bit mask for the right half of the character.
; ;
mov al,GC_BIT_MASK mov al,GC_BIT_MASK
mov ah,bl mov ah,bl
out dx,ax out dx,ax
; ;
; Get the character byte again & write it to display memory. ; Get the character byte again & write it to display memory.
; (Right half of character.) ; (Right half of character.)
; ;
lodsb ;get character byte lodsb ;get character byte
mov ah,es:[di] ;load latches mov ah,es:[di] ;load latches
stosb ;write character byte stosb ;write character byte
; ;
; Point to next line of character in display memory. ; Point to next line of character in display memory.
; ;
add di,cx add di,cx
; ;
dec bp dec bp
jnz CharacterLoop jnz CharacterLoop
; ;
pop ds pop ds
pop bp pop bp
pop di pop di
pop si pop si
pop dx pop dx
pop cx pop cx
pop bx pop bx
pop ax pop ax
ret ret
DrawChar endp DrawChar endp
; ;
; Set the pointer to the font to draw from to ES:BP. ; Set the pointer to the font to draw from to ES:BP.
; ;
SelectFont proc near SelectFont proc near
mov word ptr [FontPointer],bp ;save pointer mov word ptr [FontPointer],bp ;save pointer
mov word ptr [FontPointer+2],es mov word ptr [FontPointer+2],es
ret ret
SelectFont endp SelectFont endp
; ;
cseg ends cseg ends
end start end start
```
The bit mask can be used for much more than bit-aligned fonts. For The bit mask can be used for much more than bit-aligned fonts. For
example, the bit mask is useful for fast pixel drawing, such as that example, the bit mask is useful for fast pixel drawing, such as that

164
25-04.md
View file

@ -12,87 +12,89 @@ pages: 472-474
**LISTING 25.2 L25-2.ASM** **LISTING 25.2 L25-2.ASM**
; Program to illustrate operation of Map Mask register when drawing ```nasm
; to memory that already contains data. ; Program to illustrate operation of Map Mask register when drawing
; By Michael Abrash. ; to memory that already contains data.
; ; By Michael Abrash.
stack segment para stack ‘STACK' ;
db 512 dup(?) stack segment para stack ‘STACK'
stack ends db 512 dup(?)
; stack ends
EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment ;
; EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment
; EGA register equates. ;
; ; EGA register equates.
SC_INDEX equ 3c4h ;SC index register ;
SC_MAP_MASK equ 2 ;SC map mask register SC_INDEX equ 3c4h ;SC index register
; SC_MAP_MASK equ 2 ;SC map mask register
; Macro to set indexed register INDEX of SC chip to SETTING. ;
; ; Macro to set indexed register INDEX of SC chip to SETTING.
SETSC macro INDEX, SETTING ;
mov dx,SC_INDEX SETSC macro INDEX, SETTING
mov al,INDEX mov dx,SC_INDEX
out dx,al mov al,INDEX
inc dx out dx,al
mov al,SETTING inc dx
out dx,al mov al,SETTING
dec dx out dx,al
endm dec dx
; endm
cseg segment para public ‘CODE#146; ;
assume cs:cseg cseg segment para public ‘CODE#146;
start proc near assume cs:cseg
; start proc near
; Select 640x480 graphics mode. ;
; ; Select 640x480 graphics mode.
mov ax,012h ;
int 10h mov ax,012h
; int 10h
mov ax,EGA_VIDEO_SEGMENT ;
mov es,ax ;point to video memory mov ax,EGA_VIDEO_SEGMENT
; mov es,ax ;point to video memory
; Draw 24 10-scan-line high horizontal bars in green, 10 scan lines apart. ;
; ; Draw 24 10-scan-line high horizontal bars in green, 10 scan lines apart.
SETSC SC_MAP_MASK,02h ;map mask setting enables only ;
; plane 1, the green plane SETSC SC_MAP_MASK,02h ;map mask setting enables only
sub di,di ;start at beginning of video memory ; plane 1, the green plane
mov al,0ffh sub di,di ;start at beginning of video memory
mov bp,24 ;# bars to draw mov al,0ffh
HorzBarLoop: mov bp,24 ;# bars to draw
mov cx,80*10 ;# bytes per horizontal bar HorzBarLoop:
rep stosb ;draw bar mov cx,80*10 ;# bytes per horizontal bar
add di,80*10 ;point to start of next bar rep stosb ;draw bar
dec bp add di,80*10 ;point to start of next bar
jnz HorzBarLoop dec bp
; jnz HorzBarLoop
; Fill screen with blue, using Map Mask register to enable writes ;
; to blue plane only. ; Fill screen with blue, using Map Mask register to enable writes
; ; to blue plane only.
SETSC SC_MAP_MASK,01h ;map mask setting enables only ;
; plane 0, the blue plane SETSC SC_MAP_MASK,01h ;map mask setting enables only
sub di,di ; plane 0, the blue plane
mov cx,80*480 ;# bytes per screen sub di,di
mov al,0ffh mov cx,80*480 ;# bytes per screen
rep stosb ;perform fill (affects only mov al,0ffh
; plane 0, the blue plane) rep stosb ;perform fill (affects only
; ; plane 0, the blue plane)
; Wait for a keystroke. ;
; ; Wait for a keystroke.
mov ah,1 ;
int 21h mov ah,1
; int 21h
; Restore text mode. ;
; ; Restore text mode.
mov ax,03h ;
int 10h mov ax,03h
; int 10h
; Exit to DOS. ;
; ; Exit to DOS.
mov ah,4ch ;
int 21h mov ah,4ch
start endp int 21h
cseg ends start endp
end start cseg ends
end start
```
#### Setting All Planes to a Single Color {#Heading6} #### Setting All Planes to a Single Color {#Heading6}

436
25-05.md
View file

@ -12,114 +12,116 @@ pages: 474-478
**LISTING 25.3 L25-3.ASM** **LISTING 25.3 L25-3.ASM**
; Program to illustrate operation of set/reset circuitry to force ```nasm
; setting of memory that already contains data. ; Program to illustrate operation of set/reset circuitry to force
; By Michael Abrash. ; setting of memory that already contains data.
; ; By Michael Abrash.
stack segment para stack ‘STACK#146; ;
db 512 dup(?) stack segment para stack ‘STACK#146;
stack ends db 512 dup(?)
; stack ends
EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment ;
; EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment
; EGA register equates. ;
; ; EGA register equates.
SC_INDEX equ 3c4h ;SC index register ;
SC_MAP_MASK equ 2 ;SC map mask register SC_INDEX equ 3c4h ;SC index register
GC_INDEX equ 3ceh ;GC index register SC_MAP_MASK equ 2 ;SC map mask register
GC_SET_RESET equ 0 ;GC set/reset register GC_INDEX equ 3ceh ;GC index register
GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register GC_SET_RESET equ 0 ;GC set/reset register
; GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register
; Macro to set indexed register INDEX of SC chip to SETTING. ;
; ; Macro to set indexed register INDEX of SC chip to SETTING.
SETSC macro INDEX, SETTING ;
mov dx,SC_INDEX SETSC macro INDEX, SETTING
mov al,INDEX mov dx,SC_INDEX
out dx,al mov al,INDEX
inc dx out dx,al
mov al,SETTING inc dx
out dx,al mov al,SETTING
dec dx out dx,al
endm dec dx
; endm
; Macro to set indexed register INDEX of GC chip to SETTING. ;
; ; Macro to set indexed register INDEX of GC chip to SETTING.
SETGC macro INDEX, SETTING ;
mov dx,GC_INDEX SETGC macro INDEX, SETTING
mov al,INDEX mov dx,GC_INDEX
out dx,al mov al,INDEX
inc dx out dx,al
mov al,SETTING inc dx
out dx,al mov al,SETTING
dec dx out dx,al
endm dec dx
; endm
cseg segment para public ‘CODE#146; ;
assume cs:cseg cseg segment para public ‘CODE#146;
start proc near assume cs:cseg
; start proc near
; Select 640x480 graphics mode. ;
; ; Select 640x480 graphics mode.
mov ax,012h ;
int 10h mov ax,012h
; int 10h
mov ax,EGA_VIDEO_SEGMENT ;
mov es,ax ;point to video memory mov ax,EGA_VIDEO_SEGMENT
; mov es,ax ;point to video memory
; Draw 24 10-scan-line high horizontal bars in green, 10 scan lines apart. ;
; ; Draw 24 10-scan-line high horizontal bars in green, 10 scan lines apart.
SETSC SC_MAP_MASK,02h ;map mask setting enables only ;
; plane 1, the green plane SETSC SC_MAP_MASK,02h ;map mask setting enables only
sub di,di ;start at beginning of video memory ; plane 1, the green plane
mov al,0ffh sub di,di ;start at beginning of video memory
mov bp,24 ;# bars to draw mov al,0ffh
HorzBarLoop: mov bp,24 ;# bars to draw
mov cx,80*10 ;# bytes per horizontal bar HorzBarLoop:
rep stosb ;draw bar mov cx,80*10 ;# bytes per horizontal bar
add di,80*10 ;point to start of next bar rep stosb ;draw bar
dec bp add di,80*10 ;point to start of next bar
jnz HorzBarLoop dec bp
; jnz HorzBarLoop
; Fill screen with blue, using set/reset to force plane 0 to 1#146;s and all ;
; other plane to 0#146;s. ; Fill screen with blue, using set/reset to force plane 0 to 1#146;s and all
; ; other plane to 0#146;s.
SETSC SC_MAP_MASK,0fh ;must set map mask to enable all ;
; planes, so set/reset values can SETSC SC_MAP_MASK,0fh ;must set map mask to enable all
; be written to memory ; planes, so set/reset values can
SETGC GC_ENABLE_SET_RESET,0fh ;CPU data to all planes will be ; be written to memory
; replaced by set/reset value SETGC GC_ENABLE_SET_RESET,0fh ;CPU data to all planes will be
SETGC GC_SET_RESET,01h ;set/reset value is 0ffh for plane 0 ; replaced by set/reset value
; (the blue plane) and 0 for other SETGC GC_SET_RESET,01h ;set/reset value is 0ffh for plane 0
; planes ; (the blue plane) and 0 for other
sub di,di ; planes
mov cx,80*480 ;# bytes per screen sub di,di
mov al,0ffh ;since set/reset is enabled for all mov cx,80*480 ;# bytes per screen
; planes, the CPU data is ignored- mov al,0ffh ;since set/reset is enabled for all
; only the act of writing is ; planes, the CPU data is ignored-
; important ; only the act of writing is
rep stosb ;perform fill (affects all planes) ; important
; rep stosb ;perform fill (affects all planes)
; Turn off set/reset. ;
; ; Turn off set/reset.
SETGC GC_ENABLE_SET_RESET,0 ;
; SETGC GC_ENABLE_SET_RESET,0
; Wait for a keystroke. ;
; ; Wait for a keystroke.
mov ah,1 ;
int 21h mov ah,1
; int 21h
; Restore text mode. ;
; ; Restore text mode.
mov ax,03h ;
int 10h mov ax,03h
; int 10h
; Exit to DOS. ;
; ; Exit to DOS.
mov ah,4ch ;
int 21h mov ah,4ch
start endp int 21h
cseg ends start endp
end start cseg ends
end start
```
#### Manipulating Planes Individually {#Heading7} #### Manipulating Planes Individually {#Heading7}
@ -142,111 +144,113 @@ be used to control individual pixels.
**LISTING 25.4 L25-4.ASM** **LISTING 25.4 L25-4.ASM**
; Program to illustrate operation of set/reset circuitry in conjunction ```nasm
; with CPU data to modify setting of memory that already contains data. ; Program to illustrate operation of set/reset circuitry in conjunction
; By Michael Abrash. ; with CPU data to modify setting of memory that already contains data.
; ; By Michael Abrash.
stack segment para stack ‘STACK#146; ;
db 512 dup(?) stack segment para stack ‘STACK#146;
stack ends db 512 dup(?)
; stack ends
EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment ;
; EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment
; EGA register equates. ;
; ; EGA register equates.
SC_INDEX equ 3c4h ;SC index register ;
SC_MAP_MASK equ 2 ;SC map mask register SC_INDEX equ 3c4h ;SC index register
GC_INDEX equ 3ceh ;GC index register SC_MAP_MASK equ 2 ;SC map mask register
GC_SET_RESET equ 0 ;GC set/reset register GC_INDEX equ 3ceh ;GC index register
GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register GC_SET_RESET equ 0 ;GC set/reset register
; GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register
; Macro to set indexed register INDEX of SC chip to SETTING. ;
; ; Macro to set indexed register INDEX of SC chip to SETTING.
SETSC macro INDEX, SETTING ;
mov dx,SC_INDEX SETSC macro INDEX, SETTING
mov al,INDEX mov dx,SC_INDEX
out dx,al mov al,INDEX
inc dx out dx,al
mov al,SETTING inc dx
out dx,al mov al,SETTING
dec dx out dx,al
endm dec dx
; endm
; Macro to set indexed register INDEX of GC chip to SETTING. ;
; ; Macro to set indexed register INDEX of GC chip to SETTING.
SETGC macro INDEX, SETTING ;
mov dx,GC_INDEX SETGC macro INDEX, SETTING
mov al,INDEX mov dx,GC_INDEX
out dx,al mov al,INDEX
inc dx out dx,al
mov al,SETTING inc dx
out dx,al mov al,SETTING
dec dx out dx,al
endm dec dx
; endm
cseg segment para public ‘CODE#146; ;
assume cs:cseg cseg segment para public ‘CODE#146;
start proc near assume cs:cseg
; start proc near
; Select 640x350 graphics mode. ;
; ; Select 640x350 graphics mode.
mov ax,010h ;
int 10h mov ax,010h
; int 10h
mov ax,EGA_VIDEO_SEGMENT ;
mov es,ax ;point to video memory mov ax,EGA_VIDEO_SEGMENT
; mov es,ax ;point to video memory
; Draw 18 10-scan-line high horizontal bars in green, 10 scan lines apart. ;
; ; Draw 18 10-scan-line high horizontal bars in green, 10 scan lines apart.
SETSC SC_MAP_MASK,02h;map mask setting enables only ;
; plane 1, the green plane SETSC SC_MAP_MASK,02h;map mask setting enables only
sub di,di;start at beginning of video memory ; plane 1, the green plane
mov al,0ffh sub di,di;start at beginning of video memory
mov bp,18;# bars to draw mov al,0ffh
HorzBarLoop: mov bp,18;# bars to draw
mov cx,80*10;# bytes per horizontal bar HorzBarLoop:
rep stosb;draw bar mov cx,80*10;# bytes per horizontal bar
add di,80*10;point to start of next bar rep stosb;draw bar
dec bp add di,80*10;point to start of next bar
jnz HorzBarLoop dec bp
; jnz HorzBarLoop
; Fill screen with alternating bars of red and brown, using CPU data ;
; to set plane 1 and set/reset to set planes 0, 2 & 3. ; Fill screen with alternating bars of red and brown, using CPU data
; ; to set plane 1 and set/reset to set planes 0, 2 & 3.
SETSC SC_MAP_MASK,0fh ;must set map mask to enable all ;
; planes, so set/reset values can SETSC SC_MAP_MASK,0fh ;must set map mask to enable all
; be written to planes 0, 2 & 3 ; planes, so set/reset values can
; and CPU data can be written to ; be written to planes 0, 2 & 3
; plane 1 (the green plane) ; and CPU data can be written to
SETGC GC_ENABLE_SET_RESET,0dh ;CPU data to planes 0, 2 & 3 will be ; plane 1 (the green plane)
; replaced by set/reset value SETGC GC_ENABLE_SET_RESET,0dh ;CPU data to planes 0, 2 & 3 will be
SETGC GC_SET_RESET,04h ;set/reset value is 0ffh for plane 2 ; replaced by set/reset value
; (the red plane) and 0 for other SETGC GC_SET_RESET,04h ;set/reset value is 0ffh for plane 2
; planes ; (the red plane) and 0 for other
sub di,di ; planes
mov cx,80*350/2 ;# words per screen sub di,di
mov ax,07e0h ;CPU data controls only plane 1; mov cx,80*350/2 ;# words per screen
; set/reset controls other planes mov ax,07e0h ;CPU data controls only plane 1;
rep stosw ;perform fill (affects all planes) ; set/reset controls other planes
; rep stosw ;perform fill (affects all planes)
; Turn off set/reset. ;
; ; Turn off set/reset.
SETGC GC_ENABLE_SET_RESET,0 ;
; SETGC GC_ENABLE_SET_RESET,0
; Wait for a keystroke. ;
; ; Wait for a keystroke.
mov ah,1 ;
int 21h mov ah,1
; int 21h
; Restore text mode. ;
; ; Restore text mode.
mov ax,03h ;
int 10h mov ax,03h
; int 10h
; Exit to DOS. ;
; ; Exit to DOS.
mov ah,4ch ;
int 21h mov ah,4ch
start endp int 21h
cseg ends start endp
end start cseg ends
end start
```

586
26-02.md
View file

@ -12,296 +12,298 @@ pages: 484-489
**LISTING 26.1 L26-1.ASM** **LISTING 26.1 L26-1.ASM**
; Program to illustrate operation of write mode 3 of the VGA. ```nasm
; Draws 8x8 characters at arbitrary locations without disturbing ; Program to illustrate operation of write mode 3 of the VGA.
; the background, using VGA's 8x8 ROM font. Designed ; Draws 8x8 characters at arbitrary locations without disturbing
; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h. ; the background, using VGA's 8x8 ROM font. Designed
; Runs only on VGAs (in Models 50 & up and IBM Display Adapter ; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h.
; and 100% compatibles). ; Runs only on VGAs (in Models 50 & up and IBM Display Adapter
; Assembled with MASM ; and 100% compatibles).
; By Michael Abrash ; Assembled with MASM
; ; By Michael Abrash
stack segment para stack ‘STACK' ;
db 512 dup(?) stack segment para stack ‘STACK'
stack ends db 512 dup(?)
; stack ends
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment ;
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
; FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char
; VGA register equates. ;
; ; VGA register equates.
SC_INDEX equ 3c4h ;SC index register ;
SC_MAP_MASK equ 2 ;SC map mask register index SC_INDEX equ 3c4h ;SC index register
GC_INDEX equ 3ceh ;GC index register SC_MAP_MASK equ 2 ;SC map mask register index
GC_SET_RESET equ 0 ;GC set/reset register index GC_INDEX equ 3ceh ;GC index register
GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index GC_SET_RESET equ 0 ;GC set/reset register index
GC_ROTATE equ 3 ;GC data rotate/logical function GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index
; register index GC_ROTATE equ 3 ;GC data rotate/logical function
GC_MODE equ 5 ;GC Mode register ; register index
GC_BIT_MASK equ 8 ;GC bit mask register index GC_MODE equ 5 ;GC Mode register
; GC_BIT_MASK equ 8 ;GC bit mask register index
dseg segment para common ‘DATA' ;
TEST_TEXT_ROW equ 69 ;row to display test text at dseg segment para common ‘DATA'
TEST_TEXT_COL equ 17 ;column to display test text at TEST_TEXT_ROW equ 69 ;row to display test text at
TEST_TEXT_WIDTH equ 8 ;width of a character in pixels TEST_TEXT_COL equ 17 ;column to display test text at
TestString label byte TEST_TEXT_WIDTH equ 8 ;width of a character in pixels
db ‘Hello, world!',0 ;test string to print. TestString label byte
FontPointer dd ? ;font offset db ‘Hello, world!',0 ;test string to print.
dseg ends FontPointer dd ? ;font offset
; dseg ends
cseg segment para public ‘CODE' ;
assume cs:cseg, ds:dseg cseg segment para public ‘CODE'
start proc near assume cs:cseg, ds:dseg
mov ax,dseg start proc near
mov ds,ax mov ax,dseg
; mov ds,ax
; Select 640x480 graphics mode. ;
; ; Select 640x480 graphics mode.
mov ax,012h ;
int 10h mov ax,012h
; int 10h
; Set the screen to all blue, using the readability of VGA registers ;
; to preserve reserved bits. ; Set the screen to all blue, using the readability of VGA registers
; ; to preserve reserved bits.
mov dx,GC_INDEX ;
mov al,GC_SET_RESET mov dx,GC_INDEX
out dx,al mov al,GC_SET_RESET
inc dx out dx,al
in al,dx inc dx
and al,0f0h in al,dx
or al,1 ;blue plane only set, others reset and al,0f0h
out dx,al or al,1 ;blue plane only set, others reset
dec dx out dx,al
mov al,GC_ENABLE_SET_RESET dec dx
out dx,al mov al,GC_ENABLE_SET_RESET
inc dx out dx,al
in al,dx inc dx
and al,0f0h in al,dx
or al,0fh ;enable set/reset for all planes and al,0f0h
out dx,al or al,0fh ;enable set/reset for all planes
mov dx,VGA_VIDEO_SEGMENT out dx,al
mov es,dx ;point to display memory mov dx,VGA_VIDEO_SEGMENT
mov di,0 mov es,dx ;point to display memory
mov cx,8000h ;fill all 32k words mov di,0
mov ax,0ffffh ;because of set/reset, the value mov cx,8000h ;fill all 32k words
; written actually doesn't matter mov ax,0ffffh ;because of set/reset, the value
rep stosw ;fill with blue ; written actually doesn't matter
; rep stosw ;fill with blue
; Set driver to use the 8x8 font. ;
; ; Set driver to use the 8x8 font.
mov ah,11h ;VGA BIOS character generator function, ;
mov al,30h ; return info subfunction mov ah,11h ;VGA BIOS character generator function,
mov bh,3 ;get 8x8 font pointer mov al,30h ; return info subfunction
int 10h mov bh,3 ;get 8x8 font pointer
call SelectFont int 10h
; call SelectFont
; Print the test string, cycling through colors. ;
; ; Print the test string, cycling through colors.
mov si,offset TestString ;
mov bx,TEST_TEXT_ROW mov si,offset TestString
mov cx,TEST_TEXT_COL mov bx,TEST_TEXT_ROW
mov ah,0 ;start with color 0 mov cx,TEST_TEXT_COL
StringOutLoop: mov ah,0 ;start with color 0
lodsb StringOutLoop:
and al,al lodsb
jz StringOutDone and al,al
push ax ;preserve color jz StringOutDone
call DrawChar push ax ;preserve color
pop ax ;restore color call DrawChar
inc ah ;next color pop ax ;restore color
and ah,0fh ;colors range from 0 to 15 inc ah ;next color
add cx,TEST_TEXT_WIDTH and ah,0fh ;colors range from 0 to 15
jmp StringOutLoop add cx,TEST_TEXT_WIDTH
StringOutDone: jmp StringOutLoop
; StringOutDone:
; Wait for a key, then set to text mode & end. ;
; ; Wait for a key, then set to text mode & end.
mov ah,1 ;
int 21h ;wait for a key mov ah,1
mov ax,3 int 21h ;wait for a key
int 10h ;restore text mode mov ax,3
; int 10h ;restore text mode
; Exit to DOS. ;
; ; Exit to DOS.
mov ah,4ch ;
int 21h mov ah,4ch
Start endp int 21h
; Start endp
; Subroutine to draw a text character in a linear graphics mode ;
; (0Dh, 0Eh, 0Fh, 010h, 012h). Background around the pixels that ; Subroutine to draw a text character in a linear graphics mode
; make up the character is preserved. ; (0Dh, 0Eh, 0Fh, 010h, 012h). Background around the pixels that
; Font used should be pointed to by FontPointer. ; make up the character is preserved.
; ; Font used should be pointed to by FontPointer.
; Input: ;
; AL = character to draw ; Input:
; AH = color to draw character in (0-15) ; AL = character to draw
; BX = row to draw text character at ; AH = color to draw character in (0-15)
; CX = column to draw text character at ; BX = row to draw text character at
; ; CX = column to draw text character at
; Forces ALU function to "move". ;
; Forces write mode 3. ; Forces ALU function to "move".
; ; Forces write mode 3.
DrawChar proc near ;
push ax DrawChar proc near
push bx push ax
push cx push bx
push dx push cx
push si push dx
push di push si
push bp push di
push ds push bp
push ax ;preserve character to draw in AL push ds
; push ax ;preserve character to draw in AL
; Set up set/reset to produce character color, using the readability ;
; of VGA register to preserve the setting of reserved bits 7-4. ; Set up set/reset to produce character color, using the readability
; ; of VGA register to preserve the setting of reserved bits 7-4.
mov dx,GC_INDEX ;
mov al,GC_SET_RESET mov dx,GC_INDEX
out dx,al mov al,GC_SET_RESET
inc dx out dx,al
in al,dx inc dx
and al,0f0h in al,dx
and ah,0fh and al,0f0h
or al,ah and ah,0fh
out dx,al or al,ah
; out dx,al
; Select write mode 3, using the readability of VGA registers ;
; to leave bits other than the write mode bits unchanged. ; Select write mode 3, using the readability of VGA registers
; ; to leave bits other than the write mode bits unchanged.
mov dx,GC_INDEX ;
mov al,GC_MODE mov dx,GC_INDEX
out dx,al mov al,GC_MODE
inc dx out dx,al
in al,dx inc dx
or al,3 in al,dx
out dx,al or al,3
; out dx,al
; Set DS:SI to point to font and ES to point to display memory. ;
; ; Set DS:SI to point to font and ES to point to display memory.
lds si,[FontPointer] ;point to font ;
mov dx,VGA_VIDEO_SEGMENT lds si,[FontPointer] ;point to font
mov es,dx ;point to display memory mov dx,VGA_VIDEO_SEGMENT
; mov es,dx ;point to display memory
; Calculate screen address of byte character starts in. ;
; ; Calculate screen address of byte character starts in.
pop ax ;get back character to draw in AL ;
pop ax ;get back character to draw in AL
push ds ;point to BIOS data segment push ds ;point to BIOS data segment
sub dx,dx sub dx,dx
mov ds,dx mov ds,dx
xchg ax,bx xchg ax,bx
mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS
; screen width ; screen width
pop ds pop ds
mul di ;calculate offset of start of row mul di ;calculate offset of start of row
push di ;set aside screen width push di ;set aside screen width
mov di,cx ;set aside the column mov di,cx ;set aside the column
and cl,0111b ;keep only the column in-byte address and cl,0111b ;keep only the column in-byte address
shr di,1 shr di,1
shr di,1 shr di,1
shr di,1 ;divide column by 8 to make a byte address shr di,1 ;divide column by 8 to make a byte address
add di,ax ;and point to byte add di,ax ;and point to byte
; ;
; Calculate font address of character. ; Calculate font address of character.
; ;
sub bh,bh sub bh,bh
shl bx,1 ;assumes 8 bytes per character; use shl bx,1 ;assumes 8 bytes per character; use
shl bx,1 ; a multiply otherwise shl bx,1 ; a multiply otherwise
shl bx,1 ;offset in font of character shl bx,1 ;offset in font of character
add si,bx ;offset in font segment of character add si,bx ;offset in font segment of character
; ;
; Set up the GC rotation. In write mode 3, this is the rotation ; Set up the GC rotation. In write mode 3, this is the rotation
; of CPU data before it is ANDed with the Bit Mask register to ; of CPU data before it is ANDed with the Bit Mask register to
; form the bit mask. Force the ALU function to "move". Uses the ; form the bit mask. Force the ALU function to "move". Uses the
; readability of VGA registers to leave reserved bits unchanged. ; readability of VGA registers to leave reserved bits unchanged.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,GC_ROTATE mov al,GC_ROTATE
out dx,al out dx,al
inc dx inc dx
in al,dx in al,dx
and al,0e0h and al,0e0h
or al,cl or al,cl
out dx,al out dx,al
; ;
; Set up BH as bit mask for left half, BL as rotation for right half. ; Set up BH as bit mask for left half, BL as rotation for right half.
; ;
mov bx,0ffffh mov bx,0ffffh
shr bh,cl shr bh,cl
neg cl neg cl
add cl,8 add cl,8
shl bl,cl shl bl,cl
; ;
; Draw the character, left half first, then right half in the ; Draw the character, left half first, then right half in the
; succeeding byte, using the data rotation to position the character ; succeeding byte, using the data rotation to position the character
; across the byte boundary and then using write mode 3 to combine the ; across the byte boundary and then using write mode 3 to combine the
; character data with the bit mask to allow the set/reset value (the ; character data with the bit mask to allow the set/reset value (the
; character color) through only for the proper portion (where the ; character color) through only for the proper portion (where the
; font bits for the character are 1) of the character for each byte. ; font bits for the character are 1) of the character for each byte.
; Wherever the font bits for the character are 0, the background ; Wherever the font bits for the character are 0, the background
; color is preserved. ; color is preserved.
; Does not check for case where character is byte-aligned and ; Does not check for case where character is byte-aligned and
; no rotation and only one write is required. ; no rotation and only one write is required.
; ;
mov bp,FONT_CHARACTER_SIZE mov bp,FONT_CHARACTER_SIZE
mov dx,GC_INDEX mov dx,GC_INDEX
pop cx ;get back screen width pop cx ;get back screen width
dec cx dec cx
dec cx ; -2 because do two bytes for each char dec cx ; -2 because do two bytes for each char
CharacterLoop: CharacterLoop:
; ;
; Set the bit mask for the left half of the character. ; Set the bit mask for the left half of the character.
; ;
mov al,GC_BIT_MASK mov al,GC_BIT_MASK
mov ah,bh mov ah,bh
out dx,ax out dx,ax
; ;
; Get the next character byte & write it to display memory. ; Get the next character byte & write it to display memory.
; (Left half of character.) ; (Left half of character.)
; ;
mov al,[si] ;get character byte mov al,[si] ;get character byte
mov ah,es:[di] ;load latches mov ah,es:[di] ;load latches
stosb ;write character byte stosb ;write character byte
; ;
; Set the bit mask for the right half of the character. ; Set the bit mask for the right half of the character.
; ;
mov al,GC_BIT_MASK mov al,GC_BIT_MASK
mov ah,bl mov ah,bl
out dx,ax out dx,ax
; ;
; Get the character byte again & write it to display memory. ; Get the character byte again & write it to display memory.
; (Right half of character.) ; (Right half of character.)
; ;
lodsb ;get character byte lodsb ;get character byte
mov ah,es:[di] ;load latches mov ah,es:[di] ;load latches
stosb ;write character byte stosb ;write character byte
; ;
; Point to next line of character in display memory. ; Point to next line of character in display memory.
; ;
add di,cx add di,cx
; ;
dec bp dec bp
jnz CharacterLoop jnz CharacterLoop
; ;
pop ds pop ds
pop bp pop bp
pop di pop di
pop si pop si
pop dx pop dx
pop cx pop cx
pop bx pop bx
pop ax pop ax
ret ret
DrawChar endp DrawChar endp
; ;
; Set the pointer to the font to draw from to ES:BP. ; Set the pointer to the font to draw from to ES:BP.
; ;
SelectFont proc near SelectFont proc near
mov word ptr [FontPointer],bp ;save pointer mov word ptr [FontPointer],bp ;save pointer
mov word ptr [FontPointer+2],es mov word ptr [FontPointer+2],es
ret ret
SelectFont endp SelectFont endp
; ;
cseg ends cseg ends
end start end start
```

636
26-03.md
View file

@ -80,324 +80,326 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into
**LISTING 26.2 L26-2.ASM** **LISTING 26.2 L26-2.ASM**
; Program to illustrate high-speed text-drawing operation of ```nasm
; write mode 3 of the VGA. ; Program to illustrate high-speed text-drawing operation of
; Draws a string of 8x14 characters at arbitrary locations ; write mode 3 of the VGA.
; without disturbing the background, using VGA's 8x14 ROM font. ; Draws a string of 8x14 characters at arbitrary locations
; Designed for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h. ; without disturbing the background, using VGA's 8x14 ROM font.
; Runs only on VGAs (in Models 50 & up and IBM Display Adapter ; Designed for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h.
; and 100% compatibles). ; Runs only on VGAs (in Models 50 & up and IBM Display Adapter
; Assembled with MASM ; and 100% compatibles).
; By Michael Abrash ; Assembled with MASM
; ; By Michael Abrash
stack segment para stack ‘STACK' ;
db 512 dup(?) stack segment para stack ‘STACK'
stack ends db 512 dup(?)
; stack ends
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment ;
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
FONT_CHARACTER_SIZE equ 14 ;# bytes in each font char SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
; FONT_CHARACTER_SIZE equ 14 ;# bytes in each font char
; VGA register equates. ;
; ; VGA register equates.
SC_INDEX equ 3c4h ;SC index register ;
SC_MAP_MASK equ 2 ;SC map mask register index SC_INDEX equ 3c4h ;SC index register
GC_INDEX equ 3ceh ;GC index register SC_MAP_MASK equ 2 ;SC map mask register index
GC_SET_RESET equ 0 ;GC set/reset register index GC_INDEX equ 3ceh ;GC index register
GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index GC_SET_RESET equ 0 ;GC set/reset register index
GC_ROTATE equ 3 ;GC data rotate/logical function GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index
; register index GC_ROTATE equ 3 ;GC data rotate/logical function
GC_MODE equ 5 ;GC Mode register ; register index
GC_BIT_MASK equ 8 ;GC bit mask register index GC_MODE equ 5 ;GC Mode register
; GC_BIT_MASK equ 8 ;GC bit mask register index
dseg segment para common ‘DATA' ;
TEST_TEXT_ROW equ 69 ;row to display test text at dseg segment para common ‘DATA'
TEST_TEXT_COL equ 17 ;column to display test text at TEST_TEXT_ROW equ 69 ;row to display test text at
TEST_TEXT_COLOR equ 0fh ;high intensity white TEST_TEXT_COL equ 17 ;column to display test text at
TestString label byte TEST_TEXT_COLOR equ 0fh ;high intensity white
db ‘Hello, world!',0 ;test string to print. TestString label byte
FontPointer dd ? ;font offset db ‘Hello, world!',0 ;test string to print.
dseg ends FontPointer dd ? ;font offset
; dseg ends
cseg segment para public ‘CODE' ;
assume cs:cseg, ds:dseg cseg segment para public ‘CODE'
start proc near assume cs:cseg, ds:dseg
mov ax,dseg start proc near
mov ds,ax mov ax,dseg
; mov ds,ax
; Select 640x480 graphics mode. ;
; ; Select 640x480 graphics mode.
mov ax,012h ;
int 10h mov ax,012h
; int 10h
; Set the screen to all blue, using the readability of VGA registers ;
; to preserve reserved bits. ; Set the screen to all blue, using the readability of VGA registers
; ; to preserve reserved bits.
mov dx,GC_INDEX ;
mov al,GC_SET_RESET mov dx,GC_INDEX
out dx,al mov al,GC_SET_RESET
inc dx out dx,al
in al,dx inc dx
and al,0f0h in al,dx
or al,1 ;blue plane only set, others reset and al,0f0h
out dx,al or al,1 ;blue plane only set, others reset
dec dx out dx,al
mov al,GC_ENABLE_SET_RESET dec dx
out dx,al mov al,GC_ENABLE_SET_RESET
inc dx out dx,al
in al,dx inc dx
and al,0f0h in al,dx
or al,0fh ;enable set/reset for all planes and al,0f0h
out dx,al or al,0fh ;enable set/reset for all planes
mov dx,VGA_VIDEO_SEGMENT out dx,al
mov es,dx ;point to display memory mov dx,VGA_VIDEO_SEGMENT
mov di,0 mov es,dx ;point to display memory
mov cx,8000h ;fill all 32k words mov di,0
mov ax,0ffffh ;because of set/reset, the value mov cx,8000h ;fill all 32k words
; written actually doesn't matter mov ax,0ffffh ;because of set/reset, the value
rep stosw ;fill with blue ; written actually doesn't matter
; rep stosw ;fill with blue
; Set driver to use the 8x14 font. ;
; ; Set driver to use the 8x14 font.
mov ah,11h ;VGA BIOS character generator function, ;
mov al,30h ; return info subfunction mov ah,11h ;VGA BIOS character generator function,
mov bh,2 ;get 8x14 font pointer mov al,30h ; return info subfunction
int 10h mov bh,2 ;get 8x14 font pointer
call SelectFont int 10h
; call SelectFont
; Print the test string. ;
; ; Print the test string.
mov si,offset TestString ;
mov bx,TEST_TEXT_ROW mov si,offset TestString
mov cx,TEST_TEXT_COL mov bx,TEST_TEXT_ROW
mov ah,TEST_TEXT_COLOR mov cx,TEST_TEXT_COL
call DrawString mov ah,TEST_TEXT_COLOR
; call DrawString
; Wait for a key, then set to text mode & end. ;
; ; Wait for a key, then set to text mode & end.
mov ah,1 ;
int 21h ;wait for a key mov ah,1
mov ax,3 int 21h ;wait for a key
int 10h ;restore text mode mov ax,3
; int 10h ;restore text mode
; Exit to DOS. ;
; ; Exit to DOS.
mov ah,4ch ;
int 21h mov ah,4ch
Start endp int 21h
; Start endp
; Subroutine to draw a text string left-to-right in a linear ;
; graphics mode (0Dh, 0Eh, 0Fh, 010h, 012h) with 8-dot-wide ; Subroutine to draw a text string left-to-right in a linear
; characters. Background around the pixels that make up the ; graphics mode (0Dh, 0Eh, 0Fh, 010h, 012h) with 8-dot-wide
; characters is preserved. ; characters. Background around the pixels that make up the
; Font used should be pointed to by FontPointer. ; characters is preserved.
; ; Font used should be pointed to by FontPointer.
; Input: ;
; AH = color to draw string in ; Input:
; BX = row to draw string on ; AH = color to draw string in
; CX = column to start string at ; BX = row to draw string on
; DS:SI = string to draw ; CX = column to start string at
; ; DS:SI = string to draw
; Forces ALU function to "move". ;
; Forces write mode 3. ; Forces ALU function to "move".
; ; Forces write mode 3.
DrawString proc near ;
push ax DrawString proc near
push bx push ax
push cx push bx
push dx push cx
push si push dx
push di push si
push bp push di
push ds push bp
; push ds
; Set up set/reset to produce character color, using the readability ;
; of VGA register to preserve the setting of reserved bits 7-4. ; Set up set/reset to produce character color, using the readability
; ; of VGA register to preserve the setting of reserved bits 7-4.
mov dx,GC_INDEX ;
mov al,GC_SET_RESET mov dx,GC_INDEX
out dx,al mov al,GC_SET_RESET
inc dx out dx,al
in al,dx inc dx
and al,0f0h in al,dx
and ah,0fh and al,0f0h
or al,ah and ah,0fh
out dx,al or al,ah
; out dx,al
; Select write mode 3, using the readability of VGA registers ;
; to leave bits other than the write mode bits unchanged. ; Select write mode 3, using the readability of VGA registers
; ; to leave bits other than the write mode bits unchanged.
mov dx,GC_INDEX ;
mov al,GC_MODE mov dx,GC_INDEX
out dx,al mov al,GC_MODE
inc dx out dx,al
in al,dx inc dx
or al,3 in al,dx
out dx,al or al,3
mov dx,VGA_VIDEO_SEGMENT out dx,al
mov es,dx ;point to display memory mov dx,VGA_VIDEO_SEGMENT
; mov es,dx ;point to display memory
; Calculate screen address of byte character starts in. ;
; ; Calculate screen address of byte character starts in.
push ds ;point to BIOS data segment ;
sub dx,dx push ds ;point to BIOS data segment
mov ds,dx sub dx,dx
mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS mov ds,dx
; screen width mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS
pop ds ; screen width
mov ax,bx ;row pop ds
mul di ;calculate offset of start of row mov ax,bx ;row
push di ;set aside screen width mul di ;calculate offset of start of row
mov di,cx ;set aside the column push di ;set aside screen width
and cl,0111b ;keep only the column in-byte address mov di,cx ;set aside the column
shr di,1 and cl,0111b ;keep only the column in-byte address
shr di,1 shr di,1
shr di,1 ;divide column by 8 to make a byte address shr di,1
add di,ax ;and point to byte shr di,1 ;divide column by 8 to make a byte address
; add di,ax ;and point to byte
; Set up the GC rotation. In write mode 3, this is the rotation ;
; of CPU data before it is ANDed with the Bit Mask register to ; Set up the GC rotation. In write mode 3, this is the rotation
; form the bit mask. Force the ALU function to "move". Uses the ; of CPU data before it is ANDed with the Bit Mask register to
; readability of VGA registers to leave reserved bits unchanged. ; form the bit mask. Force the ALU function to "move". Uses the
; ; readability of VGA registers to leave reserved bits unchanged.
mov dx,GC_INDEX ;
mov al,GC_ROTATE mov dx,GC_INDEX
out dx,al mov al,GC_ROTATE
inc dx out dx,al
in al,dx inc dx
and al,0e0h in al,dx
or al,cl and al,0e0h
out dx,al or al,cl
; out dx,al
; Set up BH as bit mask for left half, BL as rotation for right half. ;
; ; Set up BH as bit mask for left half, BL as rotation for right half.
mov bx,0ffffh ;
shr bh,cl mov bx,0ffffh
neg cl shr bh,cl
add cl,8 neg cl
shl bl,cl add cl,8
; shl bl,cl
; Draw all characters, left portion first, then right portion in the ;
; succeeding byte, using the data rotation to position the character ; Draw all characters, left portion first, then right portion in the
; across the byte boundary and then using write mode 3 to combine the ; succeeding byte, using the data rotation to position the character
; character data with the bit mask to allow the set/reset value (the ; across the byte boundary and then using write mode 3 to combine the
; character color) through only for the proper portion (where the ; character data with the bit mask to allow the set/reset value (the
; font bits for the character are 1) of the character for each byte. ; character color) through only for the proper portion (where the
; Wherever the font bits for the character are 0, the background ; font bits for the character are 1) of the character for each byte.
; color is preserved. ; Wherever the font bits for the character are 0, the background
; Does not check for case where character is byte-aligned and ; color is preserved.
; no rotation and only one write is required. ; Does not check for case where character is byte-aligned and
; ; no rotation and only one write is required.
; Draw the left portion of each character in the string. ;
; ; Draw the left portion of each character in the string.
pop cx ;get back screen width ;
push si pop cx ;get back screen width
push di push si
push bx push di
; push bx
; Set the bit mask for the left half of the character. ;
; ; Set the bit mask for the left half of the character.
mov dx,GC_INDEX ;
mov al,GC_BIT_MASK mov dx,GC_INDEX
mov ah,bh mov al,GC_BIT_MASK
out dx,ax mov ah,bh
LeftHalfLoop: out dx,ax
lodsb LeftHalfLoop:
and al,al lodsb
jz LeftHalfLoopDone and al,al
call CharacterUp jz LeftHalfLoopDone
inc di ;point to next character location call CharacterUp
jmp LeftHalfLoop inc di ;point to next character location
LeftHalfLoopDone: jmp LeftHalfLoop
pop bx LeftHalfLoopDone:
pop di pop bx
pop si pop di
; pop si
; Draw the right portion of each character in the string. ;
; ; Draw the right portion of each character in the string.
inc di ;right portion of each character is across ;
; byte boundary inc di ;right portion of each character is across
; ; byte boundary
; Set the bit mask for the right half of the character. ;
; ; Set the bit mask for the right half of the character.
mov dx,GC_INDEX ;
mov al,GC_BIT_MASK mov dx,GC_INDEX
mov ah,bl mov al,GC_BIT_MASK
out dx,ax mov ah,bl
RightHalfLoop: out dx,ax
lodsb RightHalfLoop:
and al,al lodsb
jz RightHalfLoopDone and al,al
call CharacterUp jz RightHalfLoopDone
inc di ;point to next character location call CharacterUp
jmp RightHalfLoop inc di ;point to next character location
RightHalfLoopDone: jmp RightHalfLoop
; RightHalfLoopDone:
pop ds ;
pop bp pop ds
pop di pop bp
pop si pop di
pop dx pop si
pop cx pop dx
pop bx pop cx
pop ax pop bx
ret pop ax
DrawString endp ret
; DrawString endp
; Draw a character. ;
; ; Draw a character.
; Input: ;
; AL = character ; Input:
; CX = screen width ; AL = character
; ES:DI = address to draw character at ; CX = screen width
; ; ES:DI = address to draw character at
CharacterUp proc near ;
push cx CharacterUp proc near
push si push cx
push di push si
push ds push di
; push ds
; Set DS:SI to point to font and ES to point to display memory. ;
; ; Set DS:SI to point to font and ES to point to display memory.
lds si,[FontPointer] ;point to font ;
; lds si,[FontPointer] ;point to font
; Calculate font address of character. ;
; ; Calculate font address of character.
mov bl,14 ;14 bytes per character ;
mul bl mov bl,14 ;14 bytes per character
add si,ax ;offset in font segment of character mul bl
add si,ax ;offset in font segment of character
mov bp,FONT_CHARACTER_SIZE mov bp,FONT_CHARACTER_SIZE
dec cx ; -1 because one byte per char dec cx ; -1 because one byte per char
CharacterLoop: CharacterLoop:
lodsb ;get character byte lodsb ;get character byte
mov ah,es:[di] ;load latches mov ah,es:[di] ;load latches
stosb ;write character byte stosb ;write character byte
; ;
; Point to next line of character in display memory. ; Point to next line of character in display memory.
; ;
add di,cx add di,cx
; ;
dec bp dec bp
jnz CharacterLoop jnz CharacterLoop
; ;
pop ds pop ds
pop di pop di
pop si pop si
pop cx pop cx
ret ret
CharacterUp endp CharacterUp endp
; ;
; Set the pointer to the font to draw from to ES:BP. ; Set the pointer to the font to draw from to ES:BP.
; ;
SelectFont proc near SelectFont proc near
mov word ptr [FontPointer],bp ;save pointer mov word ptr [FontPointer],bp ;save pointer
mov word ptr [FontPointer+2],es mov word ptr [FontPointer+2],es
ret ret
SelectFont endp SelectFont endp
; ;
cseg ends cseg ends
end start end start
```
In this chapter, I've tried to give you a feel for how write mode 3 In this chapter, I've tried to give you a feel for how write mode 3
works and what it might be used for, rather than providing polished, works and what it might be used for, rather than providing polished,

414
27-02.md
View file

@ -52,220 +52,222 @@ image.
**LISTING 27.1 L27-1.ASM** **LISTING 27.1 L27-1.ASM**
; Program to illustrate one use of write mode 2 of the VGA and EGA by ```nasm
; animating the image of an "A" drawn by copying it from a chunky ; Program to illustrate one use of write mode 2 of the VGA and EGA by
; bit-map in system memory to a planar bit-map in VGA or EGA memory. ; animating the image of an "A" drawn by copying it from a chunky
; ; bit-map in system memory to a planar bit-map in VGA or EGA memory.
; Assemble with MASM or TASM ;
; ; Assemble with MASM or TASM
; By Michael Abrash ;
; ; By Michael Abrash
Stack segment para stack ‘STACK' ;
db 512 dup(0) Stack segment para stack ‘STACK'
Stack ends db 512 dup(0)
Stack ends
SCREEN_WIDTH_IN_BYTES equ 80 SCREEN_WIDTH_IN_BYTES equ 80
DISPLAY_MEMORY_SEGMENT equ 0a000h DISPLAY_MEMORY_SEGMENT equ 0a000h
SC_INDEX equ 3c4h ;Sequence Controller Index register SC_INDEX equ 3c4h ;Sequence Controller Index register
MAP_MASK equ 2 ;index of Map Mask register MAP_MASK equ 2 ;index of Map Mask register
GC_INDEX equ 03ceh ;Graphics Controller Index reg GC_INDEX equ 03ceh ;Graphics Controller Index reg
GRAPHICS_MODE equ 5 ;index of Graphics Mode reg GRAPHICS_MODE equ 5 ;index of Graphics Mode reg
BIT_MASKequ 8 ;index of Bit Mask reg BIT_MASKequ 8 ;index of Bit Mask reg
Data segment para common ‘DATA' Data segment para common ‘DATA'
; ;
; Current location of "A" as it is animated across the screen. ; Current location of "A" as it is animated across the screen.
; ;
CurrentX dw ? CurrentX dw ?
CurrentY dw ? CurrentY dw ?
RemainingLength dw ? RemainingLength dw ?
; ;
; Chunky bit-map image of a yellow "A" on a bright blue background ; Chunky bit-map image of a yellow "A" on a bright blue background
; ;
AImage label byte AImage label byte
dw 13, 13 ;width, height in pixels dw 13, 13 ;width, height in pixels
db 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h
db 009h, 099h, 099h, 099h, 099h, 099h, 000h db 009h, 099h, 099h, 099h, 099h, 099h, 000h
db 009h, 099h, 099h, 099h, 099h, 099h, 000h db 009h, 099h, 099h, 099h, 099h, 099h, 000h
db 009h, 099h, 099h, 0e9h, 099h, 099h, 000h db 009h, 099h, 099h, 0e9h, 099h, 099h, 000h
db 009h, 099h, 09eh, 0eeh, 099h, 099h, 000h db 009h, 099h, 09eh, 0eeh, 099h, 099h, 000h
db 009h, 099h, 0eeh, 09eh, 0e9h, 099h, 000h db 009h, 099h, 0eeh, 09eh, 0e9h, 099h, 000h
db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h
db 009h, 09eh, 0eeh, 0eeh, 0eeh, 099h, 000h db 009h, 09eh, 0eeh, 0eeh, 0eeh, 099h, 000h
db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h
db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h db 009h, 09eh, 0e9h, 099h, 0eeh, 099h, 000h
db 009h, 099h, 099h, 099h, 099h, 099h, 000h db 009h, 099h, 099h, 099h, 099h, 099h, 000h
db 009h, 099h, 099h, 099h, 099h, 099h, 000h db 009h, 099h, 099h, 099h, 099h, 099h, 000h
db 000h, 000h, 000h, 000h, 000h, 000h, 000h db 000h, 000h, 000h, 000h, 000h, 000h, 000h
Data ends Data ends
Code segment para public ‘CODE' Code segment para public ‘CODE'
assume cs:Code, ds:Data assume cs:Code, ds:Data
Start proc near Start proc near
mov ax,Data mov ax,Data
mov ds,ax mov ds,ax
mov ax,10h mov ax,10h
int 10h ;select video mode 10h (640x350) int 10h ;select video mode 10h (640x350)
; ;
; Prepare for animation. ; Prepare for animation.
; ;
mov [CurrentX],0 mov [CurrentX],0
mov [CurrentY],200 mov [CurrentY],200
mov [RemainingLength],600 ;move 600 times mov [RemainingLength],600 ;move 600 times
; ;
; Animate, repeating RemainingLength times. It's unnecessary to erase ; Animate, repeating RemainingLength times. It's unnecessary to erase
; the old image, since the one pixel of blank fringe around the image ; the old image, since the one pixel of blank fringe around the image
; erases the part of the old image not overlapped by the new image. ; erases the part of the old image not overlapped by the new image.
; ;
AnimationLoop: AnimationLoop:
mov bx,[CurrentX] mov bx,[CurrentX]
mov cx,[CurrentY] mov cx,[CurrentY]
mov si,offset AImage mov si,offset AImage
call DrawFromChunkyBitmap ;draw the "A" image call DrawFromChunkyBitmap ;draw the "A" image
inc [CurrentX] ;move one pixel to the right inc [CurrentX] ;move one pixel to the right
mov cx,0 ;delay so we don't move the mov cx,0 ;delay so we don't move the
DelayLoop: ; image too fast; adjust as DelayLoop: ; image too fast; adjust as
; needed ; needed
loop DelayLoop loop DelayLoop
dec [RemainingLength] dec [RemainingLength]
jnz AnimationLoop jnz AnimationLoop
; ;
; Wait for a key before returning to text mode and ending. ; Wait for a key before returning to text mode and ending.
; ;
mov ah,01h mov ah,01h
int 21h int 21h
mov ax,03h mov ax,03h
int 10h int 10h
mov ah,4ch mov ah,4ch
int 21h int 21h
Start endp Start endp
; ;
; Draw an image stored in a chunky-bit map into planar VGA/EGA memory ; Draw an image stored in a chunky-bit map into planar VGA/EGA memory
; at the specified location. ; at the specified location.
; ;
; Input: ; Input:
; BX = X screen location at which to draw the upper-left corner ; BX = X screen location at which to draw the upper-left corner
; of the image ; of the image
; CX = Y screen location at which to draw the upper-left corner ; CX = Y screen location at which to draw the upper-left corner
; of the image ; of the image
; DS:SI = pointer to chunky image to draw, as follows: ; DS:SI = pointer to chunky image to draw, as follows:
; word at 0: width of image, in pixels ; word at 0: width of image, in pixels
; word at 2: height of image, in pixels ; word at 2: height of image, in pixels
; byte at 4: msb/lsb = first & second chunky pixels, ; byte at 4: msb/lsb = first & second chunky pixels,
; repeating for the remainder of the scan line ; repeating for the remainder of the scan line
; of the image, then for all scan lines. Images ; of the image, then for all scan lines. Images
; with odd widths have an unused null nibble ; with odd widths have an unused null nibble
; padding each scan line out to a byte width ; padding each scan line out to a byte width
; ;
; AX, BX, CX, DX, SI, DI, ES destroyed. ; AX, BX, CX, DX, SI, DI, ES destroyed.
; ;
DrawFromChunkyBitmap proc near DrawFromChunkyBitmap proc near
cld cld
; ;
; Select write mode 2. ; Select write mode 2.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,GRAPHICS_MODE mov al,GRAPHICS_MODE
out dx,al out dx,al
inc dx inc dx
mov al,02h mov al,02h
out dx,al out dx,al
; ;
; Enable writes to all 4 planes. ; Enable writes to all 4 planes.
; ;
mov dx,SC_INDEX mov dx,SC_INDEX
mov al,MAP_MASK mov al,MAP_MASK
out dx,al out dx,al
inc dx inc dx
mov al,0fh mov al,0fh
out dx,al out dx,al
; ;
; Point ES:DI to the display memory byte in which the first pixel ; Point ES:DI to the display memory byte in which the first pixel
; of the image goes, with AH set up as the bit mask to access that ; of the image goes, with AH set up as the bit mask to access that
; pixel within the addressed byte. ; pixel within the addressed byte.
; ;
mov ax,SCREEN_WIDTH_IN_BYTES mov ax,SCREEN_WIDTH_IN_BYTES
mul cx ;offset of start of top scan line mul cx ;offset of start of top scan line
mov di,ax mov di,ax
mov cl,bl mov cl,bl
and cl,111b and cl,111b
mov ah,80h ;set AH to the bit mask for the mov ah,80h ;set AH to the bit mask for the
shr ah,cl ; initial pixel shr ah,cl ; initial pixel
shr bx,1 shr bx,1
shr bx,1 shr bx,1
shr bx,1 ;X in bytes shr bx,1 ;X in bytes
add di,bx ;offset of upper-left byte of image add di,bx ;offset of upper-left byte of image
mov bx,DISPLAY_MEMORY_SEGMENT mov bx,DISPLAY_MEMORY_SEGMENT
mov es,bx ;ES:DI points to the byte at which the mov es,bx ;ES:DI points to the byte at which the
; upper left of the image goes ; upper left of the image goes
; ;
; Get the width and height of the image. ; Get the width and height of the image.
; ;
mov cx,[si] ;get the width mov cx,[si] ;get the width
inc si inc si
inc si inc si
mov bx,[si] ;get the height mov bx,[si] ;get the height
inc si inc si
inc si inc si
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,BIT_MASK mov al,BIT_MASK
out dx,al ;leave the GC Index register pointing out dx,al ;leave the GC Index register pointing
inc dx ; to the Bit Mask register inc dx ; to the Bit Mask register
RowLoop: RowLoop:
push ax ;preserve the left column's bit mask push ax ;preserve the left column's bit mask
push cx ;preserve the width push cx ;preserve the width
push di ;preserve the destination offset push di ;preserve the destination offset
ColumnLoop: ColumnLoop:
mov al,ah mov al,ah
out dx,al ;set the bit mask to draw this pixel out dx,al ;set the bit mask to draw this pixel
mov al,es:[di] ;load the latches mov al,es:[di] ;load the latches
mov al,[si] ;get the next two chunky pixels mov al,[si] ;get the next two chunky pixels
shr al,1 shr al,1
shr al,1 shr al,1
shr al,1 shr al,1
shr al,1 ;move the first pixel into the lsb shr al,1 ;move the first pixel into the lsb
stosb ;draw the first pixel stosb ;draw the first pixel
ror ah,1 ;move mask to next pixel position ror ah,1 ;move mask to next pixel position
jc CheckMorePixels ;is next pixel in the adjacent byte? jc CheckMorePixels ;is next pixel in the adjacent byte?
dec di ;no dec di ;no
CheckMorePixels: CheckMorePixels:
dec cx ;see if there are any more pixels dec cx ;see if there are any more pixels
jz AdvanceToNextScanLine ; across in image jz AdvanceToNextScanLine ; across in image
mov al,ah mov al,ah
out dx,al ;set the bit mask to draw this pixel out dx,al ;set the bit mask to draw this pixel
mov al,es:[di] ;load the latches mov al,es:[di] ;load the latches
lodsb ;get the same two chunky pixels again lodsb ;get the same two chunky pixels again
; and advance pointer to the next ; and advance pointer to the next
; two pixels ; two pixels
stosb ;draw the second of the two pixels stosb ;draw the second of the two pixels
ror ah,1 ;move mask to next pixel position ror ah,1 ;move mask to next pixel position
jc CheckMorePixels2 ;is next pixel in the adjacent byte? jc CheckMorePixels2 ;is next pixel in the adjacent byte?
dec di ;no dec di ;no
CheckMorePixels2: CheckMorePixels2:
loop ColumnLoop ;see if there are any more pixels loop ColumnLoop ;see if there are any more pixels
; across in the image ; across in the image
jmp short CheckMoreScanLines jmp short CheckMoreScanLines
AdvanceToNextScanLine: AdvanceToNextScanLine:
inc si ;advance to the start of the next inc si ;advance to the start of the next
; scan line in the image ; scan line in the image
CheckMoreScanLines: CheckMoreScanLines:
pop di ;get back the destination offset pop di ;get back the destination offset
pop cx ;get back the width pop cx ;get back the width
pop ax ;get back the left column's bit mask pop ax ;get back the left column's bit mask
add di,SCREEN_WIDTH_IN_BYTES add di,SCREEN_WIDTH_IN_BYTES
;point to the start of the next scan ;point to the start of the next scan
; line of the image ; line of the image
dec bx ;see if there are any more scan lines dec bx ;see if there are any more scan lines
jnz RowLoop ; in the image jnz RowLoop ; in the image
ret ret
DrawFromChunkyBitmap endp DrawFromChunkyBitmap endp
Code ends Code ends
end Start end Start
```

628
27-03.md
View file

@ -53,329 +53,331 @@ the CPU byte in write mode 2 to select the color in which to draw.
**LISTING 27.2 L27-2.ASM** **LISTING 27.2 L27-2.ASM**
; Program to illustrate one use of write mode 2 of the VGA and EGA by ```nasm
; drawing lines in color patterns. ; Program to illustrate one use of write mode 2 of the VGA and EGA by
; ; drawing lines in color patterns.
; Assemble with MASM or TASM ;
; ; Assemble with MASM or TASM
; By Michael Abrash ;
; ; By Michael Abrash
Stack segment para stack ‘STACK' ;
db 512 dup(0) Stack segment para stack ‘STACK'
Stack ends db 512 dup(0)
Stack ends
SCREEN_WIDTH_IN_BYTES equ 80 SCREEN_WIDTH_IN_BYTES equ 80
GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment
SC_INDEX equ 3c4h ;Sequence Controller Index register SC_INDEX equ 3c4h ;Sequence Controller Index register
MAP_MASK equ 2 ;index of Map Mask register MAP_MASK equ 2 ;index of Map Mask register
GC_INDEX equ 03ceh ;Graphics Controller Index reg GC_INDEX equ 03ceh ;Graphics Controller Index reg
GRAPHICS_MODE equ 5 ;index of Graphics Mode reg GRAPHICS_MODE equ 5 ;index of Graphics Mode reg
BIT_MASK equ 8 ;index of Bit Mask reg BIT_MASK equ 8 ;index of Bit Mask reg
Data segment para common ‘DATA' Data segment para common ‘DATA'
Pattern0 db 16 Pattern0 db 16
db 0, 1, 2, 3, 4, 5, 6, 7, 8 db 0, 1, 2, 3, 4, 5, 6, 7, 8
db 9, 10, 11, 12, 13, 14, 15 db 9, 10, 11, 12, 13, 14, 15
Pattern1 db 6 Pattern1 db 6
db 2, 2, 2, 10, 10, 10 db 2, 2, 2, 10, 10, 10
Pattern2 db 8 Pattern2 db 8
db 15, 15, 15, 0, 0, 15, 0, 0 db 15, 15, 15, 0, 0, 15, 0, 0
Pattern3 db 9 Pattern3 db 9
db 1, 1, 1, 2, 2, 2, 4, 4, 4 db 1, 1, 1, 2, 2, 2, 4, 4, 4
Data ends Data ends
Code segment para public ‘CODE' Code segment para public ‘CODE'
assume cs:Code, ds:Data assume cs:Code, ds:Data
Start proc near Start proc near
mov ax,Data mov ax,Data
mov ds,ax mov ds,ax
mov ax,10h mov ax,10h
int 10h ;select video mode 10h (640x350) int 10h ;select video mode 10h (640x350)
; ;
; Draw 8 radial lines in upper-left quadrant in pattern 0. ; Draw 8 radial lines in upper-left quadrant in pattern 0.
; ;
mov bx,0 mov bx,0
mov cx,0 mov cx,0
mov si,offset Pattern0 mov si,offset Pattern0
call QuadrantUp call QuadrantUp
; ;
; Draw 8 radial lines in upper-right quadrant in pattern 1. ; Draw 8 radial lines in upper-right quadrant in pattern 1.
; ;
mov bx,320 mov bx,320
mov cx,0 mov cx,0
mov si,offset Pattern1 mov si,offset Pattern1
call QuadrantUp call QuadrantUp
; ;
; Draw 8 radial lines in lower-left quadrant in pattern 2. ; Draw 8 radial lines in lower-left quadrant in pattern 2.
; ;
mov bx,0 mov bx,0
mov cx,175 mov cx,175
mov si,offset Pattern2 mov si,offset Pattern2
call QuadrantUp call QuadrantUp
; ;
; Draw 8 radial lines in lower-right quadrant in pattern 3. ; Draw 8 radial lines in lower-right quadrant in pattern 3.
; ;
mov bx,320 mov bx,320
mov cx,175 mov cx,175
mov si,offset Pattern3 mov si,offset Pattern3
call QuadrantUp call QuadrantUp
; ;
; Wait for a key before returning to text mode and ending. ; Wait for a key before returning to text mode and ending.
; ;
mov ah,01h mov ah,01h
int 21h int 21h
mov ax,03h mov ax,03h
int 10h int 10h
mov ah,4ch mov ah,4ch
int 21h int 21h
; ;
; Draws 8 radial lines with specified pattern in specified mode 10h ; Draws 8 radial lines with specified pattern in specified mode 10h
; quadrant. ; quadrant.
; ;
; Input: ; Input:
; BX = X coordinate of upper left corner of quadrant ; BX = X coordinate of upper left corner of quadrant
; CX = Y coordinate of upper left corner of quadrant ; CX = Y coordinate of upper left corner of quadrant
; SI = pointer to pattern, in following form: ; SI = pointer to pattern, in following form:
; Byte 0: Length of pattern ; Byte 0: Length of pattern
; Byte 1: Start of pattern, one color per byte ; Byte 1: Start of pattern, one color per byte
; ;
; AX, BX, CX, DX destroyed ; AX, BX, CX, DX destroyed
; ;
QuadrantUp proc near QuadrantUp proc near
add bx,160 add bx,160
add cx,87 ;point to the center of the quadrant add cx,87 ;point to the center of the quadrant
mov ax,0 mov ax,0
mov dx,160 mov dx,160
call LineUp ;draw horizontal line to right edge call LineUp ;draw horizontal line to right edge
mov ax,1 mov ax,1
mov dx,88 mov dx,88
call LineUp ;draw diagonal line to upper right call LineUp ;draw diagonal line to upper right
mov ax,2 mov ax,2
mov dx,88 mov dx,88
call LineUp ;draw vertical line to top edge call LineUp ;draw vertical line to top edge
mov ax,3 mov ax,3
mov dx,88 mov dx,88
call LineUp ;draw diagonal line to upper left call LineUp ;draw diagonal line to upper left
mov ax,4 mov ax,4
mov dx,161 mov dx,161
call LineUp ;draw horizontal line to left edge call LineUp ;draw horizontal line to left edge
mov ax,5 mov ax,5
mov dx,88 mov dx,88
call LineUp ;draw diagonal line to lower left call LineUp ;draw diagonal line to lower left
mov ax,6 mov ax,6
mov dx,88 mov dx,88
call LineUp ;draw vertical line to bottom edge call LineUp ;draw vertical line to bottom edge
mov ax,7 mov ax,7
mov dx,88 mov dx,88
call LineUp ;draw diagonal line to bottom right call LineUp ;draw diagonal line to bottom right
ret ret
QuadrantUp endp QuadrantUp endp
; ;
; Draws a horizontal, vertical, or diagonal line (one of the eight ; Draws a horizontal, vertical, or diagonal line (one of the eight
; possible radial lines) of the specified length from the specified ; possible radial lines) of the specified length from the specified
; starting point. ; starting point.
; ;
; Input: ; Input:
; AX = line direction, as follows: ; AX = line direction, as follows:
; 3 2 1 ; 3 2 1
; 4 * 0 ; 4 * 0
; 5 6 7 ; 5 6 7
; BX = X coordinate of starting point ; BX = X coordinate of starting point
; CX = Y coordinate of starting point ; CX = Y coordinate of starting point
; DX = length of line (number of pixels drawn) ; DX = length of line (number of pixels drawn)
; ;
; All registers preserved. ; All registers preserved.
; ;
; Table of vectors to routines for each of the 8 possible lines. ; Table of vectors to routines for each of the 8 possible lines.
; ;
LineUpVectors label word LineUpVectors label word
dw LineUp0, LineUp1, LineUp2, LineUp3 dw LineUp0, LineUp1, LineUp2, LineUp3
dw LineUp4, LineUp5, LineUp6, LineUp7 dw LineUp4, LineUp5, LineUp6, LineUp7
; ;
; Macro to draw horizontal, vertical, or diagonal line. ; Macro to draw horizontal, vertical, or diagonal line.
; ;
; Input: ; Input:
; XParm = 1 to draw right, -1 to draw left, 0 to not move horz. ; XParm = 1 to draw right, -1 to draw left, 0 to not move horz.
; YParm = 1 to draw up, -1 to draw down, 0 to not move vert. ; YParm = 1 to draw up, -1 to draw down, 0 to not move vert.
; BX = X start location ; BX = X start location
; CX = Y start location ; CX = Y start location
; DX = number of pixels to draw ; DX = number of pixels to draw
; DS:SI = line pattern ; DS:SI = line pattern
; ;
MLineUp macro XParm, YParm MLineUp macro XParm, YParm
local LineUpLoop, CheckMoreLine local LineUpLoop, CheckMoreLine
mov di,si ;set aside start offset of pattern mov di,si ;set aside start offset of pattern
lodsb ;get length of pattern lodsb ;get length of pattern
mov ah,al mov ah,al
LineUpLoop: LineUpLoop:
lodsb ;get color of this pixel... lodsb ;get color of this pixel...
call DotUpInColor ;...and draw it call DotUpInColor ;...and draw it
if XParm EQ 1 if XParm EQ 1
inc bx inc bx
endif endif
if XParm EQ -1 if XParm EQ -1
dec bx dec bx
endif endif
if YParm EQ 1 if YParm EQ 1
inc cx inc cx
endif endif
if YParm EQ -1 if YParm EQ -1
dec cx dec cx
endif endif
dec ah ;at end of pattern? dec ah ;at end of pattern?
jnz CheckMoreLine jnz CheckMoreLine
mov si,di ;get back start of pattern mov si,di ;get back start of pattern
lodsb lodsb
mov ah,al ;reset pattern count mov ah,al ;reset pattern count
CheckMoreLine: CheckMoreLine:
dec dx dec dx
jnz LineUpLoop jnz LineUpLoop
jmp LineUpEnd jmp LineUpEnd
endm endm
LineUp proc near LineUp proc near
push ax push ax
push bx push bx
push cx push cx
push dx push dx
push si push si
push di push di
push es push es
mov di,ax mov di,ax
mov ax,GRAPHICS_SEGMENT mov ax,GRAPHICS_SEGMENT
mov es,ax mov es,ax
push dx ;save line length push dx ;save line length
; ;
; Enable writes to all planes. ; Enable writes to all planes.
; ;
mov dx,SC_INDEX mov dx,SC_INDEX
mov al,MAP_MASK mov al,MAP_MASK
out dx,al out dx,al
inc dx inc dx
mov al,0fh mov al,0fh
out dx,al out dx,al
; ;
; Select write mode 2. ; Select write mode 2.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,GRAPHICS_MODE mov al,GRAPHICS_MODE
out dx,al out dx,al
inc dx inc dx
mov al,02h mov al,02h
out dx,al out dx,al
; ;
; Vector to proper routine. ; Vector to proper routine.
; ;
pop dx ;get back line length pop dx ;get back line length
shl di,1 shl di,1
jmp cs:[LineUpVectors+di] jmp cs:[LineUpVectors+di]
; ;
; Horizontal line to right. ; Horizontal line to right.
; ;
LineUp0: LineUp0:
MLineUp 1, 0 MLineUp 1, 0
; ;
; Diagonal line to upper right. ; Diagonal line to upper right.
; ;
LineUp1: LineUp1:
MLineUp 1, -1 MLineUp 1, -1
; ;
; Vertical line to top. ; Vertical line to top.
; ;
LineUp2: LineUp2:
MLineUp 0, -1 MLineUp 0, -1
; ;
; Diagonal line to upper left. ; Diagonal line to upper left.
; ;
LineUp3: LineUp3:
MLineUp -1, -1 MLineUp -1, -1
; ;
; Horizontal line to left. ; Horizontal line to left.
; ;
LineUp4: LineUp4:
MLineUp -1, 0 MLineUp -1, 0
; ;
; Diagonal line to bottom left. ; Diagonal line to bottom left.
; ;
LineUp5: LineUp5:
MLineUp -1, 1 MLineUp -1, 1
; ;
; Vertical line to bottom. ; Vertical line to bottom.
; ;
LineUp6: LineUp6:
MLineUp 0, 1 MLineUp 0, 1
; ;
; Diagonal line to bottom right. ; Diagonal line to bottom right.
; ;
LineUp7: LineUp7:
MLineUp 1, 1 MLineUp 1, 1
LineUpEnd: LineUpEnd:
pop es pop es
pop di pop di
pop si pop si
pop dx pop dx
pop cx pop cx
pop bx pop bx
pop ax pop ax
ret ret
LineUp endp LineUp endp
; ;
; Draws a dot in the specified color at the specified location. ; Draws a dot in the specified color at the specified location.
; Assumes that the VGA is in write mode 2 with writes to all planes ; Assumes that the VGA is in write mode 2 with writes to all planes
; enabled and that ES points to display memory. ; enabled and that ES points to display memory.
; ;
; Input: ; Input:
; AL = dot color ; AL = dot color
; BX = X coordinate of dot ; BX = X coordinate of dot
; CX = Y coordinate of dot ; CX = Y coordinate of dot
; ES = display memory segment ; ES = display memory segment
; ;
; All registers preserved. ; All registers preserved.
; ;
DotUpInColor proc near DotUpInColor proc near
push bx push bx
push cx push cx
push dx push dx
push di push di
; ;
; Point ES:DI to the display memory byte in which the pixel goes, with ; Point ES:DI to the display memory byte in which the pixel goes, with
; the bit mask set up to access that pixel within the addressed byte. ; the bit mask set up to access that pixel within the addressed byte.
; ;
push ax ;preserve dot color push ax ;preserve dot color
mov ax,SCREEN_WIDTH_IN_BYTES mov ax,SCREEN_WIDTH_IN_BYTES
mul cx ;offset of start of top scan line mul cx ;offset of start of top scan line
mov di,ax mov di,ax
mov cl,bl mov cl,bl
and cl,111b and cl,111b
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,BIT_MASK mov al,BIT_MASK
out dx,al out dx,al
inc dx inc dx
mov al,80h mov al,80h
shr al,cl shr al,cl
out dx,al ;set the bit mask for the pixel out dx,al ;set the bit mask for the pixel
shr bx,1 shr bx,1
shr bx,1 shr bx,1
shr bx,1 ;X in bytes shr bx,1 ;X in bytes
add di,bx ;offset of byte pixel is in add di,bx ;offset of byte pixel is in
mov al,es:[di] ;load latches mov al,es:[di] ;load latches
pop ax ;get back dot color pop ax ;get back dot color
stosb ;write dot in desired color stosb ;write dot in desired color
pop di pop di
pop dx pop dx
pop cx pop cx
pop bx pop bx
ret ret
DotUpInColor endp DotUpInColor endp
Start endp Start endp
Code ends Code ends
end Start end Start
```

368
27-05.md
View file

@ -53,194 +53,196 @@ rewarding!) VGA to cover.
**LISTING 27.3 L27-3.ASM** **LISTING 27.3 L27-3.ASM**
; Program to illustrate flipping from bit-mapped graphics mode to ```nasm
; text mode and back without losing any of the graphics bit-map. ; Program to illustrate flipping from bit-mapped graphics mode to
; ; text mode and back without losing any of the graphics bit-map.
; Assemble with MASM or TASM ;
; ; Assemble with MASM or TASM
; By Michael Abrash ;
; ; By Michael Abrash
Stack segment para stack ‘STACK' ;
db 512 dup(0) Stack segment para stack ‘STACK'
Stack ends db 512 dup(0)
Stack ends
GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment
TEXT_SEGMENT equ 0b800h ;mode 3 bit-map segment TEXT_SEGMENT equ 0b800h ;mode 3 bit-map segment
SC_INDEX equ 3c4h ;Sequence Controller Index register SC_INDEX equ 3c4h ;Sequence Controller Index register
MAP_MASK equ 2 ;index of Map Mask register MAP_MASK equ 2 ;index of Map Mask register
GC_INDEX equ 3ceh ;Graphics Controller Index register GC_INDEX equ 3ceh ;Graphics Controller Index register
READ_MAP equ 4 ;index of Read Map register READ_MAP equ 4 ;index of Read Map register
Data segment para common ‘DATA' Data segment para common ‘DATA'
GStrikeAnyKeyMsg0 label byte GStrikeAnyKeyMsg0 label byte
db 0dh, 0ah, ‘Graphics mode', 0dh, 0ah db 0dh, 0ah, ‘Graphics mode', 0dh, 0ah
db ‘Strike any key to continue...', 0dh, 0ah, ‘$' db ‘Strike any key to continue...', 0dh, 0ah, ‘$'
GStrikeAnyKeyMsg1 label byte GStrikeAnyKeyMsg1 label byte
db 0dh, 0ah, ‘Graphics mode again', 0dh, 0ah db 0dh, 0ah, ‘Graphics mode again', 0dh, 0ah
db ‘Strike any key to continue...', 0dh, 0ah, ‘$' db ‘Strike any key to continue...', 0dh, 0ah, ‘$'
TStrikeAnyKeyMsg label byte TStrikeAnyKeyMsg label byte
db 0dh, 0ah, ‘Text mode', 0dh, 0ah db 0dh, 0ah, ‘Text mode', 0dh, 0ah
db ‘Strike any key to continue...', 0dh, 0ah, ‘$' db ‘Strike any key to continue...', 0dh, 0ah, ‘$'
Plane2Save db 2000h dup (?) ;save area for plane 2 data Plane2Save db 2000h dup (?) ;save area for plane 2 data
; where font gets loaded ; where font gets loaded
CharAttSave db 4000 dup (?) ;save area for memory wiped CharAttSave db 4000 dup (?) ;save area for memory wiped
; out by character/attribute ; out by character/attribute
; data in text mode ; data in text mode
Data ends Data ends
Code segment para public ‘CODE' Code segment para public ‘CODE'
assume cs:Code, ds:Data assume cs:Code, ds:Data
Start proc near Start proc near
mov ax,10h mov ax,10h
int 10h ;select video mode 10h (640x350) int 10h ;select video mode 10h (640x350)
; ;
; Fill the graphics bit-map with a colored pattern. ; Fill the graphics bit-map with a colored pattern.
; ;
cld cld
mov ax,GRAPHICS_SEGMENT mov ax,GRAPHICS_SEGMENT
mov es,ax mov es,ax
mov ah,3 ;initial fill pattern mov ah,3 ;initial fill pattern
mov cx,4 ;four planes to fill mov cx,4 ;four planes to fill
mov dx,SC_INDEX mov dx,SC_INDEX
mov al,MAP_MASK mov al,MAP_MASK
out dx,al ;leave the SC Index pointing to the out dx,al ;leave the SC Index pointing to the
inc dx ; Map Mask register inc dx ; Map Mask register
FillBitMap: FillBitMap:
mov al,10h mov al,10h
shr al,cl ;generate map mask for this plane shr al,cl ;generate map mask for this plane
out dx,al ;set map mask for this plane out dx,al ;set map mask for this plane
sub di,di ;start at offset 0 sub di,di ;start at offset 0
mov al,ah ;get the fill pattern mov al,ah ;get the fill pattern
push cx ;preserve plane count push cx ;preserve plane count
mov cx,8000h ;fill 32K words mov cx,8000h ;fill 32K words
rep stosw ;do fill for this plane rep stosw ;do fill for this plane
pop cx ;get back plane count pop cx ;get back plane count
shl ah,1 shl ah,1
shl ah,1 shl ah,1
loop FillBitMap loop FillBitMap
; ;
; Put up "strike any key" message. ; Put up "strike any key" message.
; ;
mov ax,Data mov ax,Data
mov ds,ax mov ds,ax
mov dx,offset GStrikeAnyKeyMsg0 mov dx,offset GStrikeAnyKeyMsg0
mov ah,9 mov ah,9
int 21h int 21h
; ;
; Wait for a key. ; Wait for a key.
; ;
mov ah,01h mov ah,01h
int 21h int 21h
; ;
; Save the 8K of plane 2 that will be used by the font. ; Save the 8K of plane 2 that will be used by the font.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,READ_MAP mov al,READ_MAP
out dx,al out dx,al
inc dx inc dx
mov al,2 mov al,2
out dx,al ;set up to read from plane 2 out dx,al ;set up to read from plane 2
mov ax,Data mov ax,Data
mov es,ax mov es,ax
mov ax,GRAPHICS_SEGMENT mov ax,GRAPHICS_SEGMENT
mov ds,ax mov ds,ax
sub si,si sub si,si
mov di,offset Plane2Save mov di,offset Plane2Save
mov cx,2000h/2 ;save 8K (length of default font) mov cx,2000h/2 ;save 8K (length of default font)
rep movsw rep movsw
; ;
; Go to text mode without clearing display memory. ; Go to text mode without clearing display memory.
; ;
mov ax,083h mov ax,083h
int 10h int 10h
; ;
; Save the text mode bit-map. ; Save the text mode bit-map.
; ;
mov ax,Data mov ax,Data
mov es,ax mov es,ax
mov ax,TEXT_SEGMENT mov ax,TEXT_SEGMENT
mov ds,ax mov ds,ax
sub si,si sub si,si
mov di,offset CharAttSave mov di,offset CharAttSave
mov cx,4000/2 ;length of one text screen in words mov cx,4000/2 ;length of one text screen in words
rep movsw rep movsw
; ;
; Fill the text mode screen with dots and put up "strike any key" ; Fill the text mode screen with dots and put up "strike any key"
; message. ; message.
; ;
mov ax,TEXT_SEGMENT mov ax,TEXT_SEGMENT
mov es,ax mov es,ax
sub di,di sub di,di
mov al,‘.' ;fill character mov al,‘.' ;fill character
mov ah,7 ;fill attribute mov ah,7 ;fill attribute
mov cx,4000/2 ;length of one text screen in words mov cx,4000/2 ;length of one text screen in words
rep stosw rep stosw
mov ax,Data mov ax,Data
mov ds,ax mov ds,ax
mov dx,offset TStrikeAnyKeyMsg mov dx,offset TStrikeAnyKeyMsg
mov ah,9 mov ah,9
int 21h int 21h
; ;
; Wait for a key. ; Wait for a key.
; ;
mov ah,01h mov ah,01h
int 21h int 21h
; ;
; Restore the text mode screen to the state it was in on entering ; Restore the text mode screen to the state it was in on entering
; text mode. ; text mode.
; ;
mov ax,Data mov ax,Data
mov ds,ax mov ds,ax
mov ax,TEXT_SEGMENT mov ax,TEXT_SEGMENT
mov es,ax mov es,ax
mov si,offset CharAttSave mov si,offset CharAttSave
sub di,di sub di,di
mov cx,4000/2 ;length of one text screen in words mov cx,4000/2 ;length of one text screen in words
rep movsw rep movsw
; ;
; Return to mode 10h without clearing display memory. ; Return to mode 10h without clearing display memory.
; ;
mov ax,90h mov ax,90h
int 10h int 10h
; ;
; Restore the portion of plane 2 that was wiped out by the font. ; Restore the portion of plane 2 that was wiped out by the font.
; ;
mov dx,SC_INDEX mov dx,SC_INDEX
mov al,MAP_MASK mov al,MAP_MASK
out dx,al out dx,al
inc dx inc dx
mov al,4 mov al,4
out dx,al ;set up to write to plane 2 out dx,al ;set up to write to plane 2
mov ax,Data mov ax,Data
mov ds,ax mov ds,ax
mov ax,GRAPHICS_SEGMENT mov ax,GRAPHICS_SEGMENT
mov es,ax mov es,ax
mov si,offset Plane2Save mov si,offset Plane2Save
sub di,di sub di,di
mov cx,2000h/2 ;restore 8K (length of default font) mov cx,2000h/2 ;restore 8K (length of default font)
rep movsw rep movsw
; ;
; Put up "strike any key" message. ; Put up "strike any key" message.
; ;
mov ax,Data mov ax,Data
mov ds,ax mov ds,ax
mov dx,offset GStrikeAnyKeyMsg1 mov dx,offset GStrikeAnyKeyMsg1
mov ah,9 mov ah,9
int 21h int 21h
; ;
; Wait for a key before returning to text mode and ending. ; Wait for a key before returning to text mode and ending.
; ;
mov ah,01h mov ah,01h
int 21h int 21h
mov ax,03h mov ax,03h
int 10h int 10h
mov ah,4ch mov ah,4ch
int 21h int 21h
Start endp Start endp
Code ends Code ends
end Start end Start
```

472
28-02.md
View file

@ -12,238 +12,240 @@ pages: 526-530
**LISTING 28.1 L28-1.ASM** **LISTING 28.1 L28-1.ASM**
; Program to illustrate the use of the Read Map register in read mode 0. ```nasm
; Animates by copying a 16-color image from VGA memory to system memory, ; Program to illustrate the use of the Read Map register in read mode 0.
; one plane at a time, then copying the image back to a new location ; Animates by copying a 16-color image from VGA memory to system memory,
; in VGA memory. ; one plane at a time, then copying the image back to a new location
; ; in VGA memory.
; By Michael Abrash ;
; ; By Michael Abrash
stacksegmentword stack 'STACK' ;
db512 dup (?) stacksegmentword stack 'STACK'
stackends db512 dup (?)
; stackends
datasegment word 'DATA' ;
IMAGE_WIDTHEQU 4 ;in bytes datasegment word 'DATA'
IMAGE_HEIGHT EQU 32 ;in pixels IMAGE_WIDTHEQU 4 ;in bytes
LEFT_BOUND EQU 10 ;in bytes IMAGE_HEIGHT EQU 32 ;in pixels
RIGHT_BOUND EQU 66 ;in bytes LEFT_BOUND EQU 10 ;in bytes
VGA_SEGMENT EQU 0a000h RIGHT_BOUND EQU 66 ;in bytes
SCREEN_WIDTH EQU 80 ;in bytes VGA_SEGMENT EQU 0a000h
SC_INDEX EQU 3c4h ;Sequence Controller Index register SCREEN_WIDTH EQU 80 ;in bytes
GC_INDEX EQU 3ceh ;Graphics Controller Index register SC_INDEX EQU 3c4h ;Sequence Controller Index register
MAP_MASK EQU 2 ;Map Mask register index in SC GC_INDEX EQU 3ceh ;Graphics Controller Index register
READ_MAP EQU 4 ;Read Map register index in GC MAP_MASK EQU 2 ;Map Mask register index in SC
; READ_MAP EQU 4 ;Read Map register index in GC
; Base pattern for 16-color image. ;
; ; Base pattern for 16-color image.
PatternPlane0 label byte ;
db 32 dup (0ffh,0ffh,0,0) PatternPlane0 label byte
PatternPlane1 labelbyte db 32 dup (0ffh,0ffh,0,0)
db 32 dup (0ffh,0,0ffh,0) PatternPlane1 labelbyte
PatternPlane2 labelbyte db 32 dup (0ffh,0,0ffh,0)
db 32 dup (0f0h,0f0h,0f0h,0f0h) PatternPlane2 labelbyte
PatternPlane3 labelbyte db 32 dup (0f0h,0f0h,0f0h,0f0h)
db 32 dup (0cch,0cch,0cch,0cch) PatternPlane3 labelbyte
; db 32 dup (0cch,0cch,0cch,0cch)
; Temporary storage for 16-color image during animation. ;
; ; Temporary storage for 16-color image during animation.
ImagePlane0 db 32*4 dup (?) ;
ImagePlane1 db 32*4 dup (?) ImagePlane0 db 32*4 dup (?)
ImagePlane2 db 32*4 dup (?) ImagePlane1 db 32*4 dup (?)
ImagePlane3 db 32*4 dup (?) ImagePlane2 db 32*4 dup (?)
; ImagePlane3 db 32*4 dup (?)
; Current image location & direction. ;
; ; Current image location & direction.
ImageX dw 40 ;in bytes ;
ImageY dw 100 ;in pixels ImageX dw 40 ;in bytes
ImageXDirection dw 1 ;in bytes ImageY dw 100 ;in pixels
dataends ImageXDirection dw 1 ;in bytes
; dataends
code segment word 'CODE' ;
assume cs:code,ds:data code segment word 'CODE'
Start proc near assume cs:code,ds:data
cld Start proc near
mov ax,data cld
mov ds,ax mov ax,data
; mov ds,ax
; Select graphics mode 10h. ;
; ; Select graphics mode 10h.
mov ax,10h ;
int 10h mov ax,10h
; int 10h
; Draw the initial image. ;
; ; Draw the initial image.
mov si,offset PatternPlane0 ;
call DrawImage mov si,offset PatternPlane0
; call DrawImage
; Loop to animate by copying the image from VGA memory to system memory, ;
; erasing the image, and copying the image from system memory to a new ; Loop to animate by copying the image from VGA memory to system memory,
; location in VGA memory. Ends when a key is hit. ; erasing the image, and copying the image from system memory to a new
; ; location in VGA memory. Ends when a key is hit.
AnimateLoop: ;
; AnimateLoop:
; Copy the image from VGA memory to system memory. ;
; ; Copy the image from VGA memory to system memory.
mov di,offset ImagePlane0 ;
call GetImage mov di,offset ImagePlane0
; call GetImage
; Clear the image from VGA memory. ;
; ; Clear the image from VGA memory.
call EraseImage ;
; call EraseImage
; Advance the image X coordinate, reversing direction if either edge ;
; of the screen has been reached. ; Advance the image X coordinate, reversing direction if either edge
; ; of the screen has been reached.
mov ax,[ImageX] ;
cmp ax,LEFT_BOUND mov ax,[ImageX]
jz ReverseDirection cmp ax,LEFT_BOUND
cmp ax,RIGHT_BOUND jz ReverseDirection
jnz SetNewX cmp ax,RIGHT_BOUND
ReverseDirection: jnz SetNewX
neg [ImageXDirection] ReverseDirection:
SetNewX: neg [ImageXDirection]
add ax,[ImageXDirection] SetNewX:
mov [ImageX],ax add ax,[ImageXDirection]
; mov [ImageX],ax
; Draw the image by copying it from system memory to VGA memory. ;
; ; Draw the image by copying it from system memory to VGA memory.
mov si,offset ImagePlane0 ;
call DrawImage mov si,offset ImagePlane0
; call DrawImage
; Slow things down a bit for visibility (adjust as needed). ;
; ; Slow things down a bit for visibility (adjust as needed).
mov cx,0 ;
DelayLoop: mov cx,0
loop DelayLoop DelayLoop:
; loop DelayLoop
; See if a key has been hit, ending the program. ;
; ; See if a key has been hit, ending the program.
mov ah,1 ;
int 16h mov ah,1
jz AnimateLoop int 16h
; jz AnimateLoop
; Clear the key, return to text mode, and return to DOS. ;
; ; Clear the key, return to text mode, and return to DOS.
sub ah,ah ;
int 16h sub ah,ah
mov ax,3 int 16h
int 10h mov ax,3
mov ah,4ch int 10h
int 21h mov ah,4ch
Startendp int 21h
; Startendp
; Draws the image at offset DS:SI to the current image location in ;
; VGA memory. ; Draws the image at offset DS:SI to the current image location in
; ; VGA memory.
DrawImageprocnear ;
mov ax,VGA_SEGMENT DrawImageprocnear
mov es,ax mov ax,VGA_SEGMENT
call GetImageOffset ;ES:DI is the destination address for the mov es,ax
; image in VGA memory call GetImageOffset ;ES:DI is the destination address for the
mov dx,SC_INDEX ; image in VGA memory
mov al,1 ;do plane 0 first mov dx,SC_INDEX
DrawImagePlaneLoop: mov al,1 ;do plane 0 first
push di ;image is drawn at the same offset in DrawImagePlaneLoop:
; each plane push di ;image is drawn at the same offset in
push ax ;preserve plane select ; each plane
mov al,MAP_MASK ;Map Mask index push ax ;preserve plane select
out dx,al ;point SC Index to the Map Mask register mov al,MAP_MASK ;Map Mask index
pop ax ;get back plane select out dx,al ;point SC Index to the Map Mask register
inc dx ;point to SC index register pop ax ;get back plane select
out dx,al ;set up the Map Mask to allow writes to inc dx ;point to SC index register
; the plane of interest out dx,al ;set up the Map Mask to allow writes to
dec dx ;point back to SC Data register ; the plane of interest
mov bx,IMAGE_HEIGHT ;# of scan lines in image dec dx ;point back to SC Data register
DrawImageLoop: mov bx,IMAGE_HEIGHT ;# of scan lines in image
mov cx,IMAGE_WIDTH ;# of bytes across image DrawImageLoop:
rep movsb mov cx,IMAGE_WIDTH ;# of bytes across image
add di,SCREEN_WIDTH-IMAGE_WIDTH rep movsb
;point to next scan line of image add di,SCREEN_WIDTH-IMAGE_WIDTH
dec bx ;any more scan lines? ;point to next scan line of image
jnz DrawImageLoop dec bx ;any more scan lines?
pop di ;get back image start offset in VGA memory jnz DrawImageLoop
shl al,1 ;Map Mask setting for next plane pop di ;get back image start offset in VGA memory
cmp al,10h ;have we done all four planes? shl al,1 ;Map Mask setting for next plane
jnz DrawImagePlaneLoop cmp al,10h ;have we done all four planes?
ret jnz DrawImagePlaneLoop
DrawImageendp ret
; DrawImageendp
; Copies the image from its current location in VGA memory into the ;
; buffer at DS:DI. ; Copies the image from its current location in VGA memory into the
; ; buffer at DS:DI.
GetImage proc near ;
mov si,di ;move destination offset into SI GetImage proc near
call GetImageOffset ;DI is offset of image in VGA memory mov si,di ;move destination offset into SI
xchg si,di ;SI is offset of image, DI is destination offset call GetImageOffset ;DI is offset of image in VGA memory
push ds xchg si,di ;SI is offset of image, DI is destination offset
pop es ;ES:DI is destination push ds
mov ax,VGA_SEGMENT pop es ;ES:DI is destination
mov ds,ax ;DS:SI is source mov ax,VGA_SEGMENT
; mov ds,ax ;DS:SI is source
mov dx,GC_INDEX ;
sub al,al;do plane 0 first mov dx,GC_INDEX
GetImagePlaneLoop: sub al,al;do plane 0 first
push si ;image comes from same offset in each plane GetImagePlaneLoop:
push ax ;preserve plane select push si ;image comes from same offset in each plane
mov al,READ_MAP ;Read Map index push ax ;preserve plane select
out dx,al ;point GC Index to Read Map register mov al,READ_MAP ;Read Map index
pop ax ;get back plane select out dx,al ;point GC Index to Read Map register
inc dx ;point to GC Index register pop ax ;get back plane select
out dx,al ;set up the Read Map to select reads from inc dx ;point to GC Index register
; the plane of interest out dx,al ;set up the Read Map to select reads from
dec dx ;point back to GC data register ; the plane of interest
mov bx,IMAGE_HEIGHT ;# of scan lines in image dec dx ;point back to GC data register
GetImageLoop: mov bx,IMAGE_HEIGHT ;# of scan lines in image
mov cx,IMAGE_WIDTH ;# of bytes across image GetImageLoop:
rep movsb mov cx,IMAGE_WIDTH ;# of bytes across image
add si,SCREEN_WIDTH-IMAGE_WIDTH rep movsb
;point to next scan line of image add si,SCREEN_WIDTH-IMAGE_WIDTH
dec bx ;any more scan lines? ;point to next scan line of image
jnz GetImageLoop dec bx ;any more scan lines?
pop si ;get back image start offset jnz GetImageLoop
inc al ;Read Map setting for next plane pop si ;get back image start offset
cmp al,4 ;have we done all four planes? inc al ;Read Map setting for next plane
jnz GetImagePlaneLoop cmp al,4 ;have we done all four planes?
push es jnz GetImagePlaneLoop
pop ds ;restore original DS push es
ret pop ds ;restore original DS
GetImageendp ret
; GetImageendp
; Erases the image at its current location. ;
; ; Erases the image at its current location.
EraseImage proc near ;
mov dx,SC_INDEX EraseImage proc near
mov al,MAP_MASK mov dx,SC_INDEX
out dx,al ;point SC Index to the Map Mask register mov al,MAP_MASK
inc dx ;point to SC Data register out dx,al ;point SC Index to the Map Mask register
mov al,0fh inc dx ;point to SC Data register
out dx,al ;set up the Map Mask to allow writes to go to mov al,0fh
; all 4 planes out dx,al ;set up the Map Mask to allow writes to go to
mov ax,VGA_SEGMENT ; all 4 planes
mov es,ax mov ax,VGA_SEGMENT
call GetImageOffset ;ES:DI points to the start address mov es,ax
; of the image call GetImageOffset ;ES:DI points to the start address
sub al,al ;erase with zeros ; of the image
mov bx,IMAGE_HEIGHT ;# of scan lines in image sub al,al ;erase with zeros
EraseImageLoop: mov bx,IMAGE_HEIGHT ;# of scan lines in image
mov cx,IMAGE_WIDTH ;# of bytes across image EraseImageLoop:
rep stosb mov cx,IMAGE_WIDTH ;# of bytes across image
add di,SCREEN_WIDTH-IMAGE_WIDTH rep stosb
;point to next scan line of image add di,SCREEN_WIDTH-IMAGE_WIDTH
dec bx ;any more scan lines? ;point to next scan line of image
jnz EraseImageLoop dec bx ;any more scan lines?
ret jnz EraseImageLoop
EraseImage endp ret
; EraseImage endp
; Returns the current offset of the image in the VGA segment in DI. ;
; ; Returns the current offset of the image in the VGA segment in DI.
GetImageOffset proc near ;
mov ax,SCREEN_WIDTH GetImageOffset proc near
mul [ImageY] mov ax,SCREEN_WIDTH
add ax,[ImageX] mul [ImageY]
mov di,ax add ax,[ImageX]
ret mov di,ax
GetImageOffset endp ret
code ends GetImageOffset endp
end Start code ends
end Start
```

Some files were not shown because too many files have changed in this diff Show more