Use backtick code blocks for syntax highlighting
This commit is contained in:
parent
8ed9dea394
commit
f658bfbb40
203 changed files with 26546 additions and 25801 deletions
70
01-02.md
70
01-02.md
|
|
@ -82,46 +82,48 @@ It's *slow*.
|
|||
|
||||
**LISTING 1.1 L1-1.C**
|
||||
|
||||
/*
|
||||
* Program to calculate the 16-bit checksum of all bytes in the
|
||||
* specified file. Obtains the bytes one at a time via read(),
|
||||
* letting DOS perform all data buffering.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
```c
|
||||
/*
|
||||
* Program to calculate the 16-bit checksum of all bytes in the
|
||||
* specified file. Obtains the bytes one at a time via read(),
|
||||
* letting DOS perform all data buffering.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
main(int argc, char *argv[]) {
|
||||
int Handle;
|
||||
unsigned char Byte;
|
||||
unsigned int Checksum;
|
||||
int ReadLength;
|
||||
main(int argc, char *argv[]) {
|
||||
int Handle;
|
||||
unsigned char Byte;
|
||||
unsigned int Checksum;
|
||||
int ReadLength;
|
||||
|
||||
if ( argc != 2 ) {
|
||||
printf("usage: checksum filename\n");
|
||||
exit(1);
|
||||
}
|
||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
if ( argc != 2 ) {
|
||||
printf("usage: checksum filename\n");
|
||||
exit(1);
|
||||
}
|
||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Initialize the checksum accumulator */
|
||||
Checksum = 0;
|
||||
/* Initialize the checksum accumulator */
|
||||
Checksum = 0;
|
||||
|
||||
/* Add each byte in turn into the checksum accumulator */
|
||||
while ( (ReadLength = read(Handle, &Byte, sizeof(Byte))) > 0 ) {
|
||||
Checksum += (unsigned int) Byte;
|
||||
}
|
||||
if ( ReadLength == -1 ) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
/* Add each byte in turn into the checksum accumulator */
|
||||
while ( (ReadLength = read(Handle, &Byte, sizeof(Byte))) > 0 ) {
|
||||
Checksum += (unsigned int) Byte;
|
||||
}
|
||||
if ( ReadLength == -1 ) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
/* Report the result */
|
||||
printf("The checksum is: %u\n", Checksum);
|
||||
exit(0);
|
||||
}
|
||||
/* Report the result */
|
||||
printf("The checksum is: %u\n", Checksum);
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
188
01-03.md
188
01-03.md
|
|
@ -30,105 +30,109 @@ disk caching turned off.
|
|||
|
||||
**LISTING 1.2 L1-2.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
|
||||
* assembler, via direct calls to DOS.
|
||||
*/
|
||||
```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
|
||||
* assembler, via direct calls to DOS.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
main(int argc, char *argv[]) {
|
||||
int Handle;
|
||||
unsigned char Byte;
|
||||
unsigned int Checksum;
|
||||
int ReadLength;
|
||||
main(int argc, char *argv[]) {
|
||||
int Handle;
|
||||
unsigned char Byte;
|
||||
unsigned int Checksum;
|
||||
int ReadLength;
|
||||
|
||||
if ( argc != 2 ) {
|
||||
printf("usage: checksum filename\n");
|
||||
exit(1);
|
||||
}
|
||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
if ( !ChecksumFile(Handle, &Checksum) ) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
if ( argc != 2 ) {
|
||||
printf("usage: checksum filename\n");
|
||||
exit(1);
|
||||
}
|
||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
if ( !ChecksumFile(Handle, &Checksum) ) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Report the result */
|
||||
printf("The checksum is: %u\n", Checksum);
|
||||
exit(0);
|
||||
}
|
||||
/* Report the result */
|
||||
printf("The checksum is: %u\n", Checksum);
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
**LISTING 1.3 L1-3.ASM**
|
||||
|
||||
; Assembler subroutine to perform a 16-bit checksum on the file
|
||||
; 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);
|
||||
;
|
||||
; where:
|
||||
; Handle = handle # under which file to checksum is open
|
||||
; Checksum = pointer to unsigned int variable checksum is
|
||||
; to be stored in
|
||||
;
|
||||
; Parameter structure:
|
||||
;
|
||||
Parms struc
|
||||
dw ? ;pushed BP
|
||||
dw ? ;return address
|
||||
Handle dw ?
|
||||
Checksum dw ?
|
||||
Parms ends
|
||||
;
|
||||
.model small
|
||||
.data
|
||||
TempWord label word
|
||||
TempByte db ? ;each byte read by DOS will be stored here
|
||||
db 0 ;high byte of TempWord is always 0
|
||||
;for 16-bit adds
|
||||
;
|
||||
.code
|
||||
public _ChecksumFile
|
||||
_ChecksumFile proc near
|
||||
push bp
|
||||
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 cx,1 ;request one byte on each ;read
|
||||
mov dx,offset TempByte ;point DX to the byte in
|
||||
;which DOS should store
|
||||
;each byte read
|
||||
ChecksumLoop:
|
||||
mov ah,3fh ;DOS read file function #
|
||||
int 21h ;read the byte
|
||||
jcErrorEnd;an error occurred
|
||||
and ax,ax ;any bytes read?
|
||||
jz Success ;no-end of file reached-we're done
|
||||
add si,[TempWord] ;add the byte into the
|
||||
;checksum total
|
||||
jmpChecksumLoop
|
||||
ErrorEnd:
|
||||
sub ax,ax ;error
|
||||
jmp short Done
|
||||
Success:
|
||||
mov bx,[bp+Checksum] ;point to the checksum variable
|
||||
mov [bx],si ;save the new checksum
|
||||
mov ax,1 ;success
|
||||
;
|
||||
Done:
|
||||
pop si ;restore C's register variable
|
||||
pop bp
|
||||
ret
|
||||
_ChecksumFileendp
|
||||
end
|
||||
```nasm
|
||||
; Assembler subroutine to perform a 16-bit checksum on the file
|
||||
; 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);
|
||||
;
|
||||
; where:
|
||||
; Handle = handle # under which file to checksum is open
|
||||
; Checksum = pointer to unsigned int variable checksum is
|
||||
; to be stored in
|
||||
;
|
||||
; Parameter structure:
|
||||
;
|
||||
Parms struc
|
||||
dw ? ;pushed BP
|
||||
dw ? ;return address
|
||||
Handle dw ?
|
||||
Checksum dw ?
|
||||
Parms ends
|
||||
;
|
||||
.model small
|
||||
.data
|
||||
TempWord label word
|
||||
TempByte db ? ;each byte read by DOS will be stored here
|
||||
db 0 ;high byte of TempWord is always 0
|
||||
;for 16-bit adds
|
||||
;
|
||||
.code
|
||||
public _ChecksumFile
|
||||
_ChecksumFile proc near
|
||||
push bp
|
||||
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 cx,1 ;request one byte on each ;read
|
||||
mov dx,offset TempByte ;point DX to the byte in
|
||||
;which DOS should store
|
||||
;each byte read
|
||||
ChecksumLoop:
|
||||
mov ah,3fh ;DOS read file function #
|
||||
int 21h ;read the byte
|
||||
jcErrorEnd;an error occurred
|
||||
and ax,ax ;any bytes read?
|
||||
jz Success ;no-end of file reached-we're done
|
||||
add si,[TempWord] ;add the byte into the
|
||||
;checksum total
|
||||
jmpChecksumLoop
|
||||
ErrorEnd:
|
||||
sub ax,ax ;error
|
||||
jmp short Done
|
||||
Success:
|
||||
mov bx,[bp+Checksum] ;point to the checksum variable
|
||||
mov [bx],si ;save the new checksum
|
||||
mov ax,1 ;success
|
||||
;
|
||||
Done:
|
||||
pop si ;restore C's register variable
|
||||
pop bp
|
||||
ret
|
||||
_ChecksumFileendp
|
||||
end
|
||||
```
|
||||
|
||||
The lesson is clear: Optimization makes code faster, but without proper
|
||||
design, optimization just creates fast slow code.
|
||||
|
|
|
|||
58
01-04.md
58
01-04.md
|
|
@ -20,39 +20,41 @@ libraries do their work. In other words, *know the territory*!
|
|||
|
||||
**LISTING 1.4 L1-4.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
|
||||
* getc(), allowing C to perform data buffering.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
```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
|
||||
* getc(), allowing C to perform data buffering.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
main(int argc, char *argv[]) {
|
||||
FILE *CheckFile;
|
||||
int Byte;
|
||||
unsigned int Checksum;
|
||||
main(int argc, char *argv[]) {
|
||||
FILE *CheckFile;
|
||||
int Byte;
|
||||
unsigned int Checksum;
|
||||
|
||||
if ( argc != 2 ) {
|
||||
printf("usage: checksum filename\n");
|
||||
exit(1);
|
||||
}
|
||||
if ( (CheckFile = fopen(argv[1], "rb")) == NULL ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
if ( argc != 2 ) {
|
||||
printf("usage: checksum filename\n");
|
||||
exit(1);
|
||||
}
|
||||
if ( (CheckFile = fopen(argv[1], "rb")) == NULL ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Initialize the checksum accumulator */
|
||||
Checksum = 0;
|
||||
/* Initialize the checksum accumulator */
|
||||
Checksum = 0;
|
||||
|
||||
/* Add each byte in turn into the checksum accumulator */
|
||||
while ( (Byte = getc(CheckFile)) != EOF ) {
|
||||
Checksum += (unsigned int) Byte;
|
||||
}
|
||||
/* Add each byte in turn into the checksum accumulator */
|
||||
while ( (Byte = getc(CheckFile)) != EOF ) {
|
||||
Checksum += (unsigned int) Byte;
|
||||
}
|
||||
|
||||
/* Report the result */
|
||||
printf("The checksum is: %u\n", Checksum);
|
||||
exit(0);
|
||||
}
|
||||
/* Report the result */
|
||||
printf("The checksum is: %u\n", Checksum);
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
#### Know When It Matters {#Heading10}
|
||||
|
||||
|
|
|
|||
194
01-05.md
194
01-05.md
|
|
@ -30,62 +30,64 @@ uses no assembly at all.
|
|||
|
||||
**LISTING 1.5 L1-5.C**
|
||||
|
||||
/*
|
||||
* Program to calculate the 16-bit checksum of the stream of bytes
|
||||
* from the specified file. Buffers the bytes internally, rather
|
||||
* than letting C or DOS do the work.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <alloc.h> /* alloc.h for Borland,
|
||||
malloc.h for Microsoft */
|
||||
```c
|
||||
/*
|
||||
* Program to calculate the 16-bit checksum of the stream of bytes
|
||||
* from the specified file. Buffers the bytes internally, rather
|
||||
* than letting C or DOS do the work.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#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[]) {
|
||||
int Handle;
|
||||
unsigned int Checksum;
|
||||
unsigned char *WorkingBuffer, *WorkingPtr;
|
||||
int WorkingLength, LengthCount;
|
||||
main(int argc, char *argv[]) {
|
||||
int Handle;
|
||||
unsigned int Checksum;
|
||||
unsigned char *WorkingBuffer, *WorkingPtr;
|
||||
int WorkingLength, LengthCount;
|
||||
|
||||
if ( argc != 2 ) {
|
||||
printf("usage: checksum filename\n");
|
||||
exit(1);
|
||||
}
|
||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
if ( argc != 2 ) {
|
||||
printf("usage: checksum filename\n");
|
||||
exit(1);
|
||||
}
|
||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Get memory in which to buffer the data */
|
||||
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
|
||||
printf("Can't get enough memory\n");
|
||||
exit(1);
|
||||
}
|
||||
/* Get memory in which to buffer the data */
|
||||
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
|
||||
printf("Can't get enough memory\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Initialize the checksum accumulator */
|
||||
Checksum = 0;
|
||||
/* Initialize the checksum accumulator */
|
||||
Checksum = 0;
|
||||
|
||||
/* Process the file in BUFFER_SIZE chunks */
|
||||
do {
|
||||
if ( (WorkingLength = read(Handle, WorkingBuffer,
|
||||
BUFFER_SIZE)) == -1 ) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
/* Checksum this chunk */
|
||||
WorkingPtr = WorkingBuffer;
|
||||
LengthCount = WorkingLength;
|
||||
while ( LengthCount-- ) {
|
||||
/* Add each byte in turn into the checksum accumulator */
|
||||
Checksum += (unsigned int) *WorkingPtr++;
|
||||
}
|
||||
} while ( WorkingLength );
|
||||
/* Process the file in BUFFER_SIZE chunks */
|
||||
do {
|
||||
if ( (WorkingLength = read(Handle, WorkingBuffer,
|
||||
BUFFER_SIZE)) == -1 ) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
/* Checksum this chunk */
|
||||
WorkingPtr = WorkingBuffer;
|
||||
LengthCount = WorkingLength;
|
||||
while ( LengthCount-- ) {
|
||||
/* Add each byte in turn into the checksum accumulator */
|
||||
Checksum += (unsigned int) *WorkingPtr++;
|
||||
}
|
||||
} while ( WorkingLength );
|
||||
|
||||
/* Report the result */
|
||||
printf("The checksum is: %u\n", Checksum);
|
||||
exit(0);
|
||||
}
|
||||
/* Report the result */
|
||||
printf("The checksum is: %u\n", Checksum);
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
That brings us to the fourth reason: avoiding an internal-buffered
|
||||
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**
|
||||
|
||||
/*
|
||||
* Program to calculate the 16-bit checksum of the stream of bytes
|
||||
* from the specified file. Buffers the bytes internally, rather
|
||||
* 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 <alloc.h> /* alloc.h for Borland,
|
||||
malloc.h for Microsoft */
|
||||
```c
|
||||
/*
|
||||
* Program to calculate the 16-bit checksum of the stream of bytes
|
||||
* from the specified file. Buffers the bytes internally, rather
|
||||
* 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 <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[]) {
|
||||
int Handle;
|
||||
unsigned int Checksum;
|
||||
unsigned char *WorkingBuffer;
|
||||
int WorkingLength;
|
||||
main(int argc, char *argv[]) {
|
||||
int Handle;
|
||||
unsigned int Checksum;
|
||||
unsigned char *WorkingBuffer;
|
||||
int WorkingLength;
|
||||
|
||||
if ( argc != 2 ) {
|
||||
printf("usage: checksum filename\n");
|
||||
exit(1);
|
||||
}
|
||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
if ( argc != 2 ) {
|
||||
printf("usage: checksum filename\n");
|
||||
exit(1);
|
||||
}
|
||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Get memory in which to buffer the data */
|
||||
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
|
||||
printf("Can't get enough memory\n");
|
||||
exit(1);
|
||||
}
|
||||
/* Get memory in which to buffer the data */
|
||||
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
|
||||
printf("Can't get enough memory\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Initialize the checksum accumulator */
|
||||
Checksum = 0;
|
||||
/* Initialize the checksum accumulator */
|
||||
Checksum = 0;
|
||||
|
||||
/* Process the file in 32K chunks */
|
||||
do {
|
||||
if ( (WorkingLength = read(Handle, WorkingBuffer,
|
||||
BUFFER_SIZE)) == -1 ) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
/* Checksum this chunk if there's anything in it */
|
||||
if ( WorkingLength )
|
||||
ChecksumChunk(WorkingBuffer, WorkingLength, &Checksum);
|
||||
} while ( WorkingLength );
|
||||
/* Process the file in 32K chunks */
|
||||
do {
|
||||
if ( (WorkingLength = read(Handle, WorkingBuffer,
|
||||
BUFFER_SIZE)) == -1 ) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
/* Checksum this chunk if there's anything in it */
|
||||
if ( WorkingLength )
|
||||
ChecksumChunk(WorkingBuffer, WorkingLength, &Checksum);
|
||||
} while ( WorkingLength );
|
||||
|
||||
/* Report the result */
|
||||
printf("The checksum is: %u\n", Checksum);
|
||||
exit(0);
|
||||
}
|
||||
/* Report the result */
|
||||
printf("The checksum is: %u\n", Checksum);
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
|
|
|||
100
01-06.md
100
01-06.md
|
|
@ -12,55 +12,57 @@ pages: 018-019
|
|||
|
||||
**LISTING 1.7 L1-7.ASM**
|
||||
|
||||
; Assembler subroutine to perform a 16-bit checksum on a block of
|
||||
; bytes 1 to 64K in size. Adds checksum for block into passed-in
|
||||
; checksum.
|
||||
;
|
||||
; Call as:
|
||||
; void ChecksumChunk(unsigned char *Buffer,
|
||||
; unsigned int BufferLength, unsigned int *Checksum);
|
||||
;
|
||||
; where:
|
||||
; Buffer = pointer to start of block of bytes to checksum
|
||||
; BufferLength = # of bytes to checksum (0 means 64K, not 0)
|
||||
; Checksum = pointer to unsigned int variable checksum is
|
||||
;stored in
|
||||
;
|
||||
; Parameter structure:
|
||||
;
|
||||
Parms struc
|
||||
dw ? ;pushed BP
|
||||
dw ? ;return address
|
||||
Buffer dw ?
|
||||
BufferLength dw ?
|
||||
Checksum dw ?
|
||||
Parmsends
|
||||
;
|
||||
.model small
|
||||
.code
|
||||
public _ChecksumChunk
|
||||
_ChecksumChunkprocnear
|
||||
push bp
|
||||
mov bp,sp
|
||||
push si ;save C's register variable
|
||||
;
|
||||
cld ;make LODSB increment SI
|
||||
mov si,[bp+Buffer] ;point to buffer
|
||||
mov cx,[bp+BufferLength] ;get buffer length
|
||||
mov bx,[bp+Checksum] ;point to checksum variable
|
||||
mov dx,[bx] ;get the current checksum
|
||||
sub ah,ah ;so AX will be a 16-bit value after LODSB
|
||||
ChecksumLoop:
|
||||
lodsb ;get the next byte
|
||||
add dx,ax ;add it into the checksum total
|
||||
loop ChecksumLoop ;continue for all bytes in block
|
||||
mov [bx],dx ;save the new checksum
|
||||
;
|
||||
pop si ;restore C's register variable
|
||||
pop bp
|
||||
ret
|
||||
_ChecksumChunkendp
|
||||
end
|
||||
```nasm
|
||||
; Assembler subroutine to perform a 16-bit checksum on a block of
|
||||
; bytes 1 to 64K in size. Adds checksum for block into passed-in
|
||||
; checksum.
|
||||
;
|
||||
; Call as:
|
||||
; void ChecksumChunk(unsigned char *Buffer,
|
||||
; unsigned int BufferLength, unsigned int *Checksum);
|
||||
;
|
||||
; where:
|
||||
; Buffer = pointer to start of block of bytes to checksum
|
||||
; BufferLength = # of bytes to checksum (0 means 64K, not 0)
|
||||
; Checksum = pointer to unsigned int variable checksum is
|
||||
;stored in
|
||||
;
|
||||
; Parameter structure:
|
||||
;
|
||||
Parms struc
|
||||
dw ? ;pushed BP
|
||||
dw ? ;return address
|
||||
Buffer dw ?
|
||||
BufferLength dw ?
|
||||
Checksum dw ?
|
||||
Parmsends
|
||||
;
|
||||
.model small
|
||||
.code
|
||||
public _ChecksumChunk
|
||||
_ChecksumChunkprocnear
|
||||
push bp
|
||||
mov bp,sp
|
||||
push si ;save C's register variable
|
||||
;
|
||||
cld ;make LODSB increment SI
|
||||
mov si,[bp+Buffer] ;point to buffer
|
||||
mov cx,[bp+BufferLength] ;get buffer length
|
||||
mov bx,[bp+Checksum] ;point to checksum variable
|
||||
mov dx,[bx] ;get the current checksum
|
||||
sub ah,ah ;so AX will be a 16-bit value after LODSB
|
||||
ChecksumLoop:
|
||||
lodsb ;get the next byte
|
||||
add dx,ax ;add it into the checksum total
|
||||
loop ChecksumLoop ;continue for all bytes in block
|
||||
mov [bx],dx ;save the new checksum
|
||||
;
|
||||
pop si ;restore C's register variable
|
||||
pop bp
|
||||
ret
|
||||
_ChecksumChunkendp
|
||||
end
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
|
|||
38
02-01.md
38
02-01.md
|
|
@ -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
|
||||
done, the key part of the code looked something like this:
|
||||
|
||||
LoopTop:
|
||||
lodsb ;get the next byte to extract a bit from
|
||||
and al,ah ;isolate the bit we want
|
||||
rol al,cl ;rotate the bit into the desired position
|
||||
or bl,al ;insert the bit into the final nibble
|
||||
dec cx ;the next bit goes 1 place to the right
|
||||
dec dx ;count down the number of bits
|
||||
jnz LoopTop ;process the next bit, if any
|
||||
```nasm
|
||||
LoopTop:
|
||||
lodsb ;get the next byte to extract a bit from
|
||||
and al,ah ;isolate the bit we want
|
||||
rol al,cl ;rotate the bit into the desired position
|
||||
or bl,al ;insert the bit into the final nibble
|
||||
dec cx ;the next bit goes 1 place to the right
|
||||
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,
|
||||
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:
|
||||
|
||||
LoopTop:
|
||||
lodsb ;get the next byte to extract a bit from
|
||||
and al,ah ;isolate the bit we want
|
||||
or bl,al ;insert the bit into the final nibble
|
||||
rol bl,1 ;make room for the next bit
|
||||
dec dx ;count down the number of bits
|
||||
jnz LoopTop ;process the next bit, if any
|
||||
rol bl,cl ;rotate all four bits into their final
|
||||
; positions at the same time
|
||||
```nasm
|
||||
LoopTop:
|
||||
lodsb ;get the next byte to extract a bit from
|
||||
and al,ah ;isolate the bit we want
|
||||
or bl,al ;insert the bit into the final nibble
|
||||
rol bl,1 ;make room for the next bit
|
||||
dec dx ;count down the number of bits
|
||||
jnz LoopTop ;process the next bit, if any
|
||||
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
|
||||
performed just once, rather than four times. While the code may not look
|
||||
|
|
|
|||
860
03-02.md
860
03-02.md
|
|
@ -12,442 +12,444 @@ pages: 035-042
|
|||
|
||||
**LISTING 3.1 PZTIMER.ASM**
|
||||
|
||||
; 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
|
||||
; of better than 10 microseconds.
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
; Externally callable routines:
|
||||
;
|
||||
; 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
|
||||
; state they were in when ZTimerOn was called.
|
||||
;
|
||||
; 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
|
||||
; inaccurate. When this happens, an error message is displayed
|
||||
; 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
|
||||
; timer overflow.
|
||||
;
|
||||
; Note: These routines can introduce slight inaccuracies into the
|
||||
; system clock count for each code section timed even if
|
||||
; timer 0 doesn't overflow. If timer 0 does overflow, the
|
||||
; system clock can become slow by virtually any amount of
|
||||
; time, since the system clock can't advance while the
|
||||
; precison timer is timing. Consequently, it's a good idea
|
||||
; to reboot at the end of each timing session. (The
|
||||
; 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
|
||||
; by ZTimerOn, and are restored by ZTimerOff to the state they were
|
||||
; in when ZTimerOn was called.
|
||||
;
|
||||
```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
|
||||
; of better than 10 microseconds.
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
; Externally callable routines:
|
||||
;
|
||||
; 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
|
||||
; state they were in when ZTimerOn was called.
|
||||
;
|
||||
; 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
|
||||
; inaccurate. When this happens, an error message is displayed
|
||||
; 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
|
||||
; timer overflow.
|
||||
;
|
||||
; Note: These routines can introduce slight inaccuracies into the
|
||||
; system clock count for each code section timed even if
|
||||
; timer 0 doesn't overflow. If timer 0 does overflow, the
|
||||
; system clock can become slow by virtually any amount of
|
||||
; time, since the system clock can't advance while the
|
||||
; precison timer is timing. Consequently, it's a good idea
|
||||
; to reboot at the end of each timing session. (The
|
||||
; 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
|
||||
; by ZTimerOn, and are restored by ZTimerOff to the state they were
|
||||
; in when ZTimerOn was called.
|
||||
;
|
||||
|
||||
Code segment word public ‘CODE'
|
||||
assumecs: Code, ds:nothing
|
||||
public ZTimerOn, ZTimerOff, ZTimerReport
|
||||
Code segment word public ‘CODE'
|
||||
assumecs: Code, ds:nothing
|
||||
public ZTimerOn, ZTimerOff, ZTimerReport
|
||||
|
||||
;
|
||||
; Base address of the 8253 timer chip.
|
||||
;
|
||||
BASE_8253equ40h
|
||||
;
|
||||
; The address of the timer 0 count registers in the 8253.
|
||||
;
|
||||
TIMER_0_8253 equBASE_8253 + 0
|
||||
;
|
||||
; The address of the mode register in the 8253.
|
||||
;
|
||||
MODE_8253 equBASE_8253 + 3
|
||||
;
|
||||
; The address of Operation Command Word 3 in the 8259 Programmable
|
||||
; 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).
|
||||
;
|
||||
OCW3 equ20h
|
||||
;
|
||||
; 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
|
||||
; of OCW3 = 0).
|
||||
;
|
||||
IRR equ20h
|
||||
;
|
||||
; 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
|
||||
; interrupts remain disabled.
|
||||
;
|
||||
MPOPF macro
|
||||
local p1, p2
|
||||
jmp short p2
|
||||
p1: iret ; jump to pushed address & pop flags
|
||||
p2: push cs ; construct far return address to
|
||||
call p1 ; the next instruction
|
||||
endm
|
||||
;
|
||||
; Base address of the 8253 timer chip.
|
||||
;
|
||||
BASE_8253equ40h
|
||||
;
|
||||
; The address of the timer 0 count registers in the 8253.
|
||||
;
|
||||
TIMER_0_8253 equBASE_8253 + 0
|
||||
;
|
||||
; The address of the mode register in the 8253.
|
||||
;
|
||||
MODE_8253 equBASE_8253 + 3
|
||||
;
|
||||
; The address of Operation Command Word 3 in the 8259 Programmable
|
||||
; 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).
|
||||
;
|
||||
OCW3 equ20h
|
||||
;
|
||||
; 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
|
||||
; of OCW3 = 0).
|
||||
;
|
||||
IRR equ20h
|
||||
;
|
||||
; 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
|
||||
; interrupts remain disabled.
|
||||
;
|
||||
MPOPF macro
|
||||
local p1, p2
|
||||
jmp short p2
|
||||
p1: iret ; jump to pushed address & pop flags
|
||||
p2: push cs ; construct far return address to
|
||||
call p1 ; the next instruction
|
||||
endm
|
||||
|
||||
;
|
||||
; Macro to delay briefly to ensure that enough time has elapsed
|
||||
; between successive I/O accesses so that the device being accessed
|
||||
; can respond to both accesses even on a very fast PC.
|
||||
;
|
||||
DELAY macro
|
||||
jmp $+2
|
||||
jmp $+2
|
||||
jmp $+2
|
||||
endm
|
||||
;
|
||||
; Macro to delay briefly to ensure that enough time has elapsed
|
||||
; between successive I/O accesses so that the device being accessed
|
||||
; can respond to both accesses even on a very fast PC.
|
||||
;
|
||||
DELAY macro
|
||||
jmp $+2
|
||||
jmp $+2
|
||||
jmp $+2
|
||||
endm
|
||||
|
||||
OriginalFlags db ? ; storage for upper byte of
|
||||
; FLAGS register when
|
||||
; ZTimerOn called
|
||||
TimedCount dw ? ; timer 0 count when the timer
|
||||
; is stopped
|
||||
ReferenceCount dw ; number of counts required to
|
||||
; execute timer overhead code
|
||||
OverflowFlag db ? ; used to indicate whether the
|
||||
; timer overflowed during the
|
||||
; timing interval
|
||||
;
|
||||
; String printed to report results.
|
||||
;
|
||||
OutputStr label byte
|
||||
db 0dh, 0ah, ‘Timed count: ‘, 5 dup (?)
|
||||
ASCIICountEnd labelbyte
|
||||
db ‘ microseconds', 0dh, 0ah
|
||||
db ‘$'
|
||||
;
|
||||
; String printed to report timer overflow.
|
||||
;
|
||||
OverflowStr label byte
|
||||
db 0dh, 0ah
|
||||
db ‘****************************************************'
|
||||
db 0dh, 0ah
|
||||
db ‘* The timer overflowed, so the interval timed was *'
|
||||
db 0dh, 0ah
|
||||
db ‘* too long for the precision timer to measure. *'
|
||||
db 0dh, 0ah
|
||||
db ‘* Please perform the timing test again with the *'
|
||||
db0dh, 0ah
|
||||
db ‘* long-period timer. *'
|
||||
db 0dh, 0ah
|
||||
db ‘****************************************************'
|
||||
db 0dh, 0ah
|
||||
db ‘$'
|
||||
OriginalFlags db ? ; storage for upper byte of
|
||||
; FLAGS register when
|
||||
; ZTimerOn called
|
||||
TimedCount dw ? ; timer 0 count when the timer
|
||||
; is stopped
|
||||
ReferenceCount dw ; number of counts required to
|
||||
; execute timer overhead code
|
||||
OverflowFlag db ? ; used to indicate whether the
|
||||
; timer overflowed during the
|
||||
; timing interval
|
||||
;
|
||||
; String printed to report results.
|
||||
;
|
||||
OutputStr label byte
|
||||
db 0dh, 0ah, ‘Timed count: ‘, 5 dup (?)
|
||||
ASCIICountEnd labelbyte
|
||||
db ‘ microseconds', 0dh, 0ah
|
||||
db ‘$'
|
||||
;
|
||||
; String printed to report timer overflow.
|
||||
;
|
||||
OverflowStr label byte
|
||||
db 0dh, 0ah
|
||||
db ‘****************************************************'
|
||||
db 0dh, 0ah
|
||||
db ‘* The timer overflowed, so the interval timed was *'
|
||||
db 0dh, 0ah
|
||||
db ‘* too long for the precision timer to measure. *'
|
||||
db 0dh, 0ah
|
||||
db ‘* Please perform the timing test again with the *'
|
||||
db0dh, 0ah
|
||||
db ‘* long-period timer. *'
|
||||
db 0dh, 0ah
|
||||
db ‘****************************************************'
|
||||
db 0dh, 0ah
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
ZTimerOn endp
|
||||
ZTimerReport 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
|
||||
|
||||
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
|
||||
Code ends
|
||||
end
|
||||
```
|
||||
|
|
|
|||
126
03-05.md
126
03-05.md
|
|
@ -82,43 +82,45 @@ and should contain calls to **ZTimerOn** and **ZTimerOff** .
|
|||
|
||||
**LISTING 3.2 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
|
||||
; measured must be in the file TESTCODE; Listing 3.3 shows
|
||||
; a sample TESTCODE file.
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
mystack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
mystack ends
|
||||
;
|
||||
Code segment para public ‘CODE'
|
||||
assume cs:Code, ds:Code
|
||||
extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near
|
||||
Start proc near
|
||||
push cs
|
||||
pop ds ; set DS to point to the code segment,
|
||||
; so data as well as code can easily
|
||||
; be included in TESTCODE
|
||||
;
|
||||
include TESTCODE ;code to be measured, including
|
||||
; calls to ZTimerOn and ZTimerOff
|
||||
;
|
||||
; Display the results.
|
||||
;
|
||||
call ZTimerReport
|
||||
;
|
||||
; Terminate the program.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Start endp
|
||||
Code ends
|
||||
end Start
|
||||
```nasm
|
||||
; 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
|
||||
; measured must be in the file TESTCODE; Listing 3.3 shows
|
||||
; a sample TESTCODE file.
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
mystack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
mystack ends
|
||||
;
|
||||
Code segment para public ‘CODE'
|
||||
assume cs:Code, ds:Code
|
||||
extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near
|
||||
Start proc near
|
||||
push cs
|
||||
pop ds ; set DS to point to the code segment,
|
||||
; so data as well as code can easily
|
||||
; be included in TESTCODE
|
||||
;
|
||||
include TESTCODE ;code to be measured, including
|
||||
; calls to ZTimerOn and ZTimerOff
|
||||
;
|
||||
; Display the results.
|
||||
;
|
||||
call ZTimerReport
|
||||
;
|
||||
; Terminate the program.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Start endp
|
||||
Code ends
|
||||
end Start
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -130,30 +132,32 @@ after the code in Listing 3.3 has been run.
|
|||
|
||||
**LISTING 3.3 LST3-3.ASM**
|
||||
|
||||
; Test file;
|
||||
; Measures the performance of 1,000 loads of AL from
|
||||
; memory. (Use by renaming to TESTCODE, which is
|
||||
; included by PZTEST.ASM (Listing 3.2). PZTIME.BAT
|
||||
; (Listing 3.4) does this, along with all assembly
|
||||
; and linking.)
|
||||
;
|
||||
jmp Skip ;jump around defined data
|
||||
;
|
||||
MemVar db ?
|
||||
;
|
||||
Skip:
|
||||
;
|
||||
; Start timing.
|
||||
;
|
||||
call ZTimerOn
|
||||
;
|
||||
rept 1000
|
||||
mov al,[MemVar]
|
||||
endm
|
||||
;
|
||||
; Stop timing.
|
||||
;
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Test file;
|
||||
; Measures the performance of 1,000 loads of AL from
|
||||
; memory. (Use by renaming to TESTCODE, which is
|
||||
; included by PZTEST.ASM (Listing 3.2). PZTIME.BAT
|
||||
; (Listing 3.4) does this, along with all assembly
|
||||
; and linking.)
|
||||
;
|
||||
jmp Skip ;jump around defined data
|
||||
;
|
||||
MemVar db ?
|
||||
;
|
||||
Skip:
|
||||
;
|
||||
; Start timing.
|
||||
;
|
||||
call ZTimerOn
|
||||
;
|
||||
rept 1000
|
||||
mov al,[MemVar]
|
||||
endm
|
||||
;
|
||||
; Stop timing.
|
||||
;
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
132
03-06.md
132
03-06.md
|
|
@ -25,72 +25,76 @@ and "link" with "tlink" in Listing 3.4. The same is true of Listing
|
|||
|
||||
**LISTING 3.4 PZTIME.BAT**
|
||||
|
||||
echo off
|
||||
rem
|
||||
rem *** Listing 3.4 ***
|
||||
rem
|
||||
rem ***************************************************************
|
||||
rem * Batch file PZTIME.BAT, which builds and runs the precision *
|
||||
rem * Zen timer program PZTEST.EXE to time the code named as the *
|
||||
rem * command-line parameter. Listing 3.1 must be named *
|
||||
rem * PZTIMER.ASM, and Listing 3.2 must be named PZTEST.ASM. To *
|
||||
rem * time the code in LST3-3, you'd type the DOS command: *
|
||||
rem * *
|
||||
rem * pztime lst3-3 *
|
||||
rem * *
|
||||
rem * Note that MASM and LINK must be in the current directory or *
|
||||
rem * on the current path in order for this batch file to work. *
|
||||
rem * *
|
||||
rem * This batch file can be speeded up by assembling PZTIMER.ASM *
|
||||
rem * once, then removing the lines: *
|
||||
rem * *
|
||||
rem * masm pztimer; *
|
||||
rem * if errorlevel 1 goto errorend *
|
||||
rem * *
|
||||
rem * from this file. *
|
||||
rem * *
|
||||
rem * By Michael Abrash *
|
||||
rem ***************************************************************
|
||||
rem
|
||||
rem Make sure a file to test was specified.
|
||||
rem
|
||||
if not x%1==x goto ckexist
|
||||
echo ***************************************************************
|
||||
echo * Please specify a file to test. *
|
||||
echo ***************************************************************
|
||||
goto end
|
||||
rem
|
||||
rem Make sure the file exists.
|
||||
rem
|
||||
:ckexist
|
||||
if exist %1 goto docopy
|
||||
echo ***************************************************************
|
||||
echo * The specified file, "%1," doesn't exist. *
|
||||
echo ***************************************************************
|
||||
goto end
|
||||
rem
|
||||
rem copy the file to measure to TESTCODE.
|
||||
rem
|
||||
:docopy
|
||||
copy %1 testcode
|
||||
masm pztest;
|
||||
if errorlevel 1 goto errorend
|
||||
masm pztimer;
|
||||
if errorlevel 1 goto errorend
|
||||
link pztest+pztimer;
|
||||
if errorlevel 1 goto errorend
|
||||
pztest
|
||||
goto end
|
||||
:errorend
|
||||
echo ***************************************************************
|
||||
echo * An error occurred while building the precision Zen timer. *
|
||||
echo ***************************************************************
|
||||
:end
|
||||
```bat
|
||||
echo off
|
||||
rem
|
||||
rem *** Listing 3.4 ***
|
||||
rem
|
||||
rem ***************************************************************
|
||||
rem * Batch file PZTIME.BAT, which builds and runs the precision *
|
||||
rem * Zen timer program PZTEST.EXE to time the code named as the *
|
||||
rem * command-line parameter. Listing 3.1 must be named *
|
||||
rem * PZTIMER.ASM, and Listing 3.2 must be named PZTEST.ASM. To *
|
||||
rem * time the code in LST3-3, you'd type the DOS command: *
|
||||
rem * *
|
||||
rem * pztime lst3-3 *
|
||||
rem * *
|
||||
rem * Note that MASM and LINK must be in the current directory or *
|
||||
rem * on the current path in order for this batch file to work. *
|
||||
rem * *
|
||||
rem * This batch file can be speeded up by assembling PZTIMER.ASM *
|
||||
rem * once, then removing the lines: *
|
||||
rem * *
|
||||
rem * masm pztimer; *
|
||||
rem * if errorlevel 1 goto errorend *
|
||||
rem * *
|
||||
rem * from this file. *
|
||||
rem * *
|
||||
rem * By Michael Abrash *
|
||||
rem ***************************************************************
|
||||
rem
|
||||
rem Make sure a file to test was specified.
|
||||
rem
|
||||
if not x%1==x goto ckexist
|
||||
echo ***************************************************************
|
||||
echo * Please specify a file to test. *
|
||||
echo ***************************************************************
|
||||
goto end
|
||||
rem
|
||||
rem Make sure the file exists.
|
||||
rem
|
||||
:ckexist
|
||||
if exist %1 goto docopy
|
||||
echo ***************************************************************
|
||||
echo * The specified file, "%1," doesn't exist. *
|
||||
echo ***************************************************************
|
||||
goto end
|
||||
rem
|
||||
rem copy the file to measure to TESTCODE.
|
||||
rem
|
||||
:docopy
|
||||
copy %1 testcode
|
||||
masm pztest;
|
||||
if errorlevel 1 goto errorend
|
||||
masm pztimer;
|
||||
if errorlevel 1 goto errorend
|
||||
link pztest+pztimer;
|
||||
if errorlevel 1 goto errorend
|
||||
pztest
|
||||
goto end
|
||||
:errorend
|
||||
echo ***************************************************************
|
||||
echo * An error occurred while building the precision Zen timer. *
|
||||
echo ***************************************************************
|
||||
:end
|
||||
```
|
||||
|
||||
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 LST3-3.ASM
|
||||
```sh
|
||||
pztime LST3-3.ASM
|
||||
```
|
||||
|
||||
which performs all assembly and linking, and reports the execution time
|
||||
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
|
||||
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.
|
||||
Code fragments you write yourself can be timed in just the same way. If
|
||||
|
|
|
|||
118
03-08.md
118
03-08.md
|
|
@ -70,64 +70,66 @@ timing.
|
|||
|
||||
**LISTING 3.6 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
|
||||
; 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
|
||||
;
|
||||
mystack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
mystack ends
|
||||
;
|
||||
Code segment para public ‘CODE'
|
||||
assume cs:Code, ds:Code
|
||||
extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near
|
||||
Startproc near
|
||||
push cs
|
||||
pop ds ;point DS to the code segment,
|
||||
; 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.
|
||||
;
|
||||
mov ah,2ch
|
||||
int 21h ;get the current time
|
||||
mov bh,dh ;set the current time aside
|
||||
DelayLoop:
|
||||
mov ah,2ch
|
||||
push bx ;preserve start time
|
||||
int 21h ;get time
|
||||
pop bx ;retrieve start time
|
||||
cmp dh,bh ;is the new seconds count less than
|
||||
; the start seconds count?
|
||||
jnb CheckDelayTime ;no
|
||||
add dh,60 ;yes, a minute must have turned over,
|
||||
; so add one minute
|
||||
CheckDelayTime:
|
||||
sub dh,bh ;get time that's passed
|
||||
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
|
||||
;
|
||||
; Display the results.
|
||||
;
|
||||
call ZTimerReport
|
||||
;
|
||||
; Terminate the program.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Start endp
|
||||
Code ends
|
||||
end Start
|
||||
```nasm
|
||||
; 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
|
||||
; 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
|
||||
;
|
||||
mystack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
mystack ends
|
||||
;
|
||||
Code segment para public ‘CODE'
|
||||
assume cs:Code, ds:Code
|
||||
extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near
|
||||
Startproc near
|
||||
push cs
|
||||
pop ds ;point DS to the code segment,
|
||||
; 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.
|
||||
;
|
||||
mov ah,2ch
|
||||
int 21h ;get the current time
|
||||
mov bh,dh ;set the current time aside
|
||||
DelayLoop:
|
||||
mov ah,2ch
|
||||
push bx ;preserve start time
|
||||
int 21h ;get time
|
||||
pop bx ;retrieve start time
|
||||
cmp dh,bh ;is the new seconds count less than
|
||||
; the start seconds count?
|
||||
jnb CheckDelayTime ;no
|
||||
add dh,60 ;yes, a minute must have turned over,
|
||||
; so add one minute
|
||||
CheckDelayTime:
|
||||
sub dh,bh ;get time that's passed
|
||||
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
|
||||
;
|
||||
; Display the results.
|
||||
;
|
||||
call ZTimerReport
|
||||
;
|
||||
; Terminate the program.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Start endp
|
||||
Code ends
|
||||
end Start
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
188
03-09.md
188
03-09.md
|
|
@ -12,67 +12,69 @@ pages: 067-070
|
|||
|
||||
**LISTING 3.7 LZTIME.BAT**
|
||||
|
||||
echo off
|
||||
rem
|
||||
rem *** Listing 3.7 ***
|
||||
rem
|
||||
rem ***************************************************************
|
||||
rem * Batch file LZTIME.BAT, which builds and runs the *
|
||||
rem * long-period Zen timer program LZTEST.EXE to time the code *
|
||||
rem * named as the command-line parameter. Listing 3.5 must be *
|
||||
rem * named LZTIMER.ASM, and Listing 3.6 must be named *
|
||||
rem * LZTEST.ASM. To time the code in LST3-8, you'd type the *
|
||||
rem * DOS command: *
|
||||
rem * *
|
||||
rem * lztime lst3-8 *
|
||||
rem * *
|
||||
rem * Note that MASM and LINK must be in the current directory or *
|
||||
rem * on the current path in order for this batch file to work. *
|
||||
rem * *
|
||||
rem * This batch file can be speeded up by assembling LZTIMER.ASM *
|
||||
rem * once, then removing the lines: *
|
||||
rem * *
|
||||
rem * masm lztimer; *
|
||||
rem * if errorlevel 1 goto errorend *
|
||||
rem * *
|
||||
rem * from this file. *
|
||||
rem * *
|
||||
rem * By Michael Abrash *
|
||||
rem ***************************************************************
|
||||
rem
|
||||
rem Make sure a file to test was specified.
|
||||
rem
|
||||
if not x%1==x goto ckexist
|
||||
echo ***************************************************************
|
||||
echo * Please specify a file to test. *
|
||||
echo ***************************************************************
|
||||
goto end
|
||||
rem
|
||||
rem Make sure the file exists.
|
||||
rem
|
||||
:ckexist
|
||||
if exist %1 goto docopy
|
||||
echo ***************************************************************
|
||||
echo * The specified file, "%1," doesn't exist. *
|
||||
echo ***************************************************************
|
||||
goto end
|
||||
rem
|
||||
rem copy the file to measure to TESTCODE.
|
||||
:docopy
|
||||
copy %1 testcode
|
||||
masm lztest;
|
||||
if errorlevel 1 goto errorend
|
||||
masm lztimer;
|
||||
if errorlevel 1 goto errorend
|
||||
link lztest+lztimer;
|
||||
if errorlevel 1 goto errorend
|
||||
lztest
|
||||
goto end
|
||||
:errorend
|
||||
echo ***************************************************************
|
||||
echo * An error occurred while building the long-period Zen timer. *
|
||||
echo ***************************************************************
|
||||
:end
|
||||
```bat
|
||||
echo off
|
||||
rem
|
||||
rem *** Listing 3.7 ***
|
||||
rem
|
||||
rem ***************************************************************
|
||||
rem * Batch file LZTIME.BAT, which builds and runs the *
|
||||
rem * long-period Zen timer program LZTEST.EXE to time the code *
|
||||
rem * named as the command-line parameter. Listing 3.5 must be *
|
||||
rem * named LZTIMER.ASM, and Listing 3.6 must be named *
|
||||
rem * LZTEST.ASM. To time the code in LST3-8, you'd type the *
|
||||
rem * DOS command: *
|
||||
rem * *
|
||||
rem * lztime lst3-8 *
|
||||
rem * *
|
||||
rem * Note that MASM and LINK must be in the current directory or *
|
||||
rem * on the current path in order for this batch file to work. *
|
||||
rem * *
|
||||
rem * This batch file can be speeded up by assembling LZTIMER.ASM *
|
||||
rem * once, then removing the lines: *
|
||||
rem * *
|
||||
rem * masm lztimer; *
|
||||
rem * if errorlevel 1 goto errorend *
|
||||
rem * *
|
||||
rem * from this file. *
|
||||
rem * *
|
||||
rem * By Michael Abrash *
|
||||
rem ***************************************************************
|
||||
rem
|
||||
rem Make sure a file to test was specified.
|
||||
rem
|
||||
if not x%1==x goto ckexist
|
||||
echo ***************************************************************
|
||||
echo * Please specify a file to test. *
|
||||
echo ***************************************************************
|
||||
goto end
|
||||
rem
|
||||
rem Make sure the file exists.
|
||||
rem
|
||||
:ckexist
|
||||
if exist %1 goto docopy
|
||||
echo ***************************************************************
|
||||
echo * The specified file, "%1," doesn't exist. *
|
||||
echo ***************************************************************
|
||||
goto end
|
||||
rem
|
||||
rem copy the file to measure to TESTCODE.
|
||||
:docopy
|
||||
copy %1 testcode
|
||||
masm lztest;
|
||||
if errorlevel 1 goto errorend
|
||||
masm lztimer;
|
||||
if errorlevel 1 goto errorend
|
||||
link lztest+lztimer;
|
||||
if errorlevel 1 goto errorend
|
||||
lztest
|
||||
goto end
|
||||
:errorend
|
||||
echo ***************************************************************
|
||||
echo * An error occurred while building the long-period Zen timer. *
|
||||
echo ***************************************************************
|
||||
:end
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -81,38 +83,42 @@ the precision Zen timer to handle on the 8088.
|
|||
|
||||
**LISTING 3.8 LST3-8.ASM**
|
||||
|
||||
;
|
||||
; Measures the performance of 20,000 loads of AL from
|
||||
; memory. (Use by renaming to TESTCODE, which is
|
||||
; included by LZTEST.ASM (Listing 3.6). LZTIME.BAT
|
||||
; (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
|
||||
;
|
||||
jmpSkip;jump around defined data
|
||||
;
|
||||
MemVardb?
|
||||
;
|
||||
Skip:
|
||||
;
|
||||
; Start timing.
|
||||
;
|
||||
callZTimerOn
|
||||
;
|
||||
rept20000
|
||||
moval,[MemVar]
|
||||
endm
|
||||
;
|
||||
; Stop timing.
|
||||
;
|
||||
callZTimerOff
|
||||
```nasm
|
||||
;
|
||||
; Measures the performance of 20,000 loads of AL from
|
||||
; memory. (Use by renaming to TESTCODE, which is
|
||||
; included by LZTEST.ASM (Listing 3.6). LZTIME.BAT
|
||||
; (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
|
||||
;
|
||||
jmpSkip;jump around defined data
|
||||
;
|
||||
MemVardb?
|
||||
;
|
||||
Skip:
|
||||
;
|
||||
; Start timing.
|
||||
;
|
||||
callZTimerOn
|
||||
;
|
||||
rept20000
|
||||
moval,[MemVar]
|
||||
endm
|
||||
;
|
||||
; Stop timing.
|
||||
;
|
||||
callZTimerOff
|
||||
```
|
||||
|
||||
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)
|
||||
|
||||
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.
|
||||
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
|
||||
segment. (In C++, use the "C" specifier, as in
|
||||
|
||||
extern "C" ZTimerOn(void);
|
||||
```c
|
||||
extern "C" ZTimerOn(void);
|
||||
```
|
||||
|
|
|
|||
22
03-10.md
22
03-10.md
|
|
@ -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
|
||||
timer from C, as, for example, in:
|
||||
|
||||
ZTimerOn():
|
||||
for (i=0, x=0; i<100; i++)
|
||||
x += i;
|
||||
ZTimerOff();
|
||||
ZTimerReport();
|
||||
```c
|
||||
ZTimerOn():
|
||||
for (i=0, x=0; i<100; i++)
|
||||
x += i;
|
||||
ZTimerOff();
|
||||
ZTimerReport();
|
||||
```
|
||||
|
||||
(I'm talking about the precision timer here. The long-period
|
||||
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
|
||||
replaces
|
||||
|
||||
call far ptr ReferenceZTimerOn
|
||||
```nasm
|
||||
call far ptr ReferenceZTimerOn
|
||||
```
|
||||
|
||||
with
|
||||
|
||||
push cs
|
||||
call near ptr ReferenceZTimerOn
|
||||
```nasm
|
||||
push cs
|
||||
call near ptr ReferenceZTimerOn
|
||||
```
|
||||
|
||||
(and likewise for **ReferenceZTimerOff** ), which works because
|
||||
**ReferenceZTimerOn** is in the same segment as the calling code. This
|
||||
|
|
|
|||
72
04-02.md
72
04-02.md
|
|
@ -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
|
||||
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
|
||||
|
||||
mov al,byte ptr [MemVar]
|
||||
```nasm
|
||||
mov al,byte ptr [MemVar]
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
penalties. As a result
|
||||
|
||||
add word ptr [MemVar],ax
|
||||
```nasm
|
||||
add word ptr [MemVar],ax
|
||||
```
|
||||
|
||||
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
|
||||
greater extent than other instructions. Believe it or not, a single
|
||||
|
|
@ -114,35 +122,39 @@ in all.
|
|||
|
||||
**LISTING 4.1 LST4-1.ASM**
|
||||
|
||||
; Measures the performance of a loop which uses a
|
||||
; byte-sized memory variable as the loop counter.
|
||||
;
|
||||
jmp Skip
|
||||
;
|
||||
Counter db 100
|
||||
;
|
||||
Skip:
|
||||
call ZTimerOn
|
||||
LoopTop:
|
||||
dec [Counter]
|
||||
jnz LoopTop
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Measures the performance of a loop which uses a
|
||||
; byte-sized memory variable as the loop counter.
|
||||
;
|
||||
jmp Skip
|
||||
;
|
||||
Counter db 100
|
||||
;
|
||||
Skip:
|
||||
call ZTimerOn
|
||||
LoopTop:
|
||||
dec [Counter]
|
||||
jnz LoopTop
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
**LISTING 4.2 LST4-2.ASM**
|
||||
|
||||
; Measures the performance of a loop which uses a
|
||||
; word-sized memory variable as the loop counter.
|
||||
;
|
||||
jmp Skip
|
||||
;
|
||||
Counter dw 100
|
||||
;
|
||||
Skip:
|
||||
call ZTimerOn
|
||||
LoopTop:
|
||||
dec [Counter]
|
||||
jnz LoopTop
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Measures the performance of a loop which uses a
|
||||
; word-sized memory variable as the loop counter.
|
||||
;
|
||||
jmp Skip
|
||||
;
|
||||
Counter dw 100
|
||||
;
|
||||
Skip:
|
||||
call ZTimerOn
|
||||
LoopTop:
|
||||
dec [Counter]
|
||||
jnz LoopTop
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
62
04-03.md
62
04-03.md
|
|
@ -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
|
||||
memory access, as, for instance,
|
||||
|
||||
mov dl,byte ptr [MemVar]
|
||||
mov dh,byte ptr [MemVar+1]
|
||||
```nasm
|
||||
mov dl,byte ptr [MemVar]
|
||||
mov dh,byte ptr [MemVar+1]
|
||||
```
|
||||
|
||||
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
|
||||
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**
|
||||
|
||||
; Measures the performance of reading 1,000 words
|
||||
; from memory with 1,000 word-sized accesses.
|
||||
;
|
||||
sub si,si
|
||||
mov cx,1000
|
||||
call ZTimerOn
|
||||
rep lodsw
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Measures the performance of reading 1,000 words
|
||||
; from memory with 1,000 word-sized accesses.
|
||||
;
|
||||
sub si,si
|
||||
mov cx,1000
|
||||
call ZTimerOn
|
||||
rep lodsw
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
**LISTING 4.4 LST4-4.ASM**
|
||||
|
||||
; Measures the performance of reading 1000 words
|
||||
; from memory with 2,000 byte-sized accesses.
|
||||
;
|
||||
sub si,si
|
||||
mov cx,2000
|
||||
call ZTimerOn
|
||||
rep lodsb
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Measures the performance of reading 1000 words
|
||||
; from memory with 2,000 byte-sized accesses.
|
||||
;
|
||||
sub si,si
|
||||
mov cx,2000
|
||||
call ZTimerOn
|
||||
rep lodsb
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
access, such as
|
||||
|
||||
mov [bx],ax
|
||||
```nasm
|
||||
mov [bx],ax
|
||||
```
|
||||
|
||||
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
|
||||
cycle-eater lurking nearby. For instance, the instructions
|
||||
|
||||
shr ax,1
|
||||
shr ax,1
|
||||
shr ax,1
|
||||
shr ax,1
|
||||
shr ax,1
|
||||
```nasm
|
||||
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
|
||||
execute, according to Intel's specifications. Those specifications
|
||||
|
|
|
|||
50
04-04.md
50
04-04.md
|
|
@ -104,32 +104,36 @@ that the "true" execution time of **SHR** is 8.64 cycles.
|
|||
|
||||
**LISTING 4.5 LST4-5.ASM**
|
||||
|
||||
; Measures the performance of 1,000 SHR instructions
|
||||
; in a row. Since SHR executes in 2 cycles but is
|
||||
; 2 bytes long, the prefetch queue is always empty,
|
||||
; and prefetching time determines the overall
|
||||
; performance of the code.
|
||||
;
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
shr ax,1
|
||||
endm
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Measures the performance of 1,000 SHR instructions
|
||||
; in a row. Since SHR executes in 2 cycles but is
|
||||
; 2 bytes long, the prefetch queue is always empty,
|
||||
; and prefetching time determines the overall
|
||||
; performance of the code.
|
||||
;
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
shr ax,1
|
||||
endm
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
**LISTING 4.6 LST4-6.ASM**
|
||||
|
||||
; Measures the performance of 1,000 MUL/SHR instruction
|
||||
; pairs in a row. The lengthy execution time of MUL
|
||||
; should keep the prefetch queue from ever emptying.
|
||||
;
|
||||
mov cx,1000
|
||||
sub ax,ax
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
mul ax
|
||||
shr ax,1
|
||||
endm
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Measures the performance of 1,000 MUL/SHR instruction
|
||||
; pairs in a row. The lengthy execution time of MUL
|
||||
; should keep the prefetch queue from ever emptying.
|
||||
;
|
||||
mov cx,1000
|
||||
sub ax,ax
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
mul ax
|
||||
shr ax,1
|
||||
endm
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||

|
||||
|
|
|
|||
44
04-05.md
44
04-05.md
|
|
@ -43,29 +43,33 @@ Listing 4.6.*](images/04-04.jpg)
|
|||
|
||||
**LISTING 4.7 LST4-7.ASM**
|
||||
|
||||
; Measures the performance of repeated MOV AL,0 instructions,
|
||||
; which take 4 cycles each according to Intel's official
|
||||
; specifications.
|
||||
;
|
||||
sub ax,ax
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
mov al,0
|
||||
endm
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Measures the performance of repeated MOV AL,0 instructions,
|
||||
; which take 4 cycles each according to Intel's official
|
||||
; specifications.
|
||||
;
|
||||
sub ax,ax
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
mov al,0
|
||||
endm
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
**LISTING 4.8 LST4-8.ASM**
|
||||
|
||||
; Measures the performance of repeated SUB AL,AL instructions,
|
||||
; which take 3 cycles each according to Intel's official
|
||||
; specifications.
|
||||
;
|
||||
sub ax,ax
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
sub al,al
|
||||
endm
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Measures the performance of repeated SUB AL,AL instructions,
|
||||
; which take 3 cycles each according to Intel's official
|
||||
; specifications.
|
||||
;
|
||||
sub ax,ax
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
sub al,al
|
||||
endm
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
44
04-07.md
44
04-07.md
|
|
@ -28,17 +28,19 @@ request a memory access from the Bus Interface Unit.)
|
|||
|
||||
**LISTING 4.9 LST4-9.ASM**
|
||||
|
||||
; Measures the performance of repeated MUL instructions,
|
||||
; which allow the prefetch queue to be full at all times,
|
||||
; to demonstrate a case in which DRAM refresh has no impact
|
||||
; on code performance.
|
||||
;
|
||||
sub ax,ax
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
mul ax
|
||||
endm
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Measures the performance of repeated MUL instructions,
|
||||
; which allow the prefetch queue to be full at all times,
|
||||
; to demonstrate a case in which DRAM refresh has no impact
|
||||
; on code performance.
|
||||
;
|
||||
sub ax,ax
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
mul ax
|
||||
endm
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -56,15 +58,17 @@ to fetch the instruction bytes.
|
|||
|
||||
**LISTING 4.10 LST4-10.ASM**
|
||||
|
||||
; Measures the performance of repeated SHR instructions,
|
||||
; which empty the prefetch queue, to demonstrate the
|
||||
; worst-case impact of DRAM refresh on code performance.
|
||||
;
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
shr ax,1
|
||||
endm
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Measures the performance of repeated SHR instructions,
|
||||
; which empty the prefetch queue, to demonstrate the
|
||||
; worst-case impact of DRAM refresh on code performance.
|
||||
;
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
shr ax,1
|
||||
endm
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
96
04-09.md
96
04-09.md
|
|
@ -86,34 +86,36 @@ long as we had assumed, but a long time nonetheless.
|
|||
|
||||
**LISTING 4.11 LST4-11.ASM**
|
||||
|
||||
; 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
|
||||
; mode 10 hex (AH=0 selects
|
||||
; BIOS set mode function,
|
||||
; with AL=mode to select)
|
||||
;
|
||||
mov ax,0a000h
|
||||
mov ds,ax
|
||||
mov es,ax ;move to & from same segment
|
||||
sub si,si ;move to & from same offset
|
||||
mov di,si
|
||||
mov cx,800h ;move 2K words
|
||||
cld
|
||||
call ZTimerOn
|
||||
rep movsw ;simply read each of the first
|
||||
; 2K words of the destination segment,
|
||||
; writing each byte immediately back
|
||||
; to the same address. No memory
|
||||
; locations are actually altered; this
|
||||
; is just to measure memory access
|
||||
; times
|
||||
call ZTimerOff
|
||||
;
|
||||
mov ax,0003h
|
||||
int 10h ;return to text mode
|
||||
```nasm
|
||||
; 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
|
||||
; mode 10 hex (AH=0 selects
|
||||
; BIOS set mode function,
|
||||
; with AL=mode to select)
|
||||
;
|
||||
mov ax,0a000h
|
||||
mov ds,ax
|
||||
mov es,ax ;move to & from same segment
|
||||
sub si,si ;move to & from same offset
|
||||
mov di,si
|
||||
mov cx,800h ;move 2K words
|
||||
cld
|
||||
call ZTimerOn
|
||||
rep movsw ;simply read each of the first
|
||||
; 2K words of the destination segment,
|
||||
; writing each byte immediately back
|
||||
; to the same address. No memory
|
||||
; locations are actually altered; this
|
||||
; is just to measure memory access
|
||||
; times
|
||||
call ZTimerOff
|
||||
;
|
||||
mov ax,0003h
|
||||
int 10h ;return to text mode
|
||||
```
|
||||
|
||||
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,
|
||||
|
|
@ -125,24 +127,26 @@ cycle-eater can *more than double* the execution time of 8088 code!
|
|||
|
||||
**LISTING 4.12 LST4-12.ASM**
|
||||
|
||||
; Times speed of memory access to normal system
|
||||
; memory.
|
||||
;
|
||||
mov ax,ds
|
||||
mov es,ax ;move to & from same segment
|
||||
sub si,si ;move to & from same offset
|
||||
mov di,si
|
||||
mov cx,800h ;move 2K words
|
||||
cld
|
||||
call ZTimerOn
|
||||
rep movsw ;simply read each of the first
|
||||
; 2K words of the destination segment,
|
||||
; writing each byte immediately back
|
||||
; to the same address. No memory
|
||||
; locations are actually altered; this
|
||||
; is just to measure memory access
|
||||
; times
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
; Times speed of memory access to normal system
|
||||
; memory.
|
||||
;
|
||||
mov ax,ds
|
||||
mov es,ax ;move to & from same segment
|
||||
sub si,si ;move to & from same offset
|
||||
mov di,si
|
||||
mov cx,800h ;move 2K words
|
||||
cld
|
||||
call ZTimerOn
|
||||
rep movsw ;simply read each of the first
|
||||
; 2K words of the destination segment,
|
||||
; writing each byte immediately back
|
||||
; to the same address. No memory
|
||||
; locations are actually altered; this
|
||||
; is just to measure memory access
|
||||
; times
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
306
05-03.md
306
05-03.md
|
|
@ -12,168 +12,170 @@ pages: 118-121
|
|||
|
||||
**LISTING 5.1 SEARCH.C**
|
||||
|
||||
/* Program to search the file specified by the first command-line
|
||||
* argument for the string specified by the second command-line
|
||||
* argument. Performs the search by reading and searching blocks
|
||||
* of size BLOCK_SIZE. */
|
||||
```c
|
||||
/* Program to search the file specified by the first command-line
|
||||
* argument for the string specified by the second command-line
|
||||
* argument. Performs the search by reading and searching blocks
|
||||
* of size BLOCK_SIZE. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <alloc.h> /* alloc.h for Borland compilers,
|
||||
malloc.h for Microsoft compilers */
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <alloc.h> /* alloc.h for Borland 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
|
||||
buffer for matches to SearchString of SearchStringLength. Note
|
||||
that the calling code should already have shortened SearchLength
|
||||
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.
|
||||
*/
|
||||
/* Searches the specified number of sequences in the specified
|
||||
buffer for matches to SearchString of SearchStringLength. Note
|
||||
that the calling code should already have shortened SearchLength
|
||||
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.
|
||||
*/
|
||||
|
||||
int SearchForString(unsigned char *Buffer, int SearchLength,
|
||||
unsigned char *SearchString, int SearchStringLength)
|
||||
{
|
||||
unsigned char *PotentialMatch;
|
||||
int SearchForString(unsigned char *Buffer, int SearchLength,
|
||||
unsigned char *SearchString, int SearchStringLength)
|
||||
{
|
||||
unsigned char *PotentialMatch;
|
||||
|
||||
/* Search so long as there are potential-match locations
|
||||
remaining */
|
||||
while ( SearchLength ) {
|
||||
/* See if the first character of SearchString can be found */
|
||||
if ( (PotentialMatch =
|
||||
memchr(Buffer, *SearchString, SearchLength)) == NULL ) {
|
||||
break; /* No matches in this buffer */
|
||||
/* Search so long as there are potential-match locations
|
||||
remaining */
|
||||
while ( SearchLength ) {
|
||||
/* See if the first character of SearchString can be found */
|
||||
if ( (PotentialMatch =
|
||||
memchr(Buffer, *SearchString, SearchLength)) == NULL ) {
|
||||
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 */
|
||||
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 string doesn't match; keep going by pointing past the
|
||||
potential match location we just rejected */
|
||||
SearchLength -= PotentialMatch - Buffer + 1;
|
||||
Buffer = PotentialMatch + 1;
|
||||
}
|
||||
}
|
||||
/* 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[]) {
|
||||
int Done; /* Indicates whether search is done */
|
||||
int Handle; /* Handle of file being searched */
|
||||
int WorkingLength; /* Length of current block */
|
||||
int SearchStringLength; /* Length of string to search for */
|
||||
int BlockSearchLength; /* Length to search in current block */
|
||||
int Found; /* Indicates final search completion
|
||||
status */
|
||||
int NextLoadCount; /* # of bytes to read into next block,
|
||||
accounting for bytes copied from the
|
||||
last block */
|
||||
unsigned char *WorkingBlock; /* Block storage buffer */
|
||||
unsigned char *SearchString; /* Pointer to the string to search for */
|
||||
unsigned char *NextLoadPtr; /* Offset at which to start loading
|
||||
the next block, accounting for
|
||||
bytes copied from the last block */
|
||||
main(int argc, char *argv[]) {
|
||||
int Done; /* Indicates whether search is done */
|
||||
int Handle; /* Handle of file being searched */
|
||||
int WorkingLength; /* Length of current block */
|
||||
int SearchStringLength; /* Length of string to search for */
|
||||
int BlockSearchLength; /* Length to search in current block */
|
||||
int Found; /* Indicates final search completion
|
||||
status */
|
||||
int NextLoadCount; /* # of bytes to read into next block,
|
||||
accounting for bytes copied from the
|
||||
last block */
|
||||
unsigned char *WorkingBlock; /* Block storage buffer */
|
||||
unsigned char *SearchString; /* Pointer to the string to search for */
|
||||
unsigned char *NextLoadPtr; /* Offset at which to start loading
|
||||
the next block, accounting for
|
||||
bytes copied from the last block */
|
||||
|
||||
/* Check for the proper number of arguments */
|
||||
if ( argc != 3 ) {
|
||||
printf("usage: search filename search-string\n");
|
||||
exit(1);
|
||||
}
|
||||
/* Check for the proper number of arguments */
|
||||
if ( argc != 3 ) {
|
||||
printf("usage: search filename search-string\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Try to open the file to be searched */
|
||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
/* Calculate the length of text to search for */
|
||||
SearchString = argv[2];
|
||||
SearchStringLength = strlen(SearchString);
|
||||
/* Try to get memory in which to buffer the data */
|
||||
if ( (WorkingBlock = malloc(BLOCK_SIZE)) == NULL ) {
|
||||
printf("Can't get enough memory\n");
|
||||
exit(1);
|
||||
}
|
||||
/* Try to open the file to be searched */
|
||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
/* Calculate the length of text to search for */
|
||||
SearchString = argv[2];
|
||||
SearchStringLength = strlen(SearchString);
|
||||
/* Try to get memory in which to buffer the data */
|
||||
if ( (WorkingBlock = malloc(BLOCK_SIZE)) == NULL ) {
|
||||
printf("Can't get enough memory\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Load the first block at the start of the buffer, and try to
|
||||
fill the entire buffer */
|
||||
NextLoadPtr = WorkingBlock;
|
||||
NextLoadCount = BLOCK_SIZE;
|
||||
Done = 0; /* Not done with search yet */
|
||||
Found = 0; /* Assume we won't find a match */
|
||||
/* Search the file in BLOCK_SIZE chunks */
|
||||
do {
|
||||
/* Read in however many bytes are needed to fill out the block
|
||||
(accounting for bytes copied over from the last block), or
|
||||
the rest of the bytes in the file, whichever is less */
|
||||
if ( (WorkingLength = read(Handle, NextLoadPtr,
|
||||
NextLoadCount)) == -1 ) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
/* If we didn't read all the bytes we requested, we're done
|
||||
after this block, whether we find a match or not */
|
||||
if ( WorkingLength != NextLoadCount ) {
|
||||
Done = 1;
|
||||
}
|
||||
/* Load the first block at the start of the buffer, and try to
|
||||
fill the entire buffer */
|
||||
NextLoadPtr = WorkingBlock;
|
||||
NextLoadCount = BLOCK_SIZE;
|
||||
Done = 0; /* Not done with search yet */
|
||||
Found = 0; /* Assume we won't find a match */
|
||||
/* Search the file in BLOCK_SIZE chunks */
|
||||
do {
|
||||
/* Read in however many bytes are needed to fill out the block
|
||||
(accounting for bytes copied over from the last block), or
|
||||
the rest of the bytes in the file, whichever is less */
|
||||
if ( (WorkingLength = read(Handle, NextLoadPtr,
|
||||
NextLoadCount)) == -1 ) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
/* If we didn't read all the bytes we requested, we're done
|
||||
after this block, whether we find a match or not */
|
||||
if ( WorkingLength != NextLoadCount ) {
|
||||
Done = 1;
|
||||
}
|
||||
|
||||
/* Account for any bytes we copied from the end of the last
|
||||
block in the total length of this block */
|
||||
WorkingLength += NextLoadPtr - WorkingBlock;
|
||||
/* Calculate the number of bytes in this block that could
|
||||
possibly be the start of a matching sequence that lies
|
||||
entirely in this block (sequences that run off the end of
|
||||
the block will be transferred to the next block and found
|
||||
when that block is searched)
|
||||
*/
|
||||
if ( (BlockSearchLength =
|
||||
WorkingLength - SearchStringLength + 1) <= 0 ) {
|
||||
Done = 1; /* Too few characters in this block for
|
||||
there to be any possible matches, so this
|
||||
is the final block and we're done without
|
||||
finding a match
|
||||
*/
|
||||
}
|
||||
else {
|
||||
/* Search this block */
|
||||
if ( SearchForString(WorkingBlock, BlockSearchLength,
|
||||
SearchString, SearchStringLength) ) {
|
||||
Found = 1; /* We've found a match */
|
||||
Done = 1;
|
||||
}
|
||||
else {
|
||||
/* Copy any bytes from the end of the block that start
|
||||
potentially-matching sequences that would run off
|
||||
the end of the block over to the next block */
|
||||
if ( SearchStringLength > 1 ) {
|
||||
memcpy(WorkingBlock,
|
||||
WorkingBlock+BLOCK_SIZE - SearchStringLength + 1,
|
||||
SearchStringLength - 1);
|
||||
}
|
||||
/* Set up to load the next bytes from the file after the
|
||||
bytes copied from the end of the current block */
|
||||
NextLoadPtr = WorkingBlock + SearchStringLength - 1;
|
||||
NextLoadCount = BLOCK_SIZE - SearchStringLength + 1;
|
||||
}
|
||||
}
|
||||
} while ( !Done );
|
||||
/* Account for any bytes we copied from the end of the last
|
||||
block in the total length of this block */
|
||||
WorkingLength += NextLoadPtr - WorkingBlock;
|
||||
/* Calculate the number of bytes in this block that could
|
||||
possibly be the start of a matching sequence that lies
|
||||
entirely in this block (sequences that run off the end of
|
||||
the block will be transferred to the next block and found
|
||||
when that block is searched)
|
||||
*/
|
||||
if ( (BlockSearchLength =
|
||||
WorkingLength - SearchStringLength + 1) <= 0 ) {
|
||||
Done = 1; /* Too few characters in this block for
|
||||
there to be any possible matches, so this
|
||||
is the final block and we're done without
|
||||
finding a match
|
||||
*/
|
||||
}
|
||||
else {
|
||||
/* Search this block */
|
||||
if ( SearchForString(WorkingBlock, BlockSearchLength,
|
||||
SearchString, SearchStringLength) ) {
|
||||
Found = 1; /* We've found a match */
|
||||
Done = 1;
|
||||
}
|
||||
else {
|
||||
/* Copy any bytes from the end of the block that start
|
||||
potentially-matching sequences that would run off
|
||||
the end of the block over to the next block */
|
||||
if ( SearchStringLength > 1 ) {
|
||||
memcpy(WorkingBlock,
|
||||
WorkingBlock+BLOCK_SIZE - SearchStringLength + 1,
|
||||
SearchStringLength - 1);
|
||||
}
|
||||
/* Set up to load the next bytes from the file after the
|
||||
bytes copied from the end of the current block */
|
||||
NextLoadPtr = WorkingBlock + SearchStringLength - 1;
|
||||
NextLoadCount = BLOCK_SIZE - SearchStringLength + 1;
|
||||
}
|
||||
}
|
||||
} while ( !Done );
|
||||
|
||||
/* Report the results */
|
||||
if ( Found ) {
|
||||
printf("String found\n");
|
||||
} else {
|
||||
printf("String not found\n");
|
||||
}
|
||||
exit(Found); /* Return the found/not found status as the
|
||||
DOS errorlevel */
|
||||
}
|
||||
/* Report the results */
|
||||
if ( Found ) {
|
||||
printf("String found\n");
|
||||
} else {
|
||||
printf("String not found\n");
|
||||
}
|
||||
exit(Found); /* Return the found/not found status as the
|
||||
DOS errorlevel */
|
||||
}
|
||||
```
|
||||
|
|
|
|||
10
06-01.md
10
06-01.md
|
|
@ -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
|
||||
address memory, like this:
|
||||
|
||||
add bx,si
|
||||
mov al,[bx]
|
||||
```nasm
|
||||
add bx,si
|
||||
mov al,[bx]
|
||||
```
|
||||
|
||||
Or you could let the processor do the arithmetic for you in a single
|
||||
instruction:
|
||||
|
||||
mov al,[bx+si]
|
||||
```nasm
|
||||
mov al,[bx+si]
|
||||
```
|
||||
|
|
|
|||
60
06-02.md
60
06-02.md
|
|
@ -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
|
||||
calculation time inside the loop, as in the following:
|
||||
|
||||
add bx,si
|
||||
LoopTop:
|
||||
mov al,[bx]
|
||||
inc bx
|
||||
loop LoopTop
|
||||
```nasm
|
||||
add bx,si
|
||||
LoopTop:
|
||||
mov al,[bx]
|
||||
inc bx
|
||||
loop LoopTop
|
||||
```
|
||||
|
||||
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
|
||||
the result in AX. The obvious solution is this:
|
||||
|
||||
mov ax,bx
|
||||
add ax,di
|
||||
add ax,2
|
||||
```nasm
|
||||
mov ax,bx
|
||||
add ax,di
|
||||
add ax,2
|
||||
```
|
||||
|
||||
(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
|
||||
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
|
||||
|
||||
mov di,si
|
||||
add di,2
|
||||
```nasm
|
||||
mov di,si
|
||||
add di,2
|
||||
```
|
||||
|
||||
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,
|
||||
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
|
||||
single instruction. For example,
|
||||
|
||||
lea edi,TableBase[ecx+edx*4]
|
||||
```nasm
|
||||
lea edi,TableBase[ecx+edx*4]
|
||||
```
|
||||
|
||||
replaces all this
|
||||
|
||||
mov edi,edx
|
||||
shl edi,2
|
||||
add edi,ecx
|
||||
add edi,offset TableBase
|
||||
```nasm
|
||||
mov edi,edx
|
||||
shl edi,2
|
||||
add edi,ecx
|
||||
add edi,offset TableBase
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
either a relatively slow **MUL**, along with a set-up instruction or
|
||||
two, or three separate instructions along the lines of the following
|
||||
|
||||
mov edx,ebx
|
||||
shl ebx,2
|
||||
add ebx,edx
|
||||
```nasm
|
||||
mov edx,ebx
|
||||
shl ebx,2
|
||||
add ebx,edx
|
||||
```
|
||||
|
||||
and would in either case require the destruction of the contents of
|
||||
another register.
|
||||
|
|
|
|||
12
07-02.md
12
07-02.md
|
|
@ -13,13 +13,17 @@ pages: 139-141
|
|||
By the way, don't fall victim to the lures of **JCXZ** and do something
|
||||
like this:
|
||||
|
||||
and cx,ofh ;Isolate the desired field
|
||||
jcxz SkipLoop ;If field is 0, don't bother
|
||||
```nasm
|
||||
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
|
||||
|
||||
and cx,0fh ;Isolate the desired field
|
||||
jz SkipLoop ;If field is 0, don't bother
|
||||
```nasm
|
||||
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
|
||||
when the Zero flag isn't already set to reflect the status of CX.
|
||||
|
|
|
|||
170
07-03.md
170
07-03.md
|
|
@ -12,96 +12,98 @@ pages: 141-143
|
|||
|
||||
**LISTING 7.1 L7-1.ASM**
|
||||
|
||||
; Program to illustrate searching through a buffer of a specified
|
||||
; length until either a specified byte or a zero byte is
|
||||
; encountered.
|
||||
; A standard loop terminated with LOOP is used.
|
||||
```nasm
|
||||
; Program to illustrate searching through a buffer of a specified
|
||||
; length until either a specified byte or a zero byte is
|
||||
; encountered.
|
||||
; A standard loop terminated with LOOP is used.
|
||||
|
||||
.model small
|
||||
.stack 100h
|
||||
.data
|
||||
; Sample string to search through.
|
||||
SampleString labelbyte
|
||||
db ‘This is a sample string of a long enough length '
|
||||
db ‘so that raw searching speed can outweigh any '
|
||||
db ‘extra set-up time that may be required.',0
|
||||
SAMPLE_STRING_LENGTH equ $-SampleString
|
||||
.model small
|
||||
.stack 100h
|
||||
.data
|
||||
; Sample string to search through.
|
||||
SampleString labelbyte
|
||||
db ‘This is a sample string of a long enough length '
|
||||
db ‘so that raw searching speed can outweigh any '
|
||||
db ‘extra set-up time that may be required.',0
|
||||
SAMPLE_STRING_LENGTH equ $-SampleString
|
||||
|
||||
; User prompt.
|
||||
Prompt db ‘Enter character to search for:$'
|
||||
; User prompt.
|
||||
Prompt db ‘Enter character to search for:$'
|
||||
|
||||
; Result status messages.
|
||||
ByteFoundMsg db 0dh,0ah
|
||||
db ‘Specified byte found.',0dh,0ah,‘$'
|
||||
ZeroByteFoundMsg db 0dh, 0ah
|
||||
db ‘Zero byte encountered.',0dh,0ah,‘$'
|
||||
NoByteFoundMsg db 0dh,0ah
|
||||
db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$'
|
||||
; Result status messages.
|
||||
ByteFoundMsg db 0dh,0ah
|
||||
db ‘Specified byte found.',0dh,0ah,‘$'
|
||||
ZeroByteFoundMsg db 0dh, 0ah
|
||||
db ‘Zero byte encountered.',0dh,0ah,‘$'
|
||||
NoByteFoundMsg db 0dh,0ah
|
||||
db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$'
|
||||
|
||||
.code
|
||||
Startprocnear
|
||||
mov ax,@data ;point to standard data segment
|
||||
mov ds,ax
|
||||
mov dx,offset Prompt
|
||||
mov ah,9 ;DOS print string function
|
||||
int 21h ;prompt the user
|
||||
mov ah,1 ;DOS get key function
|
||||
int 21h ;get the key to search for
|
||||
mov ah,al ;put character to search for in AH
|
||||
mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search
|
||||
mov si,offset SampleString ;point to buffer to search
|
||||
call SearchMaxLength ;search the buffer
|
||||
mov dx,offset ByteFoundMsg ;assume we found the byte
|
||||
jc PrintStatus ;we did find the byte
|
||||
;we didn't find the byte, figure out
|
||||
;whether we found a zero byte or
|
||||
;ran out of buffer
|
||||
mov dx,offset NoByteFoundMsg
|
||||
;assume 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
|
||||
PrintStatus:
|
||||
mov ah,9 ;DOS print string function
|
||||
int 21h ;report status
|
||||
mov ah,4ch ;return to DOS
|
||||
int 21h
|
||||
Startendp
|
||||
.code
|
||||
Startprocnear
|
||||
mov ax,@data ;point to standard data segment
|
||||
mov ds,ax
|
||||
mov dx,offset Prompt
|
||||
mov ah,9 ;DOS print string function
|
||||
int 21h ;prompt the user
|
||||
mov ah,1 ;DOS get key function
|
||||
int 21h ;get the key to search for
|
||||
mov ah,al ;put character to search for in AH
|
||||
mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search
|
||||
mov si,offset SampleString ;point to buffer to search
|
||||
call SearchMaxLength ;search the buffer
|
||||
mov dx,offset ByteFoundMsg ;assume we found the byte
|
||||
jc PrintStatus ;we did find the byte
|
||||
;we didn't find the byte, figure out
|
||||
;whether we found a zero byte or
|
||||
;ran out of buffer
|
||||
mov dx,offset NoByteFoundMsg
|
||||
;assume 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
|
||||
PrintStatus:
|
||||
mov ah,9 ;DOS print string function
|
||||
int 21h ;report status
|
||||
mov ah,4ch ;return to DOS
|
||||
int 21h
|
||||
Startendp
|
||||
|
||||
; Function to search a buffer of a specified length until either a
|
||||
; specified byte or a zero byte is encountered.
|
||||
; Input:
|
||||
; AH = character to search for
|
||||
; CX = maximum length to be searched (must be > 0)
|
||||
; DS:SI = pointer to buffer to be searched
|
||||
; Output:
|
||||
; CX = 0 if and only if we ran out of bytes without finding
|
||||
; either the desired byte or a zero byte
|
||||
; DS:SI = pointer to searched-for byte if found, otherwise byte
|
||||
; after zero byte if found, otherwise byte after last
|
||||
; byte checked if neither searched-for byte nor zero
|
||||
; byte is found
|
||||
; Carry Flag = set if searched-for byte found, reset otherwise
|
||||
; Function to search a buffer of a specified length until either a
|
||||
; specified byte or a zero byte is encountered.
|
||||
; Input:
|
||||
; AH = character to search for
|
||||
; CX = maximum length to be searched (must be > 0)
|
||||
; DS:SI = pointer to buffer to be searched
|
||||
; Output:
|
||||
; CX = 0 if and only if we ran out of bytes without finding
|
||||
; either the desired byte or a zero byte
|
||||
; DS:SI = pointer to searched-for byte if found, otherwise byte
|
||||
; after zero byte if found, otherwise byte after last
|
||||
; byte checked if neither searched-for byte nor zero
|
||||
; byte is found
|
||||
; Carry Flag = set if searched-for byte found, reset otherwise
|
||||
|
||||
SearchMaxLengthprocnear
|
||||
cld
|
||||
SearchMaxLengthLoop:
|
||||
lodsb ;get the next byte
|
||||
cmp al,ah ;is this the byte we want?
|
||||
jz ByteFound ;yes, we're done with success
|
||||
and al,al ;is this the terminating 0 byte?
|
||||
jz ByteNotFound ;yes, we're done with failure
|
||||
loop SearchMaxLengthLoop ;it's neither, so check the next
|
||||
;byte, if any
|
||||
ByteNotFound:
|
||||
clc ;return "not found" status
|
||||
ret
|
||||
ByteFound:
|
||||
dec si ;point back to the location at which
|
||||
;we found the searched-for byte
|
||||
stc ;return "found" status
|
||||
ret
|
||||
SearchMaxLengthendp
|
||||
end Start
|
||||
SearchMaxLengthprocnear
|
||||
cld
|
||||
SearchMaxLengthLoop:
|
||||
lodsb ;get the next byte
|
||||
cmp al,ah ;is this the byte we want?
|
||||
jz ByteFound ;yes, we're done with success
|
||||
and al,al ;is this the terminating 0 byte?
|
||||
jz ByteNotFound ;yes, we're done with failure
|
||||
loop SearchMaxLengthLoop ;it's neither, so check the next
|
||||
;byte, if any
|
||||
ByteNotFound:
|
||||
clc ;return "not found" status
|
||||
ret
|
||||
ByteFound:
|
||||
dec si ;point back to the location at which
|
||||
;we found the searched-for byte
|
||||
stc ;return "found" status
|
||||
ret
|
||||
SearchMaxLengthendp
|
||||
end Start
|
||||
```
|
||||
|
||||
### Unrolling Loops {#Heading7}
|
||||
|
||||
|
|
|
|||
242
07-04.md
242
07-04.md
|
|
@ -12,134 +12,136 @@ pages: 143-145
|
|||
|
||||
**LISTING 7.2 L7-2.ASM**
|
||||
|
||||
; Program to illustrate searching through a buffer of a specified
|
||||
; length until a specified zero byte is encountered.
|
||||
; A loop unrolled four times and terminated with LOOP is used.
|
||||
```nasm
|
||||
; Program to illustrate searching through a buffer of a specified
|
||||
; length until a specified zero byte is encountered.
|
||||
; A loop unrolled four times and terminated with LOOP is used.
|
||||
|
||||
.model small
|
||||
.stack 100h
|
||||
.data
|
||||
; Sample string to search through.
|
||||
SampleStringlabelbyte
|
||||
db ‘This is a sample string of a long enough length '
|
||||
db ‘so that raw searching speed can outweigh any '
|
||||
db ‘extra set-up time that may be required.',0
|
||||
SAMPLE_STRING_LENGTH equ $-SampleString
|
||||
.model small
|
||||
.stack 100h
|
||||
.data
|
||||
; Sample string to search through.
|
||||
SampleStringlabelbyte
|
||||
db ‘This is a sample string of a long enough length '
|
||||
db ‘so that raw searching speed can outweigh any '
|
||||
db ‘extra set-up time that may be required.',0
|
||||
SAMPLE_STRING_LENGTH equ $-SampleString
|
||||
|
||||
; User prompt.
|
||||
Prompt db ‘Enter character to search for:$'
|
||||
; User prompt.
|
||||
Prompt db ‘Enter character to search for:$'
|
||||
|
||||
; Result status messages.
|
||||
ByteFoundMsg db 0dh,0ah
|
||||
db ‘Specified byte found.',0dh,0ah,‘$'
|
||||
ZeroByteFoundMsg db 0dh,0ah
|
||||
db ‘Zero byte encountered.', 0dh, 0ah, ‘$'
|
||||
NoByteFoundMsg db 0dh,0ah
|
||||
db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$'
|
||||
; Result status messages.
|
||||
ByteFoundMsg db 0dh,0ah
|
||||
db ‘Specified byte found.',0dh,0ah,‘$'
|
||||
ZeroByteFoundMsg db 0dh,0ah
|
||||
db ‘Zero byte encountered.', 0dh, 0ah, ‘$'
|
||||
NoByteFoundMsg db 0dh,0ah
|
||||
db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$'
|
||||
|
||||
; Table of initial, possibly partial loop entry points for
|
||||
; SearchMaxLength.
|
||||
SearchMaxLengthEntryTable labelword
|
||||
dw SearchMaxLengthEntry4
|
||||
dw SearchMaxLengthEntry1
|
||||
dw SearchMaxLengthEntry2
|
||||
dw SearchMaxLengthEntry3
|
||||
; Table of initial, possibly partial loop entry points for
|
||||
; SearchMaxLength.
|
||||
SearchMaxLengthEntryTable labelword
|
||||
dw SearchMaxLengthEntry4
|
||||
dw SearchMaxLengthEntry1
|
||||
dw SearchMaxLengthEntry2
|
||||
dw SearchMaxLengthEntry3
|
||||
|
||||
.code
|
||||
Start proc near
|
||||
mov ax,@data ;point to standard data segment
|
||||
mov ds,ax
|
||||
mov dx,offset Prompt
|
||||
mov ah,9 ;DOS print string function
|
||||
int 21h ;prompt the user
|
||||
mov ah,1 ;DOS get key function
|
||||
int 21h ;get the key to search for
|
||||
mov ah,al ;put character to search for in AH
|
||||
mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search
|
||||
mov si,offset SampleString ;point to buffer to search
|
||||
call SearchMaxLength ;search the buffer
|
||||
mov dx,offset ByteFoundMsg ;assume we found the byte
|
||||
jc PrintStatus ;we did find the byte
|
||||
;we didn't find the byte, figure out
|
||||
;whether we found a zero byte or
|
||||
;ran out of buffer
|
||||
mov dx,offset NoByteFoundMsg
|
||||
;assume 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
|
||||
PrintStatus:
|
||||
mov ah,9 ;DOS print string function
|
||||
int 21h ;report status
|
||||
.code
|
||||
Start proc near
|
||||
mov ax,@data ;point to standard data segment
|
||||
mov ds,ax
|
||||
mov dx,offset Prompt
|
||||
mov ah,9 ;DOS print string function
|
||||
int 21h ;prompt the user
|
||||
mov ah,1 ;DOS get key function
|
||||
int 21h ;get the key to search for
|
||||
mov ah,al ;put character to search for in AH
|
||||
mov cx,SAMPLE_STRING_LENGTH ;# of bytes to search
|
||||
mov si,offset SampleString ;point to buffer to search
|
||||
call SearchMaxLength ;search the buffer
|
||||
mov dx,offset ByteFoundMsg ;assume we found the byte
|
||||
jc PrintStatus ;we did find the byte
|
||||
;we didn't find the byte, figure out
|
||||
;whether we found a zero byte or
|
||||
;ran out of buffer
|
||||
mov dx,offset NoByteFoundMsg
|
||||
;assume 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
|
||||
PrintStatus:
|
||||
mov ah,9 ;DOS print string function
|
||||
int 21h ;report status
|
||||
|
||||
mov ah,4ch ;return to DOS
|
||||
int 21h
|
||||
Startendp
|
||||
mov ah,4ch ;return to DOS
|
||||
int 21h
|
||||
Startendp
|
||||
|
||||
; Function to search a buffer of a specified length until either a
|
||||
; specified byte or a zero byte is encountered.
|
||||
; Input:
|
||||
; AH = character to search for
|
||||
; CX = maximum length to be searched (must be > 0)
|
||||
; DS:SI = pointer to buffer to be searched
|
||||
; Output:
|
||||
; CX = 0 if and only if we ran out of bytes without finding
|
||||
; either the desired byte or a zero byte
|
||||
; DS:SI = pointer to searched-for byte if found, otherwise byte
|
||||
; after zero byte if found, otherwise byte after last
|
||||
; byte checked if neither searched-for byte nor zero
|
||||
; byte is found
|
||||
; Carry Flag = set if searched-for byte found, reset otherwise
|
||||
; Function to search a buffer of a specified length until either a
|
||||
; specified byte or a zero byte is encountered.
|
||||
; Input:
|
||||
; AH = character to search for
|
||||
; CX = maximum length to be searched (must be > 0)
|
||||
; DS:SI = pointer to buffer to be searched
|
||||
; Output:
|
||||
; CX = 0 if and only if we ran out of bytes without finding
|
||||
; either the desired byte or a zero byte
|
||||
; DS:SI = pointer to searched-for byte if found, otherwise byte
|
||||
; after zero byte if found, otherwise byte after last
|
||||
; byte checked if neither searched-for byte nor zero
|
||||
; byte is found
|
||||
; Carry Flag = set if searched-for byte found, reset otherwise
|
||||
|
||||
SearchMaxLength proc near
|
||||
cld
|
||||
mov bx,cx
|
||||
add cx,3 ;calculate the maximum # of passes
|
||||
shr cx,1 ;through the loop, which is
|
||||
shr cx,1 ;unrolled 4 times
|
||||
and bx,3 ;calculate the index into the entry
|
||||
;point table for the first,
|
||||
;possibly partial loop
|
||||
shl bx,1 ;prepare for a word-sized look-up
|
||||
jmp SearchMaxLengthEntryTable[bx]
|
||||
;branch into the unrolled loop to do
|
||||
;the first, possibly partial loop
|
||||
SearchMaxLengthLoop:
|
||||
SearchMaxLengthEntry4:
|
||||
lodsb ;get the next byte
|
||||
cmp al,ah ;is this the byte we want?
|
||||
jz ByteFound ;yes, we're done with success
|
||||
and al,al ;is this the terminating 0 byte?
|
||||
jz ByteNotFound ;yes, we're done with failure
|
||||
SearchMaxLengthEntry3:
|
||||
lodsb ;get the next byte
|
||||
cmp al,ah ;is this the byte we want?
|
||||
jz ByteFound ;yes, we're done with success
|
||||
and al,al ;is this the terminating 0 byte?
|
||||
jz ByteNotFound ;yes, we're done with failure
|
||||
SearchMaxLengthEntry2:
|
||||
lodsb ;get the next byte
|
||||
cmp al,ah ;is this the byte we want?
|
||||
jz ByteFound ;yes, we're done with success
|
||||
and al,al ;is this the terminating 0 byte?
|
||||
jz ByteNotFound ;yes, we're done with failure
|
||||
SearchMaxLengthEntry1:
|
||||
lodsb ;get the next byte
|
||||
cmp al,ah ;is this the byte we want?
|
||||
jz ByteFound ;yes, we're done with success
|
||||
and al,al ;is this the terminating 0 byte?
|
||||
jz ByteNotFound ;yes, we're done with failure
|
||||
loop SearchMaxLengthLoop ;it's neither, so check the next
|
||||
; four bytes, if any
|
||||
ByteNotFound:
|
||||
clc ;return "not found" status
|
||||
ret
|
||||
ByteFound:
|
||||
dec si ;point back to the location at which
|
||||
; we found the searched-for byte
|
||||
stc ;return "found" status
|
||||
ret
|
||||
SearchMaxLengthendp
|
||||
end Start
|
||||
SearchMaxLength proc near
|
||||
cld
|
||||
mov bx,cx
|
||||
add cx,3 ;calculate the maximum # of passes
|
||||
shr cx,1 ;through the loop, which is
|
||||
shr cx,1 ;unrolled 4 times
|
||||
and bx,3 ;calculate the index into the entry
|
||||
;point table for the first,
|
||||
;possibly partial loop
|
||||
shl bx,1 ;prepare for a word-sized look-up
|
||||
jmp SearchMaxLengthEntryTable[bx]
|
||||
;branch into the unrolled loop to do
|
||||
;the first, possibly partial loop
|
||||
SearchMaxLengthLoop:
|
||||
SearchMaxLengthEntry4:
|
||||
lodsb ;get the next byte
|
||||
cmp al,ah ;is this the byte we want?
|
||||
jz ByteFound ;yes, we're done with success
|
||||
and al,al ;is this the terminating 0 byte?
|
||||
jz ByteNotFound ;yes, we're done with failure
|
||||
SearchMaxLengthEntry3:
|
||||
lodsb ;get the next byte
|
||||
cmp al,ah ;is this the byte we want?
|
||||
jz ByteFound ;yes, we're done with success
|
||||
and al,al ;is this the terminating 0 byte?
|
||||
jz ByteNotFound ;yes, we're done with failure
|
||||
SearchMaxLengthEntry2:
|
||||
lodsb ;get the next byte
|
||||
cmp al,ah ;is this the byte we want?
|
||||
jz ByteFound ;yes, we're done with success
|
||||
and al,al ;is this the terminating 0 byte?
|
||||
jz ByteNotFound ;yes, we're done with failure
|
||||
SearchMaxLengthEntry1:
|
||||
lodsb ;get the next byte
|
||||
cmp al,ah ;is this the byte we want?
|
||||
jz ByteFound ;yes, we're done with success
|
||||
and al,al ;is this the terminating 0 byte?
|
||||
jz ByteNotFound ;yes, we're done with failure
|
||||
loop SearchMaxLengthLoop ;it's neither, so check the next
|
||||
; four bytes, if any
|
||||
ByteNotFound:
|
||||
clc ;return "not found" status
|
||||
ret
|
||||
ByteFound:
|
||||
dec si ;point back to the location at which
|
||||
; we found the searched-for byte
|
||||
stc ;return "found" status
|
||||
ret
|
||||
SearchMaxLengthendp
|
||||
end Start
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
98
07-05.md
98
07-05.md
|
|
@ -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
|
||||
position, and OR it with AX, as follows:
|
||||
|
||||
MOV BX,1
|
||||
SHL BX,CL
|
||||
OR AX,BX
|
||||
```nasm
|
||||
MOV BX,1
|
||||
SHL BX,CL
|
||||
OR AX,BX
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -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**
|
||||
|
||||
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
|
||||
REPT 16
|
||||
DW BIT_PATTERN
|
||||
BIT_PATTERN=BIT_PATTERN SHL 1
|
||||
ENDM
|
||||
```nasm
|
||||
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
|
||||
REPT 16
|
||||
DW BIT_PATTERN
|
||||
BIT_PATTERN=BIT_PATTERN SHL 1
|
||||
ENDM
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -55,14 +59,16 @@ code snippet in Listing 7.4.
|
|||
|
||||
**LISTING 7.4 L7-4.ASM**
|
||||
|
||||
OR EAX,ShiftTable[EBX*4] ;look up the bit and OR it in
|
||||
:
|
||||
ShiftTable LABEL DWORD
|
||||
BIT_PATTERN=0001H
|
||||
REPT 32
|
||||
DD BIT_PATTERN
|
||||
BIT_PATTERN=BIT_PATTERN SHL 1
|
||||
ENDM
|
||||
```nasm
|
||||
OR EAX,ShiftTable[EBX*4] ;look up the bit and OR it in
|
||||
:
|
||||
ShiftTable LABEL DWORD
|
||||
BIT_PATTERN=0001H
|
||||
REPT 32
|
||||
DD BIT_PATTERN
|
||||
BIT_PATTERN=BIT_PATTERN SHL 1
|
||||
ENDM
|
||||
```
|
||||
|
||||
> 
|
||||
> Besides illustrating the advantages of local optimization, this example
|
||||
|
|
@ -119,30 +125,34 @@ addition to the next.
|
|||
|
||||
**LISTING 7.5 L7-5.ASM**
|
||||
|
||||
CLC ;clear the Carry for the initial addition
|
||||
LOOP_TOP:
|
||||
MOV AX,[SI];get next source operand word
|
||||
ADC [DI],AX;add with Carry to dest operand word
|
||||
INC SI ;point to next source operand word
|
||||
INC SI
|
||||
INC DI ;point to next dest operand word
|
||||
INC DI
|
||||
LOOP LOOP_TOP
|
||||
```nasm
|
||||
CLC ;clear the Carry for the initial addition
|
||||
LOOP_TOP:
|
||||
MOV AX,[SI];get next source operand word
|
||||
ADC [DI],AX;add with Carry to dest operand word
|
||||
INC SI ;point to next source operand word
|
||||
INC SI
|
||||
INC DI ;point to next dest operand word
|
||||
INC DI
|
||||
LOOP LOOP_TOP
|
||||
```
|
||||
|
||||
If **ADD** were used, the Carry flag would have to be saved between
|
||||
additions, with code along the lines shown in Listing 7.6.
|
||||
|
||||
**LISTING 7.6 L7-6.ASM**
|
||||
|
||||
CLC ;clear the carry for the initial addition
|
||||
LOOP_TOP:
|
||||
MOV AX,[SI] ;get next source operand word
|
||||
ADC [DI],AX ;add with carry to dest operand word
|
||||
LAHF ;set aside the carry flag
|
||||
ADD SI,2 ;point to next source operand word
|
||||
ADD DI,2 ;point to next dest operand word
|
||||
SAHF ;restore the carry flag
|
||||
LOOP LOOP_TOP
|
||||
```nasm
|
||||
CLC ;clear the carry for the initial addition
|
||||
LOOP_TOP:
|
||||
MOV AX,[SI] ;get next source operand word
|
||||
ADC [DI],AX ;add with carry to dest operand word
|
||||
LAHF ;set aside the carry flag
|
||||
ADD SI,2 ;point to next source operand word
|
||||
ADD DI,2 ;point to next dest operand word
|
||||
SAHF ;restore the carry flag
|
||||
LOOP LOOP_TOP
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
Carry flag. For instance
|
||||
|
||||
INC AX
|
||||
ADC DX,0
|
||||
```nasm
|
||||
INC AX
|
||||
ADC DX,0
|
||||
```
|
||||
|
||||
does *not* increment the 32-bit value in DX:AX. To do that, you'd need
|
||||
the following:
|
||||
|
||||
ADD AX,1
|
||||
ADC DX,0
|
||||
```nasm
|
||||
ADD AX,1
|
||||
ADC DX,0
|
||||
```
|
||||
|
||||
As always, pay attention!
|
||||
6
08-02.md
6
08-02.md
|
|
@ -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
|
||||
compiler. Try this:
|
||||
|
||||
repnz scasw
|
||||
jz Match
|
||||
```nasm
|
||||
repnz scasw
|
||||
jz Match
|
||||
```
|
||||
|
||||
It's a simple example—but, I hope, a convincing one. Stretch your brain
|
||||
when you optimize.
|
||||
|
|
|
|||
284
08-03.md
284
08-03.md
|
|
@ -12,125 +12,127 @@ pages: 156-160
|
|||
|
||||
**LISTING 8.1 L8-1.C**
|
||||
|
||||
/* Program to search an array spanning a linked list of variable-
|
||||
sized blocks, for all entries with a specified ID number,
|
||||
and return the average of the values of all such entries. Each of
|
||||
the variable-sized blocks may contain any number of data entries,
|
||||
stored as an array of structures within the block. */
|
||||
```c
|
||||
/* Program to search an array spanning a linked list of variable-
|
||||
sized blocks, for all entries with a specified ID number,
|
||||
and return the average of the values of all such entries. Each of
|
||||
the variable-sized blocks may contain any number of data entries,
|
||||
stored as an array of structures within the block. */
|
||||
|
||||
#include <stdio.h>
|
||||
#ifdef __TURBOC__
|
||||
#include <alloc.h>
|
||||
#else
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#ifdef __TURBOC__
|
||||
#include <alloc.h>
|
||||
#else
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
void main(void);
|
||||
void exit(int);
|
||||
unsigned int FindIDAverage(unsigned int, struct BlockHeader *);
|
||||
/* Structure that starts each variable-sized block */
|
||||
struct BlockHeader {
|
||||
struct BlockHeader *NextBlock; /* Pointer to next block, or NULL
|
||||
if this is the last block in the
|
||||
linked list */
|
||||
unsigned int BlockCount; /* The number of DataElement entries
|
||||
in this variable-sized block */
|
||||
};
|
||||
void main(void);
|
||||
void exit(int);
|
||||
unsigned int FindIDAverage(unsigned int, struct BlockHeader *);
|
||||
/* Structure that starts each variable-sized block */
|
||||
struct BlockHeader {
|
||||
struct BlockHeader *NextBlock; /* Pointer to next block, or NULL
|
||||
if this is the last block in the
|
||||
linked list */
|
||||
unsigned int BlockCount; /* The number of DataElement entries
|
||||
in this variable-sized block */
|
||||
};
|
||||
|
||||
/* Structure that contains one element of the array we'll search */
|
||||
struct DataElement {
|
||||
unsigned int ID; /* ID # for array entry */
|
||||
unsigned int Value; /* Value of array entry */
|
||||
};
|
||||
/* Structure that contains one element of the array we'll search */
|
||||
struct DataElement {
|
||||
unsigned int ID; /* ID # for array entry */
|
||||
unsigned int Value; /* Value of array entry */
|
||||
};
|
||||
|
||||
void main(void) {
|
||||
int i,j;
|
||||
unsigned int IDToFind;
|
||||
struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer;
|
||||
struct DataElement *WorkingDataPointer;
|
||||
struct BlockHeader **LastBlockPointer;
|
||||
void main(void) {
|
||||
int i,j;
|
||||
unsigned int IDToFind;
|
||||
struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer;
|
||||
struct DataElement *WorkingDataPointer;
|
||||
struct BlockHeader **LastBlockPointer;
|
||||
|
||||
printf("ID # for which to find average: ");
|
||||
scanf("%d",&IDToFind);
|
||||
/* Build an array across 5 blocks, for testing */
|
||||
/* Anchor the linked list to BaseArrayBlockPointer */
|
||||
LastBlockPointer = &BaseArrayBlockPointer;
|
||||
/* Create 5 blocks of varying sizes */
|
||||
for (i = 1; i < 6; i++) {
|
||||
/* Try to get memory for the next block */
|
||||
if ((WorkingBlockPointer =
|
||||
(struct BlockHeader *) malloc(sizeof(struct BlockHeader) +
|
||||
sizeof(struct DataElement) * i * 10)) == NULL) {
|
||||
exit(1);
|
||||
}
|
||||
/* Set the # of data elements in this block */
|
||||
WorkingBlockPointer->BlockCount = i * 10;
|
||||
/* Link the new block into the chain */
|
||||
*LastBlockPointer = WorkingBlockPointer;
|
||||
/* Point to the first data field */
|
||||
WorkingDataPointer =
|
||||
(struct DataElement *) ((char *)WorkingBlockPointer +
|
||||
sizeof(struct BlockHeader));
|
||||
/* Fill the data fields with ID numbers and values */
|
||||
for (j = 0; j < (i * 10); j++, WorkingDataPointer++) {
|
||||
WorkingDataPointer->ID = j;
|
||||
WorkingDataPointer->Value = i * 1000 + j;
|
||||
}
|
||||
/* Remember where to set link from this block to the next */
|
||||
LastBlockPointer = &WorkingBlockPointer->NextBlock;
|
||||
}
|
||||
/* Set the last block's "next block" pointer to NULL to indicate
|
||||
that there are no more blocks */
|
||||
WorkingBlockPointer->NextBlock = NULL;
|
||||
printf("Average of all elements with ID %d: %u\n",
|
||||
IDToFind, FindIDAverage(IDToFind, BaseArrayBlockPointer));
|
||||
exit(0);
|
||||
}
|
||||
printf("ID # for which to find average: ");
|
||||
scanf("%d",&IDToFind);
|
||||
/* Build an array across 5 blocks, for testing */
|
||||
/* Anchor the linked list to BaseArrayBlockPointer */
|
||||
LastBlockPointer = &BaseArrayBlockPointer;
|
||||
/* Create 5 blocks of varying sizes */
|
||||
for (i = 1; i < 6; i++) {
|
||||
/* Try to get memory for the next block */
|
||||
if ((WorkingBlockPointer =
|
||||
(struct BlockHeader *) malloc(sizeof(struct BlockHeader) +
|
||||
sizeof(struct DataElement) * i * 10)) == NULL) {
|
||||
exit(1);
|
||||
}
|
||||
/* Set the # of data elements in this block */
|
||||
WorkingBlockPointer->BlockCount = i * 10;
|
||||
/* Link the new block into the chain */
|
||||
*LastBlockPointer = WorkingBlockPointer;
|
||||
/* Point to the first data field */
|
||||
WorkingDataPointer =
|
||||
(struct DataElement *) ((char *)WorkingBlockPointer +
|
||||
sizeof(struct BlockHeader));
|
||||
/* Fill the data fields with ID numbers and values */
|
||||
for (j = 0; j < (i * 10); j++, WorkingDataPointer++) {
|
||||
WorkingDataPointer->ID = j;
|
||||
WorkingDataPointer->Value = i * 1000 + j;
|
||||
}
|
||||
/* Remember where to set link from this block to the next */
|
||||
LastBlockPointer = &WorkingBlockPointer->NextBlock;
|
||||
}
|
||||
/* Set the last block's "next block" pointer to NULL to indicate
|
||||
that there are no more blocks */
|
||||
WorkingBlockPointer->NextBlock = NULL;
|
||||
printf("Average of all elements with ID %d: %u\n",
|
||||
IDToFind, FindIDAverage(IDToFind, BaseArrayBlockPointer));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* Searches through the array of DataElement entries spanning the
|
||||
linked list of variable-sized blocks, starting with the block
|
||||
pointed to by BlockPointer, for all entries with IDs matching
|
||||
SearchedForID, and returns the average value of those entries. If
|
||||
no matches are found, zero is returned */
|
||||
/* Searches through the array of DataElement entries spanning the
|
||||
linked list of variable-sized blocks, starting with the block
|
||||
pointed to by BlockPointer, for all entries with IDs matching
|
||||
SearchedForID, and returns the average value of those entries. If
|
||||
no matches are found, zero is returned */
|
||||
|
||||
unsigned int FindIDAverage(unsigned int SearchedForID,
|
||||
struct BlockHeader *BlockPointer)
|
||||
{
|
||||
struct DataElement *DataPointer;
|
||||
unsigned int IDMatchSum;
|
||||
unsigned int IDMatchCount;
|
||||
unsigned int WorkingBlockCount;
|
||||
unsigned int FindIDAverage(unsigned int SearchedForID,
|
||||
struct BlockHeader *BlockPointer)
|
||||
{
|
||||
struct DataElement *DataPointer;
|
||||
unsigned int IDMatchSum;
|
||||
unsigned int IDMatchCount;
|
||||
unsigned int WorkingBlockCount;
|
||||
|
||||
IDMatchCount = IDMatchSum = 0;
|
||||
/* Search through all the linked blocks until the last block
|
||||
(marked with a NULL pointer to the next block) has been
|
||||
searched */
|
||||
do {
|
||||
/* Point to the first DataElement entry within this block */
|
||||
DataPointer =
|
||||
(struct DataElement *) ((char *)BlockPointer +
|
||||
sizeof(struct BlockHeader));
|
||||
/* Search all the DataElement entries within this block
|
||||
and accumulate data from all that match the desired ID */
|
||||
for (WorkingBlockCount=0;
|
||||
WorkingBlockCount<BlockPointer->BlockCount;
|
||||
WorkingBlockCount++, DataPointer++) {
|
||||
/* If the ID matches, add in the value and increment the
|
||||
match counter */
|
||||
if (DataPointer->ID == SearchedForID) {
|
||||
IDMatchCount++;
|
||||
IDMatchSum += DataPointer->Value;
|
||||
}
|
||||
}
|
||||
/* Point to the next block, and continue as long as that pointer
|
||||
isn't NULL */
|
||||
} while ((BlockPointer = BlockPointer->NextBlock) != NULL);
|
||||
/* Calculate the average of all matches */
|
||||
if (IDMatchCount == 0)
|
||||
return(0); /* Avoid division by 0 */
|
||||
else
|
||||
return(IDMatchSum / IDMatchCount);
|
||||
}
|
||||
IDMatchCount = IDMatchSum = 0;
|
||||
/* Search through all the linked blocks until the last block
|
||||
(marked with a NULL pointer to the next block) has been
|
||||
searched */
|
||||
do {
|
||||
/* Point to the first DataElement entry within this block */
|
||||
DataPointer =
|
||||
(struct DataElement *) ((char *)BlockPointer +
|
||||
sizeof(struct BlockHeader));
|
||||
/* Search all the DataElement entries within this block
|
||||
and accumulate data from all that match the desired ID */
|
||||
for (WorkingBlockCount=0;
|
||||
WorkingBlockCount<BlockPointer->BlockCount;
|
||||
WorkingBlockCount++, DataPointer++) {
|
||||
/* If the ID matches, add in the value and increment the
|
||||
match counter */
|
||||
if (DataPointer->ID == SearchedForID) {
|
||||
IDMatchCount++;
|
||||
IDMatchSum += DataPointer->Value;
|
||||
}
|
||||
}
|
||||
/* Point to the next block, and continue as long as that pointer
|
||||
isn't NULL */
|
||||
} while ((BlockPointer = BlockPointer->NextBlock) != NULL);
|
||||
/* Calculate the average of all matches */
|
||||
if (IDMatchCount == 0)
|
||||
return(0); /* Avoid division by 0 */
|
||||
else
|
||||
return(IDMatchSum / IDMatchCount);
|
||||
}
|
||||
```
|
||||
|
||||
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,
|
||||
|
|
@ -154,32 +156,34 @@ instruction can be used.
|
|||
|
||||
**LISTING 8.2 L8-2.COD**
|
||||
|
||||
; Code generated by Microsoft C for inner loop of FindIDAverage.
|
||||
;|*** for (WorkingBlockCount=0;
|
||||
;|*** WorkingBlockCount<BlockPointer->BlockCount;
|
||||
;|*** WorkingBlockCount++, DataPointer++) {
|
||||
mov WORD PTR [bp-6],0 ;WorkingBlockCount
|
||||
mov bx,WORD PTR [bp+6] ;BlockPointer
|
||||
cmp WORD PTR [bx+2],0
|
||||
je $FB264
|
||||
mov cx,WORD PTR [bx+2]
|
||||
add WORD PTR [bp-6],cx ;WorkingBlockCount
|
||||
mov di,WORD PTR [bp-2] ;IDMatchSum
|
||||
mov dx,WORD PTR [bp-4] ;IDMatchCount
|
||||
$L20004:
|
||||
;|*** if (DataPointer->ID == SearchedForID) {
|
||||
mov ax,WORD PTR [si]
|
||||
cmp WORD PTR [bp+4],ax ;SearchedForID
|
||||
jne $I265
|
||||
;|*** IDMatchCount++;
|
||||
inc dx
|
||||
;|*** IDMatchSum += DataPointer->Value;
|
||||
add di,WORD PTR [si+2]
|
||||
;|*** }
|
||||
;|*** }
|
||||
$I265:
|
||||
add si,4
|
||||
loop $L20004
|
||||
mov WORD PTR [bp-2],di ;IDMatchSum
|
||||
mov WORD PTR [bp-4],dx ;IDMatchCount
|
||||
$FB264:
|
||||
```nasm
|
||||
; Code generated by Microsoft C for inner loop of FindIDAverage.
|
||||
;|*** for (WorkingBlockCount=0;
|
||||
;|*** WorkingBlockCount<BlockPointer->BlockCount;
|
||||
;|*** WorkingBlockCount++, DataPointer++) {
|
||||
mov WORD PTR [bp-6],0 ;WorkingBlockCount
|
||||
mov bx,WORD PTR [bp+6] ;BlockPointer
|
||||
cmp WORD PTR [bx+2],0
|
||||
je $FB264
|
||||
mov cx,WORD PTR [bx+2]
|
||||
add WORD PTR [bp-6],cx ;WorkingBlockCount
|
||||
mov di,WORD PTR [bp-2] ;IDMatchSum
|
||||
mov dx,WORD PTR [bp-4] ;IDMatchCount
|
||||
$L20004:
|
||||
;|*** if (DataPointer->ID == SearchedForID) {
|
||||
mov ax,WORD PTR [si]
|
||||
cmp WORD PTR [bp+4],ax ;SearchedForID
|
||||
jne $I265
|
||||
;|*** IDMatchCount++;
|
||||
inc dx
|
||||
;|*** IDMatchSum += DataPointer->Value;
|
||||
add di,WORD PTR [si+2]
|
||||
;|*** }
|
||||
;|*** }
|
||||
$I265:
|
||||
add si,4
|
||||
loop $L20004
|
||||
mov WORD PTR [bp-2],di ;IDMatchSum
|
||||
mov WORD PTR [bp-4],dx ;IDMatchCount
|
||||
$FB264:
|
||||
```
|
||||
|
|
|
|||
294
08-04.md
294
08-04.md
|
|
@ -24,19 +24,21 @@ optimization, isn't it?
|
|||
|
||||
**LISTING 8.3 L8-3.ASM**
|
||||
|
||||
; Typically optimized assembly language version of FindIDAverage.
|
||||
SearchedForID equ 4 ;Passed parameter offsets in the
|
||||
BlockPointer equ 6 ; stack frame (skip over pushed BP
|
||||
; and the return address)
|
||||
NextBlock equ 0 ;Field offsets in struct BlockHeader
|
||||
BlockCount equ 2
|
||||
BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader
|
||||
ID equ 0 ;struct DataElement field offsets
|
||||
Value equ 2
|
||||
DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement
|
||||
.model small
|
||||
.code
|
||||
public _FindIDAverage
|
||||
```nasm
|
||||
; Typically optimized assembly language version of FindIDAverage.
|
||||
SearchedForID equ 4 ;Passed parameter offsets in the
|
||||
BlockPointer equ 6 ; stack frame (skip over pushed BP
|
||||
; and the return address)
|
||||
NextBlock equ 0 ;Field offsets in struct BlockHeader
|
||||
BlockCount equ 2
|
||||
BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader
|
||||
ID equ 0 ;struct DataElement field offsets
|
||||
Value equ 2
|
||||
DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement
|
||||
.model small
|
||||
.code
|
||||
public _FindIDAverage
|
||||
```
|
||||
|
||||
| | On 20 MHz 386 | On 10 MHz 286 |
|
||||
|------------------------------------------------------------|------------------|------------------|
|
||||
|
|
@ -47,49 +49,51 @@ optimization, isn't it?
|
|||
|
||||
Table: Table 8.1 Execution Times of FindIDAverage.
|
||||
|
||||
_FindIDAverage proc near
|
||||
push bp ;Save caller's stack frame
|
||||
mov bp,sp ;Point to our stack frame
|
||||
push di ;Preserve C register variables
|
||||
push si
|
||||
sub dx,dx ;IDMatchSum = 0
|
||||
mov bx,dx ;IDMatchCount = 0
|
||||
mov si,[bp+BlockPointer] ;Pointer to first block
|
||||
mov ax,[bp+SearchedForID] ;ID we're looking for
|
||||
; Search through all the linked blocks until the last block
|
||||
; (marked with a NULL pointer to the next block) has been searched.
|
||||
BlockLoop:
|
||||
; Point to the first DataElement entry within this block.
|
||||
lea di,[si+BLOCK_HEADER_SIZE]
|
||||
; Search through all the DataElement entries within this block
|
||||
; and accumulate data from all that match the desired ID.
|
||||
mov cx,[si+BlockCount]
|
||||
jcxz DoNextBlock ;No data in this block
|
||||
IntraBlockLoop:
|
||||
cmp [di+ID],ax ;Do we have an ID match?
|
||||
jnz NoMatch ;No match
|
||||
inc bx ;We have a match; IDMatchCount++;
|
||||
add dx,[di+Value] ;IDMatchSum += DataPointer->Value;
|
||||
NoMatch:
|
||||
add di,DATA_ELEMENT_SIZE ;point to the next element
|
||||
loop IntraBlockLoop
|
||||
; Point to the next block and continue if that pointer isn't NULL.
|
||||
DoNextBlock:
|
||||
mov si,[si+NextBlock] ;Get pointer to the next block
|
||||
and si,si ;Is it a NULL pointer?
|
||||
jnz BlockLoop ;No, continue
|
||||
; Calculate the average of all matches.
|
||||
sub ax,ax ;Assume we found no matches
|
||||
and bx,bx
|
||||
jz Done ;We didn't find any matches, return 0
|
||||
xchg ax,dx ;Prepare for division
|
||||
div bx ;Return IDMatchSum / IDMatchCount
|
||||
Done: pop si ;Restore C register variables
|
||||
pop di
|
||||
pop bp ;Restore caller's stack frame
|
||||
ret
|
||||
_FindIDAverage ENDP
|
||||
end
|
||||
```nasm
|
||||
_FindIDAverage proc near
|
||||
push bp ;Save caller's stack frame
|
||||
mov bp,sp ;Point to our stack frame
|
||||
push di ;Preserve C register variables
|
||||
push si
|
||||
sub dx,dx ;IDMatchSum = 0
|
||||
mov bx,dx ;IDMatchCount = 0
|
||||
mov si,[bp+BlockPointer] ;Pointer to first block
|
||||
mov ax,[bp+SearchedForID] ;ID we're looking for
|
||||
; Search through all the linked blocks until the last block
|
||||
; (marked with a NULL pointer to the next block) has been searched.
|
||||
BlockLoop:
|
||||
; Point to the first DataElement entry within this block.
|
||||
lea di,[si+BLOCK_HEADER_SIZE]
|
||||
; Search through all the DataElement entries within this block
|
||||
; and accumulate data from all that match the desired ID.
|
||||
mov cx,[si+BlockCount]
|
||||
jcxz DoNextBlock ;No data in this block
|
||||
IntraBlockLoop:
|
||||
cmp [di+ID],ax ;Do we have an ID match?
|
||||
jnz NoMatch ;No match
|
||||
inc bx ;We have a match; IDMatchCount++;
|
||||
add dx,[di+Value] ;IDMatchSum += DataPointer->Value;
|
||||
NoMatch:
|
||||
add di,DATA_ELEMENT_SIZE ;point to the next element
|
||||
loop IntraBlockLoop
|
||||
; Point to the next block and continue if that pointer isn't NULL.
|
||||
DoNextBlock:
|
||||
mov si,[si+NextBlock] ;Get pointer to the next block
|
||||
and si,si ;Is it a NULL pointer?
|
||||
jnz BlockLoop ;No, continue
|
||||
; Calculate the average of all matches.
|
||||
sub ax,ax ;Assume we found no matches
|
||||
and bx,bx
|
||||
jz Done ;We didn't find any matches, return 0
|
||||
xchg ax,dx ;Prepare for division
|
||||
div bx ;Return IDMatchSum / IDMatchCount
|
||||
Done: pop si ;Restore C register variables
|
||||
pop di
|
||||
pop bp ;Restore caller's stack frame
|
||||
ret
|
||||
_FindIDAverage ENDP
|
||||
end
|
||||
```
|
||||
|
||||
Listing 8.4 tosses some sophisticated optimization techniques into the
|
||||
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**
|
||||
|
||||
; Heavily optimized assembly language version of FindIDAverage.
|
||||
; Features an unrolled loop and more efficient pointer use.
|
||||
SearchedForID equ 4 ;Passed parameter offsets in the
|
||||
BlockPointer equ 6 ; stack frame (skip over pushed BP
|
||||
; and the return address)
|
||||
NextBlock equ 0 ;Field offsets in struct BlockHeader
|
||||
BlockCount equ 2
|
||||
BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader
|
||||
ID equ 0 ;struct DataElement field offsets
|
||||
Value equ 2
|
||||
DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement
|
||||
.model small
|
||||
.code
|
||||
public _FindIDAverage
|
||||
_FindIDAverage proc near
|
||||
push bp ;Save caller's stack frame
|
||||
mov bp,sp ;Point to our stack frame
|
||||
push di ;Preserve C register variables
|
||||
push si
|
||||
mov di,ds ;Prepare for SCASW
|
||||
mov es,di
|
||||
cld
|
||||
sub dx,dx ;IDMatchSum = 0
|
||||
mov bx,dx ;IDMatchCount = 0
|
||||
mov si,[bp+BlockPointer] ;Pointer to first block
|
||||
mov ax,[bp+SearchedForID] ;ID we're looking for
|
||||
; Search through all of the linked blocks until the last block
|
||||
; (marked with a NULL pointer to the next block) has been searched.
|
||||
BlockLoop:
|
||||
; Point to the first DataElement entry within this block.
|
||||
lea di,[si+BLOCK_HEADER_SIZE]
|
||||
; Search through all the DataElement entries within this block
|
||||
; and accumulate data from all that match the desired ID.
|
||||
mov cx,[si+BlockCount] ;Number of elements in this block
|
||||
jcxz DoNextBlock ;Skip this block if it's empty
|
||||
mov bp,cx ;***stack frame no longer available***
|
||||
add cx,7
|
||||
shr cx,1 ;Number of repetitions of the unrolled
|
||||
shr cx,1 ; loop = (BlockCount + 7) / 8
|
||||
shr cx,1
|
||||
and bp,7 ;Generate the entry point for the
|
||||
shl bp,1 ; first, possibly partial pass through
|
||||
jmp cs:[LoopEntryTable+bp] ; the unrolled loop and
|
||||
; vector to that entry point
|
||||
align 2
|
||||
LoopEntryTable label word
|
||||
dw LoopEntry8,LoopEntry1,LoopEntry2,LoopEntry3
|
||||
dw LoopEntry4,LoopEntry5,LoopEntry6,LoopEntry7
|
||||
M_IBL macro P1
|
||||
local NoMatch
|
||||
LoopEntry&P1&:
|
||||
scasw ;Do we have an ID match?
|
||||
jnz NoMatch ;No match
|
||||
;We have a match
|
||||
inc bx ;IDMatchCount++;
|
||||
add dx,[di] ;IDMatchSum += DataPointer->Value;
|
||||
NoMatch:
|
||||
add di,DATA_ELEMENT_SIZE-2 ;point to the next element
|
||||
; (SCASW advanced 2 bytes already)
|
||||
endm
|
||||
align 2
|
||||
IntraBlockLoop:
|
||||
M_IBL 8
|
||||
M_IBL 7
|
||||
M_IBL 6
|
||||
M_IBL 5
|
||||
M_IBL 4
|
||||
M_IBL 3
|
||||
M_IBL 2
|
||||
M_IBL 1
|
||||
loop IntraBlockLoop
|
||||
; Point to the next block and continue if that pointer isn't NULL.
|
||||
DoNextBlock:
|
||||
mov si,[si+NextBlock] ;Get pointer to the next block
|
||||
and si,si ;Is it a NULL pointer?
|
||||
jnz BlockLoop ;No, continue
|
||||
; Calculate the average of all matches.
|
||||
sub ax,ax ;Assume we found no matches
|
||||
and bx,bx
|
||||
jz Done ;We didn't find any matches, return 0
|
||||
xchg ax,dx ;Prepare for division
|
||||
div bx ;Return IDMatchSum / IDMatchCount
|
||||
Done: pop si ;Restore C register variables
|
||||
pop di
|
||||
pop bp ;Restore caller's stack frame
|
||||
ret
|
||||
_FindIDAverage ENDP
|
||||
end
|
||||
```nasm
|
||||
; Heavily optimized assembly language version of FindIDAverage.
|
||||
; Features an unrolled loop and more efficient pointer use.
|
||||
SearchedForID equ 4 ;Passed parameter offsets in the
|
||||
BlockPointer equ 6 ; stack frame (skip over pushed BP
|
||||
; and the return address)
|
||||
NextBlock equ 0 ;Field offsets in struct BlockHeader
|
||||
BlockCount equ 2
|
||||
BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader
|
||||
ID equ 0 ;struct DataElement field offsets
|
||||
Value equ 2
|
||||
DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement
|
||||
.model small
|
||||
.code
|
||||
public _FindIDAverage
|
||||
_FindIDAverage proc near
|
||||
push bp ;Save caller's stack frame
|
||||
mov bp,sp ;Point to our stack frame
|
||||
push di ;Preserve C register variables
|
||||
push si
|
||||
mov di,ds ;Prepare for SCASW
|
||||
mov es,di
|
||||
cld
|
||||
sub dx,dx ;IDMatchSum = 0
|
||||
mov bx,dx ;IDMatchCount = 0
|
||||
mov si,[bp+BlockPointer] ;Pointer to first block
|
||||
mov ax,[bp+SearchedForID] ;ID we're looking for
|
||||
; Search through all of the linked blocks until the last block
|
||||
; (marked with a NULL pointer to the next block) has been searched.
|
||||
BlockLoop:
|
||||
; Point to the first DataElement entry within this block.
|
||||
lea di,[si+BLOCK_HEADER_SIZE]
|
||||
; Search through all the DataElement entries within this block
|
||||
; and accumulate data from all that match the desired ID.
|
||||
mov cx,[si+BlockCount] ;Number of elements in this block
|
||||
jcxz DoNextBlock ;Skip this block if it's empty
|
||||
mov bp,cx ;***stack frame no longer available***
|
||||
add cx,7
|
||||
shr cx,1 ;Number of repetitions of the unrolled
|
||||
shr cx,1 ; loop = (BlockCount + 7) / 8
|
||||
shr cx,1
|
||||
and bp,7 ;Generate the entry point for the
|
||||
shl bp,1 ; first, possibly partial pass through
|
||||
jmp cs:[LoopEntryTable+bp] ; the unrolled loop and
|
||||
; vector to that entry point
|
||||
align 2
|
||||
LoopEntryTable label word
|
||||
dw LoopEntry8,LoopEntry1,LoopEntry2,LoopEntry3
|
||||
dw LoopEntry4,LoopEntry5,LoopEntry6,LoopEntry7
|
||||
M_IBL macro P1
|
||||
local NoMatch
|
||||
LoopEntry&P1&:
|
||||
scasw ;Do we have an ID match?
|
||||
jnz NoMatch ;No match
|
||||
;We have a match
|
||||
inc bx ;IDMatchCount++;
|
||||
add dx,[di] ;IDMatchSum += DataPointer->Value;
|
||||
NoMatch:
|
||||
add di,DATA_ELEMENT_SIZE-2 ;point to the next element
|
||||
; (SCASW advanced 2 bytes already)
|
||||
endm
|
||||
align 2
|
||||
IntraBlockLoop:
|
||||
M_IBL 8
|
||||
M_IBL 7
|
||||
M_IBL 6
|
||||
M_IBL 5
|
||||
M_IBL 4
|
||||
M_IBL 3
|
||||
M_IBL 2
|
||||
M_IBL 1
|
||||
loop IntraBlockLoop
|
||||
; Point to the next block and continue if that pointer isn't NULL.
|
||||
DoNextBlock:
|
||||
mov si,[si+NextBlock] ;Get pointer to the next block
|
||||
and si,si ;Is it a NULL pointer?
|
||||
jnz BlockLoop ;No, continue
|
||||
; Calculate the average of all matches.
|
||||
sub ax,ax ;Assume we found no matches
|
||||
and bx,bx
|
||||
jz Done ;We didn't find any matches, return 0
|
||||
xchg ax,dx ;Prepare for division
|
||||
div bx ;Return IDMatchSum / IDMatchCount
|
||||
Done: pop si ;Restore C register variables
|
||||
pop di
|
||||
pop bp ;Restore caller's stack frame
|
||||
ret
|
||||
_FindIDAverage ENDP
|
||||
end
|
||||
```
|
||||
266
08-05.md
266
08-05.md
|
|
@ -20,149 +20,155 @@ merely rearranged.
|
|||
|
||||
**LISTING 8.5 L8-5.C**
|
||||
|
||||
/* Program to search an array spanning a linked list of variable-
|
||||
sized blocks, for all entries with a specified ID number,
|
||||
and return the average of the values of all such entries. Each of
|
||||
the variable-sized blocks may contain any number of data entries,
|
||||
stored in the form of two separate arrays, one for ID numbers and
|
||||
one for values. */
|
||||
```c
|
||||
/* Program to search an array spanning a linked list of variable-
|
||||
sized blocks, for all entries with a specified ID number,
|
||||
and return the average of the values of all such entries. Each of
|
||||
the variable-sized blocks may contain any number of data entries,
|
||||
stored in the form of two separate arrays, one for ID numbers and
|
||||
one for values. */
|
||||
|
||||
#include <stdio.h>
|
||||
#ifdef __TURBOC__
|
||||
#include <alloc.h>
|
||||
#else
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#ifdef __TURBOC__
|
||||
#include <alloc.h>
|
||||
#else
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
void main(void);
|
||||
void exit(int);
|
||||
extern unsigned int FindIDAverage2(unsigned int,
|
||||
struct BlockHeader *);
|
||||
void main(void);
|
||||
void exit(int);
|
||||
extern unsigned int FindIDAverage2(unsigned int,
|
||||
struct BlockHeader *);
|
||||
```
|
||||
|
||||

|
||||
|
||||
/* Structure that starts each variable-sized block */
|
||||
struct BlockHeader {
|
||||
struct BlockHeader *NextBlock; /* Pointer to next block, or NULL
|
||||
if this is the last block in the
|
||||
linked list */
|
||||
unsigned int BlockCount; /* The number of DataElement entries
|
||||
in this variable-sized block */
|
||||
};
|
||||
```c
|
||||
/* Structure that starts each variable-sized block */
|
||||
struct BlockHeader {
|
||||
struct BlockHeader *NextBlock; /* Pointer to next block, or NULL
|
||||
if this is the last block in the
|
||||
linked list */
|
||||
unsigned int BlockCount; /* The number of DataElement entries
|
||||
in this variable-sized block */
|
||||
};
|
||||
|
||||
void main(void) {
|
||||
int i,j;
|
||||
unsigned int IDToFind;
|
||||
struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer;
|
||||
int *WorkingDataPointer;
|
||||
struct BlockHeader **LastBlockPointer;
|
||||
void main(void) {
|
||||
int i,j;
|
||||
unsigned int IDToFind;
|
||||
struct BlockHeader *BaseArrayBlockPointer,*WorkingBlockPointer;
|
||||
int *WorkingDataPointer;
|
||||
struct BlockHeader **LastBlockPointer;
|
||||
|
||||
printf("ID # for which to find average: ");
|
||||
scanf("%d",&IDToFind);
|
||||
printf("ID # for which to find average: ");
|
||||
scanf("%d",&IDToFind);
|
||||
|
||||
/* Build an array across 5 blocks, for testing */
|
||||
/* Anchor the linked list to BaseArrayBlockPointer */
|
||||
LastBlockPointer = &BaseArrayBlockPointer;
|
||||
/* Create 5 blocks of varying sizes */
|
||||
for (i = 1; i < 6; i++) {
|
||||
/* Try to get memory for the next block */
|
||||
if ((WorkingBlockPointer =
|
||||
(struct BlockHeader *) malloc(sizeof(struct BlockHeader) +
|
||||
sizeof(int) * 2 * i * 10)) == NULL) {
|
||||
exit(1);
|
||||
}
|
||||
/* Set the number of data elements in this block */
|
||||
WorkingBlockPointer->BlockCount = i * 10;
|
||||
/* Link the new block into the chain */
|
||||
*LastBlockPointer = WorkingBlockPointer;
|
||||
/* Point to the first data field */
|
||||
WorkingDataPointer = (int *) ((char *)WorkingBlockPointer +
|
||||
sizeof(struct BlockHeader));
|
||||
/* Fill the data fields with ID numbers and values */
|
||||
for (j = 0; j < (i * 10); j++, WorkingDataPointer++) {
|
||||
*WorkingDataPointer = j;
|
||||
*(WorkingDataPointer + i * 10) = i * 1000 + j;
|
||||
}
|
||||
/* Remember where to set link from this block to the next */
|
||||
LastBlockPointer = &WorkingBlockPointer->NextBlock;
|
||||
}
|
||||
/* Set the last block's "next block" pointer to NULL to indicate
|
||||
that there are no more blocks */
|
||||
WorkingBlockPointer->NextBlock = NULL;
|
||||
printf("Average of all elements with ID %d: %u\n",
|
||||
IDToFind, FindIDAverage2(IDToFind, BaseArrayBlockPointer));
|
||||
exit(0);
|
||||
}
|
||||
/* Build an array across 5 blocks, for testing */
|
||||
/* Anchor the linked list to BaseArrayBlockPointer */
|
||||
LastBlockPointer = &BaseArrayBlockPointer;
|
||||
/* Create 5 blocks of varying sizes */
|
||||
for (i = 1; i < 6; i++) {
|
||||
/* Try to get memory for the next block */
|
||||
if ((WorkingBlockPointer =
|
||||
(struct BlockHeader *) malloc(sizeof(struct BlockHeader) +
|
||||
sizeof(int) * 2 * i * 10)) == NULL) {
|
||||
exit(1);
|
||||
}
|
||||
/* Set the number of data elements in this block */
|
||||
WorkingBlockPointer->BlockCount = i * 10;
|
||||
/* Link the new block into the chain */
|
||||
*LastBlockPointer = WorkingBlockPointer;
|
||||
/* Point to the first data field */
|
||||
WorkingDataPointer = (int *) ((char *)WorkingBlockPointer +
|
||||
sizeof(struct BlockHeader));
|
||||
/* Fill the data fields with ID numbers and values */
|
||||
for (j = 0; j < (i * 10); j++, WorkingDataPointer++) {
|
||||
*WorkingDataPointer = j;
|
||||
*(WorkingDataPointer + i * 10) = i * 1000 + j;
|
||||
}
|
||||
/* Remember where to set link from this block to the next */
|
||||
LastBlockPointer = &WorkingBlockPointer->NextBlock;
|
||||
}
|
||||
/* Set the last block's "next block" pointer to NULL to indicate
|
||||
that there are no more blocks */
|
||||
WorkingBlockPointer->NextBlock = NULL;
|
||||
printf("Average of all elements with ID %d: %u\n",
|
||||
IDToFind, FindIDAverage2(IDToFind, BaseArrayBlockPointer));
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
**LISTING 8.6 L8-6.ASM**
|
||||
|
||||
; Alternative optimized assembly language version of FindIDAverage
|
||||
; requires data organized as two arrays within each block rather
|
||||
; than as an array of two-value element structures. This allows the
|
||||
; use of REP SCASW for ID searching.
|
||||
```asm
|
||||
; Alternative optimized assembly language version of FindIDAverage
|
||||
; requires data organized as two arrays within each block rather
|
||||
; 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
|
||||
BlockPointerequ6 ; stack frame (skip over pushed BP
|
||||
; and the return address)
|
||||
NextBlockequ0 ;Field offsets in struct BlockHeader
|
||||
BlockCountequ2
|
||||
BLOCK_HEADER_SIZEequ4 ;Number of bytes in struct BlockHeader
|
||||
SearchedForIDequ4 ;Passed parameter offsets in the
|
||||
BlockPointerequ6 ; stack frame (skip over pushed BP
|
||||
; and the return address)
|
||||
NextBlockequ0 ;Field offsets in struct BlockHeader
|
||||
BlockCountequ2
|
||||
BLOCK_HEADER_SIZEequ4 ;Number of bytes in struct BlockHeader
|
||||
|
||||
.model small
|
||||
.code
|
||||
public _FindIDAverage2
|
||||
_FindIDAverage2 proc near
|
||||
push bp ;Save caller's stack frame
|
||||
mov bp,sp ;Point to our stack frame
|
||||
push di ;Preserve C register variables
|
||||
push si
|
||||
mov di,ds ;Prepare for SCASW
|
||||
mov es,di
|
||||
cld
|
||||
mov si,[bp+BlockPointer] ;Pointer to first block
|
||||
mov ax,[bp+SearchedForID] ;ID we're looking for
|
||||
sub dx,dx ;IDMatchSum = 0
|
||||
mov bp,dx ;IDMatchCount = 0
|
||||
;***stack frame no longer available***
|
||||
; Search through all the linked blocks until the last block
|
||||
; (marked with a NULL pointer to the next block) has been searched.
|
||||
BlockLoop:
|
||||
; Search through all the DataElement entries within this block
|
||||
; and accumulate data from all that match the desired ID.
|
||||
mov cx,[si+BlockCount]
|
||||
jcxz DoNextBlock;Skip this block if there's no data
|
||||
; to search through
|
||||
mov bx,cx ;We'll use BX to point to the
|
||||
shl bx,1 ; corresponding value entry in the
|
||||
; case of an ID match (BX is the
|
||||
; length in bytes of the ID array)
|
||||
; Point to the first DataElement entry within this block.
|
||||
lea di,[si+BLOCK_HEADER_SIZE]
|
||||
IntraBlockLoop:
|
||||
repnz scasw ;Search for the ID
|
||||
jnz DoNextBlock ;No match, the block is done
|
||||
inc bp ;We have a match; IDMatchCount++;
|
||||
add dx,[di+bx-2];IDMatchSum += DataPointer->Value;
|
||||
; (SCASW has advanced DI 2 bytes)
|
||||
and cx,cx ;Is there more data to search through?
|
||||
jnz IntraBlockLoop ;yes
|
||||
; Point to the next block and continue if that pointer isn't NULL.
|
||||
DoNextBlock:
|
||||
mov si,[si+NextBlock] ;Get pointer to the next block
|
||||
and si,si ;Is it a NULL pointer?
|
||||
jnz BlockLoop ;No, continue
|
||||
; Calculate the average of all matches.
|
||||
sub ax,ax ;Assume we found no matches
|
||||
and bp,bp
|
||||
jz Done ;We didn't find any matches, return 0
|
||||
xchg ax,dx ;Prepare for division
|
||||
div bp ;Return IDMatchSum / IDMatchCount
|
||||
Done: pop si ;Restore C register variables
|
||||
pop di
|
||||
pop bp ;Restore caller's stack frame
|
||||
ret
|
||||
_FindIDAverage2 ENDP
|
||||
end
|
||||
.model small
|
||||
.code
|
||||
public _FindIDAverage2
|
||||
_FindIDAverage2 proc near
|
||||
push bp ;Save caller's stack frame
|
||||
mov bp,sp ;Point to our stack frame
|
||||
push di ;Preserve C register variables
|
||||
push si
|
||||
mov di,ds ;Prepare for SCASW
|
||||
mov es,di
|
||||
cld
|
||||
mov si,[bp+BlockPointer] ;Pointer to first block
|
||||
mov ax,[bp+SearchedForID] ;ID we're looking for
|
||||
sub dx,dx ;IDMatchSum = 0
|
||||
mov bp,dx ;IDMatchCount = 0
|
||||
;***stack frame no longer available***
|
||||
; Search through all the linked blocks until the last block
|
||||
; (marked with a NULL pointer to the next block) has been searched.
|
||||
BlockLoop:
|
||||
; Search through all the DataElement entries within this block
|
||||
; and accumulate data from all that match the desired ID.
|
||||
mov cx,[si+BlockCount]
|
||||
jcxz DoNextBlock;Skip this block if there's no data
|
||||
; to search through
|
||||
mov bx,cx ;We'll use BX to point to the
|
||||
shl bx,1 ; corresponding value entry in the
|
||||
; case of an ID match (BX is the
|
||||
; length in bytes of the ID array)
|
||||
; Point to the first DataElement entry within this block.
|
||||
lea di,[si+BLOCK_HEADER_SIZE]
|
||||
IntraBlockLoop:
|
||||
repnz scasw ;Search for the ID
|
||||
jnz DoNextBlock ;No match, the block is done
|
||||
inc bp ;We have a match; IDMatchCount++;
|
||||
add dx,[di+bx-2];IDMatchSum += DataPointer->Value;
|
||||
; (SCASW has advanced DI 2 bytes)
|
||||
and cx,cx ;Is there more data to search through?
|
||||
jnz IntraBlockLoop ;yes
|
||||
; Point to the next block and continue if that pointer isn't NULL.
|
||||
DoNextBlock:
|
||||
mov si,[si+NextBlock] ;Get pointer to the next block
|
||||
and si,si ;Is it a NULL pointer?
|
||||
jnz BlockLoop ;No, continue
|
||||
; Calculate the average of all matches.
|
||||
sub ax,ax ;Assume we found no matches
|
||||
and bp,bp
|
||||
jz Done ;We didn't find any matches, return 0
|
||||
xchg ax,dx ;Prepare for division
|
||||
div bp ;Return IDMatchSum / IDMatchCount
|
||||
Done: pop si ;Restore C register variables
|
||||
pop di
|
||||
pop bp ;Restore caller's stack frame
|
||||
ret
|
||||
_FindIDAverage2 ENDP
|
||||
end
|
||||
```
|
||||
|
||||
The whole point of this rearrangement is to allow us to use **REP
|
||||
SCASW** to search through each block, and that's exactly what
|
||||
|
|
|
|||
76
09-01.md
76
09-01.md
|
|
@ -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
|
||||
flag to tie together partial results. For example, these instructions
|
||||
|
||||
ADD EAX,EBX
|
||||
ADC EDX,ECX
|
||||
```nasm
|
||||
ADD EAX,EBX
|
||||
ADC EDX,ECX
|
||||
```
|
||||
|
||||
could *not* be replaced
|
||||
|
||||
LEA EAX,[EAX+EBX]
|
||||
ADC EDX,ECX
|
||||
```nasm
|
||||
LEA EAX,[EAX+EBX]
|
||||
ADC EDX,ECX
|
||||
```
|
||||
|
||||
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
|
||||
variable to another such variable:
|
||||
|
||||
MOV ECX,4 ;# of 32-bit words to add
|
||||
CLC
|
||||
;no carry into the initial ADC
|
||||
ADDLOOP:
|
||||
```nasm
|
||||
MOV ECX,4 ;# of 32-bit words to add
|
||||
CLC
|
||||
;no carry into the initial ADC
|
||||
ADDLOOP:
|
||||
|
||||
MOV EAX,[ESI] ;get the next element of one array
|
||||
ADC [EDI],EAX ;add it to the other array, with carry
|
||||
LEA ESI,[ESI+4] ;advance one array's pointer
|
||||
LEA EDI,[EDI+4] ;advance the other array's pointer
|
||||
LOOP ADDLOOP
|
||||
MOV EAX,[ESI] ;get the next element of one array
|
||||
ADC [EDI],EAX ;add it to the other array, with carry
|
||||
LEA ESI,[ESI+4] ;advance one array's pointer
|
||||
LEA EDI,[EDI+4] ;advance the other array's pointer
|
||||
LOOP ADDLOOP
|
||||
```
|
||||
|
||||
(Yes, I could use **LODSD** instead of **MOV/LEA**; I'm just
|
||||
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:
|
||||
|
||||
CWD
|
||||
XOR AX,DX
|
||||
SUB AX,DX
|
||||
```nasm
|
||||
CWD
|
||||
XOR AX,DX
|
||||
SUB AX,DX
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
absolute value code:
|
||||
|
||||
AND AX,AX ;negative?
|
||||
JNS IsPositive ;no
|
||||
NEG AX ;yes,negate it
|
||||
IsPositive:
|
||||
```nasm
|
||||
AND AX,AX ;negative?
|
||||
JNS IsPositive ;no
|
||||
NEG AX ;yes,negate it
|
||||
IsPositive:
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
much data as possible a word at a time:
|
||||
|
||||
SHR CX,1 ;word count
|
||||
REP MOVSW ;copy as many words as possible
|
||||
ADC CX,CX ;CX=1 if copy length was odd,
|
||||
;0 else
|
||||
REP MOVSB ;copy any odd byte
|
||||
```nasm
|
||||
SHR CX,1 ;word count
|
||||
REP MOVSW ;copy as many words as possible
|
||||
ADC CX,CX ;CX=1 if copy length was odd,
|
||||
;0 else
|
||||
REP MOVSB ;copy any odd byte
|
||||
```
|
||||
|
||||
(**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
|
||||
above is faster than this:
|
||||
|
||||
SHR CX,1 ;word count
|
||||
REP MOVSW ;copy as many words as
|
||||
;possible
|
||||
JNC CopyDone ;done if even copy length
|
||||
MOVSB ;copy the odd byte
|
||||
CopyDone:
|
||||
```nasm
|
||||
SHR CX,1 ;word count
|
||||
REP MOVSW ;copy as many words as
|
||||
;possible
|
||||
JNC CopyDone ;done if even copy length
|
||||
MOVSB ;copy the odd byte
|
||||
CopyDone:
|
||||
```
|
||||
|
|
|
|||
40
09-02.md
40
09-02.md
|
|
@ -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
|
||||
display modes):
|
||||
|
||||
SHL AX,1 ;*2
|
||||
SH LAX,1 ;*4
|
||||
SH LAX,1 ;*8
|
||||
SH LAX,1 ;*16
|
||||
MO VBX,AX
|
||||
SH LAX,1 ;*32
|
||||
SH LAX,1 ;*64
|
||||
ADD AX,BX ;*80
|
||||
```nasm
|
||||
SHL AX,1 ;*2
|
||||
SH LAX,1 ;*4
|
||||
SH LAX,1 ;*8
|
||||
SH LAX,1 ;*16
|
||||
MO VBX,AX
|
||||
SH LAX,1 ;*32
|
||||
SH LAX,1 ;*64
|
||||
ADD AX,BX ;*80
|
||||
```
|
||||
|
||||
Using **LEA** on the 386, the above could be reduced to
|
||||
|
||||
LEA EAX,[EAX*2] ;*2
|
||||
LEA EAX,[EAX*8] ;*16
|
||||
LEA EAX,[EAX+EAX*4] ;*80
|
||||
```nasm
|
||||
LEA EAX,[EAX*2] ;*2
|
||||
LEA EAX,[EAX*8] ;*16
|
||||
LEA EAX,[EAX+EAX*4] ;*80
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Of course, on the 386, the shift and add version could also be reduced
|
||||
to this considerably more efficient code:
|
||||
|
||||
SH LAX,4 ;*16
|
||||
MOV BX,AX
|
||||
SHL AX,2 ;*64
|
||||
ADD AX,BX ;*80
|
||||
```nasm
|
||||
SH LAX,4 ;*16
|
||||
MOV BX,AX
|
||||
SHL AX,2 ;*64
|
||||
ADD AX,BX ;*80
|
||||
```
|
||||
|
||||
#### Speeding Up Multiplication {#Heading5}
|
||||
|
||||
|
|
|
|||
184
09-03.md
184
09-03.md
|
|
@ -40,94 +40,96 @@ using **REPZ CMPS** to check scanning matches.
|
|||
|
||||
**LISTING 9.1 L9-1.ASM**
|
||||
|
||||
; Searches a text buffer for a text string. Uses REPNZ SCASB to sca"n
|
||||
; the buffer for locations that match the first character of the
|
||||
; 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
|
||||
;
|
||||
; C small model-callable as:
|
||||
; unsigned char * FindString(unsigned char * Buffer,
|
||||
; 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
|
||||
; offset 0 in the data segment to avoid confusing a match at 0 with
|
||||
; no match found.
|
||||
Parmsstruc
|
||||
dw 2 dup(?) ;pushed BP/return address
|
||||
Buffer dw ? ;pointer to buffer to search
|
||||
BufferLength dw ? ;length of buffer to search
|
||||
SearchString dw ? ;pointer to string for which to search
|
||||
SearchStringLength dw ? ;length of string for which to search
|
||||
Parmsends
|
||||
.model small
|
||||
.code
|
||||
public _FindString
|
||||
_FindStringprocnear
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;point to our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
cld ;make string instructions increment pointers
|
||||
mov si,[bp+SearchString] ;pointer to string to search for
|
||||
mov bx,[bp+SearchStringLength] ;length of string
|
||||
and bx,bx
|
||||
jz FindStringNotFound ;no match if string is 0 length
|
||||
movd x,[bp+BufferLength] ;length of buffer
|
||||
sub dx,bx ;difference between buffer and string lengths
|
||||
jc FindStringNotFound ;no match if search string is
|
||||
; longer than buffer
|
||||
inc dx ;difference between buffer and search string
|
||||
; lengths, plus 1 (# of possible string start
|
||||
; locations to check in the buffer)
|
||||
mov di,ds
|
||||
mov es,di
|
||||
mov di,[bp+Buffer] ;point ES:DI to buffer to search thru
|
||||
lodsb ;put the first byte of the search string in AL
|
||||
mov bp,si ;set aside pointer to the second search byte
|
||||
dec bx ;don't need to compare the first byte of the
|
||||
; string with CMPS; we'll do it with SCAS
|
||||
FindStringLoop:
|
||||
mov cx,dx ;put remaining buffer search length in CX
|
||||
repnz scasb ;scan for the first byte of the string
|
||||
jnz FindStringNotFound ;not found, so there's no match
|
||||
;found, so we have a potential match-check the
|
||||
; rest of this candidate location
|
||||
push di ;remember the address of the next byte to scan
|
||||
mov dx,cx ;set aside the remaining length to search in
|
||||
; the buffer
|
||||
mov si,bp ;point to the rest of the search string
|
||||
mov cx,bx ;string length (minus first byte)
|
||||
shr cx,1 ;convert to word for faster search
|
||||
jnc FindStringWord ;do word search if no odd byte
|
||||
cmpsb ;compare the odd byte
|
||||
jnz FindStringNoMatch ;odd byte doesn't match, so we
|
||||
; haven't found the search string here
|
||||
FindStringWord:
|
||||
jcxz FindStringFound ;test whether we've already checked
|
||||
; the whole string; if so, this is a match
|
||||
; bytes long; if so, we've found a match
|
||||
repz cmpsw ;check the rest of the string a word at a time
|
||||
jz FindStringFound ;it's a match
|
||||
FindStringNoMatch:
|
||||
pop di ;get back pointer to the next byte to scan
|
||||
and dx,dx ;is there anything left to check?
|
||||
jnz FindStringLoop ;yes-check next byte
|
||||
FindStringNotFound:
|
||||
sub ax,ax ;return a NULL pointer indicating that the
|
||||
jmp FindStringDone ; string was not found
|
||||
FindStringFound:
|
||||
pop ax ;point to the buffer location at which the
|
||||
dec ax ; string was found (earlier we pushed the
|
||||
; address of the byte after the start of the
|
||||
; potential match)
|
||||
FindStringDone:
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_FindStringendp
|
||||
end
|
||||
```nasm
|
||||
; Searches a text buffer for a text string. Uses REPNZ SCASB to sca"n
|
||||
; the buffer for locations that match the first character of the
|
||||
; 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
|
||||
;
|
||||
; C small model-callable as:
|
||||
; unsigned char * FindString(unsigned char * Buffer,
|
||||
; 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
|
||||
; offset 0 in the data segment to avoid confusing a match at 0 with
|
||||
; no match found.
|
||||
Parmsstruc
|
||||
dw 2 dup(?) ;pushed BP/return address
|
||||
Buffer dw ? ;pointer to buffer to search
|
||||
BufferLength dw ? ;length of buffer to search
|
||||
SearchString dw ? ;pointer to string for which to search
|
||||
SearchStringLength dw ? ;length of string for which to search
|
||||
Parmsends
|
||||
.model small
|
||||
.code
|
||||
public _FindString
|
||||
_FindStringprocnear
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;point to our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
cld ;make string instructions increment pointers
|
||||
mov si,[bp+SearchString] ;pointer to string to search for
|
||||
mov bx,[bp+SearchStringLength] ;length of string
|
||||
and bx,bx
|
||||
jz FindStringNotFound ;no match if string is 0 length
|
||||
movd x,[bp+BufferLength] ;length of buffer
|
||||
sub dx,bx ;difference between buffer and string lengths
|
||||
jc FindStringNotFound ;no match if search string is
|
||||
; longer than buffer
|
||||
inc dx ;difference between buffer and search string
|
||||
; lengths, plus 1 (# of possible string start
|
||||
; locations to check in the buffer)
|
||||
mov di,ds
|
||||
mov es,di
|
||||
mov di,[bp+Buffer] ;point ES:DI to buffer to search thru
|
||||
lodsb ;put the first byte of the search string in AL
|
||||
mov bp,si ;set aside pointer to the second search byte
|
||||
dec bx ;don't need to compare the first byte of the
|
||||
; string with CMPS; we'll do it with SCAS
|
||||
FindStringLoop:
|
||||
mov cx,dx ;put remaining buffer search length in CX
|
||||
repnz scasb ;scan for the first byte of the string
|
||||
jnz FindStringNotFound ;not found, so there's no match
|
||||
;found, so we have a potential match-check the
|
||||
; rest of this candidate location
|
||||
push di ;remember the address of the next byte to scan
|
||||
mov dx,cx ;set aside the remaining length to search in
|
||||
; the buffer
|
||||
mov si,bp ;point to the rest of the search string
|
||||
mov cx,bx ;string length (minus first byte)
|
||||
shr cx,1 ;convert to word for faster search
|
||||
jnc FindStringWord ;do word search if no odd byte
|
||||
cmpsb ;compare the odd byte
|
||||
jnz FindStringNoMatch ;odd byte doesn't match, so we
|
||||
; haven't found the search string here
|
||||
FindStringWord:
|
||||
jcxz FindStringFound ;test whether we've already checked
|
||||
; the whole string; if so, this is a match
|
||||
; bytes long; if so, we've found a match
|
||||
repz cmpsw ;check the rest of the string a word at a time
|
||||
jz FindStringFound ;it's a match
|
||||
FindStringNoMatch:
|
||||
pop di ;get back pointer to the next byte to scan
|
||||
and dx,dx ;is there anything left to check?
|
||||
jnz FindStringLoop ;yes-check next byte
|
||||
FindStringNotFound:
|
||||
sub ax,ax ;return a NULL pointer indicating that the
|
||||
jmp FindStringDone ; string was not found
|
||||
FindStringFound:
|
||||
pop ax ;point to the buffer location at which the
|
||||
dec ax ; string was found (earlier we pushed the
|
||||
; address of the byte after the start of the
|
||||
; potential match)
|
||||
FindStringDone:
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_FindStringendp
|
||||
end
|
||||
```
|
||||
|
|
|
|||
258
09-04.md
258
09-04.md
|
|
@ -12,134 +12,138 @@ pages: 178-180
|
|||
|
||||
**LISTING 9.2 L9-2.ASM**
|
||||
|
||||
; Searches a text buffer for a text string. Uses REPNZ SCASB to scan
|
||||
; the buffer for locations that match a specified character of the
|
||||
; 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,
|
||||
; unsigned int BufferLength, unsigned char * SearchString,
|
||||
; 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
|
||||
; offset 0 in the data segment to avoid confusing a match at 0 with
|
||||
; no match found.
|
||||
Parms struc
|
||||
dw 2 dup(?) ;pushed BP/return address
|
||||
Buffer dw ? ;pointer to buffer to search
|
||||
BufferLength dw ? ;length of buffer to search
|
||||
SearchString dw ? ;pointer to string for which to search
|
||||
SearchStringLength dw ? ;length of string for which to search
|
||||
ScanCharOffset dw ? ;offset in string of character for
|
||||
; which to scan
|
||||
Parmsends
|
||||
.model small
|
||||
.code
|
||||
public _FindString
|
||||
_FindStringprocnear
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;point to our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
cld ;make string instructions increment pointers
|
||||
mov si,[bp+SearchString] ;pointer to string to search for
|
||||
mov cx,[bp+SearchStringLength] ;length of string
|
||||
jcxz FindStringNotFound ;no match if string is 0 length
|
||||
mov dx,[bp+BufferLength] ;length of buffer
|
||||
sub dx,cx ;difference between buffer and search
|
||||
; lengths
|
||||
jc FindStringNotFound ;no match if search string is
|
||||
; longer than buffer
|
||||
inc dx ; difference between buffer and search string
|
||||
; lengths, plus 1 (# of possible string start
|
||||
; locations to check in the buffer)
|
||||
mov di,ds
|
||||
mov es,di
|
||||
mov di,[bp+Buffer] ;point ES:DI to buffer to search thru
|
||||
mov bx,[bp+ScanCharOffset] ;offset in string of character
|
||||
; on which to scan
|
||||
add di,bx ;point ES:DI to first buffer byte to scan
|
||||
mov al,[si+bx] ;put the scan character in AL
|
||||
inc bx ;set BX to the offset back to the start of the
|
||||
; potential full match after a scan match,
|
||||
; accounting for the 1-byte overrun of
|
||||
; REPNZ SCASB
|
||||
FindStringLoop:
|
||||
mov cx,dx ;put remaining buffer search length in CX
|
||||
repnz scasb ;scan for the scan byte
|
||||
jnz FindStringNotFound ;not found, so there's no match
|
||||
;found, so we have a potential match-check the
|
||||
; rest of this candidate location
|
||||
push di ;remember the address of the next byte to scan
|
||||
mov dx,cx ;set aside the remaining length to search in
|
||||
; the buffer
|
||||
sub di,bx ;point back to the potential start of the
|
||||
; match in the buffer
|
||||
mov si,[bp+SearchString] ;point to the start of the string
|
||||
mov cx,[bp+SearchStringLength] ;string length
|
||||
shr cx,1 ;convert to word for faster search
|
||||
jnc FindStringWord ;do word search if no odd byte
|
||||
cmpsb ;compare the odd byte
|
||||
jnz FindStringNoMatch ;odd byte doesn't match, so we
|
||||
; haven't found the search string here
|
||||
FindStringWord:
|
||||
jcxz FindStringFound ;if the string is only 1 byte long,
|
||||
; we've found a match
|
||||
repz cmpsw ;check the rest of the string a word at a time
|
||||
jz FindStringFound ;it's a match
|
||||
FindStringNoMatch:
|
||||
pop di ;get back pointer to the next byte to scan
|
||||
and dx,dx ;is there anything left to check?
|
||||
jnz FindStringLoop ;yes-check next byte
|
||||
FindStringNotFound:
|
||||
sub ax,ax ;return a NULL pointer indicating that the
|
||||
jmp FindStringDone ; string was not found
|
||||
FindStringFound:
|
||||
pop ax ;point to the buffer location at which the
|
||||
sub ax,bx ; string was found (earlier we pushed the
|
||||
; address of the byte after the scan match)
|
||||
FindStringDone:
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_FindStringendp
|
||||
end
|
||||
```nasm
|
||||
; Searches a text buffer for a text string. Uses REPNZ SCASB to scan
|
||||
; the buffer for locations that match a specified character of the
|
||||
; 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,
|
||||
; unsigned int BufferLength, unsigned char * SearchString,
|
||||
; 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
|
||||
; offset 0 in the data segment to avoid confusing a match at 0 with
|
||||
; no match found.
|
||||
Parms struc
|
||||
dw 2 dup(?) ;pushed BP/return address
|
||||
Buffer dw ? ;pointer to buffer to search
|
||||
BufferLength dw ? ;length of buffer to search
|
||||
SearchString dw ? ;pointer to string for which to search
|
||||
SearchStringLength dw ? ;length of string for which to search
|
||||
ScanCharOffset dw ? ;offset in string of character for
|
||||
; which to scan
|
||||
Parmsends
|
||||
.model small
|
||||
.code
|
||||
public _FindString
|
||||
_FindStringprocnear
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;point to our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
cld ;make string instructions increment pointers
|
||||
mov si,[bp+SearchString] ;pointer to string to search for
|
||||
mov cx,[bp+SearchStringLength] ;length of string
|
||||
jcxz FindStringNotFound ;no match if string is 0 length
|
||||
mov dx,[bp+BufferLength] ;length of buffer
|
||||
sub dx,cx ;difference between buffer and search
|
||||
; lengths
|
||||
jc FindStringNotFound ;no match if search string is
|
||||
; longer than buffer
|
||||
inc dx ; difference between buffer and search string
|
||||
; lengths, plus 1 (# of possible string start
|
||||
; locations to check in the buffer)
|
||||
mov di,ds
|
||||
mov es,di
|
||||
mov di,[bp+Buffer] ;point ES:DI to buffer to search thru
|
||||
mov bx,[bp+ScanCharOffset] ;offset in string of character
|
||||
; on which to scan
|
||||
add di,bx ;point ES:DI to first buffer byte to scan
|
||||
mov al,[si+bx] ;put the scan character in AL
|
||||
inc bx ;set BX to the offset back to the start of the
|
||||
; potential full match after a scan match,
|
||||
; accounting for the 1-byte overrun of
|
||||
; REPNZ SCASB
|
||||
FindStringLoop:
|
||||
mov cx,dx ;put remaining buffer search length in CX
|
||||
repnz scasb ;scan for the scan byte
|
||||
jnz FindStringNotFound ;not found, so there's no match
|
||||
;found, so we have a potential match-check the
|
||||
; rest of this candidate location
|
||||
push di ;remember the address of the next byte to scan
|
||||
mov dx,cx ;set aside the remaining length to search in
|
||||
; the buffer
|
||||
sub di,bx ;point back to the potential start of the
|
||||
; match in the buffer
|
||||
mov si,[bp+SearchString] ;point to the start of the string
|
||||
mov cx,[bp+SearchStringLength] ;string length
|
||||
shr cx,1 ;convert to word for faster search
|
||||
jnc FindStringWord ;do word search if no odd byte
|
||||
cmpsb ;compare the odd byte
|
||||
jnz FindStringNoMatch ;odd byte doesn't match, so we
|
||||
; haven't found the search string here
|
||||
FindStringWord:
|
||||
jcxz FindStringFound ;if the string is only 1 byte long,
|
||||
; we've found a match
|
||||
repz cmpsw ;check the rest of the string a word at a time
|
||||
jz FindStringFound ;it's a match
|
||||
FindStringNoMatch:
|
||||
pop di ;get back pointer to the next byte to scan
|
||||
and dx,dx ;is there anything left to check?
|
||||
jnz FindStringLoop ;yes-check next byte
|
||||
FindStringNotFound:
|
||||
sub ax,ax ;return a NULL pointer indicating that the
|
||||
jmp FindStringDone ; string was not found
|
||||
FindStringFound:
|
||||
pop ax ;point to the buffer location at which the
|
||||
sub ax,bx ; string was found (earlier we pushed the
|
||||
; address of the byte after the scan match)
|
||||
FindStringDone:
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_FindStringendp
|
||||
end
|
||||
```
|
||||
|
||||
**LISTING 9.3 L9-3.C**
|
||||
|
||||
/* Program to exercise buffer-search routines in Listings 9.1 & 9.2 */
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
```c
|
||||
/* Program to exercise buffer-search routines in Listings 9.1 & 9.2 */
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DISPLAY_LENGTH 40
|
||||
extern unsigned char * FindString(unsigned char *, unsigned int,
|
||||
unsigned char *, unsigned int, unsigned int);
|
||||
void main(void);
|
||||
static unsigned char TestBuffer[] = "When, in the course of human \
|
||||
events, it becomes necessary for one people to dissolve the \
|
||||
political bands which have connected them with another, and to \
|
||||
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...";
|
||||
void main() {
|
||||
static unsigned char TestString[] = "equal";
|
||||
unsigned char TempBuffer[DISPLAY_LENGTH+1];
|
||||
unsigned char *MatchPtr;
|
||||
/* Search for TestString and report the results */
|
||||
if ((MatchPtr = FindString(TestBuffer,
|
||||
(unsigned int) strlen(TestBuffer), TestString,
|
||||
(unsigned int) strlen(TestString), 1)) == NULL) {
|
||||
/* TestString wasn't found */
|
||||
printf("\"%s\" not found\n", TestString);
|
||||
} else {
|
||||
/* TestString was found. Zero-terminate TempBuffer; strncpy
|
||||
won't do it if DISPLAY_LENGTH characters are copied */
|
||||
TempBuffer[DISPLAY_LENGTH] = 0;
|
||||
printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n",
|
||||
TestString, DISPLAY_LENGTH,
|
||||
strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH));
|
||||
}
|
||||
}
|
||||
#define DISPLAY_LENGTH 40
|
||||
extern unsigned char * FindString(unsigned char *, unsigned int,
|
||||
unsigned char *, unsigned int, unsigned int);
|
||||
void main(void);
|
||||
static unsigned char TestBuffer[] = "When, in the course of human \
|
||||
events, it becomes necessary for one people to dissolve the \
|
||||
political bands which have connected them with another, and to \
|
||||
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...";
|
||||
void main() {
|
||||
static unsigned char TestString[] = "equal";
|
||||
unsigned char TempBuffer[DISPLAY_LENGTH+1];
|
||||
unsigned char *MatchPtr;
|
||||
/* Search for TestString and report the results */
|
||||
if ((MatchPtr = FindString(TestBuffer,
|
||||
(unsigned int) strlen(TestBuffer), TestString,
|
||||
(unsigned int) strlen(TestString), 1)) == NULL) {
|
||||
/* TestString wasn't found */
|
||||
printf("\"%s\" not found\n", TestString);
|
||||
} else {
|
||||
/* TestString was found. Zero-terminate TempBuffer; strncpy
|
||||
won't do it if DISPLAY_LENGTH characters are copied */
|
||||
TempBuffer[DISPLAY_LENGTH] = 0;
|
||||
printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n",
|
||||
TestString, DISPLAY_LENGTH,
|
||||
strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
57
09-05.md
57
09-05.md
|
|
@ -43,38 +43,39 @@ one.
|
|||
|
||||
**LISTING 9.4 L9-4.ASM**
|
||||
|
||||
.
|
||||
;--------------------------------------------------------------------------
|
||||
; Sorts an array of ints. C callable (small model). 25 bytes.
|
||||
; void sort( int num, int a[] );
|
||||
;
|
||||
; Courtesy of David Stafford.
|
||||
;--------------------------------------------------------------------------
|
||||
```nasm
|
||||
;--------------------------------------------------------------------------
|
||||
; Sorts an array of ints. C callable (small model). 25 bytes.
|
||||
; void sort( int num, int a[] );
|
||||
;
|
||||
; Courtesy of David Stafford.
|
||||
;--------------------------------------------------------------------------
|
||||
|
||||
.model small
|
||||
.code
|
||||
public _sort
|
||||
.model small
|
||||
.code
|
||||
public _sort
|
||||
|
||||
top: mov dx,[bx] ;swap two adjacent integers
|
||||
xchg dx,[bx+2]
|
||||
xchg dx,[bx]
|
||||
cmp dx,[bx] ;did we put them in the right order?
|
||||
jl top ;no, swap them back
|
||||
inc bx ;go to next integer
|
||||
inc bx
|
||||
loop top
|
||||
_sort: pop dx ;get return address (entry point)
|
||||
pop cx ;get count
|
||||
pop bx ;get pointer
|
||||
push bx ;restore pointer
|
||||
dec cx ;decrement count
|
||||
push cx ;save count
|
||||
push dx ;restore return address
|
||||
jg top ;if cx > 0
|
||||
top: mov dx,[bx] ;swap two adjacent integers
|
||||
xchg dx,[bx+2]
|
||||
xchg dx,[bx]
|
||||
cmp dx,[bx] ;did we put them in the right order?
|
||||
jl top ;no, swap them back
|
||||
inc bx ;go to next integer
|
||||
inc bx
|
||||
loop top
|
||||
_sort: pop dx ;get return address (entry point)
|
||||
pop cx ;get count
|
||||
pop bx ;get pointer
|
||||
push bx ;restore pointer
|
||||
dec cx ;decrement count
|
||||
push cx ;save count
|
||||
push dx ;restore return address
|
||||
jg top ;if cx > 0
|
||||
|
||||
ret
|
||||
ret
|
||||
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### Full 32-Bit Division {#Heading8}
|
||||
|
||||
|
|
|
|||
154
09-06.md
154
09-06.md
|
|
@ -12,91 +12,95 @@ pages: 182-185
|
|||
|
||||
**LISTING 9.5 L9-5.ASM**
|
||||
|
||||
; Divides an arbitrarily long unsigned dividend by a 16-bit unsigned
|
||||
; divisor. C near-callable as:
|
||||
; unsigned int Div(unsigned int * Dividend,
|
||||
; int DividendLength, unsigned int Divisor,
|
||||
; unsigned int * Quotient);
|
||||
;
|
||||
; Returns the remainder of the division.
|
||||
;
|
||||
; Tested with TASM 2.
|
||||
```nasm
|
||||
; Divides an arbitrarily long unsigned dividend by a 16-bit unsigned
|
||||
; divisor. C near-callable as:
|
||||
; unsigned int Div(unsigned int * Dividend,
|
||||
; int DividendLength, unsigned int Divisor,
|
||||
; unsigned int * Quotient);
|
||||
;
|
||||
; Returns the remainder of the division.
|
||||
;
|
||||
; Tested with TASM 2.
|
||||
|
||||
parms struc
|
||||
dw 2 dup (?) ;pushed BP & return address
|
||||
Dividend dw ? ;pointer to value to divide, stored in Intel
|
||||
; order, with lsb at lowest address, msb at
|
||||
; highest. Must be composed of an integral
|
||||
; number of words
|
||||
DividendLength dw ? ;# of bytes in Dividend. Must be a multiple
|
||||
; of 2
|
||||
Divisor dw ? ;value by which to divide. Must not be zero,
|
||||
; or a Divide By Zero interrupt will occur
|
||||
Quotient dw ? ;pointer to buffer in which to store the
|
||||
; result of the division, in Intel order.
|
||||
; The quotient returned is of the same
|
||||
; length as the dividend
|
||||
parmsends
|
||||
parms struc
|
||||
dw 2 dup (?) ;pushed BP & return address
|
||||
Dividend dw ? ;pointer to value to divide, stored in Intel
|
||||
; order, with lsb at lowest address, msb at
|
||||
; highest. Must be composed of an integral
|
||||
; number of words
|
||||
DividendLength dw ? ;# of bytes in Dividend. Must be a multiple
|
||||
; of 2
|
||||
Divisor dw ? ;value by which to divide. Must not be zero,
|
||||
; or a Divide By Zero interrupt will occur
|
||||
Quotient dw ? ;pointer to buffer in which to store the
|
||||
; result of the division, in Intel order.
|
||||
; The quotient returned is of the same
|
||||
; length as the dividend
|
||||
parmsends
|
||||
|
||||
.model small
|
||||
.code
|
||||
public _Div
|
||||
_Divprocnear
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;point to our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
.model small
|
||||
.code
|
||||
public _Div
|
||||
_Divprocnear
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;point to our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
|
||||
std ;we're working from msb to lsb
|
||||
mov ax,ds
|
||||
mov es,ax ;for STOS
|
||||
mov cx,[bp+DividendLength]
|
||||
sub cx,2
|
||||
mov si,[bp+Dividend]
|
||||
add si,cx ;point to the last word of the dividend
|
||||
; (the most significant word)
|
||||
mov di,[bp+Quotient]
|
||||
add di,cx ;point to the last word of the quotient
|
||||
; buffer (the most significant word)
|
||||
mov bx,[bp+Divisor]
|
||||
shr cx,1
|
||||
inc cx ;# of words to process
|
||||
sub dx,dx ;convert initial divisor word to a 32-bit
|
||||
;value for DIV
|
||||
DivLoop:
|
||||
lod sw ;get next most significant word of divisor
|
||||
div bx
|
||||
sto sw ;save this word of the quotient
|
||||
;DX contains the remainder at this point,
|
||||
; ready to prepend to the next divisor word
|
||||
loop DivLoop
|
||||
mov ax,dx ;return the remainder
|
||||
cld ;restore default Direction flag setting
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_Divendp
|
||||
end
|
||||
std ;we're working from msb to lsb
|
||||
mov ax,ds
|
||||
mov es,ax ;for STOS
|
||||
mov cx,[bp+DividendLength]
|
||||
sub cx,2
|
||||
mov si,[bp+Dividend]
|
||||
add si,cx ;point to the last word of the dividend
|
||||
; (the most significant word)
|
||||
mov di,[bp+Quotient]
|
||||
add di,cx ;point to the last word of the quotient
|
||||
; buffer (the most significant word)
|
||||
mov bx,[bp+Divisor]
|
||||
shr cx,1
|
||||
inc cx ;# of words to process
|
||||
sub dx,dx ;convert initial divisor word to a 32-bit
|
||||
;value for DIV
|
||||
DivLoop:
|
||||
lod sw ;get next most significant word of divisor
|
||||
div bx
|
||||
sto sw ;save this word of the quotient
|
||||
;DX contains the remainder at this point,
|
||||
; ready to prepend to the next divisor word
|
||||
loop DivLoop
|
||||
mov ax,dx ;return the remainder
|
||||
cld ;restore default Direction flag setting
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_Divendp
|
||||
end
|
||||
```
|
||||
|
||||
**LISTING 9.6 L9-6.C**
|
||||
|
||||
/* Sample use of Div function to perform division when the result
|
||||
doesn't fit in 16 bits */
|
||||
```c
|
||||
/* 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,
|
||||
int DividendLength, unsigned int Divisor,
|
||||
unsigned int * Quotient);
|
||||
extern unsigned int Div(unsigned int * Dividend,
|
||||
int DividendLength, unsigned int Divisor,
|
||||
unsigned int * Quotient);
|
||||
|
||||
main() {
|
||||
unsigned long m, i = 0x20000001;
|
||||
unsigned int k, j = 0x10;
|
||||
main() {
|
||||
unsigned long m, i = 0x20000001;
|
||||
unsigned int k, j = 0x10;
|
||||
|
||||
k = Div((unsigned int *)&i, sizeof(i), j, (unsigned int *)&m);
|
||||
printf("%lu / %u = %lu r %u\n", i, j, m, k);
|
||||
}
|
||||
k = Div((unsigned int *)&i, sizeof(i), j, (unsigned int *)&m);
|
||||
printf("%lu / %u = %lu r %u\n", i, j, m, k);
|
||||
}
|
||||
```
|
||||
|
||||
#### Sweet Spot Revisited {#Heading9}
|
||||
|
||||
|
|
|
|||
60
09-07.md
60
09-07.md
|
|
@ -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
|
||||
the following:
|
||||
|
||||
Ptr dd ?
|
||||
:
|
||||
mov word ptr [Ptr],5
|
||||
mov word ptr [Ptr+2],1000h
|
||||
jmp [Ptr]
|
||||
```nasm
|
||||
Ptr dd ?
|
||||
:
|
||||
mov word ptr [Ptr],5
|
||||
mov word ptr [Ptr+2],1000h
|
||||
jmp [Ptr]
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -87,22 +89,24 @@ solution; if you have one, pass it along.
|
|||
|
||||
**LISTING 9.7 L9-7.ASM**
|
||||
|
||||
; Program to perform a direct far jump to address 1000:5.
|
||||
; *** 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.
|
||||
```nasm
|
||||
; Program to perform a direct far jump to address 1000:5.
|
||||
; *** 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.
|
||||
|
||||
FarSeg segment at 01000h
|
||||
org 5
|
||||
FarLabel label far
|
||||
FarSeg ends
|
||||
FarSeg segment at 01000h
|
||||
org 5
|
||||
FarLabel label far
|
||||
FarSeg ends
|
||||
|
||||
.model small
|
||||
.code
|
||||
start:
|
||||
jmp FarLabel
|
||||
end start
|
||||
.model small
|
||||
.code
|
||||
start:
|
||||
jmp FarLabel
|
||||
end start
|
||||
```
|
||||
|
||||
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:
|
||||
|
|
@ -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
|
||||
protected mode,
|
||||
|
||||
sub eax,eax
|
||||
inc eax
|
||||
```nasm
|
||||
sub eax,eax
|
||||
inc eax
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
of speed for space. Second,
|
||||
|
||||
or ebx,-1
|
||||
```nasm
|
||||
or ebx,-1
|
||||
```
|
||||
|
||||
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
|
||||
**EBX** to -1; this is a classic trade-off of—gee, it's not a trade-off
|
||||
|
|
|
|||
100
10-02.md
100
10-02.md
|
|
@ -64,28 +64,30 @@ default optimization was used. All times measured with the Zen timer
|
|||
|
||||
**LISTING 10.1 L10-1.C**
|
||||
|
||||
/* Finds and returns the greatest common divisor of two positive
|
||||
integers. Works by trying every integral divisor between the
|
||||
smaller of the two integers and 1, until a divisor that divides
|
||||
both integers evenly is found. All C code tested with Microsoft
|
||||
and Borland compilers.*/
|
||||
```c
|
||||
/* Finds and returns the greatest common divisor of two positive
|
||||
integers. Works by trying every integral divisor between the
|
||||
smaller of the two integers and 1, until a divisor that divides
|
||||
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 temp, trial_divisor;
|
||||
/* Swap if necessary to make sure that int1 >= int2 */
|
||||
if (int1 < int2) {
|
||||
temp = int1;
|
||||
int1 = int2;
|
||||
int2 = temp;
|
||||
}
|
||||
/* Now just try every divisor from int2 on down, until a common
|
||||
divisor is found. This can never be an infinite loop because
|
||||
1 divides everything evenly */
|
||||
for (trial_divisor = int2; ((int1 % trial_divisor) != 0) ||
|
||||
((int2 % trial_divisor) != 0); trial_divisor—)
|
||||
;
|
||||
return(trial_divisor);
|
||||
}
|
||||
unsigned int gcd(unsigned int int1, unsigned int int2) {
|
||||
unsigned int temp, trial_divisor;
|
||||
/* Swap if necessary to make sure that int1 >= int2 */
|
||||
if (int1 < int2) {
|
||||
temp = int1;
|
||||
int1 = int2;
|
||||
int2 = temp;
|
||||
}
|
||||
/* Now just try every divisor from int2 on down, until a common
|
||||
divisor is found. This can never be an infinite loop because
|
||||
1 divides everything evenly */
|
||||
for (trial_divisor = int2; ((int1 % trial_divisor) != 0) ||
|
||||
((int2 % trial_divisor) != 0); trial_divisor—)
|
||||
;
|
||||
return(trial_divisor);
|
||||
}
|
||||
```
|
||||
|
||||
#### Wasted Breakthroughs {#Heading5}
|
||||
|
||||
|
|
@ -99,32 +101,34 @@ in Listing 10.2.
|
|||
|
||||
**LISTING 10.2 L10-2.C**
|
||||
|
||||
/* Finds and returns the greatest common divisor of two positive
|
||||
integers. Works by subtracting the smaller integer from the
|
||||
larger integer until either the values match (in which case
|
||||
that's the gcd), or the larger integer becomes the smaller of
|
||||
the two, in which case the two integers swap roles and the
|
||||
subtraction process continues. */
|
||||
```c
|
||||
/* Finds and returns the greatest common divisor of two positive
|
||||
integers. Works by subtracting the smaller integer from the
|
||||
larger integer until either the values match (in which case
|
||||
that's the gcd), or the larger integer becomes the smaller of
|
||||
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 temp;
|
||||
/* If the two integers are the same, that's the gcd and we're
|
||||
done */
|
||||
if (int1 == int2) {
|
||||
return(int1);
|
||||
}
|
||||
/* Swap if necessary to make sure that int1 >= int2 */
|
||||
if (int1 < int2) {
|
||||
temp = int1;
|
||||
int1 = int2;
|
||||
int2 = temp;
|
||||
}
|
||||
unsigned int gcd(unsigned int int1, unsigned int int2) {
|
||||
unsigned int temp;
|
||||
/* If the two integers are the same, that's the gcd and we're
|
||||
done */
|
||||
if (int1 == int2) {
|
||||
return(int1);
|
||||
}
|
||||
/* Swap if necessary to make sure that int1 >= int2 */
|
||||
if (int1 < int2) {
|
||||
temp = int1;
|
||||
int1 = int2;
|
||||
int2 = temp;
|
||||
}
|
||||
|
||||
/* Subtract int2 from int1 until int1 is no longer the larger of
|
||||
the two */
|
||||
do {
|
||||
int1 -= int2;
|
||||
} while (int1 > int2);
|
||||
/* Now recursively call this function to continue the process */
|
||||
return(gcd(int1, int2));
|
||||
}
|
||||
/* Subtract int2 from int1 until int1 is no longer the larger of
|
||||
the two */
|
||||
do {
|
||||
int1 -= int2;
|
||||
} while (int1 > int2);
|
||||
/* Now recursively call this function to continue the process */
|
||||
return(gcd(int1, int2));
|
||||
}
|
||||
```
|
||||
|
|
|
|||
140
10-03.md
140
10-03.md
|
|
@ -45,49 +45,51 @@ Figure 10.3. Listing 10.3 is an implementation of Euclid's algorithm.
|
|||
|
||||
**LISTING 10.3 L10-3.C**
|
||||
|
||||
/* Finds and returns the greatest common divisor of two integers.
|
||||
Uses Euclid's algorithm: divides the larger integer by the
|
||||
smaller; if the remainder is 0, the smaller integer is the GCD,
|
||||
otherwise the smaller integer becomes the larger integer, the
|
||||
remainder becomes the smaller integer, and the process is
|
||||
repeated. */
|
||||
```c
|
||||
/* Finds and returns the greatest common divisor of two integers.
|
||||
Uses Euclid's algorithm: divides the larger integer by the
|
||||
smaller; if the remainder is 0, the smaller integer is the GCD,
|
||||
otherwise the smaller integer becomes the larger integer, the
|
||||
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 temp;
|
||||
/* If the two integers are the same, that's the GCD and we're
|
||||
done */
|
||||
if (int1 == int2) {
|
||||
return(int1);
|
||||
}
|
||||
/* Swap if necessary to make sure that int1 >= int2 */
|
||||
if (int1 < int2) {
|
||||
temp = int1;
|
||||
int1 = int2;
|
||||
int2 = temp;
|
||||
}
|
||||
unsigned int gcd(unsigned int int1, unsigned int int2) {
|
||||
unsigned int temp;
|
||||
/* If the two integers are the same, that's the GCD and we're
|
||||
done */
|
||||
if (int1 == int2) {
|
||||
return(int1);
|
||||
}
|
||||
/* Swap if necessary to make sure that int1 >= int2 */
|
||||
if (int1 < int2) {
|
||||
temp = int1;
|
||||
int1 = int2;
|
||||
int2 = temp;
|
||||
}
|
||||
|
||||
/* Now call the recursive form of the function, which assumes
|
||||
that the first parameter is the larger of the two */
|
||||
return(gcd_recurs(int1, int2));
|
||||
}
|
||||
/* Now call the recursive form of the function, which assumes
|
||||
that the first parameter is the larger of the two */
|
||||
return(gcd_recurs(int1, int2));
|
||||
}
|
||||
|
||||
static unsigned int gcd_recurs(unsigned int larger_int,
|
||||
unsigned int smaller_int)
|
||||
{
|
||||
int temp;
|
||||
static unsigned int gcd_recurs(unsigned int larger_int,
|
||||
unsigned int smaller_int)
|
||||
{
|
||||
int temp;
|
||||
|
||||
/* If the remainder of larger_int divided by smaller_int is 0,
|
||||
then smaller_int is the gcd */
|
||||
if ((temp = larger_int % smaller_int) == 0) {
|
||||
return(smaller_int);
|
||||
}
|
||||
/* Make smaller_int the larger integer and the remainder the
|
||||
smaller integer, and call this function recursively to
|
||||
continue the process */
|
||||
return(gcd_recurs(smaller_int, temp));
|
||||
}
|
||||
/* If the remainder of larger_int divided by smaller_int is 0,
|
||||
then smaller_int is the gcd */
|
||||
if ((temp = larger_int % smaller_int) == 0) {
|
||||
return(smaller_int);
|
||||
}
|
||||
/* Make smaller_int the larger integer and the remainder the
|
||||
smaller integer, and call this function recursively to
|
||||
continue the process */
|
||||
return(gcd_recurs(smaller_int, temp));
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -129,38 +131,40 @@ recursive operations that Listing 10.3 does.
|
|||
|
||||
**LISTING 10.4 L10-4.C**
|
||||
|
||||
/* Finds and returns the greatest common divisor of two integers.
|
||||
Uses Euclid's algorithm: divides the larger integer by the
|
||||
smaller; if the remainder is 0, the smaller integer is the GCD,
|
||||
otherwise the smaller integer becomes the larger integer, the
|
||||
remainder becomes the smaller integer, and the process is
|
||||
repeated. Avoids code recursion. */
|
||||
```c
|
||||
/* Finds and returns the greatest common divisor of two integers.
|
||||
Uses Euclid's algorithm: divides the larger integer by the
|
||||
smaller; if the remainder is 0, the smaller integer is the GCD,
|
||||
otherwise the smaller integer becomes the larger integer, the
|
||||
remainder becomes the smaller integer, and the process is
|
||||
repeated. Avoids code recursion. */
|
||||
|
||||
unsigned int gcd(unsigned int int1, unsigned int int2) {
|
||||
unsigned int temp;
|
||||
unsigned int gcd(unsigned int int1, unsigned int int2) {
|
||||
unsigned int temp;
|
||||
|
||||
/* Swap if necessary to make sure that int1 >= int2 */
|
||||
if (int1 < int2) {
|
||||
temp = int1;
|
||||
int1 = int2;
|
||||
int2 = temp;
|
||||
}
|
||||
/* 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 int2 to int1, and the remainder to int2, then
|
||||
repeat */
|
||||
for (;;) {
|
||||
/* If the remainder of int1 divided by int2 is 0, then int2 is
|
||||
the gcd */
|
||||
if ((temp = int1 % int2) == 0) {
|
||||
return(int2);
|
||||
}
|
||||
/* Make int2 the larger integer and the remainder the
|
||||
smaller integer, and repeat the process */
|
||||
int1 = int2;
|
||||
int2 = temp;
|
||||
}
|
||||
}
|
||||
/* Swap if necessary to make sure that int1 >= int2 */
|
||||
if (int1 < int2) {
|
||||
temp = int1;
|
||||
int1 = int2;
|
||||
int2 = temp;
|
||||
}
|
||||
/* 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 int2 to int1, and the remainder to int2, then
|
||||
repeat */
|
||||
for (;;) {
|
||||
/* If the remainder of int1 divided by int2 is 0, then int2 is
|
||||
the gcd */
|
||||
if ((temp = int1 % int2) == 0) {
|
||||
return(int2);
|
||||
}
|
||||
/* Make int2 the larger integer and the remainder the
|
||||
smaller integer, and repeat the process */
|
||||
int1 = int2;
|
||||
int2 = temp;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Patient Optimization {#Heading7}
|
||||
|
||||
|
|
|
|||
164
10-04.md
164
10-04.md
|
|
@ -12,93 +12,95 @@ pages: 200-203
|
|||
|
||||
**LISTING 10.5 L10-5.ASM**
|
||||
|
||||
; Finds and returns the greatest common divisor of two integers.
|
||||
; Uses Euclid's algorithm: divides the larger integer by the
|
||||
; smaller; if the remainder is 0, the smaller integer is the GCD,
|
||||
; otherwise the smaller integer becomes the larger integer, the
|
||||
; 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);
|
||||
```nasm
|
||||
; Finds and returns the greatest common divisor of two integers.
|
||||
; Uses Euclid's algorithm: divides the larger integer by the
|
||||
; smaller; if the remainder is 0, the smaller integer is the GCD,
|
||||
; otherwise the smaller integer becomes the larger integer, the
|
||||
; 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);
|
||||
|
||||
; Parameter structure:
|
||||
parms struc
|
||||
dw ? ;pushed BP
|
||||
dw ? ;pushed return address
|
||||
int1 dw ? ;integers for which to find
|
||||
int2 dw ? ; the GCD
|
||||
parms ends
|
||||
; Parameter structure:
|
||||
parms struc
|
||||
dw ? ;pushed BP
|
||||
dw ? ;pushed return address
|
||||
int1 dw ? ;integers for which to find
|
||||
int2 dw ? ; the GCD
|
||||
parms ends
|
||||
|
||||
.model small
|
||||
.code
|
||||
public _gcd
|
||||
align 2
|
||||
_gcd proc near
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;set up our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
.model small
|
||||
.code
|
||||
public _gcd
|
||||
align 2
|
||||
_gcd proc near
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;set up our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
|
||||
;Swap if necessary to make sure that int1 >= int2
|
||||
mov ax,int1[bp]
|
||||
mov bx,int2[bp]
|
||||
cmp ax,bx ;is int1 >= int2?
|
||||
jnb IntsSet ;yes, so we're all set
|
||||
xchg ax,bx ;no, so swap int1 and int2
|
||||
IntsSet:
|
||||
;Swap if necessary to make sure that int1 >= int2
|
||||
mov ax,int1[bp]
|
||||
mov bx,int2[bp]
|
||||
cmp ax,bx ;is int1 >= int2?
|
||||
jnb IntsSet ;yes, so we're all set
|
||||
xchg ax,bx ;no, so swap int1 and int2
|
||||
IntsSet:
|
||||
|
||||
; 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
|
||||
; int2 to int1, and the remainder to int2, then repeat.
|
||||
GCDLoop:
|
||||
;if the remainder of int1 divided by
|
||||
; int2 is 0, then int2 is the gcd
|
||||
sub dx,dx ;prepare int1 in DX:AX for division
|
||||
div bx ;int1/int2; remainder is in DX
|
||||
and dx,dx ;is the remainder zero?
|
||||
jz Done ;yes, so int2 (BX) is the gcd
|
||||
;no, so move int2 to int1 and the
|
||||
; remainder to int2, and repeat the
|
||||
; process
|
||||
mov ax,bx ;int1 = int2;
|
||||
mov bx,dx ;int2 = remainder from DIV
|
||||
; 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
|
||||
; int2 to int1, and the remainder to int2, then repeat.
|
||||
GCDLoop:
|
||||
;if the remainder of int1 divided by
|
||||
; int2 is 0, then int2 is the gcd
|
||||
sub dx,dx ;prepare int1 in DX:AX for division
|
||||
div bx ;int1/int2; remainder is in DX
|
||||
and dx,dx ;is the remainder zero?
|
||||
jz Done ;yes, so int2 (BX) is the gcd
|
||||
;no, so move int2 to int1 and the
|
||||
; remainder to int2, and repeat the
|
||||
; process
|
||||
mov ax,bx ;int1 = int2;
|
||||
mov bx,dx ;int2 = remainder from DIV
|
||||
|
||||
;—start of loop unrolling; the above is repeated three times—
|
||||
sub dx,dx ;prepare int1 in DX:AX for division
|
||||
div bx ;int1/int2; remainder is in DX
|
||||
and dx,dx ;is the remainder zero?
|
||||
jz Done ;yes, so int2 (BX) is the gcd
|
||||
mov ax,bx ;int1 = int2;
|
||||
mov bx,dx ;int2 = remainder from DIV
|
||||
;—
|
||||
sub dx,dx ;prepare int1 in DX:AX for division
|
||||
div bx ;int1/int2; remainder is in DX
|
||||
and dx,dx ;is the remainder zero?
|
||||
jz Done ;yes, so int2 (BX) is the gcd
|
||||
mov ax,bx ;int1 = int2;
|
||||
mov bx,dx ;int2 = remainder from DIV
|
||||
;—
|
||||
sub dx,dx ;prepare int1 in DX:AX for division
|
||||
div bx ;int1/int2; remainder is in DX
|
||||
and dx,dx ;is the remainder zero?
|
||||
jz Done ;yes, so int2 (BX) is the gcd
|
||||
mov ax,bx ;int1 = int2;
|
||||
mov bx,dx ;int2 = remainder from DIV
|
||||
;—end of loop unrolling—
|
||||
jmp GCDLoop
|
||||
;—start of loop unrolling; the above is repeated three times—
|
||||
sub dx,dx ;prepare int1 in DX:AX for division
|
||||
div bx ;int1/int2; remainder is in DX
|
||||
and dx,dx ;is the remainder zero?
|
||||
jz Done ;yes, so int2 (BX) is the gcd
|
||||
mov ax,bx ;int1 = int2;
|
||||
mov bx,dx ;int2 = remainder from DIV
|
||||
;—
|
||||
sub dx,dx ;prepare int1 in DX:AX for division
|
||||
div bx ;int1/int2; remainder is in DX
|
||||
and dx,dx ;is the remainder zero?
|
||||
jz Done ;yes, so int2 (BX) is the gcd
|
||||
mov ax,bx ;int1 = int2;
|
||||
mov bx,dx ;int2 = remainder from DIV
|
||||
;—
|
||||
sub dx,dx ;prepare int1 in DX:AX for division
|
||||
div bx ;int1/int2; remainder is in DX
|
||||
and dx,dx ;is the remainder zero?
|
||||
jz Done ;yes, so int2 (BX) is the gcd
|
||||
mov ax,bx ;int1 = int2;
|
||||
mov bx,dx ;int2 = remainder from DIV
|
||||
;—end of loop unrolling—
|
||||
jmp GCDLoop
|
||||
|
||||
align2
|
||||
Done:
|
||||
mov ax,bx ;return the GCD
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_gcd endp
|
||||
end
|
||||
align2
|
||||
Done:
|
||||
mov ax,bx ;return the GCD
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_gcd endp
|
||||
end
|
||||
```
|
||||
|
||||
Assembly language optimization is pattern matching on a local scale.
|
||||
Frankly, it's also the sort of boring, brute-force work that people are
|
||||
|
|
|
|||
12
11-01.md
12
11-01.md
|
|
@ -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
|
||||
fast indivisible 32-bit load from memory, as in
|
||||
|
||||
les ax,dword ptr [LongVar]
|
||||
mov dx,es
|
||||
```nasm
|
||||
les ax,dword ptr [LongVar]
|
||||
mov dx,es
|
||||
```
|
||||
|
||||
which loads **LongVar** into DX:AX faster than this:
|
||||
|
||||
mov ax,word ptr [LongVar]
|
||||
mov dx,word ptr [LongVar+2]
|
||||
```nasm
|
||||
mov ax,word ptr [LongVar]
|
||||
mov dx,word ptr [LongVar+2]
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
40
11-02.md
40
11-02.md
|
|
@ -126,25 +126,27 @@ shortly.)
|
|||
|
||||
**LISTING 11.1 L11-1.ASM**
|
||||
|
||||
;
|
||||
; *** Listing 11.1 ***
|
||||
;
|
||||
; Measures the performance of an immediate move to
|
||||
; memory, in order to demonstrate that the prefetch
|
||||
; queue cycle-eater is alive and well on the AT.
|
||||
;
|
||||
jmp Skip
|
||||
;
|
||||
even ;always make sure word-sized memory
|
||||
; variables are word-aligned!
|
||||
WordVar dw 0
|
||||
;
|
||||
Skip:
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
mov [WordVar],0
|
||||
endm
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
;
|
||||
; *** Listing 11.1 ***
|
||||
;
|
||||
; Measures the performance of an immediate move to
|
||||
; memory, in order to demonstrate that the prefetch
|
||||
; queue cycle-eater is alive and well on the AT.
|
||||
;
|
||||
jmp Skip
|
||||
;
|
||||
even ;always make sure word-sized memory
|
||||
; variables are word-aligned!
|
||||
WordVar dw 0
|
||||
;
|
||||
Skip:
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
mov [WordVar],0
|
||||
endm
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
70
11-03.md
70
11-03.md
|
|
@ -101,23 +101,25 @@ refresh.
|
|||
|
||||
**LISTING 11.2 L11-2.ASM**
|
||||
|
||||
;
|
||||
; *** Listing 11.2 ***
|
||||
;
|
||||
; Measures the performance of accesses to word-sized
|
||||
; variables that start at odd addresses (are not
|
||||
; word-aligned).
|
||||
;
|
||||
Skip:
|
||||
push ds
|
||||
pop es
|
||||
mov si,1 ;source and destination are the same
|
||||
mov di,si ; and both are not word-aligned
|
||||
mov cx,1000 ;move 1000 words
|
||||
cld
|
||||
call ZTimerOn
|
||||
rep movsw
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
;
|
||||
; *** Listing 11.2 ***
|
||||
;
|
||||
; Measures the performance of accesses to word-sized
|
||||
; variables that start at odd addresses (are not
|
||||
; word-aligned).
|
||||
;
|
||||
Skip:
|
||||
push ds
|
||||
pop es
|
||||
mov si,1 ;source and destination are the same
|
||||
mov di,si ; and both are not word-aligned
|
||||
mov cx,1000 ;move 1000 words
|
||||
cld
|
||||
call ZTimerOn
|
||||
rep movsw
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -128,22 +130,24 @@ we predicted.
|
|||
|
||||
**LISTING 11.3 L11-3.ASM**
|
||||
|
||||
;
|
||||
; *** Listing 11.3 ***
|
||||
;
|
||||
; Measures the performance of accesses to word-sized
|
||||
; variables that start at even addresses (are word-aligned).
|
||||
;
|
||||
Skip:
|
||||
push ds
|
||||
pop es
|
||||
sub si,si ;source and destination are the same
|
||||
mov di,si ; and both are word-aligned
|
||||
mov cx,1000 ;move 1000 words
|
||||
cld
|
||||
call ZTimerOn
|
||||
rep movsw
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
;
|
||||
; *** Listing 11.3 ***
|
||||
;
|
||||
; Measures the performance of accesses to word-sized
|
||||
; variables that start at even addresses (are word-aligned).
|
||||
;
|
||||
Skip:
|
||||
push ds
|
||||
pop es
|
||||
sub si,si ;source and destination are the same
|
||||
mov di,si ; and both are word-aligned
|
||||
mov cx,1000 ;move 1000 words
|
||||
cld
|
||||
call ZTimerOn
|
||||
rep movsw
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
35
11-04.md
35
11-04.md
|
|
@ -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
|
||||
no problems until I timed the following code:
|
||||
|
||||
|
||||
mov cx,1000
|
||||
call ZTimerOn
|
||||
LoopTop:
|
||||
loop LoopTop
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
mov cx,1000
|
||||
call ZTimerOn
|
||||
LoopTop:
|
||||
loop LoopTop
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
|
@ -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
|
||||
cycles per loop:
|
||||
|
||||
mov cx,1000
|
||||
call ZTimerOn
|
||||
even
|
||||
LoopTop:
|
||||
loop LoopTop
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
mov cx,1000
|
||||
call ZTimerOn
|
||||
even
|
||||
LoopTop:
|
||||
loop LoopTop
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
While word-aligning branch destinations can improve branching
|
||||
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
|
||||
offsets of your subroutines, as in:
|
||||
|
||||
even
|
||||
FindChar proc near
|
||||
:
|
||||
```nasm
|
||||
even
|
||||
FindChar proc near
|
||||
:
|
||||
```
|
||||
|
||||
In my experience, this simple practice is the one form of code alignment
|
||||
that consistently provides a reasonable return for bytes and effort
|
||||
|
|
|
|||
26
11-06.md
26
11-06.md
|
|
@ -113,15 +113,17 @@ Theory confirmed.
|
|||
|
||||
**LISTING 11.4 L11-4.ASM**
|
||||
|
||||
;
|
||||
; *** Listing 11.4 ***
|
||||
;
|
||||
; Measures the performance of adding an immediate value
|
||||
; to a register, for comparison with Listing 11.5, which
|
||||
; adds an immediate value to a memory variable.
|
||||
;
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
add dx,100h
|
||||
endm
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
;
|
||||
; *** Listing 11.4 ***
|
||||
;
|
||||
; Measures the performance of adding an immediate value
|
||||
; to a register, for comparison with Listing 11.5, which
|
||||
; adds an immediate value to a memory variable.
|
||||
;
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
add dx,100h
|
||||
endm
|
||||
call ZTimerOff
|
||||
```
|
||||
|
|
|
|||
40
11-07.md
40
11-07.md
|
|
@ -12,25 +12,27 @@ pages: 224-226
|
|||
|
||||
**LISTING 11.5 L11-5.ASM**
|
||||
|
||||
;
|
||||
; *** Listing 11.5 ***
|
||||
;
|
||||
; Measures the performance of adding an immediate value
|
||||
; to a memory variable, for comparison with Listing 11.4,
|
||||
; which adds an immediate value to a register.
|
||||
;
|
||||
jmp Skip
|
||||
;
|
||||
even ;always make sure word-sized memory
|
||||
; variables are word-aligned!
|
||||
WordVar dw 0
|
||||
;
|
||||
Skip:
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
add [WordVar]100h
|
||||
endm
|
||||
call ZTimerOff
|
||||
```nasm
|
||||
;
|
||||
; *** Listing 11.5 ***
|
||||
;
|
||||
; Measures the performance of adding an immediate value
|
||||
; to a memory variable, for comparison with Listing 11.4,
|
||||
; which adds an immediate value to a register.
|
||||
;
|
||||
jmp Skip
|
||||
;
|
||||
even ;always make sure word-sized memory
|
||||
; variables are word-aligned!
|
||||
WordVar dw 0
|
||||
;
|
||||
Skip:
|
||||
call ZTimerOn
|
||||
rept 1000
|
||||
add [WordVar]100h
|
||||
endm
|
||||
call ZTimerOff
|
||||
```
|
||||
|
||||
What's going on? Simply this: Instruction fetching is controlling
|
||||
overall execution time on *both* processors. Both the 8088 in a PC and
|
||||
|
|
|
|||
84
11-08.md
84
11-08.md
|
|
@ -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
|
||||
with. The code works out like this:
|
||||
|
||||
jmpshort popfskip
|
||||
popfiret:
|
||||
iret; branches to the instruction after the
|
||||
; call, popping the word below the address
|
||||
; pushed by CALL into the FLAGS register
|
||||
popfskip:
|
||||
call far ptr popfiret
|
||||
;pushes the segment:offset of the next
|
||||
; instruction on the stack just above
|
||||
; the flags word, setting things up so
|
||||
; that IRET will branch to the next
|
||||
; instruction and pop the flags
|
||||
; When execution reaches the instruction following this comment,
|
||||
; the word that was on top of the stack when JMP SHORT POPFSKIP
|
||||
; was reached has been popped into the FLAGS register, just as
|
||||
; if a POPF instruction had been executed.
|
||||
```nasm
|
||||
jmpshort popfskip
|
||||
popfiret:
|
||||
iret; branches to the instruction after the
|
||||
; call, popping the word below the address
|
||||
; pushed by CALL into the FLAGS register
|
||||
popfskip:
|
||||
call far ptr popfiret
|
||||
;pushes the segment:offset of the next
|
||||
; instruction on the stack just above
|
||||
; the flags word, setting things up so
|
||||
; that IRET will branch to the next
|
||||
; instruction and pop the flags
|
||||
; When execution reaches the instruction following this comment,
|
||||
; the word that was on top of the stack when JMP SHORT POPFSKIP
|
||||
; was reached has been popped into the FLAGS register, just as
|
||||
; if a POPF instruction had been executed.
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
|
@ -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
|
||||
shrinking the workaround code by 1 byte:
|
||||
|
||||
EMULATE_POPF macro
|
||||
local popfskip, popfiret
|
||||
jmp short popfskip
|
||||
popfiret:
|
||||
iret
|
||||
popfskip:
|
||||
push cs
|
||||
call popfiret
|
||||
endm
|
||||
```nasm
|
||||
EMULATE_POPF macro
|
||||
local popfskip, popfiret
|
||||
jmp short popfskip
|
||||
popfiret:
|
||||
iret
|
||||
popfskip:
|
||||
push cs
|
||||
call popfiret
|
||||
endm
|
||||
```
|
||||
|
||||
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
|
||||
emulates **POPF** with just one branch, but wipes out AX:
|
||||
|
||||
EMULATE_POPF_TRASH_AX macro
|
||||
push cs
|
||||
mov ax,offset $+5
|
||||
push ax
|
||||
iret
|
||||
endm
|
||||
```nasm
|
||||
EMULATE_POPF_TRASH_AX macro
|
||||
push cs
|
||||
mov ax,offset $+5
|
||||
push ax
|
||||
iret
|
||||
endm
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
an 8088.)
|
||||
|
||||
.286
|
||||
:
|
||||
EMULATE_POPFmacro
|
||||
pushcs
|
||||
pushoffset $+4
|
||||
iret
|
||||
endm
|
||||
```nasm
|
||||
.286
|
||||
:
|
||||
EMULATE_POPFmacro
|
||||
pushcs
|
||||
pushoffset $+4
|
||||
iret
|
||||
endm
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
28
12-01.md
28
12-01.md
|
|
@ -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
|
||||
|
||||
LoopTop:
|
||||
add ax,[bx+si]
|
||||
add si,2
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```nasm
|
||||
LoopTop:
|
||||
add ax,[bx+si]
|
||||
add si,2
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```
|
||||
|
||||
with this
|
||||
|
||||
add si,bx
|
||||
LoopTop:
|
||||
add ax,[si]
|
||||
add si,2
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
sub si,bx
|
||||
```nasm
|
||||
add si,bx
|
||||
LoopTop:
|
||||
add ax,[si]
|
||||
add si,2
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
sub si,bx
|
||||
```
|
||||
|
|
|
|||
66
12-02.md
66
12-02.md
|
|
@ -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
|
||||
addressing registers. For example, in the code
|
||||
|
||||
MOV BX,OFFSET MemVar
|
||||
MOV AX,[BX]
|
||||
```nasm
|
||||
MOV BX,OFFSET MemVar
|
||||
MOV AX,[BX]
|
||||
```
|
||||
|
||||
there's no way that the 486 can calculate the address referenced by
|
||||
**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,
|
||||
postdecrementing, as in the following
|
||||
|
||||
LoopTop:
|
||||
add ax,[si]
|
||||
add si,2
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```nasm
|
||||
LoopTop:
|
||||
add ax,[si]
|
||||
add si,2
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```
|
||||
|
||||
is faster than preincrementing, as in:
|
||||
|
||||
LoopTop:
|
||||
add si,2
|
||||
add ax,[SI]
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```nasm
|
||||
LoopTop:
|
||||
add si,2
|
||||
add ax,[SI]
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
cycle is lost. Therefore, whereas this code
|
||||
|
||||
mov bx,offset MemVar
|
||||
mov ax,[bx]
|
||||
inc dx
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```nasm
|
||||
mov bx,offset MemVar
|
||||
mov ax,[bx]
|
||||
inc dx
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```
|
||||
|
||||
loses two cycles from interrupting the address calculation pipeline,
|
||||
this code
|
||||
|
||||
mov bx,offset MemVar
|
||||
inc dx
|
||||
mov ax,[bx]
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```nasm
|
||||
mov bx,offset MemVar
|
||||
inc dx
|
||||
mov ax,[bx]
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```
|
||||
|
||||
loses only one cycle, and this code
|
||||
|
||||
mov bx,offset MemVar
|
||||
inc dx
|
||||
dec cx
|
||||
mov ax,[bx]
|
||||
jnz LoopTop
|
||||
```nasm
|
||||
mov bx,offset MemVar
|
||||
inc dx
|
||||
dec cx
|
||||
mov ax,[bx]
|
||||
jnz LoopTop
|
||||
```
|
||||
|
||||
loses no cycles at all. Apparently, the 486's addressing calculation
|
||||
pipeline actually starts 2 cycles ahead, as shown in Figure 12.2. (In
|
||||
|
|
|
|||
70
12-03.md
70
12-03.md
|
|
@ -41,22 +41,26 @@ pops up only spottily when the stack pointer is involved.
|
|||
|
||||
For example, you'd certainly expect a sequence such as
|
||||
|
||||
:
|
||||
pop ax
|
||||
ret
|
||||
pop ax
|
||||
et
|
||||
:
|
||||
```nasm
|
||||
:
|
||||
pop ax
|
||||
ret
|
||||
pop ax
|
||||
et
|
||||
:
|
||||
```
|
||||
|
||||
to exhibit the addressing pipeline interruption phenomenon (SP is both
|
||||
destination and addressing register for both instructions, according to
|
||||
Intel), but this code runs in six cycles per **POP/RET** pair, matching
|
||||
the official execution times exactly. Likewise, a sequence like
|
||||
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
pop ax
|
||||
```nasm
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
pop ax
|
||||
```
|
||||
|
||||
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
|
||||
pipeline. For example
|
||||
|
||||
add sp,10h
|
||||
ret
|
||||
```nasm
|
||||
add sp,10h
|
||||
ret
|
||||
```
|
||||
|
||||
loses two cycles because SP is the explicit destination of one
|
||||
instruction and then the implied addressing register for the next, and
|
||||
the sequence
|
||||
|
||||
add sp,10h
|
||||
pop ax
|
||||
```nasm
|
||||
add sp,10h
|
||||
pop ax
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
mov ah,o
|
||||
:
|
||||
mov cx,[MemVar1]
|
||||
mov al,[MemVar2]
|
||||
add cx,ax
|
||||
```nasm
|
||||
mov ah,o
|
||||
:
|
||||
mov cx,[MemVar1]
|
||||
mov al,[MemVar2]
|
||||
add cx,ax
|
||||
```
|
||||
|
||||
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
|
||||
rearranging the instructions so that the byte register load isn't
|
||||
immediately followed by the word register usage, like so:
|
||||
|
||||
mov ah,o
|
||||
:
|
||||
mov al,[MemVar2]
|
||||
mov cx,[MemVar1]
|
||||
add cx,ax
|
||||
```nasm
|
||||
mov ah,o
|
||||
:
|
||||
mov al,[MemVar2]
|
||||
mov cx,[MemVar1]
|
||||
add cx,ax
|
||||
```
|
||||
|
||||
Strange as it may seem, this rule is neither arbitrary nor nonsensical.
|
||||
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**
|
||||
sequence like
|
||||
|
||||
mov bx,offset MemTable
|
||||
:
|
||||
mov al,[si]
|
||||
xlat
|
||||
```nasm
|
||||
mov bx,offset MemTable
|
||||
:
|
||||
mov al,[si]
|
||||
xlat
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
105
12-04.md
105
12-04.md
|
|
@ -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
|
||||
used to point to memory, one cycle is lost. So, for example, this code
|
||||
|
||||
mov al,bl
|
||||
mov cx,dx
|
||||
mov si,[di]
|
||||
```nasm
|
||||
mov al,bl
|
||||
mov cx,dx
|
||||
mov si,[di]
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
register is used to address memory costs two cycles, as in
|
||||
|
||||
mov bl,al
|
||||
mov cl,3
|
||||
mov bx,[si]
|
||||
|
||||
```nasm
|
||||
mov bl,al
|
||||
mov cl,3
|
||||
mov bx,[si]
|
||||
```
|
||||
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
|
||||
a register is used to address memory. Therefore,
|
||||
|
||||
mov cx,3
|
||||
mov dl,al
|
||||
mov si,[bx]
|
||||
```nasm
|
||||
mov cx,3
|
||||
mov dl,al
|
||||
mov si,[bx]
|
||||
```
|
||||
|
||||
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
|
||||
observer would expect the following code to run in 3 cycles:
|
||||
|
||||
mov bx,offset MemVar
|
||||
mov cl,al
|
||||
mov ax,[bx]
|
||||
```nasm
|
||||
mov bx,offset MemVar
|
||||
mov cl,al
|
||||
mov ax,[bx]
|
||||
```
|
||||
|
||||
A more sophisticated programmer would expect to lose one cycle, because
|
||||
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**
|
||||
|
||||
; Measures the effect of loading a byte register 2 cycles before
|
||||
; using a register to address memory.
|
||||
mov bp,2 ;run the test code twice to make sure
|
||||
; it's cached
|
||||
sub bx,bx
|
||||
CacheFillLoop:
|
||||
call ZTimerOn ;start timing
|
||||
rept 1000
|
||||
mov dl,cl
|
||||
nop
|
||||
mov ax,[bx]
|
||||
endm
|
||||
call ZTimerOff ;stop timing
|
||||
dec bp
|
||||
jz Done
|
||||
jmp CacheFillLoop
|
||||
Done:
|
||||
```nasm
|
||||
; Measures the effect of loading a byte register 2 cycles before
|
||||
; using a register to address memory.
|
||||
mov bp,2 ;run the test code twice to make sure
|
||||
; it's cached
|
||||
sub bx,bx
|
||||
CacheFillLoop:
|
||||
call ZTimerOn ;start timing
|
||||
rept 1000
|
||||
mov dl,cl
|
||||
nop
|
||||
mov ax,[bx]
|
||||
endm
|
||||
call ZTimerOff ;stop timing
|
||||
dec bp
|
||||
jz Done
|
||||
jmp CacheFillLoop
|
||||
Done:
|
||||
```
|
||||
|
||||
**LISTING 12.2 LST12-2.ASM**
|
||||
|
||||
; Measures the effect of loading a byte register 1 cycle before
|
||||
; using a register to address memory.
|
||||
mov bp,2 ;run the test code twice to make sure
|
||||
; it's cached
|
||||
sub bx,bx
|
||||
CacheFillLoop:
|
||||
call ZTimerOn ;start timing
|
||||
rept 1000
|
||||
nop
|
||||
mov dl,cl
|
||||
mov ax,[bx]
|
||||
endm
|
||||
call ZTimerOff ;stop timing
|
||||
dec bp
|
||||
jz Done
|
||||
jmp CacheFillLoop
|
||||
Done:
|
||||
```nasm
|
||||
; Measures the effect of loading a byte register 1 cycle before
|
||||
; using a register to address memory.
|
||||
mov bp,2 ;run the test code twice to make sure
|
||||
; it's cached
|
||||
sub bx,bx
|
||||
CacheFillLoop:
|
||||
call ZTimerOn ;start timing
|
||||
rept 1000
|
||||
nop
|
||||
mov dl,cl
|
||||
mov ax,[bx]
|
||||
endm
|
||||
call ZTimerOff ;stop timing
|
||||
dec bp
|
||||
jz Done
|
||||
jmp CacheFillLoop
|
||||
Done:
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
20
13-01.md
20
13-01.md
|
|
@ -78,10 +78,12 @@ also lines, at a rate of three instructions for every two characters!
|
|||
|
||||
**LISTING 13.1 L13-1.ASM**
|
||||
|
||||
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
|
||||
; appropriately 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
|
||||
; appropriately for the pair
|
||||
```
|
||||
|
||||
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,
|
||||
|
|
@ -113,10 +115,12 @@ the intervening instruction takes two cycles, there's no penalty at all.
|
|||
|
||||
**LISTING 13.2 L13-2.ASM**
|
||||
|
||||
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
|
||||
; appropriately for the pair
|
||||
```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
|
||||
; appropriately for the pair
|
||||
```
|
||||
|
||||
At this point, Terje had nearly doubled the performance of this code
|
||||
simply by moving one instruction. (Note that swapping the instructions
|
||||
|
|
|
|||
54
13-02.md
54
13-02.md
|
|
@ -22,18 +22,22 @@ throughput of two cycles/char."
|
|||
|
||||
**LISTING 13.3 L13-3.ASM**
|
||||
|
||||
mov bl,[di] ;get the state value for the pair
|
||||
mov di,[bp+OFFS] ;get the next pair of characters
|
||||
mov ax,[bx+8000h] ;increment word and line count
|
||||
add dx,ax ; appropriately for the pair
|
||||
```nasm
|
||||
mov bl,[di] ;get the state value for the pair
|
||||
mov di,[bp+OFFS] ;get the next pair of characters
|
||||
mov ax,[bx+8000h] ;increment word and line count
|
||||
add dx,ax ; appropriately for the pair
|
||||
```
|
||||
|
||||
**LISTING 13.4 L13-4.ASM**
|
||||
|
||||
mov bl,[di] ;get the state value for the pair
|
||||
mov di,[bp+OFFS] ;get the next pair of characters
|
||||
add dx,ax ;increment word and line count
|
||||
; appropriately for the pair
|
||||
mov ax,[bx+8000h] ;get increments for next time
|
||||
```nasm
|
||||
mov bl,[di] ;get the state value for the pair
|
||||
mov di,[bp+OFFS] ;get the next pair of characters
|
||||
add dx,ax ;increment word and line count
|
||||
; 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
|
||||
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
|
||||
significant byte first in memory, or *big endian*), like so:
|
||||
|
||||
lodsd
|
||||
bswap
|
||||
stosd
|
||||
```nasm
|
||||
lodsd
|
||||
bswap
|
||||
stosd
|
||||
```
|
||||
|
||||
**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
|
||||
|
|
@ -94,17 +100,19 @@ memory, isn't it?
|
|||
|
||||
**LISTING 13.5 L13-5.ASM**
|
||||
|
||||
mov cx,[initialskip]
|
||||
shl ecx,16 ;put skip value in upper half of ECX
|
||||
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
|
||||
inc cx ;set next skip value
|
||||
ror ecx,16 ;put loop count in CX
|
||||
dec cx ;count down loop
|
||||
jnz looptop
|
||||
```nasm
|
||||
mov cx,[initialskip]
|
||||
shl ecx,16 ;put skip value in upper half of ECX
|
||||
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
|
||||
inc cx ;set next skip value
|
||||
ror ecx,16 ;put loop count in CX
|
||||
dec cx ;count down loop
|
||||
jnz looptop
|
||||
```
|
||||
|
||||
Not necessarily. Shifts and rotates are among the worst performing
|
||||
instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes
|
||||
|
|
|
|||
42
13-03.md
42
13-03.md
|
|
@ -21,17 +21,19 @@ registers into one 32-bit register much more useful.
|
|||
|
||||
**LISTING 13.6 L13-6.ASM**
|
||||
|
||||
mov cx,[initialskip]
|
||||
bswap ecx ;put skip value in upper half of ECX
|
||||
mov cx,100 ;put loop count in CX
|
||||
looptop:
|
||||
:
|
||||
bswap ecx ;make skip value word accessible in CX
|
||||
add bx,cx ;skip BX ahead
|
||||
inc cx ;set next skip value
|
||||
bswap ecx ;put loop count in CX
|
||||
dec cx ;count down loop
|
||||
jnz looptop
|
||||
```nasm
|
||||
mov cx,[initialskip]
|
||||
bswap ecx ;put skip value in upper half of ECX
|
||||
mov cx,100 ;put loop count in CX
|
||||
looptop:
|
||||
:
|
||||
bswap ecx ;make skip value word accessible in CX
|
||||
add bx,cx ;skip BX ahead
|
||||
inc cx ;set next skip value
|
||||
bswap ecx ;put loop count in CX
|
||||
dec cx ;count down loop
|
||||
jnz looptop
|
||||
```
|
||||
|
||||
### 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
|
||||
two cycles. Therefore,
|
||||
|
||||
mov ax,[bx]
|
||||
push ax
|
||||
```nasm
|
||||
mov ax,[bx]
|
||||
push ax
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
|
@ -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
|
||||
code as follows:
|
||||
|
||||
mov ax,1
|
||||
db 0c1h, 0e0h, 001h
|
||||
mov dx,ax
|
||||
```nasm
|
||||
mov ax,1
|
||||
db 0c1h, 0e0h, 001h
|
||||
mov dx,ax
|
||||
```
|
||||
|
||||
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,
|
||||
|
|
|
|||
32
13-04.md
32
13-04.md
|
|
@ -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
|
||||
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
|
||||
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
|
||||
calculation outside the loop as, for example, in a loop like this:
|
||||
|
||||
LoopTop:
|
||||
add ax,DataTable[ebx*2]
|
||||
inc ebx
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```nasm
|
||||
LoopTop:
|
||||
add ax,DataTable[ebx*2]
|
||||
inc ebx
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
```
|
||||
|
||||
You could change this to the following for greater performance:
|
||||
|
||||
add ebx,ebx ;ebx*2
|
||||
LoopTop:
|
||||
add ax,DataTable[ebx]
|
||||
add ebxX,2
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
shr ebx,1 ;ebx*2/2
|
||||
```nasm
|
||||
add ebx,ebx ;ebx*2
|
||||
LoopTop:
|
||||
add ax,DataTable[ebx]
|
||||
add ebxX,2
|
||||
dec cx
|
||||
jnz LoopTop
|
||||
shr ebx,1 ;ebx*2/2
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
256
14-04.md
256
14-04.md
|
|
@ -12,148 +12,152 @@ pages: 268-271
|
|||
|
||||
**LISTING 14.1 L14-1.C**
|
||||
|
||||
/* Searches a buffer for a specified pattern. In case of a mismatch,
|
||||
uses the value of the mismatched byte to skip across as many
|
||||
potential match locations as possible (partial Boyer-Moore).
|
||||
Returns start offset of first match searching forward, or NULL if
|
||||
no match is found.
|
||||
Tested with Borland C++ in C mode and the small model. */
|
||||
```c
|
||||
/* Searches a buffer for a specified pattern. In case of a mismatch,
|
||||
uses the value of the mismatched byte to skip across as many
|
||||
potential match locations as possible (partial Boyer-Moore).
|
||||
Returns start offset of first match searching forward, or NULL if
|
||||
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 int BufferLength, unsigned char * PatternPtr,
|
||||
unsigned int PatternLength)
|
||||
{
|
||||
unsigned char * WorkingPatternPtr, * WorkingBufferPtr;
|
||||
unsigned int CompCount, SkipTable[256], Skip, DistanceMatched;
|
||||
int i;
|
||||
unsigned char * FindString(unsigned char * BufferPtr,
|
||||
unsigned int BufferLength, unsigned char * PatternPtr,
|
||||
unsigned int PatternLength)
|
||||
{
|
||||
unsigned char * WorkingPatternPtr, * WorkingBufferPtr;
|
||||
unsigned int CompCount, SkipTable[256], Skip, DistanceMatched;
|
||||
int i;
|
||||
|
||||
/* Reject if the buffer is too small */
|
||||
if (BufferLength < PatternLength) return(NULL);
|
||||
/* Reject if the buffer is too small */
|
||||
if (BufferLength < PatternLength) return(NULL);
|
||||
|
||||
/* Return an instant match if the pattern is 0-length */
|
||||
if (PatternLength == 0) return(BufferPtr);
|
||||
/* Return an instant match if the pattern is 0-length */
|
||||
if (PatternLength == 0) return(BufferPtr);
|
||||
|
||||
/* Create the table of distances by which to skip ahead on
|
||||
mismatches for every possible byte value */
|
||||
/* Initialize all skips to the pattern length; this is the skip
|
||||
distance for bytes that don't appear in the pattern */
|
||||
for (i = 0; i < 256; i++) SkipTable[i] = PatternLength;
|
||||
/*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. When there are multiple instances of the same byte,
|
||||
the rightmost 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 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 for skipping purposes */
|
||||
for (i = 0; i < (PatternLength - 1); i++)
|
||||
SkipTable[PatternPtr[i]] = PatternLength - i - 1;
|
||||
/* Create the table of distances by which to skip ahead on
|
||||
mismatches for every possible byte value */
|
||||
/* Initialize all skips to the pattern length; this is the skip
|
||||
distance for bytes that don't appear in the pattern */
|
||||
for (i = 0; i < 256; i++) SkipTable[i] = PatternLength;
|
||||
/*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. When there are multiple instances of the same byte,
|
||||
the rightmost 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 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 for skipping purposes */
|
||||
for (i = 0; i < (PatternLength - 1); i++)
|
||||
SkipTable[PatternPtr[i]] = PatternLength - i - 1;
|
||||
|
||||
/* Point to rightmost byte of the pattern */
|
||||
PatternPtr += PatternLength - 1;
|
||||
/* Point to last (rightmost) byte of the first potential pattern
|
||||
match location in the buffer */
|
||||
BufferPtr += PatternLength - 1;
|
||||
/* Count of number of potential pattern match locations in
|
||||
buffer */
|
||||
BufferLength -= PatternLength - 1;
|
||||
/* Point to rightmost byte of the pattern */
|
||||
PatternPtr += PatternLength - 1;
|
||||
/* Point to last (rightmost) byte of the first potential pattern
|
||||
match location in the buffer */
|
||||
BufferPtr += PatternLength - 1;
|
||||
/* Count of number of potential pattern match locations in
|
||||
buffer */
|
||||
BufferLength -= PatternLength - 1;
|
||||
|
||||
/* Search the buffer */
|
||||
while (1) {
|
||||
/* See if we have a match at this buffer location */
|
||||
WorkingPatternPtr = PatternPtr;
|
||||
WorkingBufferPtr = BufferPtr;
|
||||
CompCount = PatternLength;
|
||||
/* Compare the pattern and the buffer location, searching from
|
||||
high memory toward low (right to left) */
|
||||
while (*WorkingPatternPtr— == *WorkingBufferPtr—) {
|
||||
/* If we've matched the entire pattern, it's a match */
|
||||
if (-CompCount == 0)
|
||||
/* Return a pointer to the start of the match location */
|
||||
return(BufferPtr - PatternLength + 1);
|
||||
}
|
||||
/* It's a mismatch; let's see what we can learn from it */
|
||||
WorkingBufferPtr++; /* point back to the mismatch location */
|
||||
/* # of bytes that did match */
|
||||
DistanceMatched = BufferPtr - WorkingBufferPtr;
|
||||
/*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 1 to the next potential match; otherwise,
|
||||
skip ahead from the mismatch location by the skip distance
|
||||
for the mismatch character */
|
||||
if (SkipTable[*WorkingBufferPtr] <= DistanceMatched)
|
||||
Skip = 1; /* skip doesn't do any good, advance by 1 */
|
||||
else
|
||||
/* Use skip value, accounting for distance covered by the
|
||||
partial match */
|
||||
Skip = SkipTable[*WorkingBufferPtr] - DistanceMatched;
|
||||
/* If skipping ahead would exhaust the buffer, we're done
|
||||
without a match */
|
||||
if (Skip >= BufferLength) return(NULL);
|
||||
/* Skip ahead and perform the next comparison */
|
||||
BufferLength -= Skip;
|
||||
BufferPtr += Skip;
|
||||
}
|
||||
}
|
||||
/* Search the buffer */
|
||||
while (1) {
|
||||
/* See if we have a match at this buffer location */
|
||||
WorkingPatternPtr = PatternPtr;
|
||||
WorkingBufferPtr = BufferPtr;
|
||||
CompCount = PatternLength;
|
||||
/* Compare the pattern and the buffer location, searching from
|
||||
high memory toward low (right to left) */
|
||||
while (*WorkingPatternPtr— == *WorkingBufferPtr—) {
|
||||
/* If we've matched the entire pattern, it's a match */
|
||||
if (-CompCount == 0)
|
||||
/* Return a pointer to the start of the match location */
|
||||
return(BufferPtr - PatternLength + 1);
|
||||
}
|
||||
/* It's a mismatch; let's see what we can learn from it */
|
||||
WorkingBufferPtr++; /* point back to the mismatch location */
|
||||
/* # of bytes that did match */
|
||||
DistanceMatched = BufferPtr - WorkingBufferPtr;
|
||||
/*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 1 to the next potential match; otherwise,
|
||||
skip ahead from the mismatch location by the skip distance
|
||||
for the mismatch character */
|
||||
if (SkipTable[*WorkingBufferPtr] <= DistanceMatched)
|
||||
Skip = 1; /* skip doesn't do any good, advance by 1 */
|
||||
else
|
||||
/* Use skip value, accounting for distance covered by the
|
||||
partial match */
|
||||
Skip = SkipTable[*WorkingBufferPtr] - DistanceMatched;
|
||||
/* If skipping ahead would exhaust the buffer, we're done
|
||||
without a match */
|
||||
if (Skip >= BufferLength) return(NULL);
|
||||
/* Skip ahead and perform the next comparison */
|
||||
BufferLength -= Skip;
|
||||
BufferPtr += Skip;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**LISTING 14.2 L14-2.C**
|
||||
|
||||
/* Program to exercise buffer-search routines in Listings 14.1 & 14.3.
|
||||
(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.) */
|
||||
```c
|
||||
/* Program to exercise buffer-search routines in Listings 14.1 & 14.3.
|
||||
(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 <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#define DISPLAY_LENGTH 40
|
||||
#define BUFFER_SIZE 0x8000
|
||||
#define DISPLAY_LENGTH 40
|
||||
#define BUFFER_SIZE 0x8000
|
||||
|
||||
extern unsigned char * FindString(unsigned char *, unsigned int,
|
||||
unsigned char *, unsigned int);
|
||||
void main(void);
|
||||
extern unsigned char * FindString(unsigned char *, unsigned int,
|
||||
unsigned char *, unsigned int);
|
||||
void main(void);
|
||||
|
||||
void main() {
|
||||
unsigned char TempBuffer[DISPLAY_LENGTH+1];
|
||||
unsigned char Filename[150], Pattern[150], *MatchPtr, *TestBuffer;
|
||||
int Handle;
|
||||
unsigned int WorkingLength;
|
||||
void main() {
|
||||
unsigned char TempBuffer[DISPLAY_LENGTH+1];
|
||||
unsigned char Filename[150], Pattern[150], *MatchPtr, *TestBuffer;
|
||||
int Handle;
|
||||
unsigned int WorkingLength;
|
||||
|
||||
printf("File to search:");
|
||||
gets(Filename);
|
||||
printf("Pattern for which to search:");
|
||||
gets(Pattern);
|
||||
printf("File to search:");
|
||||
gets(Filename);
|
||||
printf("Pattern for which to search:");
|
||||
gets(Pattern);
|
||||
|
||||
if ( (Handle = open(Filename, O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", Filename); exit(1);
|
||||
}
|
||||
/* Get memory in which to buffer the data */
|
||||
if ( (TestBuffer=(unsigned char *)malloc(BUFFER_SIZE+1)) == NULL) {
|
||||
printf("Can't get enough memory\n"); exit(1);
|
||||
}
|
||||
/* Process a BUFFER_SIZE chunk */
|
||||
if ( (int)(WorkingLength =
|
||||
read(Handle, TestBuffer, BUFFER_SIZE)) == -1 ) {
|
||||
printf("Error reading file %s\n", Filename); exit(1);
|
||||
}
|
||||
TestBuffer[WorkingLength] = 0; /* 0-terminate buffer for printf */
|
||||
/* Search for the pattern and report the results */
|
||||
if ((MatchPtr = FindString(TestBuffer, WorkingLength, Pattern,
|
||||
(unsigned int) strlen(Pattern))) == NULL) {
|
||||
/* Pattern wasn't found */
|
||||
printf("\"%s\" not found\n", Pattern);
|
||||
} else {
|
||||
/* Pattern was found. Zero-terminate TempBuffer; strncpy
|
||||
won't do it if DISPLAY_LENGTH characters are copied */
|
||||
TempBuffer[DISPLAY_LENGTH] = 0;
|
||||
printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n",
|
||||
Pattern, DISPLAY_LENGTH,
|
||||
strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH));
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
if ( (Handle = open(Filename, O_RDONLY | O_BINARY)) == -1 ) {
|
||||
printf("Can't open file: %s\n", Filename); exit(1);
|
||||
}
|
||||
/* Get memory in which to buffer the data */
|
||||
if ( (TestBuffer=(unsigned char *)malloc(BUFFER_SIZE+1)) == NULL) {
|
||||
printf("Can't get enough memory\n"); exit(1);
|
||||
}
|
||||
/* Process a BUFFER_SIZE chunk */
|
||||
if ( (int)(WorkingLength =
|
||||
read(Handle, TestBuffer, BUFFER_SIZE)) == -1 ) {
|
||||
printf("Error reading file %s\n", Filename); exit(1);
|
||||
}
|
||||
TestBuffer[WorkingLength] = 0; /* 0-terminate buffer for printf */
|
||||
/* Search for the pattern and report the results */
|
||||
if ((MatchPtr = FindString(TestBuffer, WorkingLength, Pattern,
|
||||
(unsigned int) strlen(Pattern))) == NULL) {
|
||||
/* Pattern wasn't found */
|
||||
printf("\"%s\" not found\n", Pattern);
|
||||
} else {
|
||||
/* Pattern was found. Zero-terminate TempBuffer; strncpy
|
||||
won't do it if DISPLAY_LENGTH characters are copied */
|
||||
TempBuffer[DISPLAY_LENGTH] = 0;
|
||||
printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n",
|
||||
Pattern, DISPLAY_LENGTH,
|
||||
strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH));
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
318
14-05.md
318
14-05.md
|
|
@ -12,163 +12,165 @@ pages: 271-274
|
|||
|
||||
**LISTING 14.3 L14-3.ASM**
|
||||
|
||||
; Searches a buffer for a specified pattern. In case of a mismatch,
|
||||
; uses the value of the mismatched byte to skip across as many
|
||||
; potential match locations as possible (partial Boyer-Moore).
|
||||
; Returns start offset of first match searching forward, or NULL if
|
||||
; no match is found.
|
||||
; Tested with TASM.
|
||||
; C near-callable as:
|
||||
; unsigned char * FindString(unsigned char * BufferPtr,
|
||||
; unsigned int BufferLength, unsigned char * PatternPtr,
|
||||
; unsigned int PatternLength);
|
||||
```nasm
|
||||
; Searches a buffer for a specified pattern. In case of a mismatch,
|
||||
; uses the value of the mismatched byte to skip across as many
|
||||
; potential match locations as possible (partial Boyer-Moore).
|
||||
; Returns start offset of first match searching forward, or NULL if
|
||||
; no match is found.
|
||||
; Tested with TASM.
|
||||
; C near-callable as:
|
||||
; unsigned char * FindString(unsigned char * BufferPtr,
|
||||
; unsigned int BufferLength, unsigned char * PatternPtr,
|
||||
; unsigned int PatternLength);
|
||||
|
||||
parms struc
|
||||
dw 2 dup(?) ;pushed BP & return address
|
||||
BufferPtr dw ? ;pointer to buffer to be searched
|
||||
BufferLength dw ? ;# of bytes in buffer to be searched
|
||||
PatternPtr dw ? ;pointer to pattern for which to search
|
||||
PatternLength dw ? ;length of pattern for which to search
|
||||
parms ends
|
||||
parms struc
|
||||
dw 2 dup(?) ;pushed BP & return address
|
||||
BufferPtr dw ? ;pointer to buffer to be searched
|
||||
BufferLength dw ? ;# of bytes in buffer to be searched
|
||||
PatternPtr dw ? ;pointer to pattern for which to search
|
||||
PatternLength dw ? ;length of pattern for which to search
|
||||
parms ends
|
||||
|
||||
.model small
|
||||
.code
|
||||
public _FindString
|
||||
_FindString proc near
|
||||
cld
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;point to our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
sub sp,256*2 ;allocate space for SkipTable
|
||||
; Create the table of distances by which to skip ahead on mismatches
|
||||
; for every possible byte value. First, initialize all skips to the
|
||||
; pattern length; this is the skip distance for bytes that don't
|
||||
; appear in the pattern.
|
||||
mov ax,[bp+PatternLength]
|
||||
and ax,ax ;return an instant match if the pattern is
|
||||
jz InstantMatch ;0-length
|
||||
mov di,ds
|
||||
mov es,di ;ES=DS=SS
|
||||
mov di,sp ;point to SkipBuffer
|
||||
mov cx,256
|
||||
rep stosw
|
||||
dec ax ;from now on, we only need
|
||||
mov [bp+PatternLength],ax ; PatternLength - 1
|
||||
; Point to last (rightmost) byte of first potential pattern match
|
||||
; location in buffer.
|
||||
add [bp+BufferPtr],ax
|
||||
; Reject if buffer is too small, and set the count of the number of
|
||||
; potential pattern match locations in the buffer.
|
||||
sub [bp+BufferLength],ax
|
||||
jbe NoMatch
|
||||
; 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.
|
||||
; When there are multiple instances of the same byte, the rightmost
|
||||
; 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
|
||||
; 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
|
||||
; for skipping purposes.
|
||||
mov si,[bp+PatternPtr] ;point to start of pattern
|
||||
and ax,ax ;are there any skips to set?
|
||||
jz SetSkipDone ;no
|
||||
mov di,sp ;point to SkipBuffer
|
||||
SetSkipLoop:
|
||||
sub bx,bx ;prepare for word addressing off byte value
|
||||
mov bl,[si] ;get the next pattern byte
|
||||
inc si ;advance the pattern pointer
|
||||
shl bx,1 ;prepare for word lookup
|
||||
mov [di+bx],ax ;set the skip value when this byte value is
|
||||
; the mismatch value in the buffer
|
||||
dec ax
|
||||
jnz SetSkipLoop
|
||||
SetSkipDone:
|
||||
mov dl,[si] ;DL=rightmost pattern byte from now on
|
||||
dec si ;point to next-to-rightmost byte of pattern
|
||||
mov [bp+PatternPtr],si ; from now on
|
||||
; Search the buffer.
|
||||
std ;for backward REPZ CMPSB
|
||||
mov di,[bp+BufferPtr] ;point to first search location
|
||||
mov cx,[bp+BufferLength] ;# of match locations to check
|
||||
SearchLoop:
|
||||
mov si,sp ;point SI to SkipTable
|
||||
; Skip through until there's a match for the rightmost pattern byte.
|
||||
QuickSearchLoop:
|
||||
mov bl,[di] ;rightmost buffer byte at this location
|
||||
cmp dl,bl ;does it match the rightmost pattern byte?
|
||||
jz FullCompare ;yes, so keep going
|
||||
sub bh,bh ;convert to a word
|
||||
add bx,bx ;prepare for look-up in SkipTable
|
||||
mov ax,[si+bx] ;get skip value from skip table for this
|
||||
; mismatch value
|
||||
add di,ax ;BufferPtr += Skip;
|
||||
sub cx,ax ;BufferLength -= Skip;
|
||||
ja QuickSearchLoop ;continue if any buffer left
|
||||
jmp short NoMatch
|
||||
; Return a pointer to the start of the buffer (for 0-length pattern).
|
||||
align 2
|
||||
InstantMatch:
|
||||
mov ax,[bp+BufferPtr]
|
||||
jmp short Done
|
||||
; Compare the pattern and the buffer location, searching from high
|
||||
; memory toward low (right to left).
|
||||
align 2
|
||||
FullCompare:
|
||||
mov [bp+BufferPtr],di ;save the current state of
|
||||
mov [bp+BufferLength],cx ; the search
|
||||
mov cx,[bp+PatternLength] ;# of bytes yet to compare
|
||||
jcxz Match ;done if only one character
|
||||
mov si,[bp+PatternPtr] ;point to next-to-rightmost bytes
|
||||
dec di ; of buffer location and pattern
|
||||
repz cmpsb ;compare the rest of the pattern
|
||||
jz Match ;that's it; we've found a match
|
||||
; It's a mismatch; let's see what we can learn from it.
|
||||
inc di ;compensate for 1-byte overrun of REPZ CMPSB;
|
||||
; point to mismatch location in buffer
|
||||
; # of bytes that did match.
|
||||
mov si,[bp+BufferPtr]
|
||||
sub si,di
|
||||
; 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
|
||||
; 1 to the next potential match; otherwise, skip ahead from this
|
||||
; comparison location by the skip distance for the mismatch character,
|
||||
; less the distance covered by the partial match.
|
||||
sub bx,bx ;prepare for word addressing off byte value
|
||||
mov bl,[di] ;get the value of the mismatch byte in buffer
|
||||
add bx,bx ;prepare for word look-up
|
||||
add bx,sp ;SP points to SkipTable
|
||||
mov cx,[bx] ;get the skip value for this mismatch
|
||||
mov ax,1 ;assume we'll just advance to the next
|
||||
; potential match location
|
||||
sub cx,si ;is the skip far enough to be worth taking?
|
||||
jna MoveAhead ;no, go with the default advance of 1
|
||||
mov ax,cx ;yes; this is the distance to skip ahead from
|
||||
; the last potential match location checked
|
||||
MoveAhead:
|
||||
; Skip ahead and perform the next comparison, if there's any buffer
|
||||
; left to check.
|
||||
mov di,[bp+BufferPtr]
|
||||
add di,ax ;BufferPtr += Skip;
|
||||
mov cx,[bp+BufferLength]
|
||||
sub cx,ax ;BufferLength -= Skip;
|
||||
ja SearchLoop ;continue if any buffer left
|
||||
; Return a NULL pointer for no match.
|
||||
align 2
|
||||
NoMatch:
|
||||
sub ax,ax
|
||||
jmp short Done
|
||||
; Return start of match in buffer (BufferPtr - (PatternLength - 1)).
|
||||
align 2
|
||||
Match:
|
||||
mov ax,[bp+BufferPtr]
|
||||
sub ax,[bp+PatternLength]
|
||||
Done:
|
||||
cld ;restore default direction flag
|
||||
add sp,256*2 ;deallocate space for SkipTable
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_FindString endp
|
||||
end
|
||||
.model small
|
||||
.code
|
||||
public _FindString
|
||||
_FindString proc near
|
||||
cld
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;point to our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
sub sp,256*2 ;allocate space for SkipTable
|
||||
; Create the table of distances by which to skip ahead on mismatches
|
||||
; for every possible byte value. First, initialize all skips to the
|
||||
; pattern length; this is the skip distance for bytes that don't
|
||||
; appear in the pattern.
|
||||
mov ax,[bp+PatternLength]
|
||||
and ax,ax ;return an instant match if the pattern is
|
||||
jz InstantMatch ;0-length
|
||||
mov di,ds
|
||||
mov es,di ;ES=DS=SS
|
||||
mov di,sp ;point to SkipBuffer
|
||||
mov cx,256
|
||||
rep stosw
|
||||
dec ax ;from now on, we only need
|
||||
mov [bp+PatternLength],ax ; PatternLength - 1
|
||||
; Point to last (rightmost) byte of first potential pattern match
|
||||
; location in buffer.
|
||||
add [bp+BufferPtr],ax
|
||||
; Reject if buffer is too small, and set the count of the number of
|
||||
; potential pattern match locations in the buffer.
|
||||
sub [bp+BufferLength],ax
|
||||
jbe NoMatch
|
||||
; 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.
|
||||
; When there are multiple instances of the same byte, the rightmost
|
||||
; 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
|
||||
; 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
|
||||
; for skipping purposes.
|
||||
mov si,[bp+PatternPtr] ;point to start of pattern
|
||||
and ax,ax ;are there any skips to set?
|
||||
jz SetSkipDone ;no
|
||||
mov di,sp ;point to SkipBuffer
|
||||
SetSkipLoop:
|
||||
sub bx,bx ;prepare for word addressing off byte value
|
||||
mov bl,[si] ;get the next pattern byte
|
||||
inc si ;advance the pattern pointer
|
||||
shl bx,1 ;prepare for word lookup
|
||||
mov [di+bx],ax ;set the skip value when this byte value is
|
||||
; the mismatch value in the buffer
|
||||
dec ax
|
||||
jnz SetSkipLoop
|
||||
SetSkipDone:
|
||||
mov dl,[si] ;DL=rightmost pattern byte from now on
|
||||
dec si ;point to next-to-rightmost byte of pattern
|
||||
mov [bp+PatternPtr],si ; from now on
|
||||
; Search the buffer.
|
||||
std ;for backward REPZ CMPSB
|
||||
mov di,[bp+BufferPtr] ;point to first search location
|
||||
mov cx,[bp+BufferLength] ;# of match locations to check
|
||||
SearchLoop:
|
||||
mov si,sp ;point SI to SkipTable
|
||||
; Skip through until there's a match for the rightmost pattern byte.
|
||||
QuickSearchLoop:
|
||||
mov bl,[di] ;rightmost buffer byte at this location
|
||||
cmp dl,bl ;does it match the rightmost pattern byte?
|
||||
jz FullCompare ;yes, so keep going
|
||||
sub bh,bh ;convert to a word
|
||||
add bx,bx ;prepare for look-up in SkipTable
|
||||
mov ax,[si+bx] ;get skip value from skip table for this
|
||||
; mismatch value
|
||||
add di,ax ;BufferPtr += Skip;
|
||||
sub cx,ax ;BufferLength -= Skip;
|
||||
ja QuickSearchLoop ;continue if any buffer left
|
||||
jmp short NoMatch
|
||||
; Return a pointer to the start of the buffer (for 0-length pattern).
|
||||
align 2
|
||||
InstantMatch:
|
||||
mov ax,[bp+BufferPtr]
|
||||
jmp short Done
|
||||
; Compare the pattern and the buffer location, searching from high
|
||||
; memory toward low (right to left).
|
||||
align 2
|
||||
FullCompare:
|
||||
mov [bp+BufferPtr],di ;save the current state of
|
||||
mov [bp+BufferLength],cx ; the search
|
||||
mov cx,[bp+PatternLength] ;# of bytes yet to compare
|
||||
jcxz Match ;done if only one character
|
||||
mov si,[bp+PatternPtr] ;point to next-to-rightmost bytes
|
||||
dec di ; of buffer location and pattern
|
||||
repz cmpsb ;compare the rest of the pattern
|
||||
jz Match ;that's it; we've found a match
|
||||
; It's a mismatch; let's see what we can learn from it.
|
||||
inc di ;compensate for 1-byte overrun of REPZ CMPSB;
|
||||
; point to mismatch location in buffer
|
||||
; # of bytes that did match.
|
||||
mov si,[bp+BufferPtr]
|
||||
sub si,di
|
||||
; 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
|
||||
; 1 to the next potential match; otherwise, skip ahead from this
|
||||
; comparison location by the skip distance for the mismatch character,
|
||||
; less the distance covered by the partial match.
|
||||
sub bx,bx ;prepare for word addressing off byte value
|
||||
mov bl,[di] ;get the value of the mismatch byte in buffer
|
||||
add bx,bx ;prepare for word look-up
|
||||
add bx,sp ;SP points to SkipTable
|
||||
mov cx,[bx] ;get the skip value for this mismatch
|
||||
mov ax,1 ;assume we'll just advance to the next
|
||||
; potential match location
|
||||
sub cx,si ;is the skip far enough to be worth taking?
|
||||
jna MoveAhead ;no, go with the default advance of 1
|
||||
mov ax,cx ;yes; this is the distance to skip ahead from
|
||||
; the last potential match location checked
|
||||
MoveAhead:
|
||||
; Skip ahead and perform the next comparison, if there's any buffer
|
||||
; left to check.
|
||||
mov di,[bp+BufferPtr]
|
||||
add di,ax ;BufferPtr += Skip;
|
||||
mov cx,[bp+BufferLength]
|
||||
sub cx,ax ;BufferLength -= Skip;
|
||||
ja SearchLoop ;continue if any buffer left
|
||||
; Return a NULL pointer for no match.
|
||||
align 2
|
||||
NoMatch:
|
||||
sub ax,ax
|
||||
jmp short Done
|
||||
; Return start of match in buffer (BufferPtr - (PatternLength - 1)).
|
||||
align 2
|
||||
Match:
|
||||
mov ax,[bp+BufferPtr]
|
||||
sub ax,[bp+PatternLength]
|
||||
Done:
|
||||
cld ;restore default direction flag
|
||||
add sp,256*2 ;deallocate space for SkipTable
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_FindString endp
|
||||
end
|
||||
```
|
||||
|
|
|
|||
290
14-06.md
290
14-06.md
|
|
@ -35,152 +35,154 @@ about 60 percent faster than Listing 14.3.
|
|||
|
||||
**LISTING 14.4 L14-4.ASM**
|
||||
|
||||
; Searches a buffer for a specified pattern. In case of a mismatch,
|
||||
; uses the value of the mismatched byte to skip across as many
|
||||
; potential match locations as possible (partial Boyer-Moore).
|
||||
; Returns start offset of first match searching forward, or NULL if
|
||||
; no match is found.
|
||||
; Requires that the pattern be no longer than 255 bytes, and that
|
||||
; there be a match for the pattern somewhere in the buffer (ie., a
|
||||
; copy of the pattern should be placed as a sentinel at the end of
|
||||
; the buffer if the pattern isn't already known to be in the buffer).
|
||||
; Tested with TASM.
|
||||
; C near-callable as:
|
||||
; unsigned char * FindString(unsigned char * BufferPtr,
|
||||
; unsigned int BufferLength, unsigned char * PatternPtr,
|
||||
; unsigned int PatternLength);
|
||||
```nasm
|
||||
; Searches a buffer for a specified pattern. In case of a mismatch,
|
||||
; uses the value of the mismatched byte to skip across as many
|
||||
; potential match locations as possible (partial Boyer-Moore).
|
||||
; Returns start offset of first match searching forward, or NULL if
|
||||
; no match is found.
|
||||
; Requires that the pattern be no longer than 255 bytes, and that
|
||||
; there be a match for the pattern somewhere in the buffer (ie., a
|
||||
; copy of the pattern should be placed as a sentinel at the end of
|
||||
; the buffer if the pattern isn't already known to be in the buffer).
|
||||
; Tested with TASM.
|
||||
; C near-callable as:
|
||||
; unsigned char * FindString(unsigned char * BufferPtr,
|
||||
; unsigned int BufferLength, unsigned char * PatternPtr,
|
||||
; unsigned int PatternLength);
|
||||
|
||||
parms struc
|
||||
dw 2 dup(?) ;pushed BP & return address
|
||||
BufferPtr dw ? ;pointer to buffer to be searched
|
||||
BufferLength dw ? ;# of bytes in buffer to be searched
|
||||
; (not used, actually)
|
||||
PatternPtr dw ? ;pointer to pattern for which to search
|
||||
; (pattern *MUST* exist in the buffer)
|
||||
PatternLength dw ? ;length of pattern for which to search (must
|
||||
; be <= 255)
|
||||
parms ends
|
||||
parms struc
|
||||
dw 2 dup(?) ;pushed BP & return address
|
||||
BufferPtr dw ? ;pointer to buffer to be searched
|
||||
BufferLength dw ? ;# of bytes in buffer to be searched
|
||||
; (not used, actually)
|
||||
PatternPtr dw ? ;pointer to pattern for which to search
|
||||
; (pattern *MUST* exist in the buffer)
|
||||
PatternLength dw ? ;length of pattern for which to search (must
|
||||
; be <= 255)
|
||||
parms ends
|
||||
|
||||
.model small
|
||||
.code
|
||||
public _FindString
|
||||
_FindString proc near
|
||||
cld
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;point to our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
sub sp,256 ;allocate space for SkipTable
|
||||
; Create the table of distances by which to skip ahead on mismatches
|
||||
; for every possible byte value. First, initialize all skips to the
|
||||
; pattern length; this is the skip distance for bytes that don't
|
||||
; appear in the pattern.
|
||||
mov di,ds
|
||||
mov es,di ;ES=DS=SS
|
||||
mov di,sp ;point to SkipBuffer
|
||||
mov al,byte ptr [bp+PatternLength]
|
||||
and al,al ;return an instant match if the pattern is
|
||||
jz InstantMatch ; 0-length
|
||||
mov ah,al
|
||||
mov cx,256/2
|
||||
rep stosw
|
||||
mov ax,[bp+PatternLength]
|
||||
dec ax ;from now on, we only need
|
||||
mov [bp+PatternLength],ax ; PatternLength - 1
|
||||
; Point to rightmost byte of first potential pattern match location
|
||||
; in buffer.
|
||||
add [bp+BufferPtr],ax
|
||||
; 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.
|
||||
mov si,[bp+PatternPtr] ;point to start of pattern
|
||||
and ax,ax ;are there any skips to set?
|
||||
jz SetSkipDone ;no
|
||||
mov di,sp ;point to SkipBuffer
|
||||
sub bx,bx ;prepare for word addressing off byte value
|
||||
SetSkipLoop:
|
||||
mov bl,[si] ;get the next pattern byte
|
||||
inc si ;advance the pattern pointer
|
||||
mov [di+bx],al ;set the skip value when this byte value is
|
||||
;the mismatch value in the buffer
|
||||
dec ax
|
||||
jnz SetSkipLoop
|
||||
SetSkipDone:
|
||||
mov dl,[si] ;DL=rightmost pattern byte from now on
|
||||
dec si ;point to next-to-rightmost byte of pattern
|
||||
mov [bp+PatternPtr],si ; from now on
|
||||
; Search the buffer.
|
||||
std ;for backward REPZ CMPSB
|
||||
mov di,[bp+BufferPtr] ;point to the first search location
|
||||
mov bx,sp ;point to SkipTable for XLAT
|
||||
SearchLoop:
|
||||
sub ah,ah ;used to convert AL to a word
|
||||
; Skip through until there's a match for the first pattern byte.
|
||||
QuickSearchLoop:
|
||||
; See if we have a match at the first buffer location.
|
||||
REPT 8 ;unroll loop 8 times to reduce branching
|
||||
mov al,[di] ;next buffer byte
|
||||
cmp dl,al ;does it match the pattern?
|
||||
jz FullCompare ;yes, so keep going
|
||||
xlat ;no, look up the skip value for this mismatch
|
||||
add di,ax ;BufferPtr += Skip;
|
||||
ENDM
|
||||
jmp QuickSearchLoop
|
||||
; Return a pointer to the start of the buffer (for 0-length pattern).
|
||||
align 2
|
||||
InstantMatch:
|
||||
mov ax,[bp+BufferPtr]
|
||||
jmp short Done
|
||||
; Compare the pattern and the buffer location, searching from high
|
||||
; memory toward low (right to left).
|
||||
align 2
|
||||
FullCompare:
|
||||
mov [bp+BufferPtr],di ;save the current buffer location
|
||||
mov cx,[bp+PatternLength] ;# of bytes yet to compare
|
||||
jcxz Match ;done if there was only one character
|
||||
dec di ;point to next destination byte to compare (SI
|
||||
; points to next-to-rightmost source byte)
|
||||
repz cmpsb ;compare the rest of the pattern
|
||||
jz Match ;that's it; we've found a match
|
||||
; It's a mismatch; let's see what we can learn from it.
|
||||
inc di ;compensate for 1-byte overrun of REPZ CMPSB;
|
||||
; point to mismatch location in buffer
|
||||
; # of bytes that did match.
|
||||
mov si,[bp+BufferPtr]
|
||||
sub si,di
|
||||
; 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
|
||||
; 1 to the next potential match; otherwise, skip ahead from this
|
||||
; comparison location by the skip distance for the mismatch character,
|
||||
; less the distance covered by the partial match.
|
||||
mov al,[di] ;get the value of the mismatch byte in buffer
|
||||
xlat ;get the skip value for this mismatch
|
||||
mov cx,1 ;assume we'll just advance to the next
|
||||
; potential match location
|
||||
sub ax,si ;is the skip far enough to be worth taking?
|
||||
jna MoveAhead ;no, go with the default advance of 1
|
||||
mov cx,ax ;yes, this is the distance to skip ahead from
|
||||
;the last potential match location checked
|
||||
MoveAhead:
|
||||
; Skip ahead and perform the next comparison.
|
||||
mov di,[bp+BufferPtr]
|
||||
add di,cx ;BufferPtr += Skip;
|
||||
mov si,[bp+PatternPtr] ;point to the next-to-rightmost
|
||||
; pattern byte
|
||||
jmp SearchLoop
|
||||
; Return start of match in buffer (BufferPtr - (PatternLength - 1)).
|
||||
align 2
|
||||
Match:
|
||||
mov ax,[bp+BufferPtr]
|
||||
sub ax,[bp+PatternLength]
|
||||
Done:
|
||||
cld ;restore default direction flag
|
||||
add sp,256 ;deallocate space for SkipTable
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_FindString endp
|
||||
end
|
||||
.model small
|
||||
.code
|
||||
public _FindString
|
||||
_FindString proc near
|
||||
cld
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;point to our stack frame
|
||||
push si ;preserve caller's register variables
|
||||
push di
|
||||
sub sp,256 ;allocate space for SkipTable
|
||||
; Create the table of distances by which to skip ahead on mismatches
|
||||
; for every possible byte value. First, initialize all skips to the
|
||||
; pattern length; this is the skip distance for bytes that don't
|
||||
; appear in the pattern.
|
||||
mov di,ds
|
||||
mov es,di ;ES=DS=SS
|
||||
mov di,sp ;point to SkipBuffer
|
||||
mov al,byte ptr [bp+PatternLength]
|
||||
and al,al ;return an instant match if the pattern is
|
||||
jz InstantMatch ; 0-length
|
||||
mov ah,al
|
||||
mov cx,256/2
|
||||
rep stosw
|
||||
mov ax,[bp+PatternLength]
|
||||
dec ax ;from now on, we only need
|
||||
mov [bp+PatternLength],ax ; PatternLength - 1
|
||||
; Point to rightmost byte of first potential pattern match location
|
||||
; in buffer.
|
||||
add [bp+BufferPtr],ax
|
||||
; 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.
|
||||
mov si,[bp+PatternPtr] ;point to start of pattern
|
||||
and ax,ax ;are there any skips to set?
|
||||
jz SetSkipDone ;no
|
||||
mov di,sp ;point to SkipBuffer
|
||||
sub bx,bx ;prepare for word addressing off byte value
|
||||
SetSkipLoop:
|
||||
mov bl,[si] ;get the next pattern byte
|
||||
inc si ;advance the pattern pointer
|
||||
mov [di+bx],al ;set the skip value when this byte value is
|
||||
;the mismatch value in the buffer
|
||||
dec ax
|
||||
jnz SetSkipLoop
|
||||
SetSkipDone:
|
||||
mov dl,[si] ;DL=rightmost pattern byte from now on
|
||||
dec si ;point to next-to-rightmost byte of pattern
|
||||
mov [bp+PatternPtr],si ; from now on
|
||||
; Search the buffer.
|
||||
std ;for backward REPZ CMPSB
|
||||
mov di,[bp+BufferPtr] ;point to the first search location
|
||||
mov bx,sp ;point to SkipTable for XLAT
|
||||
SearchLoop:
|
||||
sub ah,ah ;used to convert AL to a word
|
||||
; Skip through until there's a match for the first pattern byte.
|
||||
QuickSearchLoop:
|
||||
; See if we have a match at the first buffer location.
|
||||
REPT 8 ;unroll loop 8 times to reduce branching
|
||||
mov al,[di] ;next buffer byte
|
||||
cmp dl,al ;does it match the pattern?
|
||||
jz FullCompare ;yes, so keep going
|
||||
xlat ;no, look up the skip value for this mismatch
|
||||
add di,ax ;BufferPtr += Skip;
|
||||
ENDM
|
||||
jmp QuickSearchLoop
|
||||
; Return a pointer to the start of the buffer (for 0-length pattern).
|
||||
align 2
|
||||
InstantMatch:
|
||||
mov ax,[bp+BufferPtr]
|
||||
jmp short Done
|
||||
; Compare the pattern and the buffer location, searching from high
|
||||
; memory toward low (right to left).
|
||||
align 2
|
||||
FullCompare:
|
||||
mov [bp+BufferPtr],di ;save the current buffer location
|
||||
mov cx,[bp+PatternLength] ;# of bytes yet to compare
|
||||
jcxz Match ;done if there was only one character
|
||||
dec di ;point to next destination byte to compare (SI
|
||||
; points to next-to-rightmost source byte)
|
||||
repz cmpsb ;compare the rest of the pattern
|
||||
jz Match ;that's it; we've found a match
|
||||
; It's a mismatch; let's see what we can learn from it.
|
||||
inc di ;compensate for 1-byte overrun of REPZ CMPSB;
|
||||
; point to mismatch location in buffer
|
||||
; # of bytes that did match.
|
||||
mov si,[bp+BufferPtr]
|
||||
sub si,di
|
||||
; 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
|
||||
; 1 to the next potential match; otherwise, skip ahead from this
|
||||
; comparison location by the skip distance for the mismatch character,
|
||||
; less the distance covered by the partial match.
|
||||
mov al,[di] ;get the value of the mismatch byte in buffer
|
||||
xlat ;get the skip value for this mismatch
|
||||
mov cx,1 ;assume we'll just advance to the next
|
||||
; potential match location
|
||||
sub ax,si ;is the skip far enough to be worth taking?
|
||||
jna MoveAhead ;no, go with the default advance of 1
|
||||
mov cx,ax ;yes, this is the distance to skip ahead from
|
||||
;the last potential match location checked
|
||||
MoveAhead:
|
||||
; Skip ahead and perform the next comparison.
|
||||
mov di,[bp+BufferPtr]
|
||||
add di,cx ;BufferPtr += Skip;
|
||||
mov si,[bp+PatternPtr] ;point to the next-to-rightmost
|
||||
; pattern byte
|
||||
jmp SearchLoop
|
||||
; Return start of match in buffer (BufferPtr - (PatternLength - 1)).
|
||||
align 2
|
||||
Match:
|
||||
mov ax,[bp+BufferPtr]
|
||||
sub ax,[bp+PatternLength]
|
||||
Done:
|
||||
cld ;restore default direction flag
|
||||
add sp,256 ;deallocate space for SkipTable
|
||||
pop di ;restore caller's register variables
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_FindString endp
|
||||
end
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
134
15-02.md
134
15-02.md
|
|
@ -12,57 +12,63 @@ pages: 284-287
|
|||
|
||||
**LISTING 15.1 L15-1.C**
|
||||
|
||||
/* Deletes the node in a linked list that follows the indicated node.
|
||||
Assumes list is headed by a dummy node, so no special testing for
|
||||
the head-of-list pointer is required. Returns the same pointer
|
||||
that was passed in. */
|
||||
```c
|
||||
/* Deletes the node in a linked list that follows the indicated node.
|
||||
Assumes list is headed by a dummy node, so no special testing for
|
||||
the head-of-list pointer is required. Returns the same pointer
|
||||
that was passed in. */
|
||||
|
||||
#include "llist.h"
|
||||
struct LinkNode *DeleteNodeAfter(struct LinkNode *NodeToDeleteAfter)
|
||||
{
|
||||
NodeToDeleteAfter->NextNode =
|
||||
NodeToDeleteAfter->NextNode->NextNode;
|
||||
return(NodeToDeleteAfter);
|
||||
}
|
||||
#include "llist.h"
|
||||
struct LinkNode *DeleteNodeAfter(struct LinkNode *NodeToDeleteAfter)
|
||||
{
|
||||
NodeToDeleteAfter->NextNode =
|
||||
NodeToDeleteAfter->NextNode->NextNode;
|
||||
return(NodeToDeleteAfter);
|
||||
}
|
||||
```
|
||||
|
||||
**LISTING 15.2 LLIST.H**
|
||||
|
||||
/* Linked list header file. */
|
||||
#define MAX_TEXT_LENGTH 100 /* longest allowed Text field */
|
||||
#define SENTINEL 32767 /* largest possible Value field */
|
||||
```c
|
||||
/* Linked list header file. */
|
||||
#define MAX_TEXT_LENGTH 100 /* longest allowed Text field */
|
||||
#define SENTINEL 32767 /* largest possible Value field */
|
||||
|
||||
struct LinkNode {
|
||||
struct LinkNode *NextNode;
|
||||
int Value;
|
||||
char Text[MAX_TEXT_LENGTH+1];
|
||||
/* Any number of additional data fields may by present */
|
||||
};
|
||||
struct LinkNode *DeleteNodeAfter(struct LinkNode *);
|
||||
struct LinkNode *FindNodeBeforeValue(struct LinkNode *, int);
|
||||
struct LinkNode *InitLinkedList(void);
|
||||
struct LinkNode *InsertNodeSorted(struct LinkNode *,
|
||||
struct LinkNode *);
|
||||
struct LinkNode {
|
||||
struct LinkNode *NextNode;
|
||||
int Value;
|
||||
char Text[MAX_TEXT_LENGTH+1];
|
||||
/* Any number of additional data fields may by present */
|
||||
};
|
||||
struct LinkNode *DeleteNodeAfter(struct LinkNode *);
|
||||
struct LinkNode *FindNodeBeforeValue(struct LinkNode *, int);
|
||||
struct LinkNode *InitLinkedList(void);
|
||||
struct LinkNode *InsertNodeSorted(struct LinkNode *,
|
||||
struct LinkNode *);
|
||||
```
|
||||
|
||||
**LISTING 15.3 L15-3.C**
|
||||
|
||||
/* Deletes the node in the specified linked list that follows the
|
||||
indicated node. List is headed by a head-of-list pointer; if the
|
||||
pointer to the node to delete after points to the head-of-list
|
||||
pointer, special handling is performed. */
|
||||
#include "llist.h"
|
||||
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) */
|
||||
if (NodeToDeleteAfter == (struct LinkNode *)HeadOfListPtr) {
|
||||
*HeadOfListPtr = (*HeadOfListPtr)->NextNode;
|
||||
} else {
|
||||
NodeToDeleteAfter->NextNode =
|
||||
NodeToDeleteAfter->NextNode->NextNode;
|
||||
}
|
||||
return(NodeToDeleteAfter);
|
||||
}
|
||||
```c
|
||||
/* Deletes the node in the specified linked list that follows the
|
||||
indicated node. List is headed by a head-of-list pointer; if the
|
||||
pointer to the node to delete after points to the head-of-list
|
||||
pointer, special handling is performed. */
|
||||
#include "llist.h"
|
||||
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) */
|
||||
if (NodeToDeleteAfter == (struct LinkNode *)HeadOfListPtr) {
|
||||
*HeadOfListPtr = (*HeadOfListPtr)->NextNode;
|
||||
} else {
|
||||
NodeToDeleteAfter->NextNode =
|
||||
NodeToDeleteAfter->NextNode->NextNode;
|
||||
}
|
||||
return(NodeToDeleteAfter);
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -123,28 +129,30 @@ value has to perform two tests in the inner loop, as shown in Listing
|
|||
|
||||
**LISTING 15.4 L15-4.C**
|
||||
|
||||
/* Finds the first node in a linked list with a value field greater
|
||||
than or equal to a key value, and returns a pointer to the node
|
||||
preceding that node (to facilitate insertion and deletion), or a
|
||||
NULL pointer if no such value was found. Assumes the list is
|
||||
terminated with a tail node pointing to itself as the next node. */
|
||||
#include <stdio.h>
|
||||
#include "llist.h"
|
||||
struct LinkNode *FindNodeBeforeValueNotLess(
|
||||
struct LinkNode *HeadOfListNode, int SearchValue)
|
||||
{
|
||||
struct LinkNode *NodePtr = HeadOfListNode;
|
||||
```c
|
||||
/* Finds the first node in a linked list with a value field greater
|
||||
than or equal to a key value, and returns a pointer to the node
|
||||
preceding that node (to facilitate insertion and deletion), or a
|
||||
NULL pointer if no such value was found. Assumes the list is
|
||||
terminated with a tail node pointing to itself as the next node. */
|
||||
#include <stdio.h>
|
||||
#include "llist.h"
|
||||
struct LinkNode *FindNodeBeforeValueNotLess(
|
||||
struct LinkNode *HeadOfListNode, int SearchValue)
|
||||
{
|
||||
struct LinkNode *NodePtr = HeadOfListNode;
|
||||
|
||||
while ( (NodePtr->NextNode->NextNode != NodePtr->NextNode) &&
|
||||
(NodePtr->NextNode->Value < SearchValue) )
|
||||
NodePtr = NodePtr->NextNode;
|
||||
while ( (NodePtr->NextNode->NextNode != NodePtr->NextNode) &&
|
||||
(NodePtr->NextNode->Value < SearchValue) )
|
||||
NodePtr = NodePtr->NextNode;
|
||||
|
||||
if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
|
||||
return(NULL); /* we found the sentinel; failed search */
|
||||
else
|
||||
return(NodePtr); /* success; return pointer to node preceding
|
||||
node that was >= */
|
||||
}
|
||||
if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
|
||||
return(NULL); /* we found the sentinel; failed search */
|
||||
else
|
||||
return(NodePtr); /* success; return pointer to node preceding
|
||||
node that was >= */
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
166
15-03.md
166
15-03.md
|
|
@ -12,27 +12,29 @@ pages: 287-290
|
|||
|
||||
**LISTING 15.5 L15-5.C**
|
||||
|
||||
/* Finds the first node in a value-sorted linked list that
|
||||
has a Value field greater than or equal to a key value, and
|
||||
returns a pointer to the node preceding that node (to facilitate
|
||||
insertion and deletion), or a NULL pointer if no such value was
|
||||
found. Assumes the list is terminated with a sentinel tail node
|
||||
containing the largest possible Value field setting and pointing
|
||||
to itself as the next node. */
|
||||
#include <stdio.h>
|
||||
#include "llist.h"
|
||||
struct LinkNode *FindNodeBeforeValueNotLess(
|
||||
struct LinkNode *HeadOfListNode, int SearchValue)
|
||||
{
|
||||
struct LinkNode *NodePtr = HeadOfListNode;
|
||||
while (NodePtr->NextNode->Value < SearchValue)
|
||||
NodePtr = NodePtr->NextNode;
|
||||
if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
|
||||
return(NULL); /* we found the sentinel; failed search */
|
||||
else
|
||||
return(NodePtr); /* success; return pointer to node preceding
|
||||
node that was >= */
|
||||
}
|
||||
```c
|
||||
/* Finds the first node in a value-sorted linked list that
|
||||
has a Value field greater than or equal to a key value, and
|
||||
returns a pointer to the node preceding that node (to facilitate
|
||||
insertion and deletion), or a NULL pointer if no such value was
|
||||
found. Assumes the list is terminated with a sentinel tail node
|
||||
containing the largest possible Value field setting and pointing
|
||||
to itself as the next node. */
|
||||
#include <stdio.h>
|
||||
#include "llist.h"
|
||||
struct LinkNode *FindNodeBeforeValueNotLess(
|
||||
struct LinkNode *HeadOfListNode, int SearchValue)
|
||||
{
|
||||
struct LinkNode *NodePtr = HeadOfListNode;
|
||||
while (NodePtr->NextNode->Value < SearchValue)
|
||||
NodePtr = NodePtr->NextNode;
|
||||
if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
|
||||
return(NULL); /* we found the sentinel; failed search */
|
||||
else
|
||||
return(NodePtr); /* success; return pointer to node preceding
|
||||
node that was >= */
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
|
@ -80,68 +82,70 @@ before you write a single line of code.
|
|||
|
||||
**LISTING 15.6 L15-6.C**
|
||||
|
||||
/* Suite of functions for maintaining a linked list sorted by
|
||||
ascending order of the Value field. The list is circular; that
|
||||
is,it has a dummy node as both the head and the tail of the list.
|
||||
The dummy node is a sentinel, containing the largest possible
|
||||
Value field setting. Tested with Borland C++ in C mode. */
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "llist.h"
|
||||
/* Initializes an empty linked list of LinkNode structures,
|
||||
consisting of a single head/tail/sentinel node, and returns a
|
||||
pointer to the list. Returns NULL for failure. */
|
||||
struct LinkNode *InitLinkedList()
|
||||
{
|
||||
struct LinkNode *Sentinel;
|
||||
```c
|
||||
/* Suite of functions for maintaining a linked list sorted by
|
||||
ascending order of the Value field. The list is circular; that
|
||||
is,it has a dummy node as both the head and the tail of the list.
|
||||
The dummy node is a sentinel, containing the largest possible
|
||||
Value field setting. Tested with Borland C++ in C mode. */
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "llist.h"
|
||||
/* Initializes an empty linked list of LinkNode structures,
|
||||
consisting of a single head/tail/sentinel node, and returns a
|
||||
pointer to the list. Returns NULL for failure. */
|
||||
struct LinkNode *InitLinkedList()
|
||||
{
|
||||
struct LinkNode *Sentinel;
|
||||
|
||||
if ((Sentinel = malloc(sizeof(struct LinkNode))) == NULL)
|
||||
return(NULL);
|
||||
Sentinel->NextNode = Sentinel;
|
||||
Sentinel->Value = SENTINEL;
|
||||
strcpy(Sentinel->Text, "*** sentinel ***");
|
||||
return(Sentinel);
|
||||
}
|
||||
if ((Sentinel = malloc(sizeof(struct LinkNode))) == NULL)
|
||||
return(NULL);
|
||||
Sentinel->NextNode = Sentinel;
|
||||
Sentinel->Value = SENTINEL;
|
||||
strcpy(Sentinel->Text, "*** sentinel ***");
|
||||
return(Sentinel);
|
||||
}
|
||||
|
||||
/* 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
|
||||
preceding that node (to facilitate insertion and deletion), or a
|
||||
NULL pointer if no value was found. Assumes list is terminated
|
||||
with a sentinel node containing the largest possible 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
|
||||
preceding that node (to facilitate insertion and deletion), or a
|
||||
NULL pointer if no value was found. Assumes list is terminated
|
||||
with a sentinel node containing the largest possible value. */
|
||||
|
||||
struct LinkNode *FindNodeBeforeValue(struct LinkNode *HeadOfListNode,
|
||||
int SearchValue)
|
||||
{
|
||||
struct LinkNode *NodePtr = HeadOfListNode;
|
||||
struct LinkNode *FindNodeBeforeValue(struct LinkNode *HeadOfListNode,
|
||||
int SearchValue)
|
||||
{
|
||||
struct LinkNode *NodePtr = HeadOfListNode;
|
||||
|
||||
while (NodePtr->NextNode->Value < SearchValue)
|
||||
NodePtr = NodePtr->NextNode;
|
||||
if (NodePtr->NextNode->Value == SearchValue) {
|
||||
/* Found the search value; success unless we found the
|
||||
sentinel (can happen only if SearchValue == SENTINEL) */
|
||||
if (NodePtr->NextNode == HeadOfListNode) {
|
||||
return(NULL); /* failure; we found the sentinel */
|
||||
} else {
|
||||
return(NodePtr); /* success; return pointer to node
|
||||
preceding the node that was equal */
|
||||
}
|
||||
} else {
|
||||
return(NULL); /* No match; return failure status */
|
||||
}
|
||||
}
|
||||
while (NodePtr->NextNode->Value < SearchValue)
|
||||
NodePtr = NodePtr->NextNode;
|
||||
if (NodePtr->NextNode->Value == SearchValue) {
|
||||
/* Found the search value; success unless we found the
|
||||
sentinel (can happen only if SearchValue == SENTINEL) */
|
||||
if (NodePtr->NextNode == HeadOfListNode) {
|
||||
return(NULL); /* failure; we found the sentinel */
|
||||
} else {
|
||||
return(NodePtr); /* success; return pointer to node
|
||||
preceding the node that was equal */
|
||||
}
|
||||
} else {
|
||||
return(NULL); /* No match; return failure status */
|
||||
}
|
||||
}
|
||||
|
||||
/* Inserts the specified node into a value-sorted linked list, such
|
||||
that value-sorting is maintained. Returns a pointer to the node
|
||||
after which the new node is inserted. */
|
||||
struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode,
|
||||
struct LinkNode *NodeToInsert)
|
||||
{
|
||||
struct LinkNode *NodePtr = HeadOfListNode;
|
||||
int SearchValue = NodeToInsert->Value;
|
||||
while (NodePtr->NextNode->Value < SearchValue)
|
||||
NodePtr = NodePtr->NextNode;
|
||||
NodeToInsert->NextNode = NodePtr->NextNode;
|
||||
NodePtr->NextNode = NodeToInsert;
|
||||
return(NodePtr);
|
||||
}
|
||||
/* Inserts the specified node into a value-sorted linked list, such
|
||||
that value-sorting is maintained. Returns a pointer to the node
|
||||
after which the new node is inserted. */
|
||||
struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode,
|
||||
struct LinkNode *NodeToInsert)
|
||||
{
|
||||
struct LinkNode *NodePtr = HeadOfListNode;
|
||||
int SearchValue = NodeToInsert->Value;
|
||||
while (NodePtr->NextNode->Value < SearchValue)
|
||||
NodePtr = NodePtr->NextNode;
|
||||
NodeToInsert->NextNode = NodePtr->NextNode;
|
||||
NodePtr->NextNode = NodeToInsert;
|
||||
return(NodePtr);
|
||||
}
|
||||
```
|
||||
|
|
|
|||
324
15-04.md
324
15-04.md
|
|
@ -12,149 +12,153 @@ pages: 290-293
|
|||
|
||||
**LISTING 15.7 L15-7.ASM**
|
||||
|
||||
; C near-callable assembly function for inserting a new node in a
|
||||
; linked list sorted by ascending order of the Value field. The list
|
||||
; is circular; that is, it has a dummy node as both the head and the
|
||||
; tail of the list. The dummy node is a sentinel, containing the
|
||||
; largest possible Value field setting. Tested with TASM.
|
||||
MAX_TEXT_LENGTH equ 100 ;longest allowed Text field
|
||||
SENTINEL equ 32767 ;largest possible Value field
|
||||
LinkNode struc
|
||||
NextNode dw ?
|
||||
Value dw ?
|
||||
Text db MAX_TEXT_LENGTH+1 dup(?)
|
||||
;*** Any number of additional data fields may by present ***
|
||||
LinkNode ends
|
||||
```nasm
|
||||
; C near-callable assembly function for inserting a new node in a
|
||||
; linked list sorted by ascending order of the Value field. The list
|
||||
; is circular; that is, it has a dummy node as both the head and the
|
||||
; tail of the list. The dummy node is a sentinel, containing the
|
||||
; largest possible Value field setting. Tested with TASM.
|
||||
MAX_TEXT_LENGTH equ 100 ;longest allowed Text field
|
||||
SENTINEL equ 32767 ;largest possible Value field
|
||||
LinkNode struc
|
||||
NextNode dw ?
|
||||
Value dw ?
|
||||
Text db MAX_TEXT_LENGTH+1 dup(?)
|
||||
;*** Any number of additional data fields may by present ***
|
||||
LinkNode ends
|
||||
|
||||
.model small
|
||||
.code
|
||||
.model small
|
||||
.code
|
||||
|
||||
; Inserts the specified node into a ascending-value-sorted linked
|
||||
; list, such that value-sorting is maintained. Returns a pointer to
|
||||
; the node after which the new node is inserted.
|
||||
; C near-callable as:
|
||||
; struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode,
|
||||
; struct LinkNode *NodeToInsert)
|
||||
parms struc
|
||||
dw 2 dup (?) ;pushed return address & BP
|
||||
HeadOfListNode dw ? ;pointer to head node of list
|
||||
NodeToInsert dw ? ;pointer to node to insert
|
||||
parms ends
|
||||
; Inserts the specified node into a ascending-value-sorted linked
|
||||
; list, such that value-sorting is maintained. Returns a pointer to
|
||||
; the node after which the new node is inserted.
|
||||
; C near-callable as:
|
||||
; struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode,
|
||||
; struct LinkNode *NodeToInsert)
|
||||
parms struc
|
||||
dw 2 dup (?) ;pushed return address & BP
|
||||
HeadOfListNode dw ? ;pointer to head node of list
|
||||
NodeToInsert dw ? ;pointer to node to insert
|
||||
parms ends
|
||||
|
||||
public _InsertNodeSorted
|
||||
_InsertNodeSorted proc near
|
||||
push bp
|
||||
mov bp,sp ;point to stack frame
|
||||
push si ;preserve register vars
|
||||
push di
|
||||
mov si,[bp].NodeToInsert ;point to node to insert
|
||||
mov ax,[si].Value ;search value
|
||||
mov di,[bp].HeadOfListNode ;point to linked list in
|
||||
; which to insert
|
||||
SearchLoop:
|
||||
mov bx,di ;advance to the next node
|
||||
mov di,[bx].NextNode ;point to following node
|
||||
cmp [di].Value,ax ;is the following node's
|
||||
; value less than the value
|
||||
; from the node to insert?
|
||||
jl SearchLoop ;yes, so continue searching
|
||||
;no, so we have found our
|
||||
; insert point
|
||||
mov ax,[bx].NextNode ;link the new node between
|
||||
mov [si].NextNode,ax ; the current node and the
|
||||
mov [bx].NextNode,si ; following node
|
||||
mov ax,bx ;return pointer to node
|
||||
; after which we inserted
|
||||
pop di ;restore register vars
|
||||
pop si
|
||||
pop bp
|
||||
ret
|
||||
_InsertNodeSorted endp
|
||||
end
|
||||
public _InsertNodeSorted
|
||||
_InsertNodeSorted proc near
|
||||
push bp
|
||||
mov bp,sp ;point to stack frame
|
||||
push si ;preserve register vars
|
||||
push di
|
||||
mov si,[bp].NodeToInsert ;point to node to insert
|
||||
mov ax,[si].Value ;search value
|
||||
mov di,[bp].HeadOfListNode ;point to linked list in
|
||||
; which to insert
|
||||
SearchLoop:
|
||||
mov bx,di ;advance to the next node
|
||||
mov di,[bx].NextNode ;point to following node
|
||||
cmp [di].Value,ax ;is the following node's
|
||||
; value less than the value
|
||||
; from the node to insert?
|
||||
jl SearchLoop ;yes, so continue searching
|
||||
;no, so we have found our
|
||||
; insert point
|
||||
mov ax,[bx].NextNode ;link the new node between
|
||||
mov [si].NextNode,ax ; the current node and the
|
||||
mov [bx].NextNode,si ; following node
|
||||
mov ax,bx ;return pointer to node
|
||||
; after which we inserted
|
||||
pop di ;restore register vars
|
||||
pop si
|
||||
pop bp
|
||||
ret
|
||||
_InsertNodeSorted endp
|
||||
end
|
||||
```
|
||||
|
||||
**LISTING 15.8 L15-8.C**
|
||||
|
||||
/* Sample linked list program. Tested with Borland C++. */
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include "llist.h"
|
||||
```c
|
||||
/* Sample linked list program. Tested with Borland C++. */
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include "llist.h"
|
||||
|
||||
void main()
|
||||
{ int Done = 0, Char, TempValue;
|
||||
struct LinkNode *TempPtr, *ListPtr, *TempPtr2;
|
||||
char TempBuffer[MAX_TEXT_LENGTH+3];
|
||||
void main()
|
||||
{ int Done = 0, Char, TempValue;
|
||||
struct LinkNode *TempPtr, *ListPtr, *TempPtr2;
|
||||
char TempBuffer[MAX_TEXT_LENGTH+3];
|
||||
|
||||
if ((ListPtr = InitLinkedList()) == NULL) {
|
||||
printf("Out of memory\n");
|
||||
exit(1);
|
||||
}
|
||||
while (!Done) {
|
||||
printf("\nA=add; D=delete; F=find; L=list all; Q=quit\n>");
|
||||
Char = toupper(getche());
|
||||
printf("\n");
|
||||
switch (Char) {
|
||||
case 'A': /* add a node */
|
||||
if ((TempPtr = malloc(sizeof(struct LinkNode))) == NULL)
|
||||
{
|
||||
printf("Out of memory\n );
|
||||
exit(1);
|
||||
}
|
||||
printf("Node value: ");
|
||||
scanf("%d", &TempPtr->Value);
|
||||
if ((FindNodeBeforeValue(ListPtr,TempPtr->Value))!=NULL)
|
||||
{ printf("*** value already in list; try again ***\n");
|
||||
free(TempPtr);
|
||||
} else {printf("Node text: ");
|
||||
TempBuffer[0] = MAX_TEXT_LENGTH;
|
||||
cgets(TempBuffer);
|
||||
strcpy(TempPtr->Text, &TempBuffer[2]);
|
||||
InsertNodeSorted(ListPtr, TempPtr);
|
||||
printf("\n");
|
||||
}
|
||||
break;
|
||||
case 'D': /* delete a node */
|
||||
printf("Value field of node to delete: ");
|
||||
scanf("%d", &TempValue);
|
||||
if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue))
|
||||
!= NULL) {
|
||||
TempPtr2 = TempPtr->NextNode; /* -> node to delete */
|
||||
DeleteNodeAfter(TempPtr); /* delete it */
|
||||
free(TempPtr2); /* free its memory */
|
||||
} else {
|
||||
printf("*** no such value field in list ***\n")
|
||||
break;
|
||||
case 'F': /* find a node */
|
||||
printf("Value field of node to find: ");
|
||||
scanf("%d", &TempValue);
|
||||
if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue))
|
||||
!= NULL)
|
||||
printf("Value: %d\nText: %s\n",
|
||||
TempPtr->NextNode->Value, TempPtr->NextNode->Text);
|
||||
else
|
||||
printf("*** no such value field in list ***\n");
|
||||
break;
|
||||
case 'L': /* list all nodes */
|
||||
TempPtr = ListPtr->NextNode; /* point to first node */
|
||||
if (TempPtr == ListPtr) { /* empty if at sentinel */
|
||||
printf("*** List is empty ***\n");
|
||||
} else {
|
||||
do {printf("Value: %d\n Text: %s\n", TempPtr->Value,
|
||||
TempPtr->Text);
|
||||
TempPtr = TempPtr->NextNode;
|
||||
} while (TempPtr != ListPtr);
|
||||
}
|
||||
break;
|
||||
case 'Q':
|
||||
Done = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((ListPtr = InitLinkedList()) == NULL) {
|
||||
printf("Out of memory\n");
|
||||
exit(1);
|
||||
}
|
||||
while (!Done) {
|
||||
printf("\nA=add; D=delete; F=find; L=list all; Q=quit\n>");
|
||||
Char = toupper(getche());
|
||||
printf("\n");
|
||||
switch (Char) {
|
||||
case 'A': /* add a node */
|
||||
if ((TempPtr = malloc(sizeof(struct LinkNode))) == NULL)
|
||||
{
|
||||
printf("Out of memory\n );
|
||||
exit(1);
|
||||
}
|
||||
printf("Node value: ");
|
||||
scanf("%d", &TempPtr->Value);
|
||||
if ((FindNodeBeforeValue(ListPtr,TempPtr->Value))!=NULL)
|
||||
{ printf("*** value already in list; try again ***\n");
|
||||
free(TempPtr);
|
||||
} else {printf("Node text: ");
|
||||
TempBuffer[0] = MAX_TEXT_LENGTH;
|
||||
cgets(TempBuffer);
|
||||
strcpy(TempPtr->Text, &TempBuffer[2]);
|
||||
InsertNodeSorted(ListPtr, TempPtr);
|
||||
printf("\n");
|
||||
}
|
||||
break;
|
||||
case 'D': /* delete a node */
|
||||
printf("Value field of node to delete: ");
|
||||
scanf("%d", &TempValue);
|
||||
if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue))
|
||||
!= NULL) {
|
||||
TempPtr2 = TempPtr->NextNode; /* -> node to delete */
|
||||
DeleteNodeAfter(TempPtr); /* delete it */
|
||||
free(TempPtr2); /* free its memory */
|
||||
} else {
|
||||
printf("*** no such value field in list ***\n")
|
||||
break;
|
||||
case 'F': /* find a node */
|
||||
printf("Value field of node to find: ");
|
||||
scanf("%d", &TempValue);
|
||||
if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue))
|
||||
!= NULL)
|
||||
printf("Value: %d\nText: %s\n",
|
||||
TempPtr->NextNode->Value, TempPtr->NextNode->Text);
|
||||
else
|
||||
printf("*** no such value field in list ***\n");
|
||||
break;
|
||||
case 'L': /* list all nodes */
|
||||
TempPtr = ListPtr->NextNode; /* point to first node */
|
||||
if (TempPtr == ListPtr) { /* empty if at sentinel */
|
||||
printf("*** List is empty ***\n");
|
||||
} else {
|
||||
do {printf("Value: %d\n Text: %s\n", TempPtr->Value,
|
||||
TempPtr->Text);
|
||||
TempPtr = TempPtr->NextNode;
|
||||
} while (TempPtr != ListPtr);
|
||||
}
|
||||
break;
|
||||
case 'Q':
|
||||
Done = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Hi/Lo in 24 Bytes {#Heading6}
|
||||
|
||||
|
|
@ -182,33 +186,35 @@ reads closely enough.
|
|||
|
||||
**LISTING 15.9 L15-9.ASM**
|
||||
|
||||
; Find the greatest or smallest unsigned int.
|
||||
; C callable (small model); 24 bytes.
|
||||
; By David Stafford.
|
||||
; unsigned hi( int num, unsigned a[] );
|
||||
; unsigned lo( int num, unsigned a[] );
|
||||
```nasm
|
||||
; Find the greatest or smallest unsigned int.
|
||||
; C callable (small model); 24 bytes.
|
||||
; By David Stafford.
|
||||
; unsigned hi( int num, unsigned a[] );
|
||||
; unsigned lo( int num, unsigned a[] );
|
||||
|
||||
public _hi, _lo
|
||||
public _hi, _lo
|
||||
|
||||
_hi: db 0b9h ;mov cx,immediate
|
||||
_lo: xor cx,cx
|
||||
pop ax ;get return address
|
||||
pop dx ;get count
|
||||
pop bx ;get pointer
|
||||
push bx ;restore pointer
|
||||
push dx ;restore count
|
||||
push ax ;restore return address
|
||||
save: mov ax,[bx]
|
||||
top: cmp ax,[bx]
|
||||
jcxz around
|
||||
cmc
|
||||
around: ja save
|
||||
inc bx
|
||||
inc bx
|
||||
dec dx
|
||||
jnz top
|
||||
_hi: db 0b9h ;mov cx,immediate
|
||||
_lo: xor cx,cx
|
||||
pop ax ;get return address
|
||||
pop dx ;get count
|
||||
pop bx ;get pointer
|
||||
push bx ;restore pointer
|
||||
push dx ;restore count
|
||||
push ax ;restore return address
|
||||
save: mov ax,[bx]
|
||||
top: cmp ax,[bx]
|
||||
jcxz around
|
||||
cmc
|
||||
around: ja save
|
||||
inc bx
|
||||
inc bx
|
||||
dec dx
|
||||
jnz top
|
||||
|
||||
ret
|
||||
ret
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
146
16-01.md
146
16-01.md
|
|
@ -98,75 +98,77 @@ timed from a RAM disk on a 20 MHz 386.
|
|||
|
||||
**LISTING 16.1 L16-1.C**
|
||||
|
||||
/* Word-counting program. Tested with Borland C++ in C
|
||||
compilation mode and the small model. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys\stat.h>
|
||||
#include <stdlib.h>
|
||||
#include <io.h>
|
||||
|
||||
#define B UFFER_SIZE 0x8000 /* largest chunk of file worked
|
||||
with at any one time */
|
||||
int main(int, char **);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int Handle;
|
||||
unsigned int BlockSize;
|
||||
long FileSize;
|
||||
unsigned long WordCount = 0;
|
||||
char *Buffer, CharFlag = 0, PredCharFlag, *BufferPtr, Ch;
|
||||
|
||||
if (argc != 2) {
|
||||
printf("usage: wc <filename>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((Buffer = malloc(BUFFER_SIZE)) == NULL) {
|
||||
printf("Can't allocate adequate memory\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1) {
|
||||
printf("Can't open file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((FileSize = filelength(Handle)) == -1) {
|
||||
printf("Error sizing file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Process the file in chunks */
|
||||
while (FileSize > 0) {
|
||||
/* Get the next chunk */
|
||||
FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE));
|
||||
if (read(Handle, Buffer, BlockSize) == -1) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
/* Count words in the chunk */
|
||||
BufferPtr = Buffer;
|
||||
do {
|
||||
PredCharFlag = CharFlag;
|
||||
Ch = *BufferPtr++ & 0x7F; /* strip high bit, which some
|
||||
word processors set as an
|
||||
internal flag */
|
||||
CharFlag = ((Ch >= ‘a') && (Ch <= ‘z')) ||
|
||||
((Ch >= ‘A') && (Ch <= ‘Z')) ||
|
||||
((Ch >= ‘0') && (Ch <= ‘9')) ||
|
||||
(Ch == ‘\'');
|
||||
if ((!CharFlag) && PredCharFlag) {
|
||||
WordCo u nt++;
|
||||
}
|
||||
} while (—BlockSize);
|
||||
}
|
||||
|
||||
/* Catch the last word, if any */
|
||||
if (CharFlag) {
|
||||
WordCount++;
|
||||
}
|
||||
printf("\nTotal words in file: %lu\n", WordCount);
|
||||
return(0);
|
||||
}
|
||||
```c
|
||||
/* Word-counting program. Tested with Borland C++ in C
|
||||
compilation mode and the small model. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys\stat.h>
|
||||
#include <stdlib.h>
|
||||
#include <io.h>
|
||||
|
||||
#define B UFFER_SIZE 0x8000 /* largest chunk of file worked
|
||||
with at any one time */
|
||||
int main(int, char **);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int Handle;
|
||||
unsigned int BlockSize;
|
||||
long FileSize;
|
||||
unsigned long WordCount = 0;
|
||||
char *Buffer, CharFlag = 0, PredCharFlag, *BufferPtr, Ch;
|
||||
|
||||
if (argc != 2) {
|
||||
printf("usage: wc <filename>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((Buffer = malloc(BUFFER_SIZE)) == NULL) {
|
||||
printf("Can't allocate adequate memory\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1) {
|
||||
printf("Can't open file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((FileSize = filelength(Handle)) == -1) {
|
||||
printf("Error sizing file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Process the file in chunks */
|
||||
while (FileSize > 0) {
|
||||
/* Get the next chunk */
|
||||
FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE));
|
||||
if (read(Handle, Buffer, BlockSize) == -1) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
/* Count words in the chunk */
|
||||
BufferPtr = Buffer;
|
||||
do {
|
||||
PredCharFlag = CharFlag;
|
||||
Ch = *BufferPtr++ & 0x7F; /* strip high bit, which some
|
||||
word processors set as an
|
||||
internal flag */
|
||||
CharFlag = ((Ch >= ‘a') && (Ch <= ‘z')) ||
|
||||
((Ch >= ‘A') && (Ch <= ‘Z')) ||
|
||||
((Ch >= ‘0') && (Ch <= ‘9')) ||
|
||||
(Ch == ‘\'');
|
||||
if ((!CharFlag) && PredCharFlag) {
|
||||
WordCo u nt++;
|
||||
}
|
||||
} while (—BlockSize);
|
||||
}
|
||||
|
||||
/* Catch the last word, if any */
|
||||
if (CharFlag) {
|
||||
WordCount++;
|
||||
}
|
||||
printf("\nTotal words in file: %lu\n", WordCount);
|
||||
return(0);
|
||||
}
|
||||
```
|
||||
|
|
|
|||
280
16-02.md
280
16-02.md
|
|
@ -22,147 +22,151 @@ generates.
|
|||
|
||||
**LISTING 16.2 L16-2.C**
|
||||
|
||||
/* Word-counting program incorporating assembly language. Tested
|
||||
with Borland C++ in C compilation mode & the small model. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys\stat.h>
|
||||
#include <stdlib.h>
|
||||
#include <io.h>
|
||||
|
||||
#define BUFFER_SIZE 0x8000 /* largest chunk of file worked
|
||||
with at any one time */
|
||||
int main(int, char **);
|
||||
void ScanBuffer(char *, unsigned int, char *, unsigned long *);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int Handle;
|
||||
unsigned int BlockSize;
|
||||
long FileSize;
|
||||
unsigned long WordCount = 0;
|
||||
char *Buffer, CharFlag = 0;
|
||||
|
||||
if (argc != 2) {
|
||||
printf("usage: wc <filename>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((Buffer = malloc(BUFFER_SIZE)) == NULL) {
|
||||
printf("Can't allocate adequate memory\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1) {
|
||||
printf("Can't open file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((FileSize = filelength(Handle)) == -1) {
|
||||
printf("Error sizing file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
CharFlag = 0;
|
||||
while (FileSize > 0) {
|
||||
FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE));
|
||||
if (read(Handle, Buffer, BlockSize) == -1) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
ScanBuffer(Buffer, BlockSize, &CharFlag, &WordCount);
|
||||
}
|
||||
|
||||
/* Catch the last word, if any */
|
||||
if (CharFlag) {
|
||||
WordCount++;
|
||||
}
|
||||
printf("\nTotal words in file: %lu\n", WordCount);
|
||||
return(0);
|
||||
}
|
||||
```c
|
||||
/* Word-counting program incorporating assembly language. Tested
|
||||
with Borland C++ in C compilation mode & the small model. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys\stat.h>
|
||||
#include <stdlib.h>
|
||||
#include <io.h>
|
||||
|
||||
#define BUFFER_SIZE 0x8000 /* largest chunk of file worked
|
||||
with at any one time */
|
||||
int main(int, char **);
|
||||
void ScanBuffer(char *, unsigned int, char *, unsigned long *);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int Handle;
|
||||
unsigned int BlockSize;
|
||||
long FileSize;
|
||||
unsigned long WordCount = 0;
|
||||
char *Buffer, CharFlag = 0;
|
||||
|
||||
if (argc != 2) {
|
||||
printf("usage: wc <filename>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((Buffer = malloc(BUFFER_SIZE)) == NULL) {
|
||||
printf("Can't allocate adequate memory\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1) {
|
||||
printf("Can't open file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((FileSize = filelength(Handle)) == -1) {
|
||||
printf("Error sizing file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
CharFlag = 0;
|
||||
while (FileSize > 0) {
|
||||
FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE));
|
||||
if (read(Handle, Buffer, BlockSize) == -1) {
|
||||
printf("Error reading file %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
ScanBuffer(Buffer, BlockSize, &CharFlag, &WordCount);
|
||||
}
|
||||
|
||||
/* Catch the last word, if any */
|
||||
if (CharFlag) {
|
||||
WordCount++;
|
||||
}
|
||||
printf("\nTotal words in file: %lu\n", WordCount);
|
||||
return(0);
|
||||
}
|
||||
```
|
||||
|
||||
**LISTING 16.3 L16-3.ASM**
|
||||
|
||||
; Assembly subroutine for Listing 16.2. Scans through Buffer, of
|
||||
; length BufferLength, counting words and updating WordCount as
|
||||
; appropriate. BufferLength must be > 0. *CharFlag and *WordCount
|
||||
; should equal 0 on the first call. Tested with TASM.
|
||||
; C near-callable as:
|
||||
; void ScanBuffer(char *Buffer, unsigned int BufferLength,
|
||||
; char *CharFlag, unsigned long *WordCount);
|
||||
|
||||
parms struc
|
||||
dw 2 dup(?) ;pushed return address & BP
|
||||
Buffer dw ? ;buffer to scan
|
||||
BufferLength dw ? ;length of buffer to scan
|
||||
CharFlag dw ? ;pointer to flag for state of last
|
||||
; char processed on entry (0 on
|
||||
; initial call). Updated on exit
|
||||
WordCount dw ? ;pointer to 32-bit count of words
|
||||
; found (0 on initial call)
|
||||
parms ends
|
||||
|
||||
.model small
|
||||
.code
|
||||
public _ScanBuffer
|
||||
_ScanBuffer proc near
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;set up local stack frame
|
||||
push si ;preserve caller's register vars
|
||||
push di
|
||||
|
||||
mov si,[bp+Buffer] ;point to buffer to scan
|
||||
mov bx,[bp+WordCount]
|
||||
mov cx,[bx] ;get current 32-bit word count
|
||||
mov dx,[bx+2]
|
||||
mov bx,[bp+CharFlag]
|
||||
mov bl,[bx] ;get current CharFlag
|
||||
mov di,[bp+BufferLength];get # of bytes to scan
|
||||
ScanLoop:
|
||||
mov bh,bl ;PredCharFlag = CharFlag;
|
||||
lodsb ;Ch = *BufferPtr++ & 0x7F;
|
||||
and al,7fh ;strip high bit for word processors
|
||||
; that set it as an internal flag
|
||||
mov bl,1 ;assume this is a char; CharFlag = 1;
|
||||
cmp al,‘a' ;it is a char if between a and z
|
||||
jb CheckAZ
|
||||
cmp al,‘z'
|
||||
jna IsAChar
|
||||
CheckAZ:
|
||||
cmp al,‘A' ;it is a char if between A and Z
|
||||
jb Check09
|
||||
cmp al,‘Z'
|
||||
jna IsAChar
|
||||
Check09:
|
||||
cmp al,‘0' ;it is a char if between 0 and 9
|
||||
jb CheckApostrophe
|
||||
cmp al,‘9'
|
||||
jna IsAChar
|
||||
CheckApostrophe:
|
||||
cmp al,27h ;it is a char if an apostrophe
|
||||
jz IsAChar
|
||||
sub bl,bl ;not a char; CharFlag = 0;
|
||||
and bh,bh
|
||||
jz ScanLoopBottom ;if ((!CharFlag) && PredCharFlag) {
|
||||
add cx,1 ; (WordCount)++;
|
||||
adc dx,0 ;}
|
||||
IsAChar:
|
||||
ScanLoopBottom:
|
||||
dec di ;} while (—BufferLength);
|
||||
jnz ScanLoop
|
||||
|
||||
mov si,[bp+CharFlag]
|
||||
mov [si],bl ;set new CharFlag
|
||||
mov bx,[bp+WordCount]
|
||||
mov [bx],cx ;set new word count
|
||||
mov [bx+2],dx
|
||||
|
||||
pop di ;restore caller's register vars
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_ScanBuffer endp
|
||||
end
|
||||
```nasm
|
||||
; Assembly subroutine for Listing 16.2. Scans through Buffer, of
|
||||
; length BufferLength, counting words and updating WordCount as
|
||||
; appropriate. BufferLength must be > 0. *CharFlag and *WordCount
|
||||
; should equal 0 on the first call. Tested with TASM.
|
||||
; C near-callable as:
|
||||
; void ScanBuffer(char *Buffer, unsigned int BufferLength,
|
||||
; char *CharFlag, unsigned long *WordCount);
|
||||
|
||||
parms struc
|
||||
dw 2 dup(?) ;pushed return address & BP
|
||||
Buffer dw ? ;buffer to scan
|
||||
BufferLength dw ? ;length of buffer to scan
|
||||
CharFlag dw ? ;pointer to flag for state of last
|
||||
; char processed on entry (0 on
|
||||
; initial call). Updated on exit
|
||||
WordCount dw ? ;pointer to 32-bit count of words
|
||||
; found (0 on initial call)
|
||||
parms ends
|
||||
|
||||
.model small
|
||||
.code
|
||||
public _ScanBuffer
|
||||
_ScanBuffer proc near
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;set up local stack frame
|
||||
push si ;preserve caller's register vars
|
||||
push di
|
||||
|
||||
mov si,[bp+Buffer] ;point to buffer to scan
|
||||
mov bx,[bp+WordCount]
|
||||
mov cx,[bx] ;get current 32-bit word count
|
||||
mov dx,[bx+2]
|
||||
mov bx,[bp+CharFlag]
|
||||
mov bl,[bx] ;get current CharFlag
|
||||
mov di,[bp+BufferLength];get # of bytes to scan
|
||||
ScanLoop:
|
||||
mov bh,bl ;PredCharFlag = CharFlag;
|
||||
lodsb ;Ch = *BufferPtr++ & 0x7F;
|
||||
and al,7fh ;strip high bit for word processors
|
||||
; that set it as an internal flag
|
||||
mov bl,1 ;assume this is a char; CharFlag = 1;
|
||||
cmp al,‘a' ;it is a char if between a and z
|
||||
jb CheckAZ
|
||||
cmp al,‘z'
|
||||
jna IsAChar
|
||||
CheckAZ:
|
||||
cmp al,‘A' ;it is a char if between A and Z
|
||||
jb Check09
|
||||
cmp al,‘Z'
|
||||
jna IsAChar
|
||||
Check09:
|
||||
cmp al,‘0' ;it is a char if between 0 and 9
|
||||
jb CheckApostrophe
|
||||
cmp al,‘9'
|
||||
jna IsAChar
|
||||
CheckApostrophe:
|
||||
cmp al,27h ;it is a char if an apostrophe
|
||||
jz IsAChar
|
||||
sub bl,bl ;not a char; CharFlag = 0;
|
||||
and bh,bh
|
||||
jz ScanLoopBottom ;if ((!CharFlag) && PredCharFlag) {
|
||||
add cx,1 ; (WordCount)++;
|
||||
adc dx,0 ;}
|
||||
IsAChar:
|
||||
ScanLoopBottom:
|
||||
dec di ;} while (—BufferLength);
|
||||
jnz ScanLoop
|
||||
|
||||
mov si,[bp+CharFlag]
|
||||
mov [si],bl ;set new CharFlag
|
||||
mov bx,[bp+WordCount]
|
||||
mov [bx],cx ;set new word count
|
||||
mov [bx+2],dx
|
||||
|
||||
pop di ;restore caller's register vars
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
ret
|
||||
_ScanBuffer endp
|
||||
end
|
||||
```
|
||||
|
||||
#### Which Way to Go from Here? {#Heading4}
|
||||
|
||||
|
|
|
|||
183
16-03.md
183
16-03.md
|
|
@ -12,97 +12,98 @@ pages: 303-305
|
|||
|
||||
**LISTING 16.4 L16-4.ASM**
|
||||
|
||||
; Assembly subroutine for Listing 16.2. Scans through Buffer, of
|
||||
; length BufferLength, counting words and updating WordCount as
|
||||
; appropriate, using a lookup table-based approach. BufferLength
|
||||
; must be > 0. *CharFlag and *WordCount should equal 0 on the
|
||||
; first call. Tested with TASM.
|
||||
; C near-callable as:
|
||||
; void ScanBuffer(char *Buffer, unsigned int BufferLength,
|
||||
; char *CharFlag, unsigned long *WordCount);
|
||||
|
||||
parms struc
|
||||
dw 2 dup(?) ;pushed return address & BP
|
||||
Buffer dw ? ;buffer to scan
|
||||
BufferLength dw ? ;length of buffer to scan
|
||||
CharFlag dw ? ;pointer to flag for state of last
|
||||
;char processed on entry (0 on
|
||||
;initial call). Updated on exit
|
||||
WordCount dw ? ;pointer to 32-bit count of words
|
||||
; found (0 on initial call)
|
||||
parms ends
|
||||
|
||||
.model small
|
||||
.data
|
||||
; 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
|
||||
; word processors set as an internal flag).
|
||||
CharStatusTable label byte
|
||||
REPT 2
|
||||
db 39 dup(0)
|
||||
db 1 ;apostrophe
|
||||
db 8 dup(0)
|
||||
db 10 dup(1) ;0-9
|
||||
db 7 dup(0)
|
||||
db 26 dup(1) ;A-Z
|
||||
db 6 dup(0)
|
||||
db 26 dup(1) ;a-z
|
||||
db 5 dup(0)
|
||||
ENDM
|
||||
|
||||
.code
|
||||
public _ScanBuffer
|
||||
_ScanBuffer proc near
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;set up local stack frame
|
||||
push si ;preserve caller's register vars
|
||||
push di
|
||||
|
||||
mov si,[bp+Buffer] ;point to buffer to scan
|
||||
mov bx,[bp+WordCount]
|
||||
mov di,[bx] ;get current 32-bit word count
|
||||
mov dx,[bx+2]
|
||||
mov bx,[bp+CharFlag]
|
||||
mov al,[bx] ;get current CharFlag
|
||||
mov cx,[bp+BufferLength] ;get # of bytes to scan
|
||||
mov bx,offset CharStatusTable
|
||||
ScanLoop:
|
||||
and al,al ;ZF=0 if last byte was a char,
|
||||
; ZF=1 if not
|
||||
lodsb ;get the next byte
|
||||
;***doesn't change flags***
|
||||
xlat ;look up its char/not status
|
||||
;***doesn't change flags***
|
||||
jz ScanLoopBottom ;don't count a word if last byte was
|
||||
; not a character
|
||||
and al,al ;last byte was a character; is the
|
||||
; current byte a character?
|
||||
jz CountWord ;no, so count a word
|
||||
ScanLoopBottom:
|
||||
dec cx ;count down buffer length
|
||||
jnz ScanLoop
|
||||
Done:
|
||||
mov si,[bp+CharFlag]
|
||||
mov [si],al ;set new CharFlag
|
||||
mov bx,[bp+WordCount]
|
||||
mov [bx],di ;set new word count
|
||||
mov [bx+2],dx
|
||||
|
||||
pop di ;restore caller's register vars
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
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
|
||||
|
||||
```nasm
|
||||
; Assembly subroutine for Listing 16.2. Scans through Buffer, of
|
||||
; length BufferLength, counting words and updating WordCount as
|
||||
; appropriate, using a lookup table-based approach. BufferLength
|
||||
; must be > 0. *CharFlag and *WordCount should equal 0 on the
|
||||
; first call. Tested with TASM.
|
||||
; C near-callable as:
|
||||
; void ScanBuffer(char *Buffer, unsigned int BufferLength,
|
||||
; char *CharFlag, unsigned long *WordCount);
|
||||
|
||||
parms struc
|
||||
dw 2 dup(?) ;pushed return address & BP
|
||||
Buffer dw ? ;buffer to scan
|
||||
BufferLength dw ? ;length of buffer to scan
|
||||
CharFlag dw ? ;pointer to flag for state of last
|
||||
;char processed on entry (0 on
|
||||
;initial call). Updated on exit
|
||||
WordCount dw ? ;pointer to 32-bit count of words
|
||||
; found (0 on initial call)
|
||||
parms ends
|
||||
|
||||
.model small
|
||||
.data
|
||||
; 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
|
||||
; word processors set as an internal flag).
|
||||
CharStatusTable label byte
|
||||
REPT 2
|
||||
db 39 dup(0)
|
||||
db 1 ;apostrophe
|
||||
db 8 dup(0)
|
||||
db 10 dup(1) ;0-9
|
||||
db 7 dup(0)
|
||||
db 26 dup(1) ;A-Z
|
||||
db 6 dup(0)
|
||||
db 26 dup(1) ;a-z
|
||||
db 5 dup(0)
|
||||
ENDM
|
||||
|
||||
.code
|
||||
public _ScanBuffer
|
||||
_ScanBuffer proc near
|
||||
push bp ;preserve caller's stack frame
|
||||
mov bp,sp ;set up local stack frame
|
||||
push si ;preserve caller's register vars
|
||||
push di
|
||||
|
||||
mov si,[bp+Buffer] ;point to buffer to scan
|
||||
mov bx,[bp+WordCount]
|
||||
mov di,[bx] ;get current 32-bit word count
|
||||
mov dx,[bx+2]
|
||||
mov bx,[bp+CharFlag]
|
||||
mov al,[bx] ;get current CharFlag
|
||||
mov cx,[bp+BufferLength] ;get # of bytes to scan
|
||||
mov bx,offset CharStatusTable
|
||||
ScanLoop:
|
||||
and al,al ;ZF=0 if last byte was a char,
|
||||
; ZF=1 if not
|
||||
lodsb ;get the next byte
|
||||
;***doesn't change flags***
|
||||
xlat ;look up its char/not status
|
||||
;***doesn't change flags***
|
||||
jz ScanLoopBottom ;don't count a word if last byte was
|
||||
; not a character
|
||||
and al,al ;last byte was a character; is the
|
||||
; current byte a character?
|
||||
jz CountWord ;no, so count a word
|
||||
ScanLoopBottom:
|
||||
dec cx ;count down buffer length
|
||||
jnz ScanLoop
|
||||
Done:
|
||||
mov si,[bp+CharFlag]
|
||||
mov [si],al ;set new CharFlag
|
||||
mov bx,[bp+WordCount]
|
||||
mov [bx],di ;set new word count
|
||||
mov [bx+2],dx
|
||||
|
||||
pop di ;restore caller's register vars
|
||||
pop si
|
||||
pop bp ;restore caller's stack frame
|
||||
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
|
||||
```
|
||||
|
||||
Listing 16.4 features several interesting tricks. First, it uses
|
||||
**LODSB** and **XLAT** in succession, a very neat way to get a
|
||||
|
|
|
|||
6
16-04.md
6
16-04.md
|
|
@ -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
|
||||
386 native-mode instructions. However, one of the instructions was this:
|
||||
|
||||
|
||||
CMP DH,[EBX+EAX]
|
||||
|
||||
```nasm
|
||||
CMP DH,[EBX+EAX]
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
358
16-05.md
358
16-05.md
|
|
@ -36,181 +36,183 @@ Table: Table 16.2 The top four word-counting entries.
|
|||
|
||||
**LISTING 16.5 QSCAN3.ASM**
|
||||
|
||||
; QSCAN3.ASM
|
||||
; David Stafford
|
||||
|
||||
|
||||
COMMENT $
|
||||
|
||||
How it works
|
||||
——————
|
||||
The idea is to go through the buffer fetching each letter-pair (words
|
||||
rather than bytes). The carry flag indicates whether we are
|
||||
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
|
||||
(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
|
||||
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 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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
remove the high bit and the count of words remains in the count
|
||||
register.
|
||||
Sound complicated? You're right! But it's fast!
|
||||
|
||||
The beauty of this method is that no jumps are required, the
|
||||
operations are fast, it requires only one table and the process can
|
||||
be repeated (unrolled) many times. QSCAN3 can read 256 bytes without
|
||||
jumping.
|
||||
|
||||
COMMEND $
|
||||
.model small
|
||||
.code
|
||||
|
||||
Test1 macro x,y ;9 or 10 bytes
|
||||
Addr&x: mov di,[bp+y] ;3 or 4 bytes
|
||||
adc di,di
|
||||
or ax,si
|
||||
add al,[di]
|
||||
endm
|
||||
|
||||
Test2 macro x,y ;7 or 8 bytes
|
||||
Addr&x: mov di,[bp+y] ;3 or 4 bytes
|
||||
adc di,di
|
||||
add ah,[di]
|
||||
endm
|
||||
|
||||
Scan = 128 ;scan 256 bytes at a time
|
||||
Buffer = 4 ;parms
|
||||
BufferLength = 6
|
||||
CharFlag = 8
|
||||
WordCount = 10
|
||||
|
||||
public _ScanBuffer
|
||||
_ScanBuffer proc near
|
||||
push bp
|
||||
mov bp,sp
|
||||
push si
|
||||
push di
|
||||
|
||||
xor cx,cx
|
||||
mov si,[bp+Buffer] ;si = text buffer
|
||||
mov ax,[bp+BufferLength] ;dx = length in bytes
|
||||
shr ax,1 ;dx = length in words
|
||||
jnz NormalBuf
|
||||
OneByteBuf:
|
||||
mov ax,seg WordTable
|
||||
mov es,ax
|
||||
|
||||
mov di,[bp+CharFlag]
|
||||
mov bh,[di] ;bh = old CharFlag
|
||||
mov bl,[si] ;bl = character
|
||||
add bh,‘A'-1 ;make bh into character
|
||||
add bx,bx ;prepare to index
|
||||
mov al,es:[bx]
|
||||
cbw ;get hi bit in ah (then bh)
|
||||
shr al,1 ;get low bit
|
||||
adc cx,cx ;cx = 0 or 1
|
||||
xchg ax,bx
|
||||
jmp CleanUp
|
||||
NormalBuf:
|
||||
push bp ;(1)
|
||||
pushf ;(2)
|
||||
|
||||
cwd ;dx = 0
|
||||
mov cl,Scan
|
||||
div cx
|
||||
or dx,dx ;remainder?
|
||||
jz StartAtTheTop ;nope, do the whole banana
|
||||
sub cx,dx
|
||||
sub si,cx ;adjust buf pointer
|
||||
sub si,cx
|
||||
inc ax ;adjust for partial read
|
||||
|
||||
StartAtTheTop: mov bx,dx ;get index for start...
|
||||
shl bx,1
|
||||
mov di,LoopEntry[bx] ;...address in di
|
||||
xchg dx,ax ;dx is the loop counter
|
||||
xor cx,cx ;total word count
|
||||
mov bx,[bp+CharFlag]
|
||||
mov bl,[bx] ;bl = old CharFlag
|
||||
mov bp,seg WordTable
|
||||
mov ds,bp
|
||||
mov bp,si ;scan buffer with bp
|
||||
mov si,8080h ;hi bits
|
||||
mov ax,si ;init local word counter
|
||||
shr bl,1 ;carry = old CharFlag
|
||||
jmp di
|
||||
|
||||
align 2
|
||||
Top: add bx,bx ;restore carry
|
||||
n = 0
|
||||
rept Scan/2
|
||||
Test1 %n,%n*2
|
||||
Test2 %n+1,%n*2+2
|
||||
n = n+2
|
||||
endm
|
||||
EndCount:
|
||||
sbb bx,bx ;save carry
|
||||
if Scan ge 128 ;because al+ah may equal 128!
|
||||
or ax,si
|
||||
add al,ah
|
||||
mov ah,0
|
||||
else
|
||||
add al,ah
|
||||
and ax,7fh ;mask
|
||||
endif
|
||||
add cx,ax ;update word count
|
||||
mov ax,si
|
||||
add bp,Scan*2
|
||||
dec dx ;any left?
|
||||
jng Quit
|
||||
jmp Top
|
||||
|
||||
Quit: popf ;(2) even or odd buffer?
|
||||
jnc ItsEven
|
||||
clc
|
||||
Test1 Odd,-1
|
||||
sbb bx,bx ;save carry
|
||||
shr ax,1
|
||||
adc cx,0
|
||||
ItsEven:
|
||||
push ss ;restore ds
|
||||
pop ds
|
||||
pop bp ;(1)
|
||||
CleanUp:
|
||||
mov si,[bp+WordCount]
|
||||
add [si],cx
|
||||
adc word ptr [si+2],0
|
||||
and bh,1 ;save only the carry flag
|
||||
mov si,[bp+CharFlag]
|
||||
mov [si],bh
|
||||
pop di
|
||||
pop si
|
||||
pop bp
|
||||
ret
|
||||
_ScanBuffer endp
|
||||
|
||||
.data
|
||||
Address macro X
|
||||
dw Addr&X
|
||||
endm
|
||||
|
||||
LoopEntry label word
|
||||
n = Scan
|
||||
REPT Scan
|
||||
Address %n MOD Scan
|
||||
n = n - 1
|
||||
ENDM
|
||||
|
||||
.fardata WordTable
|
||||
include qscan3.inc ;built by MAKETAB
|
||||
end
|
||||
```nasm
|
||||
; QSCAN3.ASM
|
||||
; David Stafford
|
||||
|
||||
|
||||
COMMENT $
|
||||
|
||||
How it works
|
||||
——————
|
||||
The idea is to go through the buffer fetching each letter-pair (words
|
||||
rather than bytes). The carry flag indicates whether we are
|
||||
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
|
||||
(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
|
||||
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 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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
remove the high bit and the count of words remains in the count
|
||||
register.
|
||||
Sound complicated? You're right! But it's fast!
|
||||
|
||||
The beauty of this method is that no jumps are required, the
|
||||
operations are fast, it requires only one table and the process can
|
||||
be repeated (unrolled) many times. QSCAN3 can read 256 bytes without
|
||||
jumping.
|
||||
|
||||
COMMEND $
|
||||
.model small
|
||||
.code
|
||||
|
||||
Test1 macro x,y ;9 or 10 bytes
|
||||
Addr&x: mov di,[bp+y] ;3 or 4 bytes
|
||||
adc di,di
|
||||
or ax,si
|
||||
add al,[di]
|
||||
endm
|
||||
|
||||
Test2 macro x,y ;7 or 8 bytes
|
||||
Addr&x: mov di,[bp+y] ;3 or 4 bytes
|
||||
adc di,di
|
||||
add ah,[di]
|
||||
endm
|
||||
|
||||
Scan = 128 ;scan 256 bytes at a time
|
||||
Buffer = 4 ;parms
|
||||
BufferLength = 6
|
||||
CharFlag = 8
|
||||
WordCount = 10
|
||||
|
||||
public _ScanBuffer
|
||||
_ScanBuffer proc near
|
||||
push bp
|
||||
mov bp,sp
|
||||
push si
|
||||
push di
|
||||
|
||||
xor cx,cx
|
||||
mov si,[bp+Buffer] ;si = text buffer
|
||||
mov ax,[bp+BufferLength] ;dx = length in bytes
|
||||
shr ax,1 ;dx = length in words
|
||||
jnz NormalBuf
|
||||
OneByteBuf:
|
||||
mov ax,seg WordTable
|
||||
mov es,ax
|
||||
|
||||
mov di,[bp+CharFlag]
|
||||
mov bh,[di] ;bh = old CharFlag
|
||||
mov bl,[si] ;bl = character
|
||||
add bh,‘A'-1 ;make bh into character
|
||||
add bx,bx ;prepare to index
|
||||
mov al,es:[bx]
|
||||
cbw ;get hi bit in ah (then bh)
|
||||
shr al,1 ;get low bit
|
||||
adc cx,cx ;cx = 0 or 1
|
||||
xchg ax,bx
|
||||
jmp CleanUp
|
||||
NormalBuf:
|
||||
push bp ;(1)
|
||||
pushf ;(2)
|
||||
|
||||
cwd ;dx = 0
|
||||
mov cl,Scan
|
||||
div cx
|
||||
or dx,dx ;remainder?
|
||||
jz StartAtTheTop ;nope, do the whole banana
|
||||
sub cx,dx
|
||||
sub si,cx ;adjust buf pointer
|
||||
sub si,cx
|
||||
inc ax ;adjust for partial read
|
||||
|
||||
StartAtTheTop: mov bx,dx ;get index for start...
|
||||
shl bx,1
|
||||
mov di,LoopEntry[bx] ;...address in di
|
||||
xchg dx,ax ;dx is the loop counter
|
||||
xor cx,cx ;total word count
|
||||
mov bx,[bp+CharFlag]
|
||||
mov bl,[bx] ;bl = old CharFlag
|
||||
mov bp,seg WordTable
|
||||
mov ds,bp
|
||||
mov bp,si ;scan buffer with bp
|
||||
mov si,8080h ;hi bits
|
||||
mov ax,si ;init local word counter
|
||||
shr bl,1 ;carry = old CharFlag
|
||||
jmp di
|
||||
|
||||
align 2
|
||||
Top: add bx,bx ;restore carry
|
||||
n = 0
|
||||
rept Scan/2
|
||||
Test1 %n,%n*2
|
||||
Test2 %n+1,%n*2+2
|
||||
n = n+2
|
||||
endm
|
||||
EndCount:
|
||||
sbb bx,bx ;save carry
|
||||
if Scan ge 128 ;because al+ah may equal 128!
|
||||
or ax,si
|
||||
add al,ah
|
||||
mov ah,0
|
||||
else
|
||||
add al,ah
|
||||
and ax,7fh ;mask
|
||||
endif
|
||||
add cx,ax ;update word count
|
||||
mov ax,si
|
||||
add bp,Scan*2
|
||||
dec dx ;any left?
|
||||
jng Quit
|
||||
jmp Top
|
||||
|
||||
Quit: popf ;(2) even or odd buffer?
|
||||
jnc ItsEven
|
||||
clc
|
||||
Test1 Odd,-1
|
||||
sbb bx,bx ;save carry
|
||||
shr ax,1
|
||||
adc cx,0
|
||||
ItsEven:
|
||||
push ss ;restore ds
|
||||
pop ds
|
||||
pop bp ;(1)
|
||||
CleanUp:
|
||||
mov si,[bp+WordCount]
|
||||
add [si],cx
|
||||
adc word ptr [si+2],0
|
||||
and bh,1 ;save only the carry flag
|
||||
mov si,[bp+CharFlag]
|
||||
mov [si],bh
|
||||
pop di
|
||||
pop si
|
||||
pop bp
|
||||
ret
|
||||
_ScanBuffer endp
|
||||
|
||||
.data
|
||||
Address macro X
|
||||
dw Addr&X
|
||||
endm
|
||||
|
||||
LoopEntry label word
|
||||
n = Scan
|
||||
REPT Scan
|
||||
Address %n MOD Scan
|
||||
n = n - 1
|
||||
ENDM
|
||||
|
||||
.fardata WordTable
|
||||
include qscan3.inc ;built by MAKETAB
|
||||
end
|
||||
```
|
||||
|
|
|
|||
210
16-07.md
210
16-07.md
|
|
@ -12,110 +12,112 @@ pages: 313-316
|
|||
|
||||
**Listing 16.6 OPT2.ASM**
|
||||
|
||||
;
|
||||
; Opt2 Final optimization word count
|
||||
; Written by Michael Abrash
|
||||
; Modified by Willem Clements
|
||||
; C/ Moncayo 5, Laurel de la Reina
|
||||
; 18140 La Zubia
|
||||
; Granada, Spain
|
||||
; Tel 34-58-890398
|
||||
; Fax 34-58-224102
|
||||
;
|
||||
parms struc
|
||||
dw 2 dup(?)
|
||||
buffer dw ?
|
||||
bufferlength dw ?
|
||||
charflag dw ?
|
||||
wordcount dw ?
|
||||
parms ends
|
||||
.model small
|
||||
.data
|
||||
charstatustable label byte
|
||||
rept 2
|
||||
db 39 dup(0)
|
||||
db 1
|
||||
db 8 dup(0)
|
||||
db 10 dup(1)
|
||||
db 7 dup(0)
|
||||
db 26 dup(1)
|
||||
db 6 dup(0)
|
||||
db 26 dup(1)
|
||||
db 5 dup(0)
|
||||
endm
|
||||
.code
|
||||
public _ScanBuffer
|
||||
_ScanBuffer proc near
|
||||
push bp
|
||||
mov bp,sp
|
||||
push si
|
||||
push di
|
||||
mov si,[bp+buffer]
|
||||
mov bx,[bp+charflag]
|
||||
mov al,[bx]
|
||||
mov cx,[bp+bufferlength]
|
||||
mov bx,offset charstatustable
|
||||
xor di,di ; set wordcount to zero
|
||||
shr cx,1 ; change count to wordcount
|
||||
jc oddentry ; odd number of bytes to process
|
||||
cmp al,01h ; check if last one is char
|
||||
jne scanloop4 ; if not so, search for char
|
||||
jmp scanloop1 ; if so, search for zero
|
||||
oddentry: xchg al,ah ; last one in ah
|
||||
lodsb ; get first byte
|
||||
inc cx
|
||||
cmp ah,01h ; check if last one was char
|
||||
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
|
||||
xlat ; translate first
|
||||
xchg al,ah ; first in ah
|
||||
scanloop2: xlat ; translate second
|
||||
dec cx ; count down
|
||||
jz done1 ; no more bytes left
|
||||
cmp ax,0101h ; check if two chars
|
||||
je scanloop1 ; go for next two bytes
|
||||
inc di ; increase wordcount
|
||||
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
|
||||
xlat ; translate first
|
||||
xchg al,ah ; first in ah
|
||||
scanloop5: xlat ; translate second
|
||||
dec cx ; count down
|
||||
jz done2 ; no more bytes left
|
||||
cmp ax,0 ; check if word started
|
||||
je scanloop4 ; if not, locate begin
|
||||
cmp al,01h ; check one-letter word
|
||||
je scanloop1 ; if not, locate end of word
|
||||
inc di ; increase wordcount
|
||||
jmp scanloop4 ; locate begin of next word
|
||||
done1: cmp ax,0101h ; check if end-of-word
|
||||
je done ; if not, we have finished
|
||||
inc di ; increase wordcount
|
||||
jmp done
|
||||
done2: cmp ax,0100h ; check for one-letter word
|
||||
jne done ; if not, we have finished
|
||||
inc di ; increase wordcount
|
||||
done: mov si,[bp+charflag]
|
||||
mov [si],al
|
||||
mov bx,[bp+wordcount]
|
||||
mov ax,[bx]
|
||||
mov dx,[bx+2]
|
||||
add di,ax
|
||||
adc dx,0
|
||||
mov [bx],di
|
||||
mov [bx+2],dx
|
||||
pop di
|
||||
pop si
|
||||
pop bp
|
||||
ret
|
||||
_ScanBuffer endp
|
||||
end
|
||||
```nasm
|
||||
;
|
||||
; Opt2 Final optimization word count
|
||||
; Written by Michael Abrash
|
||||
; Modified by Willem Clements
|
||||
; C/ Moncayo 5, Laurel de la Reina
|
||||
; 18140 La Zubia
|
||||
; Granada, Spain
|
||||
; Tel 34-58-890398
|
||||
; Fax 34-58-224102
|
||||
;
|
||||
parms struc
|
||||
dw 2 dup(?)
|
||||
buffer dw ?
|
||||
bufferlength dw ?
|
||||
charflag dw ?
|
||||
wordcount dw ?
|
||||
parms ends
|
||||
.model small
|
||||
.data
|
||||
charstatustable label byte
|
||||
rept 2
|
||||
db 39 dup(0)
|
||||
db 1
|
||||
db 8 dup(0)
|
||||
db 10 dup(1)
|
||||
db 7 dup(0)
|
||||
db 26 dup(1)
|
||||
db 6 dup(0)
|
||||
db 26 dup(1)
|
||||
db 5 dup(0)
|
||||
endm
|
||||
.code
|
||||
public _ScanBuffer
|
||||
_ScanBuffer proc near
|
||||
push bp
|
||||
mov bp,sp
|
||||
push si
|
||||
push di
|
||||
mov si,[bp+buffer]
|
||||
mov bx,[bp+charflag]
|
||||
mov al,[bx]
|
||||
mov cx,[bp+bufferlength]
|
||||
mov bx,offset charstatustable
|
||||
xor di,di ; set wordcount to zero
|
||||
shr cx,1 ; change count to wordcount
|
||||
jc oddentry ; odd number of bytes to process
|
||||
cmp al,01h ; check if last one is char
|
||||
jne scanloop4 ; if not so, search for char
|
||||
jmp scanloop1 ; if so, search for zero
|
||||
oddentry: xchg al,ah ; last one in ah
|
||||
lodsb ; get first byte
|
||||
inc cx
|
||||
cmp ah,01h ; check if last one was char
|
||||
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
|
||||
xlat ; translate first
|
||||
xchg al,ah ; first in ah
|
||||
scanloop2: xlat ; translate second
|
||||
dec cx ; count down
|
||||
jz done1 ; no more bytes left
|
||||
cmp ax,0101h ; check if two chars
|
||||
je scanloop1 ; go for next two bytes
|
||||
inc di ; increase wordcount
|
||||
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
|
||||
xlat ; translate first
|
||||
xchg al,ah ; first in ah
|
||||
scanloop5: xlat ; translate second
|
||||
dec cx ; count down
|
||||
jz done2 ; no more bytes left
|
||||
cmp ax,0 ; check if word started
|
||||
je scanloop4 ; if not, locate begin
|
||||
cmp al,01h ; check one-letter word
|
||||
je scanloop1 ; if not, locate end of word
|
||||
inc di ; increase wordcount
|
||||
jmp scanloop4 ; locate begin of next word
|
||||
done1: cmp ax,0101h ; check if end-of-word
|
||||
je done ; if not, we have finished
|
||||
inc di ; increase wordcount
|
||||
jmp done
|
||||
done2: cmp ax,0100h ; check for one-letter word
|
||||
jne done ; if not, we have finished
|
||||
inc di ; increase wordcount
|
||||
done: mov si,[bp+charflag]
|
||||
mov [si],al
|
||||
mov bx,[bp+wordcount]
|
||||
mov ax,[bx]
|
||||
mov dx,[bx+2]
|
||||
add di,ax
|
||||
adc dx,0
|
||||
mov [bx],di
|
||||
mov [bx+2],dx
|
||||
pop di
|
||||
pop si
|
||||
pop bp
|
||||
ret
|
||||
_ScanBuffer endp
|
||||
end
|
||||
```
|
||||
|
||||
### Level 2: A New Perspective {#Heading11}
|
||||
|
||||
|
|
|
|||
96
16-08.md
96
16-08.md
|
|
@ -12,20 +12,21 @@ pages: 316-319
|
|||
|
||||
**Listing 16.7 L16-7.ASM**
|
||||
|
||||
ScanLoop:
|
||||
lodsw ;get the next 2 bytes (AL = first, AH = 2nd)
|
||||
xlat ;look up first's char/not status
|
||||
xor dl,al ;see if there's a new char/not status
|
||||
add di,dx ;we add 1 for each char/not transition
|
||||
mov dl,al
|
||||
mov al,ah ;look at the second byte
|
||||
xlat ;look up its char/not status
|
||||
xor dl,al ;see if there's a new char/not status
|
||||
add di,dx ;we add 1 for each char/not transition
|
||||
mov dl,al
|
||||
dec dx
|
||||
jnz ScanLoop
|
||||
|
||||
```nasm
|
||||
ScanLoop:
|
||||
lodsw ;get the next 2 bytes (AL = first, AH = 2nd)
|
||||
xlat ;look up first's char/not status
|
||||
xor dl,al ;see if there's a new char/not status
|
||||
add di,dx ;we add 1 for each char/not transition
|
||||
mov dl,al
|
||||
mov al,ah ;look at the second byte
|
||||
xlat ;look up its char/not status
|
||||
xor dl,al ;see if there's a new char/not status
|
||||
add di,dx ;we add 1 for each char/not transition
|
||||
mov dl,al
|
||||
dec dx
|
||||
jnz ScanLoop
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -66,39 +67,40 @@ report a "location counter overflow" warning; ignore it.)
|
|||
|
||||
**LISTING 16.8 MAKETAB.C**
|
||||
|
||||
// MAKETAB.C — Build QSCAN3.INC for QSCAN3.ASM
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define ChType( c ) (((c) & 0x7f) == ‘\'' || isalnum((c) & 0x7f))
|
||||
|
||||
int NoCarry[ 4 ] = { 0, 0x80, 1, 0x80 };
|
||||
int Carry[ 4 ] = { 1, 0x81, 1, 0x80 };
|
||||
|
||||
void main( void )
|
||||
{
|
||||
int ahChar, alChar, i;
|
||||
FILE *t = fopen( "QSCAN3.INC", "wt" );
|
||||
|
||||
printf( "Building table. Please wait..." );
|
||||
|
||||
for( ahChar = 0; ahChar < 128; ahChar++ )
|
||||
{
|
||||
for( alChar = 0; alChar < 256; alChar++ )
|
||||
{
|
||||
i = ChType( alChar ) * 2 + ChType( ahChar );
|
||||
|
||||
if( alChar % 8 == 0 ) fprintf( t, "\ndb %02Xh", NoCarry[ i ] );
|
||||
else fprintf( t, ",%02Xh", NoCarry[ i ] );
|
||||
|
||||
fprintf( t, ",%02Xh", Carry[ i ] );
|
||||
}
|
||||
}
|
||||
|
||||
fclose( t );
|
||||
}
|
||||
|
||||
```c
|
||||
// MAKETAB.C — Build QSCAN3.INC for QSCAN3.ASM
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define ChType( c ) (((c) & 0x7f) == ‘\'' || isalnum((c) & 0x7f))
|
||||
|
||||
int NoCarry[ 4 ] = { 0, 0x80, 1, 0x80 };
|
||||
int Carry[ 4 ] = { 1, 0x81, 1, 0x80 };
|
||||
|
||||
void main( void )
|
||||
{
|
||||
int ahChar, alChar, i;
|
||||
FILE *t = fopen( "QSCAN3.INC", "wt" );
|
||||
|
||||
printf( "Building table. Please wait..." );
|
||||
|
||||
for( ahChar = 0; ahChar < 128; ahChar++ )
|
||||
{
|
||||
for( alChar = 0; alChar < 256; alChar++ )
|
||||
{
|
||||
i = ChType( alChar ) * 2 + ChType( ahChar );
|
||||
|
||||
if( alChar % 8 == 0 ) fprintf( t, "\ndb %02Xh", NoCarry[ i ] );
|
||||
else fprintf( t, ",%02Xh", NoCarry[ i ] );
|
||||
|
||||
fprintf( t, ",%02Xh", Carry[ i ] );
|
||||
}
|
||||
}
|
||||
|
||||
fclose( t );
|
||||
}
|
||||
```
|
||||
|
||||
David's approach is simplicity itself, although his implementation
|
||||
arguably is not. Consider any three sequential bytes in the buffer.
|
||||
|
|
|
|||
446
17-02.md
446
17-02.md
|
|
@ -12,256 +12,260 @@ pages: 325-329
|
|||
|
||||
**LISTING 17.1 L17-1.CPP**
|
||||
|
||||
/* C++ Game of Life implementation for any mode for which mode set
|
||||
and draw pixel functions can be provided.
|
||||
Tested with Borland C++ in the small model. */
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <iostream.h>
|
||||
#include <conio.h>
|
||||
#include <time.h>
|
||||
#include <dos.h>
|
||||
#include <bios.h>
|
||||
#include <mem.h>
|
||||
```cpp
|
||||
/* C++ Game of Life implementation for any mode for which mode set
|
||||
and draw pixel functions can be provided.
|
||||
Tested with Borland C++ in the small model. */
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <iostream.h>
|
||||
#include <conio.h>
|
||||
#include <time.h>
|
||||
#include <dos.h>
|
||||
#include <bios.h>
|
||||
#include <mem.h>
|
||||
|
||||
#define ON_COLOR 15 // on-cell pixel color
|
||||
#define OFF_COLOR 0 // off-cell pixel color
|
||||
#define MSG_LINE 10 // row for text messages
|
||||
#define GENERATION_LINE 12 // row for generation # display
|
||||
#define LIMIT_18_HZ 1 // set 1 for maximum frame rate = 18Hz
|
||||
#define WRAP_EDGES 1 // set to 0 to disable wrapping around
|
||||
// at cell map edges
|
||||
class cellmap {
|
||||
private:
|
||||
unsigned char *cells;
|
||||
unsigned int width;
|
||||
unsigned int width_in_bytes;
|
||||
unsigned int height;
|
||||
unsigned int length_in_bytes;
|
||||
public:
|
||||
cellmap(unsigned int h, unsigned int v);
|
||||
~cellmap(void);
|
||||
void copy_cells(cellmap &sourcemap);
|
||||
void set_cell(unsigned int x, unsigned int y);
|
||||
void clear_cell(unsigned int x, unsigned int y);
|
||||
int cell_state(int x, int y);
|
||||
void next_generation(cellmap& dest_map);
|
||||
};
|
||||
#define ON_COLOR 15 // on-cell pixel color
|
||||
#define OFF_COLOR 0 // off-cell pixel color
|
||||
#define MSG_LINE 10 // row for text messages
|
||||
#define GENERATION_LINE 12 // row for generation # display
|
||||
#define LIMIT_18_HZ 1 // set 1 for maximum frame rate = 18Hz
|
||||
#define WRAP_EDGES 1 // set to 0 to disable wrapping around
|
||||
// at cell map edges
|
||||
class cellmap {
|
||||
private:
|
||||
unsigned char *cells;
|
||||
unsigned int width;
|
||||
unsigned int width_in_bytes;
|
||||
unsigned int height;
|
||||
unsigned int length_in_bytes;
|
||||
public:
|
||||
cellmap(unsigned int h, unsigned int v);
|
||||
~cellmap(void);
|
||||
void copy_cells(cellmap &sourcemap);
|
||||
void set_cell(unsigned int x, unsigned int y);
|
||||
void clear_cell(unsigned int x, unsigned int y);
|
||||
int cell_state(int x, int y);
|
||||
void next_generation(cellmap& dest_map);
|
||||
};
|
||||
|
||||
extern void enter_display_mode(void);
|
||||
extern void exit_display_mode(void);
|
||||
extern void draw_pixel(unsigned int X, unsigned int Y,
|
||||
unsigned int Color);
|
||||
extern void show_text(int x, int y, char *text);
|
||||
extern void enter_display_mode(void);
|
||||
extern void exit_display_mode(void);
|
||||
extern void draw_pixel(unsigned int X, unsigned int Y,
|
||||
unsigned int Color);
|
||||
extern void show_text(int x, int y, char *text);
|
||||
|
||||
/* 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
|
||||
display at right. */
|
||||
unsigned int cellmap_width = 96;
|
||||
unsigned int cellmap_height = 96;
|
||||
/* Width & height in pixels of each cell as displayed on screen. */
|
||||
unsigned int magnifier = 2;
|
||||
/* 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
|
||||
display at right. */
|
||||
unsigned int cellmap_width = 96;
|
||||
unsigned int cellmap_height = 96;
|
||||
/* Width & height in pixels of each cell as displayed on screen. */
|
||||
unsigned int magnifier = 2;
|
||||
|
||||
void main()
|
||||
{
|
||||
unsigned int init_length, x, y, seed;
|
||||
unsigned long generation = 0;
|
||||
char gen_text[80];
|
||||
long bios_time, start_bios_time;
|
||||
void main()
|
||||
{
|
||||
unsigned int init_length, x, y, seed;
|
||||
unsigned long generation = 0;
|
||||
char gen_text[80];
|
||||
long bios_time, start_bios_time;
|
||||
|
||||
cellmap current_map(cellmap_height, cellmap_width);
|
||||
cellmap next_map(cellmap_height, cellmap_width);
|
||||
cellmap current_map(cellmap_height, cellmap_width);
|
||||
cellmap next_map(cellmap_height, cellmap_width);
|
||||
|
||||
// Get the seed; seed randomly if 0 entered
|
||||
cout << "Seed (0 for random seed): ";
|
||||
cin >> seed;
|
||||
if (seed == 0) seed = (unsigned) time(NULL);
|
||||
// Get the seed; seed randomly if 0 entered
|
||||
cout << "Seed (0 for random seed): ";
|
||||
cin >> seed;
|
||||
if (seed == 0) seed = (unsigned) time(NULL);
|
||||
|
||||
// Randomly initialize the initial cell map
|
||||
cout << "Initializing...";
|
||||
srand(seed);
|
||||
init_length = (cellmap_height * cellmap_width) / 2;
|
||||
do {
|
||||
x = random(cellmap_width);
|
||||
y = random(cellmap_height);
|
||||
next_map.set_cell(x, y);
|
||||
} while (—init_length);
|
||||
current_map.copy_cells(next_map); // put init map in current_map
|
||||
// Randomly initialize the initial cell map
|
||||
cout << "Initializing...";
|
||||
srand(seed);
|
||||
init_length = (cellmap_height * cellmap_width) / 2;
|
||||
do {
|
||||
x = random(cellmap_width);
|
||||
y = random(cellmap_height);
|
||||
next_map.set_cell(x, y);
|
||||
} while (—init_length);
|
||||
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
|
||||
// is pressed
|
||||
show_text(0, MSG_LINE, "Generation: ");
|
||||
start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time);
|
||||
do {
|
||||
generation++;
|
||||
sprintf(gen_text, "%10lu", generation);
|
||||
show_text(1, GENERATION_LINE, gen_text);
|
||||
// Recalculate and draw the next generation
|
||||
current_map.next_generation(next_map);
|
||||
// Make current_map current again
|
||||
current_map.copy_cells(next_map);
|
||||
#if LIMIT_18_HZ
|
||||
// Limit to a maximum of 18.2 frames per second,for visibility
|
||||
do {
|
||||
_bios_timeofday(_TIME_GETCLOCK, &bios_time);
|
||||
} while (start_bios_time == bios_time);
|
||||
start_bios_time = bios_time;
|
||||
#endif
|
||||
} while (!kbhit());
|
||||
getch(); // clear keypress
|
||||
exit_display_mode();
|
||||
cout << "Total generations: " << generation << "\nSeed: " <<
|
||||
seed << "\n";
|
||||
}
|
||||
// Keep recalculating and redisplaying generations until a key
|
||||
// is pressed
|
||||
show_text(0, MSG_LINE, "Generation: ");
|
||||
start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time);
|
||||
do {
|
||||
generation++;
|
||||
sprintf(gen_text, "%10lu", generation);
|
||||
show_text(1, GENERATION_LINE, gen_text);
|
||||
// Recalculate and draw the next generation
|
||||
current_map.next_generation(next_map);
|
||||
// Make current_map current again
|
||||
current_map.copy_cells(next_map);
|
||||
#if LIMIT_18_HZ
|
||||
// Limit to a maximum of 18.2 frames per second,for visibility
|
||||
do {
|
||||
_bios_timeofday(_TIME_GETCLOCK, &bios_time);
|
||||
} while (start_bios_time == bios_time);
|
||||
start_bios_time = bios_time;
|
||||
#endif
|
||||
} while (!kbhit());
|
||||
getch(); // clear keypress
|
||||
exit_display_mode();
|
||||
cout << "Total generations: " << generation << "\nSeed: " <<
|
||||
seed << "\n";
|
||||
}
|
||||
|
||||
/* cellmap constructor. */
|
||||
cellmap::cellmap(unsigned int h, unsigned int w)
|
||||
{
|
||||
width = w;
|
||||
width_in_bytes = (w + 7) / 8;
|
||||
height = h;
|
||||
length_in_bytes = width_in_bytes * h;
|
||||
cells = new unsigned char[length_in_bytes]; // cell storage
|
||||
memset(cells, 0, length_in_bytes); // clear all cells, to start
|
||||
}
|
||||
/* cellmap constructor. */
|
||||
cellmap::cellmap(unsigned int h, unsigned int w)
|
||||
{
|
||||
width = w;
|
||||
width_in_bytes = (w + 7) / 8;
|
||||
height = h;
|
||||
length_in_bytes = width_in_bytes * h;
|
||||
cells = new unsigned char[length_in_bytes]; // cell storage
|
||||
memset(cells, 0, length_in_bytes); // clear all cells, to start
|
||||
}
|
||||
|
||||
/* cellmap destructor. */
|
||||
cellmap::~cellmap(void)
|
||||
{
|
||||
delete[] cells;
|
||||
}
|
||||
/* cellmap destructor. */
|
||||
cellmap::~cellmap(void)
|
||||
{
|
||||
delete[] cells;
|
||||
}
|
||||
|
||||
/* Copies one cellmap's cells to another cellmap. Both cellmaps are
|
||||
assumed to be the same size. */
|
||||
void cellmap::copy_cells(cellmap &sourcemap)
|
||||
{
|
||||
memcpy(cells, sourcemap.cells, length_in_bytes);
|
||||
}
|
||||
/* Copies one cellmap's cells to another cellmap. Both cellmaps are
|
||||
assumed to be the same size. */
|
||||
void cellmap::copy_cells(cellmap &sourcemap)
|
||||
{
|
||||
memcpy(cells, sourcemap.cells, length_in_bytes);
|
||||
}
|
||||
|
||||
/* Turns cell on. */
|
||||
void cellmap::set_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned char *cell_ptr =
|
||||
cells + (y * width_in_bytes) + (x / 8);
|
||||
/* Turns cell on. */
|
||||
void cellmap::set_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned char *cell_ptr =
|
||||
cells + (y * width_in_bytes) + (x / 8);
|
||||
|
||||
*(cell_ptr) |= 0x80 >> (x & 0x07);
|
||||
}
|
||||
*(cell_ptr) |= 0x80 >> (x & 0x07);
|
||||
}
|
||||
|
||||
/* Turns cell off. */
|
||||
void cellmap::clear_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned char *cell_ptr =
|
||||
cells + (y * width_in_bytes) + (x / 8);
|
||||
/* Turns cell off. */
|
||||
void cellmap::clear_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned char *cell_ptr =
|
||||
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
|
||||
borders around to the opposite edge. */
|
||||
int cellmap::cell_state(int x, int y)
|
||||
{
|
||||
unsigned char *cell_ptr;
|
||||
/* Returns cell state (1=on or 0=off), optionally wrapping at the
|
||||
borders around to the opposite edge. */
|
||||
int cellmap::cell_state(int x, int y)
|
||||
{
|
||||
unsigned char *cell_ptr;
|
||||
|
||||
#if WRAP_EDGES
|
||||
while (x < 0) x += width; // wrap, if necessary
|
||||
while (x >= width) x -= width;
|
||||
while (y < 0) y += height;
|
||||
while (y >= height) y -= height;
|
||||
#else
|
||||
if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
|
||||
return 0; // return 0 for off edges if no wrapping
|
||||
#endif
|
||||
cell_ptr = cells + (y * width_in_bytes) + (x / 8);
|
||||
return (*cell_ptr & (0x80 >> (x & 0x07))) ? 1 : 0;
|
||||
}
|
||||
#if WRAP_EDGES
|
||||
while (x < 0) x += width; // wrap, if necessary
|
||||
while (x >= width) x -= width;
|
||||
while (y < 0) y += height;
|
||||
while (y >= height) y -= height;
|
||||
#else
|
||||
if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
|
||||
return 0; // return 0 for off edges if no wrapping
|
||||
#endif
|
||||
cell_ptr = cells + (y * width_in_bytes) + (x / 8);
|
||||
return (*cell_ptr & (0x80 >> (x & 0x07))) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* Calculates the next generation of a cellmap and stores it in
|
||||
next_map. */
|
||||
void cellmap::next_generation(cellmap& next_map)
|
||||
{
|
||||
unsigned int x, y, neighbor_count;
|
||||
/* Calculates the next generation of a cellmap and stores it in
|
||||
next_map. */
|
||||
void cellmap::next_generation(cellmap& next_map)
|
||||
{
|
||||
unsigned int x, y, neighbor_count;
|
||||
|
||||
for (y=0; y<height; y++) {
|
||||
for (x=0; x<width; x++) {
|
||||
// Figure out how many neighbors this cell has
|
||||
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) + cell_state(x-1, y+1) +
|
||||
cell_state(x, y+1) + cell_state(x+1, y+1);
|
||||
if (cell_state(x, y) == 1) {
|
||||
// The cell is on; does it stay on?
|
||||
if ((neighbor_count != 2) && (neighbor_count != 3)) {
|
||||
next_map.clear_cell(x, y); // turn it off
|
||||
draw_pixel(x, y, OFF_COLOR);
|
||||
}
|
||||
} else {
|
||||
// The cell is off; does it turn on?
|
||||
if (neighbor_count == 3) {
|
||||
next_map.set_cell(x, y); // turn it on
|
||||
draw_pixel(x, y, ON_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (y=0; y<height; y++) {
|
||||
for (x=0; x<width; x++) {
|
||||
// Figure out how many neighbors this cell has
|
||||
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) + cell_state(x-1, y+1) +
|
||||
cell_state(x, y+1) + cell_state(x+1, y+1);
|
||||
if (cell_state(x, y) == 1) {
|
||||
// The cell is on; does it stay on?
|
||||
if ((neighbor_count != 2) && (neighbor_count != 3)) {
|
||||
next_map.clear_cell(x, y); // turn it off
|
||||
draw_pixel(x, y, OFF_COLOR);
|
||||
}
|
||||
} else {
|
||||
// The cell is off; does it turn on?
|
||||
if (neighbor_count == 3) {
|
||||
next_map.set_cell(x, y); // turn it on
|
||||
draw_pixel(x, y, ON_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**LISTING 17.2 L17-2.CPP**
|
||||
|
||||
/* VGA mode 13h functions for Game of Life.
|
||||
Tested with Borland C++. */
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <dos.h>
|
||||
```cpp
|
||||
/* VGA mode 13h functions for Game of Life.
|
||||
Tested with Borland C++. */
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <dos.h>
|
||||
|
||||
#define TEXT_X_OFFSET 27
|
||||
#define SCREEN_WIDTH_IN_BYTES 320
|
||||
#define TEXT_X_OFFSET 27
|
||||
#define SCREEN_WIDTH_IN_BYTES 320
|
||||
|
||||
/* Width & height in pixels of each cell. */
|
||||
extern unsigned int magnifier;
|
||||
/* Width & height in pixels of each cell. */
|
||||
extern unsigned int magnifier;
|
||||
|
||||
/* Mode 13h draw pixel function. Pixels are of width & height
|
||||
specified by magnifier. */
|
||||
void draw_pixel(unsigned int x, unsigned int y, unsigned int color)
|
||||
{
|
||||
#define SCREEN_SEGMENT 0xA000
|
||||
unsigned char far *screen_ptr;
|
||||
int i, j;
|
||||
/* Mode 13h draw pixel function. Pixels are of width & height
|
||||
specified by magnifier. */
|
||||
void draw_pixel(unsigned int x, unsigned int y, unsigned int color)
|
||||
{
|
||||
#define SCREEN_SEGMENT 0xA000
|
||||
unsigned char far *screen_ptr;
|
||||
int i, j;
|
||||
|
||||
FP_SEG(screen_ptr) = SCREEN_SEGMENT;
|
||||
FP_OFF(screen_ptr) =
|
||||
y * magnifier * SCREEN_WIDTH_IN_BYTES + x * magnifier;
|
||||
for (i=0; i<magnifier; i++) {
|
||||
for (j=0; j<magnifier; j++) {
|
||||
*(screen_ptr+j) = color;
|
||||
}
|
||||
screen_ptr += SCREEN_WIDTH_IN_BYTES;
|
||||
}
|
||||
}
|
||||
FP_SEG(screen_ptr) = SCREEN_SEGMENT;
|
||||
FP_OFF(screen_ptr) =
|
||||
y * magnifier * SCREEN_WIDTH_IN_BYTES + x * magnifier;
|
||||
for (i=0; i<magnifier; i++) {
|
||||
for (j=0; j<magnifier; j++) {
|
||||
*(screen_ptr+j) = color;
|
||||
}
|
||||
screen_ptr += SCREEN_WIDTH_IN_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mode 13h mode-set function. */
|
||||
void enter_display_mode()
|
||||
{
|
||||
union REGS regset;
|
||||
/* Mode 13h mode-set function. */
|
||||
void enter_display_mode()
|
||||
{
|
||||
union REGS regset;
|
||||
|
||||
regset.x.ax = 0x0013;
|
||||
int86(0x10, ®set, ®set);
|
||||
}
|
||||
regset.x.ax = 0x0013;
|
||||
int86(0x10, ®set, ®set);
|
||||
}
|
||||
|
||||
/* Text mode mode-set function. */
|
||||
void exit_display_mode()
|
||||
{
|
||||
union REGS regset;
|
||||
/* Text mode mode-set function. */
|
||||
void exit_display_mode()
|
||||
{
|
||||
union REGS regset;
|
||||
|
||||
regset.x.ax = 0x0003;
|
||||
int86(0x10, ®set, ®set);
|
||||
}
|
||||
regset.x.ax = 0x0003;
|
||||
int86(0x10, ®set, ®set);
|
||||
}
|
||||
|
||||
/* Text display function. Offsets text to non-graphics area of
|
||||
screen. */
|
||||
void show_text(int x, int y, char *text)
|
||||
{
|
||||
gotoxy(TEXT_X_OFFSET + x, y);
|
||||
puts(text);
|
||||
}
|
||||
/* Text display function. Offsets text to non-graphics area of
|
||||
screen. */
|
||||
void show_text(int x, int y, char *text)
|
||||
{
|
||||
gotoxy(TEXT_X_OFFSET + x, y);
|
||||
puts(text);
|
||||
}
|
||||
```
|
||||
|
|
|
|||
290
17-04.md
290
17-04.md
|
|
@ -48,164 +48,166 @@ improvement.
|
|||
|
||||
**LISTING 17.3 L17-3.CPP**
|
||||
|
||||
/* cellmap class definition, constructor, copy_cells(), set_cell(),
|
||||
clear_cell(), cell_state(), count_neighbors(), and
|
||||
next_generation() for fast, hard-wired neighbor count approach.
|
||||
Otherwise, the same as Listing 17.1 */
|
||||
```cpp
|
||||
/* cellmap class definition, constructor, copy_cells(), set_cell(),
|
||||
clear_cell(), cell_state(), count_neighbors(), and
|
||||
next_generation() for fast, hard-wired neighbor count approach.
|
||||
Otherwise, the same as Listing 17.1 */
|
||||
|
||||
class cellmap {
|
||||
private:
|
||||
unsigned char *cells;
|
||||
unsigned int width;
|
||||
unsigned int width_in_bytes;
|
||||
unsigned int height;
|
||||
unsigned int length_in_bytes;
|
||||
public:
|
||||
cellmap(unsigned int h, unsigned int v);
|
||||
~cellmap(void);
|
||||
void copy_cells(cellmap &sourcemap);
|
||||
void set_cell(unsigned int x, unsigned int y);
|
||||
void clear_cell(unsigned int x, unsigned int y);
|
||||
int cell_state(int x, int y);
|
||||
int count_neighbors(int x, int y);
|
||||
void next_generation(cellmap& dest_map);
|
||||
};
|
||||
class cellmap {
|
||||
private:
|
||||
unsigned char *cells;
|
||||
unsigned int width;
|
||||
unsigned int width_in_bytes;
|
||||
unsigned int height;
|
||||
unsigned int length_in_bytes;
|
||||
public:
|
||||
cellmap(unsigned int h, unsigned int v);
|
||||
~cellmap(void);
|
||||
void copy_cells(cellmap &sourcemap);
|
||||
void set_cell(unsigned int x, unsigned int y);
|
||||
void clear_cell(unsigned int x, unsigned int y);
|
||||
int cell_state(int x, int y);
|
||||
int count_neighbors(int x, int y);
|
||||
void next_generation(cellmap& dest_map);
|
||||
};
|
||||
|
||||
/* cellmap constructor. Pads around cell storage area with 1 extra
|
||||
byte, used for handling edge wrapping. */
|
||||
cellmap::cellmap(unsigned int h, unsigned int w)
|
||||
{
|
||||
width = w;
|
||||
width_in_bytes = ((w + 7) / 8) + 2; // pad each side with
|
||||
// 1 extra byte
|
||||
height = h;
|
||||
length_in_bytes = width_in_bytes * (h + 2); // pad top/bottom
|
||||
// with 1 extra byte
|
||||
cells = new unsigned char[length_in_bytes]; // cell storage
|
||||
memset(cells, 0, length_in_bytes); // clear all cells, to start
|
||||
}
|
||||
/* cellmap constructor. Pads around cell storage area with 1 extra
|
||||
byte, used for handling edge wrapping. */
|
||||
cellmap::cellmap(unsigned int h, unsigned int w)
|
||||
{
|
||||
width = w;
|
||||
width_in_bytes = ((w + 7) / 8) + 2; // pad each side with
|
||||
// 1 extra byte
|
||||
height = h;
|
||||
length_in_bytes = width_in_bytes * (h + 2); // pad top/bottom
|
||||
// with 1 extra byte
|
||||
cells = new unsigned char[length_in_bytes]; // cell storage
|
||||
memset(cells, 0, length_in_bytes); // clear all cells, to start
|
||||
}
|
||||
|
||||
/* Copies one cellmap's cells to another cellmap. If wrapping is
|
||||
enabled, copies edge (wrap) bytes into opposite padding bytes in
|
||||
source first, so that the padding bytes off each edge have the
|
||||
same values as would be found by wrapping around to the opposite
|
||||
edge. Both cellmaps are assumed to be the same size. */
|
||||
void cellmap::copy_cells(cellmap &sourcemap)
|
||||
{
|
||||
unsigned char *cell_ptr;
|
||||
int i;
|
||||
/* Copies one cellmap's cells to another cellmap. If wrapping is
|
||||
enabled, copies edge (wrap) bytes into opposite padding bytes in
|
||||
source first, so that the padding bytes off each edge have the
|
||||
same values as would be found by wrapping around to the opposite
|
||||
edge. Both cellmaps are assumed to be the same size. */
|
||||
void cellmap::copy_cells(cellmap &sourcemap)
|
||||
{
|
||||
unsigned char *cell_ptr;
|
||||
int i;
|
||||
|
||||
#if WRAP_EDGES
|
||||
// Copy left and right edges into padding bytes on right and left
|
||||
cell_ptr = sourcemap.cells + width_in_bytes;
|
||||
for (i=0; i<height; i++) {
|
||||
*cell_ptr = *(cell_ptr + width_in_bytes - 2);
|
||||
*(cell_ptr + width_in_bytes - 1) = *(cell_ptr + 1);
|
||||
cell_ptr += width_in_bytes;
|
||||
}
|
||||
// Copy top and bottom edges into padding bytes on bottom and top
|
||||
memcpy(sourcemap.cells, sourcemap.cells + length_in_bytes -
|
||||
(width_in_bytes * 2), width_in_bytes);
|
||||
memcpy(sourcemap.cells + length_in_bytes - width_in_bytes,
|
||||
sourcemap.cells + width_in_bytes, width_in_bytes);
|
||||
#endif
|
||||
// Copy all cells to the destination
|
||||
memcpy(cells, sourcemap.cells, length_in_bytes);
|
||||
}
|
||||
#if WRAP_EDGES
|
||||
// Copy left and right edges into padding bytes on right and left
|
||||
cell_ptr = sourcemap.cells + width_in_bytes;
|
||||
for (i=0; i<height; i++) {
|
||||
*cell_ptr = *(cell_ptr + width_in_bytes - 2);
|
||||
*(cell_ptr + width_in_bytes - 1) = *(cell_ptr + 1);
|
||||
cell_ptr += width_in_bytes;
|
||||
}
|
||||
// Copy top and bottom edges into padding bytes on bottom and top
|
||||
memcpy(sourcemap.cells, sourcemap.cells + length_in_bytes -
|
||||
(width_in_bytes * 2), width_in_bytes);
|
||||
memcpy(sourcemap.cells + length_in_bytes - width_in_bytes,
|
||||
sourcemap.cells + width_in_bytes, width_in_bytes);
|
||||
#endif
|
||||
// Copy all cells to the destination
|
||||
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
|
||||
padding bytes around the cellmap. */
|
||||
void cellmap::set_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned char *cell_ptr =
|
||||
cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1);
|
||||
/* 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. */
|
||||
void cellmap::set_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned char *cell_ptr =
|
||||
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,
|
||||
to compensate for the padding bytes around the cell map. */
|
||||
void cellmap::clear_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned char *cell_ptr =
|
||||
cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1);
|
||||
/* 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. */
|
||||
void cellmap::clear_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned char *cell_ptr =
|
||||
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
|
||||
down and to the right, to
|
||||
compensate for the padding bytes around
|
||||
the cell map. */
|
||||
int cellmap::cell_state(int x, int y)
|
||||
{
|
||||
unsigned char *cell_ptr =
|
||||
cells + ((y + 1) * width_in_bytes) + ((x / 8) + 1);
|
||||
/* Returns cell state (1=on or 0=off). x and y are offset by 1 byte
|
||||
down and to the right, to
|
||||
compensate for the padding bytes around
|
||||
the cell map. */
|
||||
int cellmap::cell_state(int x, int y)
|
||||
{
|
||||
unsigned char *cell_ptr =
|
||||
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. */
|
||||
int cellmap::count_neighbors(int x, int y)
|
||||
{
|
||||
unsigned char *cell_ptr, mask;
|
||||
unsigned int neighbor_count;
|
||||
/* Counts the number of neighboring on-cells for specified cell. */
|
||||
int cellmap::count_neighbors(int x, int y)
|
||||
{
|
||||
unsigned char *cell_ptr, mask;
|
||||
unsigned int neighbor_count;
|
||||
|
||||
// Point to upper left neighbor
|
||||
cell_ptr = cells + ((y * width_in_bytes) + ((x + 7) / 8));
|
||||
mask = 0x80 >> ((x - 1) & 0x07);
|
||||
// Count upper left neighbor
|
||||
neighbor_count = (*cell_ptr & mask) ? 1 : 0;
|
||||
// Count left neighbor
|
||||
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
|
||||
// Count lower left neighbor
|
||||
if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++;
|
||||
// Point to upper left neighbor
|
||||
cell_ptr = cells + ((y * width_in_bytes) + ((x + 7) / 8));
|
||||
mask = 0x80 >> ((x - 1) & 0x07);
|
||||
// Count upper left neighbor
|
||||
neighbor_count = (*cell_ptr & mask) ? 1 : 0;
|
||||
// Count left neighbor
|
||||
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
|
||||
// Count lower left neighbor
|
||||
if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++;
|
||||
|
||||
// Point to upper neighbor
|
||||
if ((mask >>= 1) == 0) {
|
||||
mask = 0x80;
|
||||
cell_ptr++;
|
||||
}
|
||||
// Count upper neighbor
|
||||
if ((*cell_ptr & mask)) neighbor_count++;
|
||||
// Count lower neighbor
|
||||
if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++;
|
||||
// Point to upper neighbor
|
||||
if ((mask >>= 1) == 0) {
|
||||
mask = 0x80;
|
||||
cell_ptr++;
|
||||
}
|
||||
// Count upper neighbor
|
||||
if ((*cell_ptr & mask)) neighbor_count++;
|
||||
// Count lower neighbor
|
||||
if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++;
|
||||
|
||||
// Point to upper right neighbor
|
||||
if ((mask >>= 1) == 0) {
|
||||
mask = 0x80;
|
||||
cell_ptr++;
|
||||
}
|
||||
// Count upper right neighbor
|
||||
if ((*cell_ptr & mask)) neighbor_count++;
|
||||
// Count right neighbor
|
||||
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
|
||||
// Count lower right neighbor
|
||||
if ((*(cell_ptr + (width_in_bytes * 2)) & mask)) neighbor_count++;
|
||||
// Point to upper right neighbor
|
||||
if ((mask >>= 1) == 0) {
|
||||
mask = 0x80;
|
||||
cell_ptr++;
|
||||
}
|
||||
// Count upper right neighbor
|
||||
if ((*cell_ptr & mask)) neighbor_count++;
|
||||
// Count right neighbor
|
||||
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
|
||||
// Count lower right neighbor
|
||||
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
|
||||
next_map. */
|
||||
void cellmap::next_generation(cellmap& next_map)
|
||||
{
|
||||
unsigned int x, y, neighbor_count;
|
||||
/* Calculates the next generation of current_map and stores it in
|
||||
next_map. */
|
||||
void cellmap::next_generation(cellmap& next_map)
|
||||
{
|
||||
unsigned int x, y, neighbor_count;
|
||||
|
||||
for (y=0; y<height; y++) {
|
||||
for (x=0; x<width; x++) {
|
||||
neighbor_count = count_neighbors(x, y);
|
||||
if (cell_state(x, y) == 1) {
|
||||
if ((neighbor_count != 2) && (neighbor_count != 3)) {
|
||||
next_map.clear_cell(x, y); // turn it off
|
||||
draw_pixel(x, y, OFF_COLOR);
|
||||
}
|
||||
} else {
|
||||
if (neighbor_count == 3) {
|
||||
next_map.set_cell(x, y); // turn it on
|
||||
draw_pixel(x, y, ON_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (y=0; y<height; y++) {
|
||||
for (x=0; x<width; x++) {
|
||||
neighbor_count = count_neighbors(x, y);
|
||||
if (cell_state(x, y) == 1) {
|
||||
if ((neighbor_count != 2) && (neighbor_count != 3)) {
|
||||
next_map.clear_cell(x, y); // turn it off
|
||||
draw_pixel(x, y, OFF_COLOR);
|
||||
}
|
||||
} else {
|
||||
if (neighbor_count == 3) {
|
||||
next_map.set_cell(x, y); // turn it on
|
||||
draw_pixel(x, y, ON_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
158
17-05.md
158
17-05.md
|
|
@ -51,86 +51,88 @@ Listing 17.3.
|
|||
|
||||
**LISTING 17.4 L17-4.CPP**
|
||||
|
||||
/* next_generation(), implemented using fast, all-in-one hard-wired
|
||||
neighbor count/update/draw function. Otherwise, the same as
|
||||
Listing 17.3. */
|
||||
```cpp
|
||||
/* next_generation(), implemented using fast, all-in-one hard-wired
|
||||
neighbor count/update/draw function. Otherwise, the same as
|
||||
Listing 17.3. */
|
||||
|
||||
/* Calculates the next generation of current_map and stores it in
|
||||
next_map. */
|
||||
void cellmap::next_generation(cellmap& next_map)
|
||||
{
|
||||
unsigned int x, y, neighbor_count;
|
||||
unsigned int width_in_bytesX2 = width_in_bytes << 1;
|
||||
unsigned char *cell_ptr, *current_cell_ptr, mask, current_mask;
|
||||
unsigned char *base_cell_ptr, *row_cell_ptr, base_mask;
|
||||
unsigned char *dest_cell_ptr = next_map.cells;
|
||||
/* Calculates the next generation of current_map and stores it in
|
||||
next_map. */
|
||||
void cellmap::next_generation(cellmap& next_map)
|
||||
{
|
||||
unsigned int x, y, neighbor_count;
|
||||
unsigned int width_in_bytesX2 = width_in_bytes << 1;
|
||||
unsigned char *cell_ptr, *current_cell_ptr, mask, current_mask;
|
||||
unsigned char *base_cell_ptr, *row_cell_ptr, base_mask;
|
||||
unsigned char *dest_cell_ptr = next_map.cells;
|
||||
|
||||
// Process all cells in the current cellmap
|
||||
row_cell_ptr = cells; // point to upper left neighbor of
|
||||
// first cell in cell map
|
||||
for (y=0; y<height; y++) { // repeat for each row of cells
|
||||
// Cell pointer and cell bit mask for first cell in row
|
||||
base_cell_ptr = row_cell_ptr; // to access upper left neighbor
|
||||
base_mask = 0x01; // of first cell in row
|
||||
for (x=0; x<width; x++) { // repeat for each cell in row
|
||||
// First, count neighbors
|
||||
// Point to upper left neighbor of current cell
|
||||
cell_ptr = base_cell_ptr; // pointer and bit mask for
|
||||
mask = base_mask; // upper left neighbor
|
||||
// Count upper left neighbor
|
||||
neighbor_count = (*cell_ptr & mask) ? 1 : 0;
|
||||
// Count left neighbor
|
||||
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
|
||||
// Count lower left neighbor
|
||||
if ((*(cell_ptr + width_in_bytesX2) & mask))
|
||||
neighbor_count++;
|
||||
// Point to upper neighbor
|
||||
if ((mask >>= 1) == 0) {
|
||||
mask = 0x80;
|
||||
cell_ptr++;
|
||||
}
|
||||
// Remember where to find the current cell
|
||||
current_cell_ptr = cell_ptr + width_in_bytes;
|
||||
current_mask = mask;
|
||||
// Count upper neighbor
|
||||
if ((*cell_ptr & mask)) neighbor_count++;
|
||||
// Count lower neighbor
|
||||
if ((*(cell_ptr + width_in_bytesX2) & mask))
|
||||
neighbor_count++;
|
||||
// Point to upper right neighbor
|
||||
if ((mask >>= 1) == 0) {
|
||||
mask = 0x80;
|
||||
cell_ptr++;
|
||||
}
|
||||
// Count upper right neighbor
|
||||
if ((*cell_ptr & mask)) neighbor_count++;
|
||||
// Count right neighbor
|
||||
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
|
||||
// Count lower right neighbor
|
||||
if ((*(cell_ptr + width_in_bytesX2) & mask))
|
||||
neighbor_count++;
|
||||
if (*current_cell_ptr & current_mask) {
|
||||
if ((neighbor_count != 2) && (neighbor_count != 3)) {
|
||||
*(dest_cell_ptr + (current_cell_ptr - cells)) &=
|
||||
~current_mask; // turn off cell
|
||||
draw_pixel(x, y, OFF_COLOR);
|
||||
}
|
||||
} else {
|
||||
if (neighbor_count == 3) {
|
||||
*(dest_cell_ptr + (current_cell_ptr - cells)) |=
|
||||
current_mask; // turn on cell
|
||||
draw_pixel(x, y, ON_COLOR);
|
||||
}
|
||||
}
|
||||
// Advance to the next cell on row
|
||||
if ((base_mask >>= 1) == 0) {
|
||||
base_mask = 0x80;
|
||||
base_cell_ptr++; // advance to the next cell byte
|
||||
}
|
||||
}
|
||||
row_cell_ptr += width_in_bytes; // point to start of next row
|
||||
}
|
||||
}
|
||||
// Process all cells in the current cellmap
|
||||
row_cell_ptr = cells; // point to upper left neighbor of
|
||||
// first cell in cell map
|
||||
for (y=0; y<height; y++) { // repeat for each row of cells
|
||||
// Cell pointer and cell bit mask for first cell in row
|
||||
base_cell_ptr = row_cell_ptr; // to access upper left neighbor
|
||||
base_mask = 0x01; // of first cell in row
|
||||
for (x=0; x<width; x++) { // repeat for each cell in row
|
||||
// First, count neighbors
|
||||
// Point to upper left neighbor of current cell
|
||||
cell_ptr = base_cell_ptr; // pointer and bit mask for
|
||||
mask = base_mask; // upper left neighbor
|
||||
// Count upper left neighbor
|
||||
neighbor_count = (*cell_ptr & mask) ? 1 : 0;
|
||||
// Count left neighbor
|
||||
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
|
||||
// Count lower left neighbor
|
||||
if ((*(cell_ptr + width_in_bytesX2) & mask))
|
||||
neighbor_count++;
|
||||
// Point to upper neighbor
|
||||
if ((mask >>= 1) == 0) {
|
||||
mask = 0x80;
|
||||
cell_ptr++;
|
||||
}
|
||||
// Remember where to find the current cell
|
||||
current_cell_ptr = cell_ptr + width_in_bytes;
|
||||
current_mask = mask;
|
||||
// Count upper neighbor
|
||||
if ((*cell_ptr & mask)) neighbor_count++;
|
||||
// Count lower neighbor
|
||||
if ((*(cell_ptr + width_in_bytesX2) & mask))
|
||||
neighbor_count++;
|
||||
// Point to upper right neighbor
|
||||
if ((mask >>= 1) == 0) {
|
||||
mask = 0x80;
|
||||
cell_ptr++;
|
||||
}
|
||||
// Count upper right neighbor
|
||||
if ((*cell_ptr & mask)) neighbor_count++;
|
||||
// Count right neighbor
|
||||
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
|
||||
// Count lower right neighbor
|
||||
if ((*(cell_ptr + width_in_bytesX2) & mask))
|
||||
neighbor_count++;
|
||||
if (*current_cell_ptr & current_mask) {
|
||||
if ((neighbor_count != 2) && (neighbor_count != 3)) {
|
||||
*(dest_cell_ptr + (current_cell_ptr - cells)) &=
|
||||
~current_mask; // turn off cell
|
||||
draw_pixel(x, y, OFF_COLOR);
|
||||
}
|
||||
} else {
|
||||
if (neighbor_count == 3) {
|
||||
*(dest_cell_ptr + (current_cell_ptr - cells)) |=
|
||||
current_mask; // turn on cell
|
||||
draw_pixel(x, y, ON_COLOR);
|
||||
}
|
||||
}
|
||||
// Advance to the next cell on row
|
||||
if ((base_mask >>= 1) == 0) {
|
||||
base_mask = 0x80;
|
||||
base_cell_ptr++; // advance to the next cell byte
|
||||
}
|
||||
}
|
||||
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
|
||||
difference lies in how **next\_generation()** is implemented. (Only
|
||||
|
|
|
|||
492
17-07.md
492
17-07.md
|
|
@ -12,276 +12,278 @@ pages: 340-345
|
|||
|
||||
**LISTING 17.5 L17-5.CPP**
|
||||
|
||||
/* C++ Game of Life implementation for any mode for which mode set
|
||||
and draw pixel functions can be provided. The cellmap stores the
|
||||
neighbor count for each cell as well as the state of each cell;
|
||||
this allows very fast next-state determination. Edges always wrap
|
||||
in this implementation.
|
||||
Tested with Borland C++. To run, link with Listing 17.2
|
||||
in the large model. */
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <iostream.h>
|
||||
#include <conio.h>
|
||||
#include <time.h>
|
||||
#include <dos.h>
|
||||
#include <bios.h>
|
||||
#include <mem.h>
|
||||
```cpp
|
||||
/* C++ Game of Life implementation for any mode for which mode set
|
||||
and draw pixel functions can be provided. The cellmap stores the
|
||||
neighbor count for each cell as well as the state of each cell;
|
||||
this allows very fast next-state determination. Edges always wrap
|
||||
in this implementation.
|
||||
Tested with Borland C++. To run, link with Listing 17.2
|
||||
in the large model. */
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <iostream.h>
|
||||
#include <conio.h>
|
||||
#include <time.h>
|
||||
#include <dos.h>
|
||||
#include <bios.h>
|
||||
#include <mem.h>
|
||||
|
||||
#define ON_COLOR 15 // on-cell pixel color
|
||||
#define OFF_COLOR 0 // off-cell pixel color
|
||||
#define MSG_LINE 10 // row for text messages
|
||||
#define GENERATION_LINE 12 // row for generation # display
|
||||
#define LIMIT_18_HZ 0 // set 1 to to maximum frame rate = 18Hz
|
||||
#define ON_COLOR 15 // on-cell pixel color
|
||||
#define OFF_COLOR 0 // off-cell pixel color
|
||||
#define MSG_LINE 10 // row for text messages
|
||||
#define GENERATION_LINE 12 // row for generation # display
|
||||
#define LIMIT_18_HZ 0 // set 1 to to maximum frame rate = 18Hz
|
||||
|
||||
class cellmap {
|
||||
private:
|
||||
unsigned char *cells;
|
||||
unsigned char *temp_cells;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int length_in_bytes;
|
||||
public:
|
||||
cellmap(unsigned int h, unsigned int v);
|
||||
~cellmap(void);
|
||||
void set_cell(unsigned int x, unsigned int y);
|
||||
void clear_cell(unsigned int x, unsigned int y);
|
||||
int cell_state(int x, int y);
|
||||
int count_neighbors(int x, int y);
|
||||
void next_generation(void);
|
||||
void init(void);
|
||||
};
|
||||
class cellmap {
|
||||
private:
|
||||
unsigned char *cells;
|
||||
unsigned char *temp_cells;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int length_in_bytes;
|
||||
public:
|
||||
cellmap(unsigned int h, unsigned int v);
|
||||
~cellmap(void);
|
||||
void set_cell(unsigned int x, unsigned int y);
|
||||
void clear_cell(unsigned int x, unsigned int y);
|
||||
int cell_state(int x, int y);
|
||||
int count_neighbors(int x, int y);
|
||||
void next_generation(void);
|
||||
void init(void);
|
||||
};
|
||||
|
||||
extern void enter_display_mode(void);
|
||||
extern void exit_display_mode(void);
|
||||
extern void draw_pixel(unsigned int X, unsigned int Y,
|
||||
unsigned int Color);
|
||||
extern void show_text(int x, int y, char *text);
|
||||
extern void enter_display_mode(void);
|
||||
extern void exit_display_mode(void);
|
||||
extern void draw_pixel(unsigned int X, unsigned int Y,
|
||||
unsigned int Color);
|
||||
extern void show_text(int x, int y, char *text);
|
||||
|
||||
/* 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
|
||||
display at right. */
|
||||
unsigned int cellmap_width = 96;
|
||||
unsigned int cellmap_height = 96;
|
||||
/* 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
|
||||
display at right. */
|
||||
unsigned int cellmap_width = 96;
|
||||
unsigned int cellmap_height = 96;
|
||||
|
||||
/* Width & height in pixels of each cell. */
|
||||
unsigned int magnifier = 2;
|
||||
/* Width & height in pixels of each cell. */
|
||||
unsigned int magnifier = 2;
|
||||
|
||||
/* Randomizing seed */
|
||||
unsigned int seed;
|
||||
/* Randomizing seed */
|
||||
unsigned int seed;
|
||||
|
||||
void main()
|
||||
{
|
||||
unsigned long generation = 0;
|
||||
char gen_text[80];
|
||||
long bios_time, start_bios_time;
|
||||
void main()
|
||||
{
|
||||
unsigned long generation = 0;
|
||||
char gen_text[80];
|
||||
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
|
||||
// is pressed
|
||||
show_text(0, MSG_LINE, "Generation: ");
|
||||
start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time);
|
||||
do {
|
||||
generation++;
|
||||
sprintf(gen_text, "%10lu", generation);
|
||||
show_text(1, GENERATION_LINE, gen_text);
|
||||
// Recalculate and draw the next generation
|
||||
current_map.next_generation();
|
||||
#if LIMIT_18_HZ
|
||||
// Limit to a maximum of 18.2 frames per second, for visibility
|
||||
do {
|
||||
_bios_timeofday(_TIME_GETCLOCK, &bios_time);
|
||||
} while (start_bios_time == bios_time);
|
||||
start_bios_time = bios_time;
|
||||
#endif
|
||||
} while (!kbhit());
|
||||
// Keep recalculating and redisplaying generations until any key
|
||||
// is pressed
|
||||
show_text(0, MSG_LINE, "Generation: ");
|
||||
start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time);
|
||||
do {
|
||||
generation++;
|
||||
sprintf(gen_text, "%10lu", generation);
|
||||
show_text(1, GENERATION_LINE, gen_text);
|
||||
// Recalculate and draw the next generation
|
||||
current_map.next_generation();
|
||||
#if LIMIT_18_HZ
|
||||
// Limit to a maximum of 18.2 frames per second, for visibility
|
||||
do {
|
||||
_bios_timeofday(_TIME_GETCLOCK, &bios_time);
|
||||
} while (start_bios_time == bios_time);
|
||||
start_bios_time = bios_time;
|
||||
#endif
|
||||
} while (!kbhit());
|
||||
|
||||
getch(); // clear keypress
|
||||
exit_display_mode();
|
||||
cout << "Total generations: " << generation << "\nSeed: " <<
|
||||
seed << "\n";
|
||||
}
|
||||
getch(); // clear keypress
|
||||
exit_display_mode();
|
||||
cout << "Total generations: " << generation << "\nSeed: " <<
|
||||
seed << "\n";
|
||||
}
|
||||
|
||||
/* cellmap constructor. */
|
||||
cellmap::cellmap(unsigned int h, unsigned int w)
|
||||
{
|
||||
width = w;
|
||||
height = h;
|
||||
length_in_bytes = w * h;
|
||||
cells = new unsigned char[length_in_bytes]; // cell storage
|
||||
temp_cells = new unsigned char[length_in_bytes]; // temp cell storage
|
||||
if ( (cells == NULL) || (temp_cells == NULL) ) {
|
||||
printf("Out of memory\n");
|
||||
exit(1);
|
||||
}
|
||||
memset(cells, 0, length_in_bytes); // clear all cells, to start
|
||||
}
|
||||
/* cellmap constructor. */
|
||||
cellmap::cellmap(unsigned int h, unsigned int w)
|
||||
{
|
||||
width = w;
|
||||
height = h;
|
||||
length_in_bytes = w * h;
|
||||
cells = new unsigned char[length_in_bytes]; // cell storage
|
||||
temp_cells = new unsigned char[length_in_bytes]; // temp cell storage
|
||||
if ( (cells == NULL) || (temp_cells == NULL) ) {
|
||||
printf("Out of memory\n");
|
||||
exit(1);
|
||||
}
|
||||
memset(cells, 0, length_in_bytes); // clear all cells, to start
|
||||
}
|
||||
|
||||
/* cellmap destructor. */
|
||||
cellmap::~cellmap(void)
|
||||
{
|
||||
delete[] cells;
|
||||
delete[] temp_cells;
|
||||
}
|
||||
/* cellmap destructor. */
|
||||
cellmap::~cellmap(void)
|
||||
{
|
||||
delete[] cells;
|
||||
delete[] temp_cells;
|
||||
}
|
||||
|
||||
/* Turns an off-cell on, incrementing the on-neighbor count for the
|
||||
eight neighboring cells. */
|
||||
void cellmap::set_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned int w = width, h = height;
|
||||
int xoleft, xoright, yoabove, yobelow;
|
||||
unsigned char *cell_ptr = cells + (y * w) + x;
|
||||
/* Turns an off-cell on, incrementing the on-neighbor count for the
|
||||
eight neighboring cells. */
|
||||
void cellmap::set_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned int w = width, h = height;
|
||||
int xoleft, xoright, yoabove, yobelow;
|
||||
unsigned char *cell_ptr = cells + (y * w) + x;
|
||||
|
||||
// Calculate the offsets to the eight neighboring cells,
|
||||
// accounting for wrapping around at the edges of the cell map
|
||||
if (x == 0)
|
||||
xoleft = w - 1;
|
||||
else
|
||||
xoleft = -1;
|
||||
if (y == 0)
|
||||
yoabove = length_in_bytes - w;
|
||||
else
|
||||
yoabove = -w;
|
||||
if (x == (w - 1))
|
||||
xoright = -(w - 1);
|
||||
else
|
||||
xoright = 1;
|
||||
if (y == (h - 1))
|
||||
yobelow = -(length_in_bytes - w);
|
||||
else
|
||||
yobelow = w;
|
||||
// Calculate the offsets to the eight neighboring cells,
|
||||
// accounting for wrapping around at the edges of the cell map
|
||||
if (x == 0)
|
||||
xoleft = w - 1;
|
||||
else
|
||||
xoleft = -1;
|
||||
if (y == 0)
|
||||
yoabove = length_in_bytes - w;
|
||||
else
|
||||
yoabove = -w;
|
||||
if (x == (w - 1))
|
||||
xoright = -(w - 1);
|
||||
else
|
||||
xoright = 1;
|
||||
if (y == (h - 1))
|
||||
yobelow = -(length_in_bytes - w);
|
||||
else
|
||||
yobelow = w;
|
||||
|
||||
*(cell_ptr) |= 0x01;
|
||||
*(cell_ptr + yoabove + xoleft) += 2;
|
||||
*(cell_ptr + yoabove) += 2;
|
||||
*(cell_ptr + yoabove + xoright) += 2;
|
||||
*(cell_ptr + xoleft) += 2;
|
||||
*(cell_ptr + xoright) += 2;
|
||||
*(cell_ptr + yobelow + xoleft) += 2;
|
||||
*(cell_ptr + yobelow) += 2;
|
||||
*(cell_ptr + yobelow + xoright) += 2;
|
||||
}
|
||||
*(cell_ptr) |= 0x01;
|
||||
*(cell_ptr + yoabove + xoleft) += 2;
|
||||
*(cell_ptr + yoabove) += 2;
|
||||
*(cell_ptr + yoabove + xoright) += 2;
|
||||
*(cell_ptr + xoleft) += 2;
|
||||
*(cell_ptr + xoright) += 2;
|
||||
*(cell_ptr + yobelow + xoleft) += 2;
|
||||
*(cell_ptr + yobelow) += 2;
|
||||
*(cell_ptr + yobelow + xoright) += 2;
|
||||
}
|
||||
|
||||
/* Turns an on-cell off, decrementing the on-neighbor count for the
|
||||
eight neighboring cells. */
|
||||
void cellmap::clear_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned int w = width, h = height;
|
||||
int xoleft, xoright, yoabove, yobelow;
|
||||
unsigned char *cell_ptr = cells + (y * w) + x;
|
||||
/* Turns an on-cell off, decrementing the on-neighbor count for the
|
||||
eight neighboring cells. */
|
||||
void cellmap::clear_cell(unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned int w = width, h = height;
|
||||
int xoleft, xoright, yoabove, yobelow;
|
||||
unsigned char *cell_ptr = cells + (y * w) + x;
|
||||
|
||||
// Calculate the offsets to the eight neighboring cells,
|
||||
// accounting for wrapping around at the edges of the cell map
|
||||
if (x == 0)
|
||||
xoleft = w - 1;
|
||||
else
|
||||
xoleft = -1;
|
||||
if (y == 0)
|
||||
yoabove = length_in_bytes - w;
|
||||
else
|
||||
yoabove = -w;
|
||||
if (x == (w - 1))
|
||||
xoright = -(w - 1);
|
||||
else
|
||||
xoright = 1;
|
||||
if (y == (h - 1))
|
||||
yobelow = -(length_in_bytes - w);
|
||||
else
|
||||
yobelow = w;
|
||||
// Calculate the offsets to the eight neighboring cells,
|
||||
// accounting for wrapping around at the edges of the cell map
|
||||
if (x == 0)
|
||||
xoleft = w - 1;
|
||||
else
|
||||
xoleft = -1;
|
||||
if (y == 0)
|
||||
yoabove = length_in_bytes - w;
|
||||
else
|
||||
yoabove = -w;
|
||||
if (x == (w - 1))
|
||||
xoright = -(w - 1);
|
||||
else
|
||||
xoright = 1;
|
||||
if (y == (h - 1))
|
||||
yobelow = -(length_in_bytes - w);
|
||||
else
|
||||
yobelow = w;
|
||||
|
||||
*(cell_ptr) &= ~0x01;
|
||||
*(cell_ptr + yoabove + xoleft) -= 2;
|
||||
*(cell_ptr + yoabove ) -= 2;
|
||||
*(cell_ptr + yoabove + xoright) -= 2;
|
||||
*(cell_ptr + xoleft) -= 2;
|
||||
*(cell_ptr + xoright) -= 2;
|
||||
*(cell_ptr + yobelow + xoleft) -= 2;
|
||||
*(cell_ptr + yobelow) -= 2;
|
||||
*(cell_ptr + yobelow + xoright) -= 2;
|
||||
}
|
||||
*(cell_ptr) &= ~0x01;
|
||||
*(cell_ptr + yoabove + xoleft) -= 2;
|
||||
*(cell_ptr + yoabove ) -= 2;
|
||||
*(cell_ptr + yoabove + xoright) -= 2;
|
||||
*(cell_ptr + xoleft) -= 2;
|
||||
*(cell_ptr + xoright) -= 2;
|
||||
*(cell_ptr + yobelow + xoleft) -= 2;
|
||||
*(cell_ptr + yobelow) -= 2;
|
||||
*(cell_ptr + yobelow + xoright) -= 2;
|
||||
}
|
||||
|
||||
/* Returns cell state (1=on or 0=off). */
|
||||
int cellmap::cell_state(int x, int y)
|
||||
{
|
||||
unsigned char *cell_ptr;
|
||||
/* Returns cell state (1=on or 0=off). */
|
||||
int cellmap::cell_state(int x, int y)
|
||||
{
|
||||
unsigned char *cell_ptr;
|
||||
|
||||
cell_ptr = cells + (y * width) + x;
|
||||
return *cell_ptr & 0x01;
|
||||
}
|
||||
cell_ptr = cells + (y * width) + x;
|
||||
return *cell_ptr & 0x01;
|
||||
}
|
||||
|
||||
/* Calculates and displays the next generation of current_map */
|
||||
void cellmap::next_generation()
|
||||
{
|
||||
unsigned int x, y, count;
|
||||
unsigned int h = height, w = width;
|
||||
unsigned char *cell_ptr, *row_cell_ptr;
|
||||
/* Calculates and displays the next generation of current_map */
|
||||
void cellmap::next_generation()
|
||||
{
|
||||
unsigned int x, y, count;
|
||||
unsigned int h = height, w = width;
|
||||
unsigned char *cell_ptr, *row_cell_ptr;
|
||||
|
||||
// Copy to temp map, so we can have an unaltered version from
|
||||
// which to work
|
||||
memcpy(temp_cells, cells, length_in_bytes);
|
||||
// Copy to temp map, so we can have an unaltered version from
|
||||
// which to work
|
||||
memcpy(temp_cells, cells, length_in_bytes);
|
||||
|
||||
// Process all cells in the current cell map
|
||||
cell_ptr = temp_cells; // first cell in cell map
|
||||
for (y=0; y<h; y++) { // repeat for each row of cells
|
||||
// Process all cells in the current row of the cell map
|
||||
x = 0;
|
||||
do { // repeat for each cell in row
|
||||
// Zip quickly through as many off-cells with no
|
||||
// neighbors as possible
|
||||
while (*cell_ptr == 0) {
|
||||
cell_ptr++; // advance to the next cell
|
||||
if (++x >= w) goto RowDone;
|
||||
}
|
||||
// Found a cell that's either on or has on-neighbors,
|
||||
// so see if its state needs to be changed
|
||||
count = *cell_ptr >> 1; // # of neighboring on-cells
|
||||
if (*cell_ptr & 0x01) {
|
||||
// Cell is on; turn it off if it doesn't have
|
||||
// 2 or 3 neighbors
|
||||
if ((count != 2) && (count != 3)) {
|
||||
clear_cell(x, y);
|
||||
draw_pixel(x, y, OFF_COLOR);
|
||||
}
|
||||
} else {
|
||||
// Cell is off; turn it on if it has exactly 3 neighbors
|
||||
if (count == 3) {
|
||||
set_cell(x, y);
|
||||
draw_pixel(x, y, ON_COLOR);
|
||||
}
|
||||
}
|
||||
// Advance to the next cell
|
||||
cell_ptr++; // advance to the next cell byte
|
||||
} while (++x < w);
|
||||
RowDone:
|
||||
}
|
||||
}
|
||||
// Process all cells in the current cell map
|
||||
cell_ptr = temp_cells; // first cell in cell map
|
||||
for (y=0; y<h; y++) { // repeat for each row of cells
|
||||
// Process all cells in the current row of the cell map
|
||||
x = 0;
|
||||
do { // repeat for each cell in row
|
||||
// Zip quickly through as many off-cells with no
|
||||
// neighbors as possible
|
||||
while (*cell_ptr == 0) {
|
||||
cell_ptr++; // advance to the next cell
|
||||
if (++x >= w) goto RowDone;
|
||||
}
|
||||
// Found a cell that's either on or has on-neighbors,
|
||||
// so see if its state needs to be changed
|
||||
count = *cell_ptr >> 1; // # of neighboring on-cells
|
||||
if (*cell_ptr & 0x01) {
|
||||
// Cell is on; turn it off if it doesn't have
|
||||
// 2 or 3 neighbors
|
||||
if ((count != 2) && (count != 3)) {
|
||||
clear_cell(x, y);
|
||||
draw_pixel(x, y, OFF_COLOR);
|
||||
}
|
||||
} else {
|
||||
// Cell is off; turn it on if it has exactly 3 neighbors
|
||||
if (count == 3) {
|
||||
set_cell(x, y);
|
||||
draw_pixel(x, y, ON_COLOR);
|
||||
}
|
||||
}
|
||||
// Advance to the next cell
|
||||
cell_ptr++; // advance to the next cell byte
|
||||
} while (++x < w);
|
||||
RowDone:
|
||||
}
|
||||
}
|
||||
|
||||
/* Randomly initializes the cellmap to about 50% on-pixels. */
|
||||
void cellmap::init()
|
||||
{
|
||||
unsigned int x, y, init_length;
|
||||
/* Randomly initializes the cellmap to about 50% on-pixels. */
|
||||
void cellmap::init()
|
||||
{
|
||||
unsigned int x, y, init_length;
|
||||
|
||||
// Get the seed; seed randomly if 0 entered
|
||||
cout << "Seed (0 for random seed): ";
|
||||
cin >> seed;
|
||||
if (seed == 0) seed = (unsigned) time(NULL);
|
||||
// Get the seed; seed randomly if 0 entered
|
||||
cout << "Seed (0 for random seed): ";
|
||||
cin >> seed;
|
||||
if (seed == 0) seed = (unsigned) time(NULL);
|
||||
|
||||
// Randomly initialize the initial cell map to 50% on-pixels
|
||||
// (actually generally fewer, because some coordinates will be
|
||||
// randomly selected more than once)
|
||||
cout << "Initializing...";
|
||||
srand(seed);
|
||||
init_length = (height * width) / 2;
|
||||
do {
|
||||
x = random(width);
|
||||
y = random(height);
|
||||
if (cell_state(x, y) == 0) {
|
||||
set_cell(x, y);
|
||||
}
|
||||
} while (—init_length);
|
||||
}
|
||||
// Randomly initialize the initial cell map to 50% on-pixels
|
||||
// (actually generally fewer, because some coordinates will be
|
||||
// randomly selected more than once)
|
||||
cout << "Initializing...";
|
||||
srand(seed);
|
||||
init_length = (height * width) / 2;
|
||||
do {
|
||||
x = random(width);
|
||||
y = random(height);
|
||||
if (cell_state(x, y) == 0) {
|
||||
set_cell(x, y);
|
||||
}
|
||||
} while (—init_length);
|
||||
}
|
||||
```
|
||||
|
|
|
|||
928
18-03.md
928
18-03.md
|
|
@ -12,503 +12,507 @@ pages: 352-361
|
|||
|
||||
**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
|
||||
lcomp > qlife.asm
|
||||
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
|
||||
```bat
|
||||
bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 lcomp.c
|
||||
lcomp > qlife.asm
|
||||
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**
|
||||
|
||||
// LCOMP.C
|
||||
//
|
||||
// Life compiler, ver 1.3
|
||||
//
|
||||
// David Stafford
|
||||
//
|
||||
```c
|
||||
// LCOMP.C
|
||||
//
|
||||
// Life compiler, ver 1.3
|
||||
//
|
||||
// David Stafford
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "life.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.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;
|
||||
char Buf[ 20 ];
|
||||
int Old, New, Edge, Label;
|
||||
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( 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++ )
|
||||
if( i == 0 || i == WIDTH-1 || j == 0 || j == HEIGHT-1 )
|
||||
{
|
||||
for( i = 0; i < WIDTH; i++ )
|
||||
{
|
||||
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" );
|
||||
printf( "dw 8000h\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
printf( "Change1 dw offset DGROUP:_ChangeList1\n" );
|
||||
printf( "Change2 dw offset DGROUP:_ChangeList2\n" );
|
||||
printf( "dw 0\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( "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 );
|
||||
printf( "dw 0%02x%02xh\n", j, i * 3 );
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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();
|
||||
y |= 0x4000;
|
||||
}
|
||||
|
||||
void Test( char *Offset, char *Str )
|
||||
if( !C1 && (N1 + C2 == 3) )
|
||||
{
|
||||
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 );
|
||||
y |= 0x4000;
|
||||
}
|
||||
|
||||
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 );
|
||||
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 );
|
||||
y |= 0x2000;
|
||||
}
|
||||
|
||||
void SecondPass( void )
|
||||
if( !C2 && (N2 + C1 + C3 == 3) )
|
||||
{
|
||||
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();
|
||||
y |= 0x2000;
|
||||
}
|
||||
|
||||
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( "LIFE segment ‘CODE'\n" );
|
||||
printf( "assume cs:LIFE,ds:DGROUP,ss:DGROUP,es:NOTHING\n" );
|
||||
printf( ".386C\n" "public _NextGen\n\n" );
|
||||
printf( "db 0%02xh\n", y >> 8 );
|
||||
}
|
||||
while( ++x != 0 );
|
||||
|
||||
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++ )
|
||||
{
|
||||
for( Old = 0; Old < 8; Old++ )
|
||||
{
|
||||
if( New != Old ) FirstPass(); Label++;
|
||||
SecondPass(); Label++;
|
||||
}
|
||||
}
|
||||
printf( "mov byte ptr fs:[bx+1],%s\n",
|
||||
(New & 2) ? "15" : "dl" );
|
||||
}
|
||||
|
||||
// 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" );
|
||||
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 )
|
||||
{
|
||||
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
244
18-04.md
|
|
@ -12,155 +12,161 @@ pages: 361-365
|
|||
|
||||
**LISTING 18.3 MAIN.C**
|
||||
|
||||
// MAIN.C
|
||||
//
|
||||
// David Stafford
|
||||
//
|
||||
```c
|
||||
// MAIN.C
|
||||
//
|
||||
// David Stafford
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <time.h>
|
||||
#include <bios.h>
|
||||
#include "life.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <time.h>
|
||||
#include <bios.h>
|
||||
#include "life.h"
|
||||
|
||||
// functions in VIDEO.C
|
||||
void enter_display_mode( void );
|
||||
void exit_display_mode( void );
|
||||
void show_text( int x, int y, char *text );
|
||||
// functions in VIDEO.C
|
||||
void enter_display_mode( void );
|
||||
void exit_display_mode( void );
|
||||
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;
|
||||
|
||||
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.
|
||||
ChangeList1[ j++ ] = (short)&CellMap[ i ];
|
||||
}
|
||||
}
|
||||
|
||||
void main( void )
|
||||
{
|
||||
unsigned long generation = 0;
|
||||
char gen_text[ 80 ];
|
||||
long start_time, end_time;
|
||||
unsigned int seed;
|
||||
NextGen(); // Set cell states, prime the pump.
|
||||
}
|
||||
|
||||
printf( "Seed (0 for random seed): " );
|
||||
scanf( "%d", &seed );
|
||||
if( seed == 0 ) seed = (unsigned) time(NULL);
|
||||
srand( seed );
|
||||
void main( void )
|
||||
{
|
||||
unsigned long generation = 0;
|
||||
char gen_text[ 80 ];
|
||||
long start_time, end_time;
|
||||
unsigned int seed;
|
||||
|
||||
#ifndef NODRAW
|
||||
enter_display_mode();
|
||||
show_text( 0, 10, "Generation:" );
|
||||
#endif
|
||||
printf( "Seed (0 for random seed): " );
|
||||
scanf( "%d", &seed );
|
||||
if( seed == 0 ) seed = (unsigned) time(NULL);
|
||||
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
|
||||
{
|
||||
NextGen();
|
||||
generation++;
|
||||
_bios_timeofday( _TIME_GETCLOCK, &start_time );
|
||||
|
||||
#ifndef NOCOUNTER
|
||||
sprintf( gen_text, "%10lu", generation );
|
||||
show_text( 0, 12, gen_text );
|
||||
#endif
|
||||
}
|
||||
#ifdef GEN
|
||||
while( generation < GEN );
|
||||
#else
|
||||
while( !kbhit() );
|
||||
#endif
|
||||
do
|
||||
{
|
||||
NextGen();
|
||||
generation++;
|
||||
|
||||
_bios_timeofday( _TIME_GETCLOCK, &end_time );
|
||||
end_time -= start_time;
|
||||
#ifndef NOCOUNTER
|
||||
sprintf( gen_text, "%10lu", generation );
|
||||
show_text( 0, 12, gen_text );
|
||||
#endif
|
||||
}
|
||||
#ifdef GEN
|
||||
while( generation < GEN );
|
||||
#else
|
||||
while( !kbhit() );
|
||||
#endif
|
||||
|
||||
#ifndef NODRAW
|
||||
getch(); // clear keypress
|
||||
exit_display_mode();
|
||||
#endif
|
||||
_bios_timeofday( _TIME_GETCLOCK, &end_time );
|
||||
end_time -= start_time;
|
||||
|
||||
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 );
|
||||
}
|
||||
#ifndef NODRAW
|
||||
getch(); // clear keypress
|
||||
exit_display_mode();
|
||||
#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**
|
||||
|
||||
/* VGA mode 13h functions for Game of Life.
|
||||
Tested with Borland C++. */
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <dos.h>
|
||||
```c
|
||||
/* VGA mode 13h functions for Game of Life.
|
||||
Tested with Borland C++. */
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <dos.h>
|
||||
|
||||
#define TEXT_X_OFFSET 28
|
||||
#define SCREEN_WIDTH_IN_BYTES 320
|
||||
#define TEXT_X_OFFSET 28
|
||||
#define SCREEN_WIDTH_IN_BYTES 320
|
||||
|
||||
#define SCREEN_SEGMENT 0xA000
|
||||
#define SCREEN_SEGMENT 0xA000
|
||||
|
||||
/* Mode 13h mode-set function. */
|
||||
void enter_display_mode()
|
||||
{
|
||||
union REGS regset;
|
||||
/* Mode 13h mode-set function. */
|
||||
void enter_display_mode()
|
||||
{
|
||||
union REGS regset;
|
||||
|
||||
regset.x.ax = 0x0013;
|
||||
int86(0x10, ®set, ®set);
|
||||
}
|
||||
regset.x.ax = 0x0013;
|
||||
int86(0x10, ®set, ®set);
|
||||
}
|
||||
|
||||
/* Text mode mode-set function. */
|
||||
void exit_display_mode()
|
||||
{
|
||||
union REGS regset;
|
||||
/* Text mode mode-set function. */
|
||||
void exit_display_mode()
|
||||
{
|
||||
union REGS regset;
|
||||
|
||||
regset.x.ax = 0x0003;
|
||||
int86(0x10, ®set, ®set);
|
||||
}
|
||||
regset.x.ax = 0x0003;
|
||||
int86(0x10, ®set, ®set);
|
||||
}
|
||||
|
||||
/* Text display function. Offsets text to non-graphics area of
|
||||
screen. */
|
||||
void show_text(int x, int y, char *text)
|
||||
{
|
||||
gotoxy(TEXT_X_OFFSET + x, y);
|
||||
puts(text);
|
||||
}
|
||||
/* Text display function. Offsets text to non-graphics area of
|
||||
screen. */
|
||||
void show_text(int x, int y, char *text)
|
||||
{
|
||||
gotoxy(TEXT_X_OFFSET + x, y);
|
||||
puts(text);
|
||||
}
|
||||
```
|
||||
|
||||
**LISTING 18.5 LIFE.H**
|
||||
|
||||
void far NextGen( void );
|
||||
```c
|
||||
void far NextGen( void );
|
||||
|
||||
extern unsigned short CellMap[];
|
||||
extern unsigned short far ChangeList1[];
|
||||
extern unsigned short CellMap[];
|
||||
extern unsigned short far ChangeList1[];
|
||||
|
||||
#define LEFT (-2)
|
||||
#define RIGHT (+2)
|
||||
#define UP (WIDTH * LEFT)
|
||||
#define DOWN (WIDTH * RIGHT)
|
||||
#define UPPERLEFT (UP + LEFT)
|
||||
#define UPPERRIGHT (UP + RIGHT)
|
||||
#define LOWERLEFT (DOWN + LEFT)
|
||||
#define LOWERRIGHT (DOWN + RIGHT)
|
||||
#define WRAPLEFT (RIGHT * (WIDTH - 1))
|
||||
#define WRAPRIGHT (LEFT * (WIDTH - 1))
|
||||
#define WRAPUP (DOWN * (HEIGHT - 1))
|
||||
#define WRAPDOWN (UP * (HEIGHT - 1))
|
||||
#define LEFT (-2)
|
||||
#define RIGHT (+2)
|
||||
#define UP (WIDTH * LEFT)
|
||||
#define DOWN (WIDTH * RIGHT)
|
||||
#define UPPERLEFT (UP + LEFT)
|
||||
#define UPPERRIGHT (UP + RIGHT)
|
||||
#define LOWERLEFT (DOWN + LEFT)
|
||||
#define LOWERRIGHT (DOWN + RIGHT)
|
||||
#define WRAPLEFT (RIGHT * (WIDTH - 1))
|
||||
#define WRAPRIGHT (LEFT * (WIDTH - 1))
|
||||
#define WRAPUP (DOWN * (HEIGHT - 1))
|
||||
#define WRAPDOWN (UP * (HEIGHT - 1))
|
||||
```
|
||||
|
||||
### Keeping Track of Change with a Change List {#Heading5}
|
||||
|
||||
|
|
|
|||
8
18-05.md
8
18-05.md
|
|
@ -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
|
||||
the desired result—just three instructions:
|
||||
|
||||
mov dh,[bp+1]
|
||||
or dh,1
|
||||
jmp dx
|
||||
```nasm
|
||||
mov dh,[bp+1]
|
||||
or dh,1
|
||||
jmp dx
|
||||
```
|
||||
|
||||
These suffice to select the proper, minimum-work code to process the
|
||||
next cell triplet that has changed, and all potentially affected
|
||||
|
|
|
|||
26
19-02.md
26
19-02.md
|
|
@ -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
|
||||
can execute in two cycles). So, for example, the code sequence
|
||||
|
||||
add edx,4 ;U-pipe cycle 1
|
||||
mov ecx,[ebx] ;V-pipe cycle 1
|
||||
add ebx,4 ;U-pipe cycle 2
|
||||
mov [edx],ecx ;V-pipe cycle 3
|
||||
; due to AGI
|
||||
; (would have been
|
||||
; V-pipe cycle 2)
|
||||
```nasm
|
||||
add edx,4 ;U-pipe cycle 1
|
||||
mov ecx,[ebx] ;V-pipe cycle 1
|
||||
add ebx,4 ;U-pipe cycle 2
|
||||
mov [edx],ecx ;V-pipe cycle 3
|
||||
; due to AGI
|
||||
; (would have been
|
||||
; V-pipe cycle 2)
|
||||
```
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
AGI. Rearranging the code like
|
||||
|
||||
mov ecx,[ebx] ;U-pipe cycle 1
|
||||
add ebx,4 ;V-pipe cycle 1
|
||||
mov [edx+4],ecx ;U-pipe cycle 2
|
||||
add edx,4 ;V-pipe cycle 2
|
||||
```nasm
|
||||
mov ecx,[ebx] ;U-pipe cycle 1
|
||||
add ebx,4 ;V-pipe cycle 1
|
||||
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
|
||||
improvement. Clearly, avoiding AGIs becomes a much more challenging and
|
||||
|
|
|
|||
8
19-03.md
8
19-03.md
|
|
@ -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
|
||||
boundary, as in
|
||||
|
||||
mov ebx,3
|
||||
:
|
||||
mov eax,[ebx]
|
||||
```nasm
|
||||
mov ebx,3
|
||||
:
|
||||
mov eax,[ebx]
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
134
20-02.md
134
20-02.md
|
|
@ -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
|
||||
conditions are met.
|
||||
|
||||
MOV reg,reg (1 cycle)
|
||||
mem,reg (1 cycle)
|
||||
reg,mem (1 cycle)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (1 cycle)†
|
||||
```nasm
|
||||
MOV reg,reg (1 cycle)
|
||||
mem,reg (1 cycle)
|
||||
reg,mem (1 cycle)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (1 cycle)†
|
||||
|
||||
AND/OR/XOR/ADD/SUB reg,reg (1 cycle)
|
||||
mem,reg (3 cycles)
|
||||
reg,mem (2 cycles)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (3 cycles)†
|
||||
AND/OR/XOR/ADD/SUB reg,reg (1 cycle)
|
||||
mem,reg (3 cycles)
|
||||
reg,mem (2 cycles)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (3 cycles)†
|
||||
|
||||
INC/DEC reg (1 cycle)
|
||||
mem (3 cycles)
|
||||
INC/DEC reg (1 cycle)
|
||||
mem (3 cycles)
|
||||
|
||||
CMP reg,reg (1 cycle)
|
||||
mem,reg (2 cycles)
|
||||
reg,mem (2 cycles)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (2 cycles)†
|
||||
CMP reg,reg (1 cycle)
|
||||
mem,reg (2 cycles)
|
||||
reg,mem (2 cycles)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (2 cycles)†
|
||||
|
||||
TEST reg,reg (1 cycle)
|
||||
EAX,immediate (1 cycle)
|
||||
TEST reg,reg (1 cycle)
|
||||
EAX,immediate (1 cycle)
|
||||
|
||||
PUSH/POP reg (1 cycle)
|
||||
immediate (1 cycle)
|
||||
PUSH/POP reg (1 cycle)
|
||||
immediate (1 cycle)
|
||||
|
||||
LEA reg,mem (1 cycle)
|
||||
LEA reg,mem (1 cycle)
|
||||
|
||||
JCC near (1 cycle if predicted correctly;
|
||||
5 cycles otherwise in V-pipe,
|
||||
4 cycles otherwise in U-pipe)
|
||||
JCC near (1 cycle if predicted correctly;
|
||||
5 cycles otherwise in V-pipe,
|
||||
4 cycles otherwise in U-pipe)
|
||||
|
||||
JMP/CALL near (1 cycle if predicted correctly;
|
||||
3 cycles otherwise)
|
||||
JMP/CALL near (1 cycle if predicted correctly;
|
||||
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.**
|
||||
|
||||
|
|
@ -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
|
||||
2 cycles.
|
||||
|
||||
MOV reg,reg (1 cycle)
|
||||
mem,reg (1 cycle)
|
||||
reg,mem (1 cycle)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (1 cycle)†
|
||||
```nasm
|
||||
MOV reg,reg (1 cycle)
|
||||
mem,reg (1 cycle)
|
||||
reg,mem (1 cycle)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (1 cycle)†
|
||||
|
||||
AND/OR/XOR/ADD/SUB/ADC/SBB reg,reg (1 cycle)
|
||||
mem,reg (3 cycles)
|
||||
reg,mem (2 cycles)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (3 cycles)†
|
||||
AND/OR/XOR/ADD/SUB/ADC/SBB reg,reg (1 cycle)
|
||||
mem,reg (3 cycles)
|
||||
reg,mem (2 cycles)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (3 cycles)†
|
||||
|
||||
INC/DEC reg (1 cycle)
|
||||
mem (3 cycles)
|
||||
INC/DEC reg (1 cycle)
|
||||
mem (3 cycles)
|
||||
|
||||
CMP reg,reg (1 cycle)
|
||||
mem,reg (2 cycles)
|
||||
reg,mem (2 cycles)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (2 cycles)†
|
||||
CMP reg,reg (1 cycle)
|
||||
mem,reg (2 cycles)
|
||||
reg,mem (2 cycles)
|
||||
reg,immediate (1 cycle)
|
||||
mem,immediate (2 cycles)†
|
||||
|
||||
TEST reg,reg (1 cycle)
|
||||
EAX,immediate (1 cycle)
|
||||
TEST reg,reg (1 cycle)
|
||||
EAX,immediate (1 cycle)
|
||||
|
||||
PUSH/POP reg (1 cycle)
|
||||
immediate (1 cycle)
|
||||
PUSH/POP reg (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
|
||||
†† Includes shift-by-1 forms of instructions
|
||||
† Can't pair if address contains a displacement\
|
||||
†† Includes shift-by-1 forms of instructions
|
||||
|
||||
**Table 20.2 Instructions that, when executed in the U-pipe, allow
|
||||
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
|
||||
substantially larger code. For example,
|
||||
|
||||
push dword ptr [esi]
|
||||
```nasm
|
||||
push dword ptr [esi]
|
||||
```
|
||||
|
||||
is one byte smaller than this sequence:
|
||||
|
||||
mov eax,[esi]
|
||||
push eax
|
||||
```nasm
|
||||
mov eax,[esi]
|
||||
push eax
|
||||
```
|
||||
|
||||

|
||||
|
||||
A more telling example is the following
|
||||
|
||||
add [MemVar],eax
|
||||
```nasm
|
||||
add [MemVar],eax
|
||||
```
|
||||
|
||||
versus the equivalent:
|
||||
|
||||
mov edx,[MemVar]
|
||||
add edx,eax
|
||||
mov [MemVar],edx
|
||||
```nasm
|
||||
mov edx,[MemVar]
|
||||
add edx,eax
|
||||
mov [MemVar],edx
|
||||
```
|
||||
|
||||
The single complex instruction takes 3 cycles and is 6 bytes long; with
|
||||
proper sequencing, interleaving the simple instructions with other
|
||||
|
|
|
|||
24
20-03.md
24
20-03.md
|
|
@ -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
|
||||
also avoid sequences like
|
||||
|
||||
mov bl,[esi]
|
||||
mov bh,[esi+1]
|
||||
```nasm
|
||||
mov bl,[esi]
|
||||
mov bh,[esi+1]
|
||||
```
|
||||
|
||||
because both operands will generally be in the same bank. An alternative
|
||||
is to place another instruction between the two instructions that access
|
||||
the same bank, as in this sequence:
|
||||
|
||||
mov bl,[esi]
|
||||
mov edi,edx
|
||||
mov bh,[esi+1]
|
||||
```nasm
|
||||
mov bl,[esi]
|
||||
mov edi,edx
|
||||
mov bh,[esi+1]
|
||||
```
|
||||
|
||||
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
|
||||
only one cycle when the two instructions can be paired with other
|
||||
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
|
||||
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
|
||||
these:
|
||||
|
||||
add esi,[SourceSkip] ;U-pipe cycles 1 and 2
|
||||
add edi,[DestinationSkip] ;V-pipe cycles 1 and 2
|
||||
```nasm
|
||||
add esi,[SourceSkip] ;U-pipe cycles 1 and 2
|
||||
add edi,[DestinationSkip] ;V-pipe cycles 1 and 2
|
||||
```
|
||||
|
|
|
|||
12
20-04.md
12
20-04.md
|
|
@ -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
|
||||
in Figure 20.7. It seems simpler just to turn
|
||||
|
||||
and [ebx],al
|
||||
```nasm
|
||||
and [ebx],al
|
||||
```
|
||||
|
||||
into
|
||||
|
||||
mov dl,[ebx]
|
||||
and dl,al
|
||||
mov [ebx],dl
|
||||
```nasm
|
||||
mov dl,[ebx]
|
||||
and dl,al
|
||||
mov [ebx],dl
|
||||
```
|
||||
|
||||
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,
|
||||
|
|
|
|||
22
21-01.md
22
21-01.md
|
|
@ -65,7 +65,9 @@ areas of AGIs and register dependencies.
|
|||
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
|
||||
|
||||
mov [ebx],eax
|
||||
```nasm
|
||||
mov [ebx],eax
|
||||
```
|
||||
|
||||
and the value of the register is not set far enough ahead for the
|
||||
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
|
||||
it's not NULL:
|
||||
|
||||
push ebx ;U-pipe cycle 1
|
||||
mov ebx,[Ptr] ;V-pipe cycle 1
|
||||
and ebx,ebx ;U-pipe cycle 2
|
||||
jz short IsNull ;V-pipe cycle 2
|
||||
mov eax,[ebx] ;U-pipe cycle 3 AGI stall
|
||||
mov edx,[ebp-8] ;V-pipe cycle 3 lockstep idle
|
||||
;U-pipe cycle 4 mov eax,[ebx]
|
||||
;V-pipe cycle 4 mov edx,[ebp-8]
|
||||
```nasm
|
||||
push ebx ;U-pipe cycle 1
|
||||
mov ebx,[Ptr] ;V-pipe cycle 1
|
||||
and ebx,ebx ;U-pipe cycle 2
|
||||
jz short IsNull ;V-pipe cycle 2
|
||||
mov eax,[ebx] ;U-pipe cycle 3 AGI stall
|
||||
mov edx,[ebp-8] ;V-pipe cycle 3 lockstep idle
|
||||
;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
|
||||
EBX,EBX**, followed by the attempt two instructions later to use EBX to
|
||||
|
|
|
|||
108
21-02.md
108
21-02.md
|
|
@ -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
|
||||
explicitly modify ESP with this instruction
|
||||
|
||||
sub esp,100h
|
||||
```nasm
|
||||
sub esp,100h
|
||||
```
|
||||
|
||||
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,
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
speed up something as simple as zeroing two memory variables, as in
|
||||
|
||||
sub eax,eax ;U-pipe 1
|
||||
;any V-pipe pairable
|
||||
; instruction can go here,
|
||||
; or SUB could be in V-pipe
|
||||
mov [MemVar1],eax ;U-pipe 2
|
||||
mov [MemVar2],eax ;V-pipe 2
|
||||
```nasm
|
||||
sub eax,eax ;U-pipe 1
|
||||
;any V-pipe pairable
|
||||
; instruction can go here,
|
||||
; or SUB could be in V-pipe
|
||||
mov [MemVar1],eax ;U-pipe 2
|
||||
mov [MemVar2],eax ;V-pipe 2
|
||||
```
|
||||
|
||||
which should never be slower and should potentially be 0.5 cycles
|
||||
faster, and six bytes smaller than this sequence:
|
||||
|
||||
mov [MemVar1],0 ;U-pipe 1
|
||||
mov [MemVar2],0 ;U-pipe 2
|
||||
```nasm
|
||||
mov [MemVar1],0 ;U-pipe 1
|
||||
mov [MemVar2],0 ;U-pipe 2
|
||||
```
|
||||
|
||||
Note, however, that my experiments thus far indicate that the two writes
|
||||
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
|
||||
single cycle. For example, you can't execute
|
||||
|
||||
inc eax ;U-pipe cycle 1
|
||||
;V-pipe idle cycle 1
|
||||
; due to dependency
|
||||
and ebx,eax ;U-pipe cycle 2
|
||||
```nasm
|
||||
inc eax ;U-pipe cycle 1
|
||||
;V-pipe idle cycle 1
|
||||
; 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
|
||||
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
|
||||
subregisters are being set, as in the following
|
||||
|
||||
sub eax,eax ;U-pipe cycle 1
|
||||
;V-pipe idle cycle 1
|
||||
; due to register contention
|
||||
mov al,[Var] ;U-pipe cycle 2
|
||||
```nasm
|
||||
sub eax,eax ;U-pipe cycle 1
|
||||
;V-pipe idle cycle 1
|
||||
; 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
|
||||
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. Such operations, as in
|
||||
|
||||
mov eax,edx ;U-pipe cycle 1
|
||||
sub edx,edxX ;V-pipe cycle 1
|
||||
```nasm
|
||||
mov eax,edx ;U-pipe cycle 1
|
||||
sub edx,edxX ;V-pipe cycle 1
|
||||
```
|
||||
|
||||
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
|
||||
following sequence would hardly use the V-pipe at all:
|
||||
|
||||
mov eax,[MemVar] ;U-pipe cycle 1
|
||||
push esi ;V-pipe cycle 1
|
||||
push eax ;U-pipe cycle 2
|
||||
push edi ;V-pipe cycle 2
|
||||
push ebx ;U-pipe cycle 3
|
||||
call FooTilde ;V-pipe cycle 3
|
||||
```nasm
|
||||
mov eax,[MemVar] ;U-pipe cycle 1
|
||||
push esi ;V-pipe cycle 1
|
||||
push eax ;U-pipe cycle 2
|
||||
push edi ;V-pipe cycle 2
|
||||
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
|
||||
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
|
||||
pairable U-pipe instruction, as illustrated by this sequence:
|
||||
|
||||
LoopTop:
|
||||
mov [esi],eax ;U-pipe cycle 1
|
||||
add esi,4 ;V-pipe cycle 1
|
||||
dec ecx ;U-pipe cycle 2
|
||||
jnz LoopTop ;V-pipe cycle 2
|
||||
```nasm
|
||||
LoopTop:
|
||||
mov [esi],eax ;U-pipe cycle 1
|
||||
add esi,4 ;V-pipe cycle 1
|
||||
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
|
||||
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,
|
||||
even though we only added 25 percent more cycles:
|
||||
|
||||
LoopTop:
|
||||
inc edx ;U-pipe cycle 1
|
||||
mov [esi],eax ;V-pipe cycle 1
|
||||
add esi,4 ;U-pipe cycle 2
|
||||
dec ecx ;V-pipe cycle 2
|
||||
jnz LoopTop ;U-pipe cycle 3
|
||||
;V-pipe idle cycle 3
|
||||
; because JNZ can't
|
||||
; pair in the U-pipe
|
||||
```nasm
|
||||
LoopTop:
|
||||
inc edx ;U-pipe cycle 1
|
||||
mov [esi],eax ;V-pipe cycle 1
|
||||
add esi,4 ;U-pipe cycle 2
|
||||
dec ecx ;V-pipe cycle 2
|
||||
jnz LoopTop ;U-pipe cycle 3
|
||||
;V-pipe idle cycle 3
|
||||
; because JNZ can't
|
||||
; pair in the U-pipe
|
||||
```
|
||||
|
|
|
|||
54
21-03.md
54
21-03.md
|
|
@ -40,34 +40,36 @@ right?
|
|||
|
||||
**LISTING 21.1 L21-1.ASM**
|
||||
|
||||
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
|
||||
; starting at ESI, of length ECX words.
|
||||
; Returns checksum in AX.
|
||||
; ECX and ESI destroyed.
|
||||
; All cycle counts assume 32-bit protected mode.
|
||||
; Assumes buffer length > 0.
|
||||
; Note that timing indicates that the pipe sequence and
|
||||
; cycle counts shown (based on documented execution rules)
|
||||
; differ from the actual execution sequence and cycle counts;
|
||||
; this loop has been measured to execute in 5 cycles; apparently,
|
||||
; the 1st half of ADD somehow pairs with the prefix byte, or the
|
||||
; refix byte gets executed ahead of time.
|
||||
```nasm
|
||||
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
|
||||
; starting at ESI, of length ECX words.
|
||||
; Returns checksum in AX.
|
||||
; ECX and ESI destroyed.
|
||||
; All cycle counts assume 32-bit protected mode.
|
||||
; Assumes buffer length > 0.
|
||||
; Note that timing indicates that the pipe sequence and
|
||||
; cycle counts shown (based on documented execution rules)
|
||||
; differ from the actual execution sequence and cycle counts;
|
||||
; this loop has been measured to execute in 5 cycles; apparently,
|
||||
; 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:
|
||||
add ax,[esi] ;cycle 1 U-pipe prefix byte
|
||||
;cycle 1 V-pipe idle (no pairing w/prefix)
|
||||
;cycle 2 U-pipe 1st half of ADD
|
||||
;cycle 2 V-pipe idle (register contention)
|
||||
;cycle 3 U-pipe 2nd half of ADD
|
||||
;cycle 3 V-pipe idle (register contention)
|
||||
adc ax,0 ;cycle 4 U-pipe prefix byte
|
||||
;cycle 4 V-pipe idle (no pairing w/prefix)
|
||||
;cycle 5 U-pipe ADC AX,0
|
||||
add esi,2 ;cycle 5 V-pipe
|
||||
dec ecx ;cycle 6 U-pipe
|
||||
jnz ckloop ;cycle 6 V-pipe
|
||||
ckloop:
|
||||
add ax,[esi] ;cycle 1 U-pipe prefix byte
|
||||
;cycle 1 V-pipe idle (no pairing w/prefix)
|
||||
;cycle 2 U-pipe 1st half of ADD
|
||||
;cycle 2 V-pipe idle (register contention)
|
||||
;cycle 3 U-pipe 2nd half of ADD
|
||||
;cycle 3 V-pipe idle (register contention)
|
||||
adc ax,0 ;cycle 4 U-pipe prefix byte
|
||||
;cycle 4 V-pipe idle (no pairing w/prefix)
|
||||
;cycle 5 U-pipe ADC AX,0
|
||||
add esi,2 ;cycle 5 V-pipe
|
||||
dec ecx ;cycle 6 U-pipe
|
||||
jnz ckloop ;cycle 6 V-pipe
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
134
21-04.md
134
21-04.md
|
|
@ -27,32 +27,34 @@ same cache data bank, as discussed in the last chapter).
|
|||
|
||||
**LISTING 21.2 L21-2.ASM**
|
||||
|
||||
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
|
||||
; starting at ESI, of length ECX words.
|
||||
; Returns checksum in AX.
|
||||
; High word of EAX, DX, ECX and ESI destroyed.
|
||||
; All cycle counts assume 32-bit protected mode.
|
||||
; Assumes buffer length > 0.
|
||||
```nasm
|
||||
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
|
||||
; starting at ESI, of length ECX words.
|
||||
; Returns checksum in AX.
|
||||
; High word of EAX, DX, ECX and ESI destroyed.
|
||||
; All cycle counts assume 32-bit protected mode.
|
||||
; Assumes buffer length > 0.
|
||||
|
||||
sub eax,eax ;initialize the checksum
|
||||
mov dx,[esi] ;first word to checksum
|
||||
dec ecx ;we'll do 1 checksum outside the loop
|
||||
jz short ckloopend ;only 1 checksum to do
|
||||
add esi,2 ;point to the next word to checksum
|
||||
sub eax,eax ;initialize the checksum
|
||||
mov dx,[esi] ;first word to checksum
|
||||
dec ecx ;we'll do 1 checksum outside the loop
|
||||
jz short ckloopend ;only 1 checksum to do
|
||||
add esi,2 ;point to the next word to checksum
|
||||
|
||||
ckloop:
|
||||
add al,dl ;cycle 1 U-pipe
|
||||
mov dl,[esi] ;cycle 1 V-pipe
|
||||
adc ah,dh ;cycle 2 U-pipe
|
||||
mov dh,[esi+1] ;cycle 2 V-pipe
|
||||
adc eax,0 ;cycle 3 U-pipe
|
||||
add esi,2 ;cycle 3 V-pipe
|
||||
dec ecx ;cycle 4 U-pipe
|
||||
jnz ckloop ;cycle 4 V-pipe
|
||||
ckloop:
|
||||
add al,dl ;cycle 1 U-pipe
|
||||
mov dl,[esi] ;cycle 1 V-pipe
|
||||
adc ah,dh ;cycle 2 U-pipe
|
||||
mov dh,[esi+1] ;cycle 2 V-pipe
|
||||
adc eax,0 ;cycle 3 U-pipe
|
||||
add esi,2 ;cycle 3 V-pipe
|
||||
dec ecx ;cycle 4 U-pipe
|
||||
jnz ckloop ;cycle 4 V-pipe
|
||||
|
||||
ckloopend:
|
||||
add ax,dx ;checksum the last word
|
||||
adc eax,0
|
||||
ckloopend:
|
||||
add ax,dx ;checksum the last word
|
||||
adc eax,0
|
||||
```
|
||||
|
||||
Listing 21.3 is a more sophisticated attempt to speed up the checksum
|
||||
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**
|
||||
|
||||
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
|
||||
; starting at ESI, of length ECX words.
|
||||
; Returns checksum in AX.
|
||||
; High word of EAX, BX, EDX, ECX and ESI destroyed.
|
||||
; All cycle counts assume 32-bit protected mode.
|
||||
; Assumes buffer length > 0.
|
||||
```nasm
|
||||
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
|
||||
; starting at ESI, of length ECX words.
|
||||
; Returns checksum in AX.
|
||||
; High word of EAX, BX, EDX, ECX and ESI destroyed.
|
||||
; All cycle counts assume 32-bit protected mode.
|
||||
; Assumes buffer length > 0.
|
||||
|
||||
sub eax,eax ;initialize the checksum
|
||||
sub edx,edx ;prepare for later ORing
|
||||
shr ecx,1 ;we'll do two words per loop
|
||||
jnc short ckloopsetup ;even number of words
|
||||
mov ax,[esi] ;do the odd word
|
||||
jz short ckloopdone ;no more words to checksum
|
||||
add esi,2 ;point to the next word
|
||||
ckloopsetup:
|
||||
mov dx,[esi] ;load most of 1st word to
|
||||
mov bl,[esi+2] ; checksum (last byte loaded in loop)
|
||||
dec ecx ;any more dwords to checksum?
|
||||
jz short ckloopend ;no
|
||||
sub eax,eax ;initialize the checksum
|
||||
sub edx,edx ;prepare for later ORing
|
||||
shr ecx,1 ;we'll do two words per loop
|
||||
jnc short ckloopsetup ;even number of words
|
||||
mov ax,[esi] ;do the odd word
|
||||
jz short ckloopdone ;no more words to checksum
|
||||
add esi,2 ;point to the next word
|
||||
ckloopsetup:
|
||||
mov dx,[esi] ;load most of 1st word to
|
||||
mov bl,[esi+2] ; checksum (last byte loaded in loop)
|
||||
dec ecx ;any more dwords to checksum?
|
||||
jz short ckloopend ;no
|
||||
|
||||
ckloop:
|
||||
mov bh,[esi+3] ;cycle 1 U-pipe
|
||||
add esi,4 ;cycle 1 V-pipe
|
||||
shl ebx,16 ;cycle 2 U-pipe
|
||||
;cycle 2 V-pipe idle
|
||||
; (register contention)
|
||||
or ebx,edx ;cycle 3 U-pipe
|
||||
mov dl,[esi] ;cycle 3 V-pipe
|
||||
add eax,ebx ;cycle 4 U-pipe
|
||||
mov bl,[esi+2] ;cycle 4 V-pipe
|
||||
adc eax,0 ;cycle 5 U-pipe
|
||||
mov dh,[esi+1] ;cycle 5 V-pipe
|
||||
dec ecx ;cycle 6 U-pipe
|
||||
jnz ckloop ;cycle 6 V-pipe
|
||||
ckloop:
|
||||
mov bh,[esi+3] ;cycle 1 U-pipe
|
||||
add esi,4 ;cycle 1 V-pipe
|
||||
shl ebx,16 ;cycle 2 U-pipe
|
||||
;cycle 2 V-pipe idle
|
||||
; (register contention)
|
||||
or ebx,edx ;cycle 3 U-pipe
|
||||
mov dl,[esi] ;cycle 3 V-pipe
|
||||
add eax,ebx ;cycle 4 U-pipe
|
||||
mov bl,[esi+2] ;cycle 4 V-pipe
|
||||
adc eax,0 ;cycle 5 U-pipe
|
||||
mov dh,[esi+1] ;cycle 5 V-pipe
|
||||
dec ecx ;cycle 6 U-pipe
|
||||
jnz ckloop ;cycle 6 V-pipe
|
||||
|
||||
ckloopend:
|
||||
mov bh,[esi+3] ;checksum the last dword
|
||||
add ax,dx
|
||||
adc ax,bx
|
||||
adc ax,0
|
||||
ckloopend:
|
||||
mov bh,[esi+3] ;checksum the last dword
|
||||
add ax,dx
|
||||
adc ax,bx
|
||||
adc ax,0
|
||||
|
||||
mov edx,eax ;compress the 32-bit checksum
|
||||
shr edx,16 ; into a 16-bit checksum
|
||||
add ax,dx
|
||||
adc eax,0
|
||||
ckloopdone:
|
||||
mov edx,eax ;compress the 32-bit checksum
|
||||
shr edx,16 ; into a 16-bit checksum
|
||||
add ax,dx
|
||||
adc eax,0
|
||||
ckloopdone:
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
132
21-05.md
132
21-05.md
|
|
@ -12,36 +12,38 @@ pages: 409-411
|
|||
|
||||
**LISTING 21.4 L21-4.ASM**
|
||||
|
||||
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
|
||||
; starting at ESI, of length ECX words.
|
||||
; Returns checksum in AX.
|
||||
; High word of EAX, ECX, EDX, and ESI destroyed.
|
||||
; All cycle counts assume 32-bit protected mode.
|
||||
; Assumes buffer starts on a dword boundary, is a dword multiple
|
||||
; in length, and length > 0.
|
||||
```nasm
|
||||
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
|
||||
; starting at ESI, of length ECX words.
|
||||
; Returns checksum in AX.
|
||||
; High word of EAX, ECX, EDX, and ESI destroyed.
|
||||
; All cycle counts assume 32-bit protected mode.
|
||||
; Assumes buffer starts on a dword boundary, is a dword multiple
|
||||
; in length, and length > 0.
|
||||
|
||||
sub eax,eax ;initialize the checksum
|
||||
shr ecx,1 ;we'll do two words per loop
|
||||
mov edx,[esi] ;preload the first dword
|
||||
add esi,4 ;point to the next dword
|
||||
dec ecx ;we'll do 1 checksum outside the loop
|
||||
jz short ckloopend ;only 1 checksum to do
|
||||
sub eax,eax ;initialize the checksum
|
||||
shr ecx,1 ;we'll do two words per loop
|
||||
mov edx,[esi] ;preload the first dword
|
||||
add esi,4 ;point to the next dword
|
||||
dec ecx ;we'll do 1 checksum outside the loop
|
||||
jz short ckloopend ;only 1 checksum to do
|
||||
|
||||
ckloop:
|
||||
add eax,edx ;cycle 1 U-pipe
|
||||
mov edx,[esi] ;cycle 1 V-pipe
|
||||
adc eax,0 ;cycle 2 U-pipe
|
||||
add esi,4 ;cycle 2 V-pipe
|
||||
dec ecx ;cycle 3 U-pipe
|
||||
jnz ckloop ;cycle 3 V-pipe
|
||||
ckloop:
|
||||
add eax,edx ;cycle 1 U-pipe
|
||||
mov edx,[esi] ;cycle 1 V-pipe
|
||||
adc eax,0 ;cycle 2 U-pipe
|
||||
add esi,4 ;cycle 2 V-pipe
|
||||
dec ecx ;cycle 3 U-pipe
|
||||
jnz ckloop ;cycle 3 V-pipe
|
||||
|
||||
ckloopend:
|
||||
add eax,edx ;checksum the last dword
|
||||
adc eax,0
|
||||
mov edx,eax ;compress the 32-bit checksum
|
||||
shr edx,16 ; into a 16-bit checksum
|
||||
add ax,dx
|
||||
adc eax,0
|
||||
ckloopend:
|
||||
add eax,edx ;checksum the last dword
|
||||
adc eax,0
|
||||
mov edx,eax ;compress the 32-bit checksum
|
||||
shr edx,16 ; into a 16-bit checksum
|
||||
add ax,dx
|
||||
adc eax,0
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
@ -53,46 +55,48 @@ more registers.
|
|||
|
||||
**LISTING 21.5 L21-5.ASM**
|
||||
|
||||
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
|
||||
; starting at ESI, of length ECX words.
|
||||
; Returns checksum in AX.
|
||||
; High word of EAX, EBX, ECX, EDX, and ESI destroyed.
|
||||
; All cycle counts assume 32-bit protected mode.
|
||||
; Assumes buffer starts on a dword boundary, is a dword multiple
|
||||
; in length, and length > 0.
|
||||
```nasm
|
||||
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
|
||||
; starting at ESI, of length ECX words.
|
||||
; Returns checksum in AX.
|
||||
; High word of EAX, EBX, ECX, EDX, and ESI destroyed.
|
||||
; All cycle counts assume 32-bit protected mode.
|
||||
; Assumes buffer starts on a dword boundary, is a dword multiple
|
||||
; in length, and length > 0.
|
||||
|
||||
sub eax,eax ;initialize the checksum
|
||||
shr ecx,2 ;we'll do two dwords per loop
|
||||
jnc short noodddword ;is there an odd dword in buffer?
|
||||
mov eax,[esi] ;checksum the odd dword
|
||||
jz short ckloopdone ;no, done
|
||||
add esi,4 ;point to the next dword
|
||||
noodddword:
|
||||
mov edx,[esi] ;preload the first dword
|
||||
mov ebx,[esi+4] ;preload the second dword
|
||||
dec ecx ;we'll do 1 checksum outside the loop
|
||||
jz short ckloopend ;only 1 checksum to do
|
||||
add esi,8 ;point to the next dword
|
||||
sub eax,eax ;initialize the checksum
|
||||
shr ecx,2 ;we'll do two dwords per loop
|
||||
jnc short noodddword ;is there an odd dword in buffer?
|
||||
mov eax,[esi] ;checksum the odd dword
|
||||
jz short ckloopdone ;no, done
|
||||
add esi,4 ;point to the next dword
|
||||
noodddword:
|
||||
mov edx,[esi] ;preload the first dword
|
||||
mov ebx,[esi+4] ;preload the second dword
|
||||
dec ecx ;we'll do 1 checksum outside the loop
|
||||
jz short ckloopend ;only 1 checksum to do
|
||||
add esi,8 ;point to the next dword
|
||||
|
||||
ckloop:
|
||||
add eax,edx ;cycle 1 U-pipe
|
||||
mov edx,[esi] ;cycle 1 V-pipe
|
||||
adc eax,ebx ;cycle 2 U-pipe
|
||||
mov ebx,[esi+4] ;cycle 2 V-pipe
|
||||
adc eax,0 ;cycle 3 U-pipe
|
||||
add esi,8 ;cycle 3 V-pipe
|
||||
dec ecx ;cycle 4 U-pipe
|
||||
jnz ckloop ;cycle 4 V-pipe
|
||||
ckloop:
|
||||
add eax,edx ;cycle 1 U-pipe
|
||||
mov edx,[esi] ;cycle 1 V-pipe
|
||||
adc eax,ebx ;cycle 2 U-pipe
|
||||
mov ebx,[esi+4] ;cycle 2 V-pipe
|
||||
adc eax,0 ;cycle 3 U-pipe
|
||||
add esi,8 ;cycle 3 V-pipe
|
||||
dec ecx ;cycle 4 U-pipe
|
||||
jnz ckloop ;cycle 4 V-pipe
|
||||
|
||||
ckloopend:
|
||||
add eax,edx ;checksum the last two dwords
|
||||
adc eax,ebx
|
||||
adc eax,0
|
||||
ckloopdone:
|
||||
mov edx,eax ;compress the 32-bit checksum
|
||||
shr edx,16 ; into a 16-bit checksum
|
||||
add ax,dx
|
||||
adc eax,0
|
||||
ckloopend:
|
||||
add eax,edx ;checksum the last two dwords
|
||||
adc eax,ebx
|
||||
adc eax,0
|
||||
ckloopdone:
|
||||
mov edx,eax ;compress the 32-bit checksum
|
||||
shr edx,16 ; into a 16-bit checksum
|
||||
add ax,dx
|
||||
adc eax,0
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
70
22-01.md
70
22-01.md
|
|
@ -55,40 +55,42 @@ are mine.
|
|||
|
||||
**LISTING 22.1 L22-1.ASM**
|
||||
|
||||
OnStack struc ;data that's stored on the stack after PUSH BP
|
||||
OldBP dw ? ;caller's BP
|
||||
RetAddr dw ? ;return address
|
||||
Filler dw ? ;character to fill the buffer with
|
||||
Attrib dw ? ;attribute to fill the buffer with
|
||||
BufSize dw ? ;number of character/attribute pairs to fill
|
||||
BufOfs dw ? ;buffer offset
|
||||
BufSeg dw ? ;buffer segment
|
||||
EndMrk db ? ;marker for the end of the stack frame
|
||||
OnStack ends
|
||||
;
|
||||
ClearS proc near
|
||||
push bp ;save caller's BP
|
||||
mov bp,sp ;point to stack frame
|
||||
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
|
||||
jne Start ; pointer is passed
|
||||
cmp word ptr [bp].BufOfs,0
|
||||
je Bye
|
||||
Start: cld ;make STOSW count up
|
||||
mov ax,[bp].Attrib ;load AX with attribute parameter
|
||||
and ax,0ff00h ;prepare for merging with fill char
|
||||
mov bx,[bp].Filler ;load BX with fill char
|
||||
and bx,0ffh ;prepare for merging with attribute
|
||||
or ax,bx ;combine attribute and fill char
|
||||
mov bx,[bp].BufOfs ;load DI with target buffer offset
|
||||
mov di,bx
|
||||
mov bx,[bp].BufSeg ;load ES with target buffer segment
|
||||
mov es,bx
|
||||
mov cx,[bp].BufSize ;load CX with buffer size
|
||||
rep stosw ;fill the buffer
|
||||
Bye:mov sp,bp ;restore original stack pointer
|
||||
pop bp ; and caller's BP
|
||||
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
|
||||
ClearS endp
|
||||
```nasm
|
||||
OnStack struc ;data that's stored on the stack after PUSH BP
|
||||
OldBP dw ? ;caller's BP
|
||||
RetAddr dw ? ;return address
|
||||
Filler dw ? ;character to fill the buffer with
|
||||
Attrib dw ? ;attribute to fill the buffer with
|
||||
BufSize dw ? ;number of character/attribute pairs to fill
|
||||
BufOfs dw ? ;buffer offset
|
||||
BufSeg dw ? ;buffer segment
|
||||
EndMrk db ? ;marker for the end of the stack frame
|
||||
OnStack ends
|
||||
;
|
||||
ClearS proc near
|
||||
push bp ;save caller's BP
|
||||
mov bp,sp ;point to stack frame
|
||||
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
|
||||
jne Start ; pointer is passed
|
||||
cmp word ptr [bp].BufOfs,0
|
||||
je Bye
|
||||
Start: cld ;make STOSW count up
|
||||
mov ax,[bp].Attrib ;load AX with attribute parameter
|
||||
and ax,0ff00h ;prepare for merging with fill char
|
||||
mov bx,[bp].Filler ;load BX with fill char
|
||||
and bx,0ffh ;prepare for merging with attribute
|
||||
or ax,bx ;combine attribute and fill char
|
||||
mov bx,[bp].BufOfs ;load DI with target buffer offset
|
||||
mov di,bx
|
||||
mov bx,[bp].BufSeg ;load ES with target buffer segment
|
||||
mov es,bx
|
||||
mov cx,[bp].BufSize ;load CX with buffer size
|
||||
rep stosw ;fill the buffer
|
||||
Bye:mov sp,bp ;restore original stack pointer
|
||||
pop bp ; and caller's BP
|
||||
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
|
||||
a **REP STOSW** instruction. That means that we're not going to improve
|
||||
|
|
|
|||
88
22-02.md
88
22-02.md
|
|
@ -12,27 +12,29 @@ pages: 417-418
|
|||
|
||||
**LISTING 22.2 L22-2.ASM**
|
||||
|
||||
ClearS proc near
|
||||
push bp ;save caller's BP
|
||||
mov bp,sp ;point to stack frame
|
||||
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
|
||||
jne Start ; pointer is passed
|
||||
cmp word ptr [bp].BufOfs,0
|
||||
je Bye
|
||||
Start: cld ;make STOSW count up
|
||||
mov ax,[bp].Attrib ;load AX with attribute parameter
|
||||
and ax,0ff00h ;prepare for merging with fill char
|
||||
mov bx,[bp].Filler ;load BX with fill char
|
||||
and bx,0ffh ;prepare for merging with attribute
|
||||
or ax,bx ;combine attribute and fill char
|
||||
mov di,[bp].BufOfs ;load DI with target buffer offset
|
||||
mov es,[bp].BufSeg ;load ES with target buffer segment
|
||||
mov cx,[bp].BufSize ;load CX with buffer size
|
||||
rep stosw ;fill the buffer
|
||||
Bye:
|
||||
pop bp ;restore caller's BP
|
||||
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
|
||||
ClearS endp
|
||||
```nasm
|
||||
ClearS proc near
|
||||
push bp ;save caller's BP
|
||||
mov bp,sp ;point to stack frame
|
||||
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
|
||||
jne Start ; pointer is passed
|
||||
cmp word ptr [bp].BufOfs,0
|
||||
je Bye
|
||||
Start: cld ;make STOSW count up
|
||||
mov ax,[bp].Attrib ;load AX with attribute parameter
|
||||
and ax,0ff00h ;prepare for merging with fill char
|
||||
mov bx,[bp].Filler ;load BX with fill char
|
||||
and bx,0ffh ;prepare for merging with attribute
|
||||
or ax,bx ;combine attribute and fill char
|
||||
mov di,[bp].BufOfs ;load DI with target buffer offset
|
||||
mov es,[bp].BufSeg ;load ES with target buffer segment
|
||||
mov cx,[bp].BufSize ;load CX with buffer size
|
||||
rep stosw ;fill the buffer
|
||||
Bye:
|
||||
pop bp ;restore caller's BP
|
||||
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
|
||||
ClearS endp
|
||||
```
|
||||
|
||||
(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
|
||||
|
|
@ -47,27 +49,29 @@ loading ES and DI as shown in Listing 22.3.
|
|||
|
||||
**LISTING 22.3 L22-3.ASM**
|
||||
|
||||
ClearS proc near
|
||||
push bp ;save caller's BP
|
||||
mov bp,sp ;point to stack frame
|
||||
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
|
||||
jne Start ; pointer is passed
|
||||
cmp word ptr [bp].BufOfs,0
|
||||
je Bye
|
||||
Start: cld ;make STOSW count up
|
||||
mov ax,[bp].Attrib ;load AX with attribute parameter
|
||||
and ax,0ff00h ;prepare for merging with fill char
|
||||
mov bx,[bp].Filler ;load BX with fill char
|
||||
and bx,0ffh ;prepare for merging with attribute
|
||||
or ax,bx ;combine attribute and fill char
|
||||
les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer
|
||||
;segment:offset
|
||||
mov cx,[bp].BufSize ;load CX with buffer size
|
||||
rep stosw ;fill the buffer
|
||||
Bye:
|
||||
pop bp ;restore caller's BP
|
||||
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
|
||||
ClearS endp
|
||||
```nasm
|
||||
ClearS proc near
|
||||
push bp ;save caller's BP
|
||||
mov bp,sp ;point to stack frame
|
||||
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
|
||||
jne Start ; pointer is passed
|
||||
cmp word ptr [bp].BufOfs,0
|
||||
je Bye
|
||||
Start: cld ;make STOSW count up
|
||||
mov ax,[bp].Attrib ;load AX with attribute parameter
|
||||
and ax,0ff00h ;prepare for merging with fill char
|
||||
mov bx,[bp].Filler ;load BX with fill char
|
||||
and bx,0ffh ;prepare for merging with attribute
|
||||
or ax,bx ;combine attribute and fill char
|
||||
les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer
|
||||
;segment:offset
|
||||
mov cx,[bp].BufSize ;load CX with buffer size
|
||||
rep stosw ;fill the buffer
|
||||
Bye:
|
||||
pop bp ;restore caller's BP
|
||||
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
|
||||
counting.
|
||||
|
|
|
|||
104
22-03.md
104
22-03.md
|
|
@ -12,23 +12,25 @@ pages: 419-420
|
|||
|
||||
**LISTING 22.5 L22-5.ASM**
|
||||
|
||||
ClearS proc near
|
||||
push bp ;save caller's BP
|
||||
mov bp,sp ;point to stack frame
|
||||
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
|
||||
jne Start ; pointer is passed
|
||||
cmp word ptr [bp].BufOfs,0
|
||||
je Bye
|
||||
Start: cld ;make STOSW count up
|
||||
mov ah,byte ptr [bp].Attrib[1];load AH with attribute
|
||||
mov al,byte ptr [bp].Filler ;load AL with fill char
|
||||
les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer segment:offset
|
||||
mov cx,[bp].BufSize ;load CX with buffer size
|
||||
rep stosw ;fill the buffer
|
||||
Bye:
|
||||
pop bp ;restore caller's BP
|
||||
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
|
||||
ClearS endp
|
||||
```nasm
|
||||
ClearS proc near
|
||||
push bp ;save caller's BP
|
||||
mov bp,sp ;point to stack frame
|
||||
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
|
||||
jne Start ; pointer is passed
|
||||
cmp word ptr [bp].BufOfs,0
|
||||
je Bye
|
||||
Start: cld ;make STOSW count up
|
||||
mov ah,byte ptr [bp].Attrib[1];load AH with attribute
|
||||
mov al,byte ptr [bp].Filler ;load AL with fill char
|
||||
les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer segment:offset
|
||||
mov cx,[bp].BufSize ;load CX with buffer size
|
||||
rep stosw ;fill the buffer
|
||||
Bye:
|
||||
pop bp ;restore caller's BP
|
||||
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
|
||||
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**
|
||||
|
||||
ClearS proc near
|
||||
push bp ;save caller's BP
|
||||
mov bp,sp ;point to stack frame
|
||||
les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer;segment:offset
|
||||
mov ax,es ;put segment where we can test it
|
||||
or ax,di ;is it a null pointer?
|
||||
je Bye ;yes, so we're done
|
||||
Start: cld ;make STOSW count up
|
||||
mov ah,byte ptr [bp].Attrib[1];load AH with attribute
|
||||
mov al,byte ptr [bp].Filler ;load AL with fill char
|
||||
mov cx,[bp].BufSize ;load CX with buffer size
|
||||
rep stosw ;fill the buffer
|
||||
Bye:
|
||||
pop bp ;restore caller's BP
|
||||
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
|
||||
ClearS endp
|
||||
```nasm
|
||||
ClearS proc near
|
||||
push bp ;save caller's BP
|
||||
mov bp,sp ;point to stack frame
|
||||
les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer;segment:offset
|
||||
mov ax,es ;put segment where we can test it
|
||||
or ax,di ;is it a null pointer?
|
||||
je Bye ;yes, so we're done
|
||||
Start: cld ;make STOSW count up
|
||||
mov ah,byte ptr [bp].Attrib[1];load AH with attribute
|
||||
mov al,byte ptr [bp].Filler ;load AL with fill char
|
||||
mov cx,[bp].BufSize ;load CX with buffer size
|
||||
rep stosw ;fill the buffer
|
||||
Bye:
|
||||
pop bp ;restore caller's BP
|
||||
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
|
||||
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**
|
||||
|
||||
ClearS procnear
|
||||
pop dx ;get the return address
|
||||
pop ax ;put fill char into AL
|
||||
pop bx ;get the attribute
|
||||
mov ah,bh ;put attribute into AH
|
||||
pop cx ;get the buffer size
|
||||
pop di ;get the offset of the buffer origin
|
||||
pop es ;get the segment of the buffer origin
|
||||
mov bx,es ;put the segment where we can test it
|
||||
or bx,di ;null pointer?
|
||||
je Bye ;yes, so we're done
|
||||
cld ;make STOSW count up
|
||||
rep stosw ;do the string store
|
||||
Bye:
|
||||
jmp dx ;return to the calling code
|
||||
ClearS endp
|
||||
```nasm
|
||||
ClearS procnear
|
||||
pop dx ;get the return address
|
||||
pop ax ;put fill char into AL
|
||||
pop bx ;get the attribute
|
||||
mov ah,bh ;put attribute into AH
|
||||
pop cx ;get the buffer size
|
||||
pop di ;get the offset of the buffer origin
|
||||
pop es ;get the segment of the buffer origin
|
||||
mov bx,es ;put the segment where we can test it
|
||||
or bx,di ;null pointer?
|
||||
je Bye ;yes, so we're done
|
||||
cld ;make STOSW count up
|
||||
rep stosw ;do the string store
|
||||
Bye:
|
||||
jmp dx ;return to the calling code
|
||||
ClearS endp
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
504
24-02.md
504
24-02.md
|
|
@ -12,254 +12,256 @@ pages: 453-458
|
|||
|
||||
**LISTING 24.1 L24-1.ASM**
|
||||
|
||||
; Program to illustrate operation of ALUs and latches of the VGA's
|
||||
; Graphics Controller. Draws a variety of patterns against
|
||||
; a horizontally striped background, using each of the 4 available
|
||||
; logical functions (data unmodified, AND, OR, XOR) in turn to combine
|
||||
; the images with the background.
|
||||
; By Michael Abrash.
|
||||
;
|
||||
stack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
|
||||
SCREEN_HEIGHT equ 350
|
||||
SCREEN_WIDTH_IN_BYTES equ 80
|
||||
DEMO_AREA_HEIGHT equ 336 ;# of scan lines in area
|
||||
; logical function operation
|
||||
; is demonstrated in
|
||||
DEMO_AREA_WIDTH_IN_BYTES equ 40 ;width in bytes of area
|
||||
; logical function operation
|
||||
; is demonstrated in
|
||||
VERTICAL_BOX_WIDTH_IN_BYTES equ 10 ;width in bytes of the box used to
|
||||
; demonstrate each logical function
|
||||
;
|
||||
; VGA register equates.
|
||||
;
|
||||
GC_INDEX equ 3ceh ;GC index register
|
||||
GC_ROTATE equ 3 ;GC data rotate/logical function
|
||||
; register index
|
||||
GC_MODE equ 5 ;GC mode register index
|
||||
;
|
||||
dseg segment para common ‘DATA'
|
||||
;
|
||||
; String used to label logical functions.
|
||||
;
|
||||
LabelString label byte
|
||||
db ‘UNMODIFIED AND OR XOR '
|
||||
LABEL_STRING_LENGTH equ $-LabelString
|
||||
;
|
||||
; Strings used to label fill patterns.
|
||||
;
|
||||
FillPatternFF db ‘Fill Pattern: 0FFh'
|
||||
FILL_PATTERN_FF_LENGTH equ $ - FillPatternFF
|
||||
FillPattern00 db ‘Fill Pattern: 000h'
|
||||
FILL_PATTERN_00_LENGTH equ $ - FillPattern00
|
||||
FillPatternVert db ‘Fill Pattern: Vertical Bar'
|
||||
FILL_PATTERN_VERT_LENGTH equ $ - FillPatternVert
|
||||
FillPatternHorz db ‘Fill Pattern: Horizontal Bar'
|
||||
FILL_PATTERN_HORZ_LENGTH equ $ - FillPatternHorz
|
||||
;
|
||||
dseg ends
|
||||
;
|
||||
; Macro to set indexed register INDEX of GC chip to SETTING.
|
||||
;
|
||||
SETGC macro INDEX, SETTING
|
||||
mov dx,GC_INDEX
|
||||
mov ax,(SETTING SHL 8) OR INDEX
|
||||
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.
|
||||
;
|
||||
TEXT_UP macro TEXT_STRING, TEXT_LENGTH, ROW, COLUMN
|
||||
mov ah,13h ;BIOS write string function
|
||||
mov bp,offset TEXT_STRING ;ES:BP points to string
|
||||
mov cx,TEXT_LENGTH
|
||||
mov dx,(ROW SHL 8) OR COLUMN ;position
|
||||
sub al,al ;string is chars only, cursor not moved
|
||||
mov bl,7 ;text attribute is white (light gray)
|
||||
int 10h
|
||||
endm
|
||||
;
|
||||
cseg segment para public ‘CODE'
|
||||
assume cs:cseg, ds:dseg
|
||||
start proc near
|
||||
mov ax,dseg
|
||||
mov ds,ax
|
||||
;
|
||||
; Select 640x350 graphics mode.
|
||||
;
|
||||
mov ax,010h
|
||||
int 10h
|
||||
;
|
||||
; ES points to VGA memory.
|
||||
;
|
||||
mov ax,VGA_VIDEO_SEGMENT
|
||||
mov es,ax
|
||||
;
|
||||
; Draw background of horizontal bars.
|
||||
;
|
||||
mov dx,SCREEN_HEIGHT/4
|
||||
;# of bars to draw (each 4 pixels high)
|
||||
sub di,di ;start at offset 0 in display memory
|
||||
mov ax,0ffffh ;fill pattern for light areas of bars
|
||||
mov bx,DEMO_AREA_WIDTH_IN_BYTES / 2 ;length of each bar
|
||||
mov si,SCREEN_WIDTH_IN_BYTES - DEMO_AREA_WIDTH_IN_BYTES
|
||||
mov bp,(SCREEN_WIDTH_IN_BYTES * 3) - DEMO_AREA_WIDTH_IN_BYTES
|
||||
BackgroundLoop:
|
||||
mov cx,bx ;length of bar
|
||||
rep stosw ;draw top half of bar
|
||||
add di,si ;point to start of bottom half of bar
|
||||
mov cx,bx ;length of bar
|
||||
rep stosw ;draw bottom half of bar
|
||||
add di,bp ;point to start of top of next bar
|
||||
dec dx
|
||||
jnz BackgroundLoop
|
||||
;
|
||||
; 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...
|
||||
mov di,0
|
||||
call DrawVerticalBox ;...and draw box
|
||||
;
|
||||
SETGC GC_ROTATE, 08h ;select AND logical function...
|
||||
mov di,10
|
||||
call DrawVerticalBox ;...and draw box
|
||||
;
|
||||
SETGC GC_ROTATE, 10h ;select OR logical function...
|
||||
mov di,20
|
||||
call DrawVerticalBox ;...and draw box
|
||||
;
|
||||
SETGC GC_ROTATE, 18h ;select XOR logical function...
|
||||
mov di,30
|
||||
call DrawVerticalBox ;...and draw box
|
||||
;
|
||||
; Reset the logical function to data unmodified, the default state.
|
||||
;
|
||||
SETGC GC_ROTATE, 0
|
||||
;
|
||||
; Label the screen.
|
||||
;
|
||||
push ds
|
||||
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.
|
||||
;
|
||||
TEXT_UP LabelString, LABEL_STRING_LENGTH, 24, 0
|
||||
;
|
||||
; 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 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.
|
||||
;
|
||||
WaitForKey:
|
||||
mov ah,1
|
||||
int 16h
|
||||
jz WaitForKey
|
||||
;
|
||||
; Finished. Clear key, reset screen mode and exit.
|
||||
;
|
||||
Done:
|
||||
mov ah,0 ;clear key that we just detected
|
||||
int 16h
|
||||
;
|
||||
mov ax,3 ;reset to text mode
|
||||
int 10h
|
||||
;
|
||||
mov ah,4ch ;exit to DOS
|
||||
int 21h
|
||||
;
|
||||
start endp
|
||||
;
|
||||
; Subroutine to draw a box 80x336 in size, using currently selected
|
||||
; logical function, with upper left corner at the display memory offset
|
||||
; in DI. Box is filled with four patterns. Top quarter of area is
|
||||
; filled with 0FFh (solid) pattern, next quarter is filled with 00h
|
||||
; (empty) pattern, next quarter is filled with 33h (double pixel wide
|
||||
; 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.
|
||||
;
|
||||
DRAW_BOX_QUARTER macro FILL, WIDTH
|
||||
local RowLoop, ColumnLoop
|
||||
mov al,FILL ;fill pattern
|
||||
mov dx,DEMO_AREA_HEIGHT / 4 ;1/4 of the full box height
|
||||
RowLoop:
|
||||
mov cx,WIDTH
|
||||
ColumnLoop:
|
||||
mov ah,es:[di] ;load display memory contents into
|
||||
; GC latches (we don't actually care
|
||||
; about value read into AH)
|
||||
stosb ;write pattern, which is logically
|
||||
; combined with latch contents for each
|
||||
; plane and then written to display
|
||||
; memory
|
||||
loop ColumnLoop
|
||||
add di,SCREEN_WIDTH_IN_BYTES - WIDTH
|
||||
;point to start of next line down in box
|
||||
dec dx
|
||||
jnz RowLoop
|
||||
endm
|
||||
;
|
||||
DrawVerticalBox proc near
|
||||
DRAW_BOX_QUARTER 0ffh, VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
;first fill pattern: solid fill
|
||||
DRAW_BOX_QUARTER 0, VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
;second fill pattern: empty fill
|
||||
DRAW_BOX_QUARTER 033h, VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
;third fill pattern: double-pixel
|
||||
; wide vertical bars
|
||||
mov dx,DEMO_AREA_HEIGHT / 4 / 4
|
||||
;fourth fill pattern: horizontal bars in
|
||||
; sets of 4 scan lines
|
||||
sub ax,ax
|
||||
mov si,VERTICAL_BOX_WIDTH_IN_BYTES ;width of fill area
|
||||
HorzBarLoop:
|
||||
dec ax ;0ffh fill (smaller to do word than byte DEC)
|
||||
mov cx,si ;width to fill
|
||||
HBLoop1:
|
||||
mov bl,es:[di] ;load latches (don't care about value)
|
||||
stosb ;write solid pattern, through ALUs
|
||||
loop HBLoop1
|
||||
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
mov cx,si ;width to fill
|
||||
HBLoop2:
|
||||
mov bl,es:[di] ;load latches
|
||||
stosb ;write solid pattern, through ALUs
|
||||
loop HBLoop2
|
||||
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
inc ax ;0 fill (smaller to do word than byte DEC)
|
||||
mov cx,si ;width to fill
|
||||
HBLoop3:
|
||||
mov bl,es:[di] ;load latches
|
||||
stosb ;write empty pattern, through ALUs
|
||||
loop HBLoop3
|
||||
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
mov cx,si ;width to fill
|
||||
HBLoop4:
|
||||
mov bl,es:[di] ;load latches
|
||||
stosb ;write empty pattern, through ALUs
|
||||
loop HBLoop4
|
||||
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
dec dx
|
||||
jnz HorzBarLoop
|
||||
;
|
||||
ret
|
||||
DrawVerticalBox endp
|
||||
cseg ends
|
||||
end start
|
||||
```nasm
|
||||
; Program to illustrate operation of ALUs and latches of the VGA's
|
||||
; Graphics Controller. Draws a variety of patterns against
|
||||
; a horizontally striped background, using each of the 4 available
|
||||
; logical functions (data unmodified, AND, OR, XOR) in turn to combine
|
||||
; the images with the background.
|
||||
; By Michael Abrash.
|
||||
;
|
||||
stack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
|
||||
SCREEN_HEIGHT equ 350
|
||||
SCREEN_WIDTH_IN_BYTES equ 80
|
||||
DEMO_AREA_HEIGHT equ 336 ;# of scan lines in area
|
||||
; logical function operation
|
||||
; is demonstrated in
|
||||
DEMO_AREA_WIDTH_IN_BYTES equ 40 ;width in bytes of area
|
||||
; logical function operation
|
||||
; is demonstrated in
|
||||
VERTICAL_BOX_WIDTH_IN_BYTES equ 10 ;width in bytes of the box used to
|
||||
; demonstrate each logical function
|
||||
;
|
||||
; VGA register equates.
|
||||
;
|
||||
GC_INDEX equ 3ceh ;GC index register
|
||||
GC_ROTATE equ 3 ;GC data rotate/logical function
|
||||
; register index
|
||||
GC_MODE equ 5 ;GC mode register index
|
||||
;
|
||||
dseg segment para common ‘DATA'
|
||||
;
|
||||
; String used to label logical functions.
|
||||
;
|
||||
LabelString label byte
|
||||
db ‘UNMODIFIED AND OR XOR '
|
||||
LABEL_STRING_LENGTH equ $-LabelString
|
||||
;
|
||||
; Strings used to label fill patterns.
|
||||
;
|
||||
FillPatternFF db ‘Fill Pattern: 0FFh'
|
||||
FILL_PATTERN_FF_LENGTH equ $ - FillPatternFF
|
||||
FillPattern00 db ‘Fill Pattern: 000h'
|
||||
FILL_PATTERN_00_LENGTH equ $ - FillPattern00
|
||||
FillPatternVert db ‘Fill Pattern: Vertical Bar'
|
||||
FILL_PATTERN_VERT_LENGTH equ $ - FillPatternVert
|
||||
FillPatternHorz db ‘Fill Pattern: Horizontal Bar'
|
||||
FILL_PATTERN_HORZ_LENGTH equ $ - FillPatternHorz
|
||||
;
|
||||
dseg ends
|
||||
;
|
||||
; Macro to set indexed register INDEX of GC chip to SETTING.
|
||||
;
|
||||
SETGC macro INDEX, SETTING
|
||||
mov dx,GC_INDEX
|
||||
mov ax,(SETTING SHL 8) OR INDEX
|
||||
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.
|
||||
;
|
||||
TEXT_UP macro TEXT_STRING, TEXT_LENGTH, ROW, COLUMN
|
||||
mov ah,13h ;BIOS write string function
|
||||
mov bp,offset TEXT_STRING ;ES:BP points to string
|
||||
mov cx,TEXT_LENGTH
|
||||
mov dx,(ROW SHL 8) OR COLUMN ;position
|
||||
sub al,al ;string is chars only, cursor not moved
|
||||
mov bl,7 ;text attribute is white (light gray)
|
||||
int 10h
|
||||
endm
|
||||
;
|
||||
cseg segment para public ‘CODE'
|
||||
assume cs:cseg, ds:dseg
|
||||
start proc near
|
||||
mov ax,dseg
|
||||
mov ds,ax
|
||||
;
|
||||
; Select 640x350 graphics mode.
|
||||
;
|
||||
mov ax,010h
|
||||
int 10h
|
||||
;
|
||||
; ES points to VGA memory.
|
||||
;
|
||||
mov ax,VGA_VIDEO_SEGMENT
|
||||
mov es,ax
|
||||
;
|
||||
; Draw background of horizontal bars.
|
||||
;
|
||||
mov dx,SCREEN_HEIGHT/4
|
||||
;# of bars to draw (each 4 pixels high)
|
||||
sub di,di ;start at offset 0 in display memory
|
||||
mov ax,0ffffh ;fill pattern for light areas of bars
|
||||
mov bx,DEMO_AREA_WIDTH_IN_BYTES / 2 ;length of each bar
|
||||
mov si,SCREEN_WIDTH_IN_BYTES - DEMO_AREA_WIDTH_IN_BYTES
|
||||
mov bp,(SCREEN_WIDTH_IN_BYTES * 3) - DEMO_AREA_WIDTH_IN_BYTES
|
||||
BackgroundLoop:
|
||||
mov cx,bx ;length of bar
|
||||
rep stosw ;draw top half of bar
|
||||
add di,si ;point to start of bottom half of bar
|
||||
mov cx,bx ;length of bar
|
||||
rep stosw ;draw bottom half of bar
|
||||
add di,bp ;point to start of top of next bar
|
||||
dec dx
|
||||
jnz BackgroundLoop
|
||||
;
|
||||
; 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...
|
||||
mov di,0
|
||||
call DrawVerticalBox ;...and draw box
|
||||
;
|
||||
SETGC GC_ROTATE, 08h ;select AND logical function...
|
||||
mov di,10
|
||||
call DrawVerticalBox ;...and draw box
|
||||
;
|
||||
SETGC GC_ROTATE, 10h ;select OR logical function...
|
||||
mov di,20
|
||||
call DrawVerticalBox ;...and draw box
|
||||
;
|
||||
SETGC GC_ROTATE, 18h ;select XOR logical function...
|
||||
mov di,30
|
||||
call DrawVerticalBox ;...and draw box
|
||||
;
|
||||
; Reset the logical function to data unmodified, the default state.
|
||||
;
|
||||
SETGC GC_ROTATE, 0
|
||||
;
|
||||
; Label the screen.
|
||||
;
|
||||
push ds
|
||||
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.
|
||||
;
|
||||
TEXT_UP LabelString, LABEL_STRING_LENGTH, 24, 0
|
||||
;
|
||||
; 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 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.
|
||||
;
|
||||
WaitForKey:
|
||||
mov ah,1
|
||||
int 16h
|
||||
jz WaitForKey
|
||||
;
|
||||
; Finished. Clear key, reset screen mode and exit.
|
||||
;
|
||||
Done:
|
||||
mov ah,0 ;clear key that we just detected
|
||||
int 16h
|
||||
;
|
||||
mov ax,3 ;reset to text mode
|
||||
int 10h
|
||||
;
|
||||
mov ah,4ch ;exit to DOS
|
||||
int 21h
|
||||
;
|
||||
start endp
|
||||
;
|
||||
; Subroutine to draw a box 80x336 in size, using currently selected
|
||||
; logical function, with upper left corner at the display memory offset
|
||||
; in DI. Box is filled with four patterns. Top quarter of area is
|
||||
; filled with 0FFh (solid) pattern, next quarter is filled with 00h
|
||||
; (empty) pattern, next quarter is filled with 33h (double pixel wide
|
||||
; 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.
|
||||
;
|
||||
DRAW_BOX_QUARTER macro FILL, WIDTH
|
||||
local RowLoop, ColumnLoop
|
||||
mov al,FILL ;fill pattern
|
||||
mov dx,DEMO_AREA_HEIGHT / 4 ;1/4 of the full box height
|
||||
RowLoop:
|
||||
mov cx,WIDTH
|
||||
ColumnLoop:
|
||||
mov ah,es:[di] ;load display memory contents into
|
||||
; GC latches (we don't actually care
|
||||
; about value read into AH)
|
||||
stosb ;write pattern, which is logically
|
||||
; combined with latch contents for each
|
||||
; plane and then written to display
|
||||
; memory
|
||||
loop ColumnLoop
|
||||
add di,SCREEN_WIDTH_IN_BYTES - WIDTH
|
||||
;point to start of next line down in box
|
||||
dec dx
|
||||
jnz RowLoop
|
||||
endm
|
||||
;
|
||||
DrawVerticalBox proc near
|
||||
DRAW_BOX_QUARTER 0ffh, VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
;first fill pattern: solid fill
|
||||
DRAW_BOX_QUARTER 0, VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
;second fill pattern: empty fill
|
||||
DRAW_BOX_QUARTER 033h, VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
;third fill pattern: double-pixel
|
||||
; wide vertical bars
|
||||
mov dx,DEMO_AREA_HEIGHT / 4 / 4
|
||||
;fourth fill pattern: horizontal bars in
|
||||
; sets of 4 scan lines
|
||||
sub ax,ax
|
||||
mov si,VERTICAL_BOX_WIDTH_IN_BYTES ;width of fill area
|
||||
HorzBarLoop:
|
||||
dec ax ;0ffh fill (smaller to do word than byte DEC)
|
||||
mov cx,si ;width to fill
|
||||
HBLoop1:
|
||||
mov bl,es:[di] ;load latches (don't care about value)
|
||||
stosb ;write solid pattern, through ALUs
|
||||
loop HBLoop1
|
||||
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
mov cx,si ;width to fill
|
||||
HBLoop2:
|
||||
mov bl,es:[di] ;load latches
|
||||
stosb ;write solid pattern, through ALUs
|
||||
loop HBLoop2
|
||||
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
inc ax ;0 fill (smaller to do word than byte DEC)
|
||||
mov cx,si ;width to fill
|
||||
HBLoop3:
|
||||
mov bl,es:[di] ;load latches
|
||||
stosb ;write empty pattern, through ALUs
|
||||
loop HBLoop3
|
||||
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
mov cx,si ;width to fill
|
||||
HBLoop4:
|
||||
mov bl,es:[di] ;load latches
|
||||
stosb ;write empty pattern, through ALUs
|
||||
loop HBLoop4
|
||||
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
|
||||
dec dx
|
||||
jnz HorzBarLoop
|
||||
;
|
||||
ret
|
||||
DrawVerticalBox endp
|
||||
cseg ends
|
||||
end start
|
||||
```
|
||||
|
|
|
|||
11
24-03.md
11
24-03.md
|
|
@ -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.
|
||||
The obvious way to implement this would be with
|
||||
|
||||
MOV DL,VALUE1
|
||||
MOV DH,VALUE2
|
||||
|
||||
```nasm
|
||||
MOV DL,VALUE1
|
||||
MOV DH,VALUE2
|
||||
```
|
||||
which requires four instruction bytes. By shifting the value destined
|
||||
for the high byte into the high byte with MASM's shift-left operator,
|
||||
**SHL** (\*100H would work also), and then logically combining the
|
||||
values with MASM's **OR** operator (or the **ADD** operator), both
|
||||
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.
|
||||
(Note, though, that in 32-bit protected mode, there's a size and
|
||||
|
|
|
|||
460
25-02.md
460
25-02.md
|
|
@ -12,236 +12,238 @@ pages: 466-470
|
|||
|
||||
**LISTING 25.1 L25-1.ASM**
|
||||
|
||||
; Program to illustrate operation of data rotate and bit mask
|
||||
; features of Graphics Controller. Draws 8x8 character at
|
||||
; specified location, using VGA's 8x8 ROM font. Designed
|
||||
; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h.
|
||||
; By Michael Abrash.
|
||||
;
|
||||
stack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
|
||||
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
|
||||
FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char
|
||||
;
|
||||
; VGA register equates.
|
||||
;
|
||||
GC_INDEX equ 3ceh ;GC index register
|
||||
GC_ROTATE equ 3 ;GC data rotate/logical function
|
||||
; 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
|
||||
TEST_TEXT_COL equ 17 ;column to display test text at
|
||||
TEST_TEXT_WIDTH equ 8 ;width of a character in pixels
|
||||
```nasm
|
||||
; Program to illustrate operation of data rotate and bit mask
|
||||
; features of Graphics Controller. Draws 8x8 character at
|
||||
; specified location, using VGA's 8x8 ROM font. Designed
|
||||
; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h.
|
||||
; By Michael Abrash.
|
||||
;
|
||||
stack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
|
||||
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
|
||||
FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char
|
||||
;
|
||||
; VGA register equates.
|
||||
;
|
||||
GC_INDEX equ 3ceh ;GC index register
|
||||
GC_ROTATE equ 3 ;GC data rotate/logical function
|
||||
; 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
|
||||
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
|
||||
db ‘Hello, world!',0 ;test string to print.
|
||||
FontPointer dd ? ;font offset
|
||||
dseg ends
|
||||
;
|
||||
; Macro to set indexed register INDEX of GC chip to SETTING.
|
||||
;
|
||||
SETGC macro INDEX, SETTING
|
||||
mov dx,GC_INDEX
|
||||
mov ax,(SETTING SHL 8) OR INDEX
|
||||
out dx,ax
|
||||
endm
|
||||
;
|
||||
cseg segment para public ‘CODE'
|
||||
assume cs:cseg, ds:dseg
|
||||
start proc near
|
||||
mov ax,dseg
|
||||
mov ds,ax
|
||||
;
|
||||
; Select 640x480 graphics mode.
|
||||
;
|
||||
mov ax,012h
|
||||
int 10h
|
||||
;
|
||||
; Set driver to use the 8x8 font.
|
||||
;
|
||||
mov ah,11h ;VGA BIOS character generator function,
|
||||
mov al,30h ; return info subfunction
|
||||
mov bh,3;get 8x8 font pointer
|
||||
int 10h
|
||||
call SelectFont
|
||||
;
|
||||
; Print the test string.
|
||||
;
|
||||
mov si,offset TestString
|
||||
mov bx,TEST_TEXT_ROW
|
||||
mov cx,TEST_TEXT_COL
|
||||
StringOutLoop:
|
||||
lodsb
|
||||
and al,al
|
||||
jz StringOutDone
|
||||
call DrawChar
|
||||
add cx,TEST_TEXT_WIDTH
|
||||
jmp StringOutLoop
|
||||
StringOutDone:
|
||||
;
|
||||
; Reset the data rotate and bit mask registers.
|
||||
;
|
||||
SETGC GC_ROTATE, 0
|
||||
SETGC GC_BIT_MASK, 0ffh
|
||||
;
|
||||
; Wait for a keystroke.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h
|
||||
;
|
||||
; Return to text mode.
|
||||
;
|
||||
mov ax,03h
|
||||
int 10h
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Start endp
|
||||
;
|
||||
; Subroutine to draw a text character in a linear graphics mode
|
||||
; (0Dh, 0Eh, 0Fh, 010h, 012h).
|
||||
; Font used should be pointed to by FontPointer.
|
||||
;
|
||||
; Input:
|
||||
; AL = character to draw
|
||||
; BX = row to draw text character at
|
||||
; CX = column to draw text character at
|
||||
;
|
||||
; Forces ALU function to "move".
|
||||
;
|
||||
DrawChar proc near
|
||||
push ax
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
push si
|
||||
push di
|
||||
push bp
|
||||
push ds
|
||||
;
|
||||
; 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
|
||||
mov es,dx ;point to display memory
|
||||
;
|
||||
; Calculate screen address of byte character starts in.
|
||||
;
|
||||
push ds ;point to BIOS data segment
|
||||
sub dx,dx
|
||||
mov ds,dx
|
||||
xchg ax,bx
|
||||
mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS
|
||||
; screen width
|
||||
pop ds
|
||||
mul di ;calculate offset of start of row
|
||||
push di ;set aside screen width
|
||||
mov di,cx ;set aside the column
|
||||
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
|
||||
add di,ax ;and point to byte
|
||||
;
|
||||
; Calculate font address of character.
|
||||
;
|
||||
sub bh,bh
|
||||
shl bx,1 ;assumes 8 bytes per character; use
|
||||
shl bx,1 ; a multiply otherwise
|
||||
shl bx,1 ;offset in font of character
|
||||
add si,bx ;offset in font segment of character
|
||||
;
|
||||
; Set up the GC rotation.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_ROTATE
|
||||
mov ah,cl
|
||||
out dx,ax
|
||||
;
|
||||
; Set up BH as bit mask for left half,
|
||||
; BL as rotation for right half.
|
||||
;
|
||||
mov bx,0ffffh
|
||||
shr bh,cl
|
||||
neg cl
|
||||
add cl,8
|
||||
shl bl,cl
|
||||
;
|
||||
; Draw the character, left half first, then right half in the
|
||||
; succeeding byte, using the data rotation to position the character
|
||||
; across the byte boundary and then using the bit mask to get the
|
||||
; proper portion of the character into each byte.
|
||||
; Does not check for case where character is byte-aligned and
|
||||
; no rotation and only one write is required.
|
||||
;
|
||||
mov bp,FONT_CHARACTER_SIZE
|
||||
mov dx,GC_INDEX
|
||||
pop cx ;get back screen width
|
||||
dec cx
|
||||
dec cx ; -2 because do two bytes for each char
|
||||
CharacterLoop:
|
||||
;
|
||||
; Set the bit mask for the left half of the character.
|
||||
;
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bh
|
||||
out dx,ax
|
||||
;
|
||||
; Get the next character byte & write it to display memory.
|
||||
; (Left half of character.)
|
||||
;
|
||||
mov al,[si] ;get character byte
|
||||
mov ah,es:[di] ;load latches
|
||||
stosb ;write character byte
|
||||
;
|
||||
; Set the bit mask for the right half of the character.
|
||||
;
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bl
|
||||
out dx,ax
|
||||
;
|
||||
; Get the character byte again & write it to display memory.
|
||||
; (Right half of character.)
|
||||
;
|
||||
lodsb ;get character byte
|
||||
mov ah,es:[di] ;load latches
|
||||
stosb ;write character byte
|
||||
;
|
||||
; Point to next line of character in display memory.
|
||||
;
|
||||
add di,cx
|
||||
;
|
||||
dec bp
|
||||
jnz CharacterLoop
|
||||
;
|
||||
pop ds
|
||||
pop bp
|
||||
pop di
|
||||
pop si
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
pop ax
|
||||
ret
|
||||
DrawChar endp
|
||||
;
|
||||
; Set the pointer to the font to draw from to ES:BP.
|
||||
;
|
||||
SelectFont proc near
|
||||
mov word ptr [FontPointer],bp ;save pointer
|
||||
mov word ptr [FontPointer+2],es
|
||||
ret
|
||||
SelectFont endp
|
||||
;
|
||||
cseg ends
|
||||
end start
|
||||
TestString label byte
|
||||
db ‘Hello, world!',0 ;test string to print.
|
||||
FontPointer dd ? ;font offset
|
||||
dseg ends
|
||||
;
|
||||
; Macro to set indexed register INDEX of GC chip to SETTING.
|
||||
;
|
||||
SETGC macro INDEX, SETTING
|
||||
mov dx,GC_INDEX
|
||||
mov ax,(SETTING SHL 8) OR INDEX
|
||||
out dx,ax
|
||||
endm
|
||||
;
|
||||
cseg segment para public ‘CODE'
|
||||
assume cs:cseg, ds:dseg
|
||||
start proc near
|
||||
mov ax,dseg
|
||||
mov ds,ax
|
||||
;
|
||||
; Select 640x480 graphics mode.
|
||||
;
|
||||
mov ax,012h
|
||||
int 10h
|
||||
;
|
||||
; Set driver to use the 8x8 font.
|
||||
;
|
||||
mov ah,11h ;VGA BIOS character generator function,
|
||||
mov al,30h ; return info subfunction
|
||||
mov bh,3;get 8x8 font pointer
|
||||
int 10h
|
||||
call SelectFont
|
||||
;
|
||||
; Print the test string.
|
||||
;
|
||||
mov si,offset TestString
|
||||
mov bx,TEST_TEXT_ROW
|
||||
mov cx,TEST_TEXT_COL
|
||||
StringOutLoop:
|
||||
lodsb
|
||||
and al,al
|
||||
jz StringOutDone
|
||||
call DrawChar
|
||||
add cx,TEST_TEXT_WIDTH
|
||||
jmp StringOutLoop
|
||||
StringOutDone:
|
||||
;
|
||||
; Reset the data rotate and bit mask registers.
|
||||
;
|
||||
SETGC GC_ROTATE, 0
|
||||
SETGC GC_BIT_MASK, 0ffh
|
||||
;
|
||||
; Wait for a keystroke.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h
|
||||
;
|
||||
; Return to text mode.
|
||||
;
|
||||
mov ax,03h
|
||||
int 10h
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Start endp
|
||||
;
|
||||
; Subroutine to draw a text character in a linear graphics mode
|
||||
; (0Dh, 0Eh, 0Fh, 010h, 012h).
|
||||
; Font used should be pointed to by FontPointer.
|
||||
;
|
||||
; Input:
|
||||
; AL = character to draw
|
||||
; BX = row to draw text character at
|
||||
; CX = column to draw text character at
|
||||
;
|
||||
; Forces ALU function to "move".
|
||||
;
|
||||
DrawChar proc near
|
||||
push ax
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
push si
|
||||
push di
|
||||
push bp
|
||||
push ds
|
||||
;
|
||||
; 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
|
||||
mov es,dx ;point to display memory
|
||||
;
|
||||
; Calculate screen address of byte character starts in.
|
||||
;
|
||||
push ds ;point to BIOS data segment
|
||||
sub dx,dx
|
||||
mov ds,dx
|
||||
xchg ax,bx
|
||||
mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS
|
||||
; screen width
|
||||
pop ds
|
||||
mul di ;calculate offset of start of row
|
||||
push di ;set aside screen width
|
||||
mov di,cx ;set aside the column
|
||||
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
|
||||
add di,ax ;and point to byte
|
||||
;
|
||||
; Calculate font address of character.
|
||||
;
|
||||
sub bh,bh
|
||||
shl bx,1 ;assumes 8 bytes per character; use
|
||||
shl bx,1 ; a multiply otherwise
|
||||
shl bx,1 ;offset in font of character
|
||||
add si,bx ;offset in font segment of character
|
||||
;
|
||||
; Set up the GC rotation.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_ROTATE
|
||||
mov ah,cl
|
||||
out dx,ax
|
||||
;
|
||||
; Set up BH as bit mask for left half,
|
||||
; BL as rotation for right half.
|
||||
;
|
||||
mov bx,0ffffh
|
||||
shr bh,cl
|
||||
neg cl
|
||||
add cl,8
|
||||
shl bl,cl
|
||||
;
|
||||
; Draw the character, left half first, then right half in the
|
||||
; succeeding byte, using the data rotation to position the character
|
||||
; across the byte boundary and then using the bit mask to get the
|
||||
; proper portion of the character into each byte.
|
||||
; Does not check for case where character is byte-aligned and
|
||||
; no rotation and only one write is required.
|
||||
;
|
||||
mov bp,FONT_CHARACTER_SIZE
|
||||
mov dx,GC_INDEX
|
||||
pop cx ;get back screen width
|
||||
dec cx
|
||||
dec cx ; -2 because do two bytes for each char
|
||||
CharacterLoop:
|
||||
;
|
||||
; Set the bit mask for the left half of the character.
|
||||
;
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bh
|
||||
out dx,ax
|
||||
;
|
||||
; Get the next character byte & write it to display memory.
|
||||
; (Left half of character.)
|
||||
;
|
||||
mov al,[si] ;get character byte
|
||||
mov ah,es:[di] ;load latches
|
||||
stosb ;write character byte
|
||||
;
|
||||
; Set the bit mask for the right half of the character.
|
||||
;
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bl
|
||||
out dx,ax
|
||||
;
|
||||
; Get the character byte again & write it to display memory.
|
||||
; (Right half of character.)
|
||||
;
|
||||
lodsb ;get character byte
|
||||
mov ah,es:[di] ;load latches
|
||||
stosb ;write character byte
|
||||
;
|
||||
; Point to next line of character in display memory.
|
||||
;
|
||||
add di,cx
|
||||
;
|
||||
dec bp
|
||||
jnz CharacterLoop
|
||||
;
|
||||
pop ds
|
||||
pop bp
|
||||
pop di
|
||||
pop si
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
pop ax
|
||||
ret
|
||||
DrawChar endp
|
||||
;
|
||||
; Set the pointer to the font to draw from to ES:BP.
|
||||
;
|
||||
SelectFont proc near
|
||||
mov word ptr [FontPointer],bp ;save pointer
|
||||
mov word ptr [FontPointer+2],es
|
||||
ret
|
||||
SelectFont endp
|
||||
;
|
||||
cseg ends
|
||||
end start
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
|||
164
25-04.md
164
25-04.md
|
|
@ -12,87 +12,89 @@ pages: 472-474
|
|||
|
||||
**LISTING 25.2 L25-2.ASM**
|
||||
|
||||
; Program to illustrate operation of Map Mask register when drawing
|
||||
; to memory that already contains data.
|
||||
; By Michael Abrash.
|
||||
;
|
||||
stack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment
|
||||
;
|
||||
; EGA register equates.
|
||||
;
|
||||
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.
|
||||
;
|
||||
SETSC macro INDEX, SETTING
|
||||
mov dx,SC_INDEX
|
||||
mov al,INDEX
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,SETTING
|
||||
out dx,al
|
||||
dec dx
|
||||
endm
|
||||
;
|
||||
cseg segment para public ‘CODE#146;
|
||||
assume cs:cseg
|
||||
start proc near
|
||||
;
|
||||
; Select 640x480 graphics mode.
|
||||
;
|
||||
mov ax,012h
|
||||
int 10h
|
||||
;
|
||||
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.
|
||||
;
|
||||
SETSC SC_MAP_MASK,02h ;map mask setting enables only
|
||||
; plane 1, the green plane
|
||||
sub di,di ;start at beginning of video memory
|
||||
mov al,0ffh
|
||||
mov bp,24 ;# bars to draw
|
||||
HorzBarLoop:
|
||||
mov cx,80*10 ;# bytes per horizontal bar
|
||||
rep stosb ;draw bar
|
||||
add di,80*10 ;point to start of next bar
|
||||
dec bp
|
||||
jnz HorzBarLoop
|
||||
;
|
||||
; 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
|
||||
sub di,di
|
||||
mov cx,80*480 ;# bytes per screen
|
||||
mov al,0ffh
|
||||
rep stosb ;perform fill (affects only
|
||||
; plane 0, the blue plane)
|
||||
;
|
||||
; Wait for a keystroke.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h
|
||||
;
|
||||
; Restore text mode.
|
||||
;
|
||||
mov ax,03h
|
||||
int 10h
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
start endp
|
||||
cseg ends
|
||||
end start
|
||||
```nasm
|
||||
; Program to illustrate operation of Map Mask register when drawing
|
||||
; to memory that already contains data.
|
||||
; By Michael Abrash.
|
||||
;
|
||||
stack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment
|
||||
;
|
||||
; EGA register equates.
|
||||
;
|
||||
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.
|
||||
;
|
||||
SETSC macro INDEX, SETTING
|
||||
mov dx,SC_INDEX
|
||||
mov al,INDEX
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,SETTING
|
||||
out dx,al
|
||||
dec dx
|
||||
endm
|
||||
;
|
||||
cseg segment para public ‘CODE#146;
|
||||
assume cs:cseg
|
||||
start proc near
|
||||
;
|
||||
; Select 640x480 graphics mode.
|
||||
;
|
||||
mov ax,012h
|
||||
int 10h
|
||||
;
|
||||
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.
|
||||
;
|
||||
SETSC SC_MAP_MASK,02h ;map mask setting enables only
|
||||
; plane 1, the green plane
|
||||
sub di,di ;start at beginning of video memory
|
||||
mov al,0ffh
|
||||
mov bp,24 ;# bars to draw
|
||||
HorzBarLoop:
|
||||
mov cx,80*10 ;# bytes per horizontal bar
|
||||
rep stosb ;draw bar
|
||||
add di,80*10 ;point to start of next bar
|
||||
dec bp
|
||||
jnz HorzBarLoop
|
||||
;
|
||||
; 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
|
||||
sub di,di
|
||||
mov cx,80*480 ;# bytes per screen
|
||||
mov al,0ffh
|
||||
rep stosb ;perform fill (affects only
|
||||
; plane 0, the blue plane)
|
||||
;
|
||||
; Wait for a keystroke.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h
|
||||
;
|
||||
; Restore text mode.
|
||||
;
|
||||
mov ax,03h
|
||||
int 10h
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
start endp
|
||||
cseg ends
|
||||
end start
|
||||
```
|
||||
|
||||
#### Setting All Planes to a Single Color {#Heading6}
|
||||
|
||||
|
|
|
|||
436
25-05.md
436
25-05.md
|
|
@ -12,114 +12,116 @@ pages: 474-478
|
|||
|
||||
**LISTING 25.3 L25-3.ASM**
|
||||
|
||||
; Program to illustrate operation of set/reset circuitry to force
|
||||
; setting of memory that already contains data.
|
||||
; By Michael Abrash.
|
||||
;
|
||||
stack segment para stack ‘STACK#146;
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment
|
||||
;
|
||||
; EGA register equates.
|
||||
;
|
||||
SC_INDEX equ 3c4h ;SC index register
|
||||
SC_MAP_MASK equ 2 ;SC map mask register
|
||||
GC_INDEX equ 3ceh ;GC index 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.
|
||||
;
|
||||
SETSC macro INDEX, SETTING
|
||||
mov dx,SC_INDEX
|
||||
mov al,INDEX
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,SETTING
|
||||
out dx,al
|
||||
dec dx
|
||||
endm
|
||||
;
|
||||
; Macro to set indexed register INDEX of GC chip to SETTING.
|
||||
;
|
||||
SETGC macro INDEX, SETTING
|
||||
mov dx,GC_INDEX
|
||||
mov al,INDEX
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,SETTING
|
||||
out dx,al
|
||||
dec dx
|
||||
endm
|
||||
;
|
||||
cseg segment para public ‘CODE#146;
|
||||
assume cs:cseg
|
||||
start proc near
|
||||
;
|
||||
; Select 640x480 graphics mode.
|
||||
;
|
||||
mov ax,012h
|
||||
int 10h
|
||||
;
|
||||
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.
|
||||
;
|
||||
SETSC SC_MAP_MASK,02h ;map mask setting enables only
|
||||
; plane 1, the green plane
|
||||
sub di,di ;start at beginning of video memory
|
||||
mov al,0ffh
|
||||
mov bp,24 ;# bars to draw
|
||||
HorzBarLoop:
|
||||
mov cx,80*10 ;# bytes per horizontal bar
|
||||
rep stosb ;draw bar
|
||||
add di,80*10 ;point to start of next bar
|
||||
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.
|
||||
;
|
||||
SETSC SC_MAP_MASK,0fh ;must set map mask to enable all
|
||||
; planes, so set/reset values can
|
||||
; be written to memory
|
||||
SETGC GC_ENABLE_SET_RESET,0fh ;CPU data to all planes will be
|
||||
; replaced by set/reset value
|
||||
SETGC GC_SET_RESET,01h ;set/reset value is 0ffh for plane 0
|
||||
; (the blue plane) and 0 for other
|
||||
; planes
|
||||
sub di,di
|
||||
mov cx,80*480 ;# bytes per screen
|
||||
mov al,0ffh ;since set/reset is enabled for all
|
||||
; planes, the CPU data is ignored-
|
||||
; only the act of writing is
|
||||
; important
|
||||
rep stosb ;perform fill (affects all planes)
|
||||
;
|
||||
; Turn off set/reset.
|
||||
;
|
||||
SETGC GC_ENABLE_SET_RESET,0
|
||||
;
|
||||
; Wait for a keystroke.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h
|
||||
;
|
||||
; Restore text mode.
|
||||
;
|
||||
mov ax,03h
|
||||
int 10h
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
start endp
|
||||
cseg ends
|
||||
end start
|
||||
```nasm
|
||||
; Program to illustrate operation of set/reset circuitry to force
|
||||
; setting of memory that already contains data.
|
||||
; By Michael Abrash.
|
||||
;
|
||||
stack segment para stack ‘STACK#146;
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment
|
||||
;
|
||||
; EGA register equates.
|
||||
;
|
||||
SC_INDEX equ 3c4h ;SC index register
|
||||
SC_MAP_MASK equ 2 ;SC map mask register
|
||||
GC_INDEX equ 3ceh ;GC index 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.
|
||||
;
|
||||
SETSC macro INDEX, SETTING
|
||||
mov dx,SC_INDEX
|
||||
mov al,INDEX
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,SETTING
|
||||
out dx,al
|
||||
dec dx
|
||||
endm
|
||||
;
|
||||
; Macro to set indexed register INDEX of GC chip to SETTING.
|
||||
;
|
||||
SETGC macro INDEX, SETTING
|
||||
mov dx,GC_INDEX
|
||||
mov al,INDEX
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,SETTING
|
||||
out dx,al
|
||||
dec dx
|
||||
endm
|
||||
;
|
||||
cseg segment para public ‘CODE#146;
|
||||
assume cs:cseg
|
||||
start proc near
|
||||
;
|
||||
; Select 640x480 graphics mode.
|
||||
;
|
||||
mov ax,012h
|
||||
int 10h
|
||||
;
|
||||
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.
|
||||
;
|
||||
SETSC SC_MAP_MASK,02h ;map mask setting enables only
|
||||
; plane 1, the green plane
|
||||
sub di,di ;start at beginning of video memory
|
||||
mov al,0ffh
|
||||
mov bp,24 ;# bars to draw
|
||||
HorzBarLoop:
|
||||
mov cx,80*10 ;# bytes per horizontal bar
|
||||
rep stosb ;draw bar
|
||||
add di,80*10 ;point to start of next bar
|
||||
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.
|
||||
;
|
||||
SETSC SC_MAP_MASK,0fh ;must set map mask to enable all
|
||||
; planes, so set/reset values can
|
||||
; be written to memory
|
||||
SETGC GC_ENABLE_SET_RESET,0fh ;CPU data to all planes will be
|
||||
; replaced by set/reset value
|
||||
SETGC GC_SET_RESET,01h ;set/reset value is 0ffh for plane 0
|
||||
; (the blue plane) and 0 for other
|
||||
; planes
|
||||
sub di,di
|
||||
mov cx,80*480 ;# bytes per screen
|
||||
mov al,0ffh ;since set/reset is enabled for all
|
||||
; planes, the CPU data is ignored-
|
||||
; only the act of writing is
|
||||
; important
|
||||
rep stosb ;perform fill (affects all planes)
|
||||
;
|
||||
; Turn off set/reset.
|
||||
;
|
||||
SETGC GC_ENABLE_SET_RESET,0
|
||||
;
|
||||
; Wait for a keystroke.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h
|
||||
;
|
||||
; Restore text mode.
|
||||
;
|
||||
mov ax,03h
|
||||
int 10h
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
start endp
|
||||
cseg ends
|
||||
end start
|
||||
```
|
||||
|
||||
#### Manipulating Planes Individually {#Heading7}
|
||||
|
||||
|
|
@ -142,111 +144,113 @@ be used to control individual pixels.
|
|||
|
||||
**LISTING 25.4 L25-4.ASM**
|
||||
|
||||
; Program to illustrate operation of set/reset circuitry in conjunction
|
||||
; 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 ends
|
||||
;
|
||||
EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment
|
||||
;
|
||||
; EGA register equates.
|
||||
;
|
||||
SC_INDEX equ 3c4h ;SC index register
|
||||
SC_MAP_MASK equ 2 ;SC map mask register
|
||||
GC_INDEX equ 3ceh ;GC index 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.
|
||||
;
|
||||
SETSC macro INDEX, SETTING
|
||||
mov dx,SC_INDEX
|
||||
mov al,INDEX
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,SETTING
|
||||
out dx,al
|
||||
dec dx
|
||||
endm
|
||||
;
|
||||
; Macro to set indexed register INDEX of GC chip to SETTING.
|
||||
;
|
||||
SETGC macro INDEX, SETTING
|
||||
mov dx,GC_INDEX
|
||||
mov al,INDEX
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,SETTING
|
||||
out dx,al
|
||||
dec dx
|
||||
endm
|
||||
;
|
||||
cseg segment para public ‘CODE#146;
|
||||
assume cs:cseg
|
||||
start proc near
|
||||
;
|
||||
; Select 640x350 graphics mode.
|
||||
;
|
||||
mov ax,010h
|
||||
int 10h
|
||||
;
|
||||
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.
|
||||
;
|
||||
SETSC SC_MAP_MASK,02h;map mask setting enables only
|
||||
; plane 1, the green plane
|
||||
sub di,di;start at beginning of video memory
|
||||
mov al,0ffh
|
||||
mov bp,18;# bars to draw
|
||||
HorzBarLoop:
|
||||
mov cx,80*10;# bytes per horizontal bar
|
||||
rep stosb;draw bar
|
||||
add di,80*10;point to start of next bar
|
||||
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.
|
||||
;
|
||||
SETSC SC_MAP_MASK,0fh ;must set map mask to enable all
|
||||
; planes, so set/reset values can
|
||||
; be written to planes 0, 2 & 3
|
||||
; and CPU data can be written to
|
||||
; plane 1 (the green plane)
|
||||
SETGC GC_ENABLE_SET_RESET,0dh ;CPU data to planes 0, 2 & 3 will be
|
||||
; replaced by set/reset value
|
||||
SETGC GC_SET_RESET,04h ;set/reset value is 0ffh for plane 2
|
||||
; (the red plane) and 0 for other
|
||||
; planes
|
||||
sub di,di
|
||||
mov cx,80*350/2 ;# words per screen
|
||||
mov ax,07e0h ;CPU data controls only plane 1;
|
||||
; set/reset controls other planes
|
||||
rep stosw ;perform fill (affects all planes)
|
||||
;
|
||||
; Turn off set/reset.
|
||||
;
|
||||
SETGC GC_ENABLE_SET_RESET,0
|
||||
;
|
||||
; Wait for a keystroke.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h
|
||||
;
|
||||
; Restore text mode.
|
||||
;
|
||||
mov ax,03h
|
||||
int 10h
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
start endp
|
||||
cseg ends
|
||||
end start
|
||||
```nasm
|
||||
; Program to illustrate operation of set/reset circuitry in conjunction
|
||||
; 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 ends
|
||||
;
|
||||
EGA_VIDEO_SEGMENT equ 0a000h ;EGA display memory segment
|
||||
;
|
||||
; EGA register equates.
|
||||
;
|
||||
SC_INDEX equ 3c4h ;SC index register
|
||||
SC_MAP_MASK equ 2 ;SC map mask register
|
||||
GC_INDEX equ 3ceh ;GC index 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.
|
||||
;
|
||||
SETSC macro INDEX, SETTING
|
||||
mov dx,SC_INDEX
|
||||
mov al,INDEX
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,SETTING
|
||||
out dx,al
|
||||
dec dx
|
||||
endm
|
||||
;
|
||||
; Macro to set indexed register INDEX of GC chip to SETTING.
|
||||
;
|
||||
SETGC macro INDEX, SETTING
|
||||
mov dx,GC_INDEX
|
||||
mov al,INDEX
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,SETTING
|
||||
out dx,al
|
||||
dec dx
|
||||
endm
|
||||
;
|
||||
cseg segment para public ‘CODE#146;
|
||||
assume cs:cseg
|
||||
start proc near
|
||||
;
|
||||
; Select 640x350 graphics mode.
|
||||
;
|
||||
mov ax,010h
|
||||
int 10h
|
||||
;
|
||||
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.
|
||||
;
|
||||
SETSC SC_MAP_MASK,02h;map mask setting enables only
|
||||
; plane 1, the green plane
|
||||
sub di,di;start at beginning of video memory
|
||||
mov al,0ffh
|
||||
mov bp,18;# bars to draw
|
||||
HorzBarLoop:
|
||||
mov cx,80*10;# bytes per horizontal bar
|
||||
rep stosb;draw bar
|
||||
add di,80*10;point to start of next bar
|
||||
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.
|
||||
;
|
||||
SETSC SC_MAP_MASK,0fh ;must set map mask to enable all
|
||||
; planes, so set/reset values can
|
||||
; be written to planes 0, 2 & 3
|
||||
; and CPU data can be written to
|
||||
; plane 1 (the green plane)
|
||||
SETGC GC_ENABLE_SET_RESET,0dh ;CPU data to planes 0, 2 & 3 will be
|
||||
; replaced by set/reset value
|
||||
SETGC GC_SET_RESET,04h ;set/reset value is 0ffh for plane 2
|
||||
; (the red plane) and 0 for other
|
||||
; planes
|
||||
sub di,di
|
||||
mov cx,80*350/2 ;# words per screen
|
||||
mov ax,07e0h ;CPU data controls only plane 1;
|
||||
; set/reset controls other planes
|
||||
rep stosw ;perform fill (affects all planes)
|
||||
;
|
||||
; Turn off set/reset.
|
||||
;
|
||||
SETGC GC_ENABLE_SET_RESET,0
|
||||
;
|
||||
; Wait for a keystroke.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h
|
||||
;
|
||||
; Restore text mode.
|
||||
;
|
||||
mov ax,03h
|
||||
int 10h
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
start endp
|
||||
cseg ends
|
||||
end start
|
||||
```
|
||||
|
|
|
|||
586
26-02.md
586
26-02.md
|
|
@ -12,296 +12,298 @@ pages: 484-489
|
|||
|
||||
**LISTING 26.1 L26-1.ASM**
|
||||
|
||||
; Program to illustrate operation of write mode 3 of the VGA.
|
||||
; Draws 8x8 characters at arbitrary locations without disturbing
|
||||
; the background, using VGA's 8x8 ROM font. Designed
|
||||
; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h.
|
||||
; Runs only on VGAs (in Models 50 & up and IBM Display Adapter
|
||||
; and 100% compatibles).
|
||||
; Assembled with MASM
|
||||
; By Michael Abrash
|
||||
;
|
||||
stack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
|
||||
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
|
||||
FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char
|
||||
;
|
||||
; VGA register equates.
|
||||
;
|
||||
SC_INDEX equ 3c4h ;SC index register
|
||||
SC_MAP_MASK equ 2 ;SC map mask register index
|
||||
GC_INDEX equ 3ceh ;GC index register
|
||||
GC_SET_RESET equ 0 ;GC set/reset register index
|
||||
GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index
|
||||
GC_ROTATE equ 3 ;GC data rotate/logical function
|
||||
; 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
|
||||
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
|
||||
db ‘Hello, world!',0 ;test string to print.
|
||||
FontPointer dd ? ;font offset
|
||||
dseg ends
|
||||
;
|
||||
cseg segment para public ‘CODE'
|
||||
assume cs:cseg, ds:dseg
|
||||
start proc near
|
||||
mov ax,dseg
|
||||
mov ds,ax
|
||||
;
|
||||
; Select 640x480 graphics mode.
|
||||
;
|
||||
mov ax,012h
|
||||
int 10h
|
||||
;
|
||||
; 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
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
or al,1 ;blue plane only set, others reset
|
||||
out dx,al
|
||||
dec dx
|
||||
mov al,GC_ENABLE_SET_RESET
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
or al,0fh ;enable set/reset for all planes
|
||||
out dx,al
|
||||
mov dx,VGA_VIDEO_SEGMENT
|
||||
mov es,dx ;point to display memory
|
||||
mov di,0
|
||||
mov cx,8000h ;fill all 32k words
|
||||
mov ax,0ffffh ;because of set/reset, the value
|
||||
; written actually doesn't matter
|
||||
rep stosw ;fill with blue
|
||||
;
|
||||
; Set driver to use the 8x8 font.
|
||||
;
|
||||
mov ah,11h ;VGA BIOS character generator function,
|
||||
mov al,30h ; return info subfunction
|
||||
mov bh,3 ;get 8x8 font pointer
|
||||
int 10h
|
||||
call SelectFont
|
||||
;
|
||||
; Print the test string, cycling through colors.
|
||||
;
|
||||
mov si,offset TestString
|
||||
mov bx,TEST_TEXT_ROW
|
||||
mov cx,TEST_TEXT_COL
|
||||
mov ah,0 ;start with color 0
|
||||
StringOutLoop:
|
||||
lodsb
|
||||
and al,al
|
||||
jz StringOutDone
|
||||
push ax ;preserve color
|
||||
call DrawChar
|
||||
pop ax ;restore color
|
||||
inc ah ;next color
|
||||
and ah,0fh ;colors range from 0 to 15
|
||||
add cx,TEST_TEXT_WIDTH
|
||||
jmp StringOutLoop
|
||||
StringOutDone:
|
||||
;
|
||||
; Wait for a key, then set to text mode & end.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h ;wait for a key
|
||||
mov ax,3
|
||||
int 10h ;restore text mode
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
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
|
||||
; make up the character is preserved.
|
||||
; Font used should be pointed to by FontPointer.
|
||||
;
|
||||
; Input:
|
||||
; AL = character to draw
|
||||
; AH = color to draw character in (0-15)
|
||||
; BX = row to draw text character at
|
||||
; CX = column to draw text character at
|
||||
;
|
||||
; Forces ALU function to "move".
|
||||
; Forces write mode 3.
|
||||
;
|
||||
DrawChar proc near
|
||||
push ax
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
push si
|
||||
push di
|
||||
push bp
|
||||
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.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_SET_RESET
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
and ah,0fh
|
||||
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.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_MODE
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
or al,3
|
||||
out dx,al
|
||||
;
|
||||
; 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
|
||||
mov es,dx ;point to display memory
|
||||
;
|
||||
; Calculate screen address of byte character starts in.
|
||||
;
|
||||
pop ax ;get back character to draw in AL
|
||||
```nasm
|
||||
; Program to illustrate operation of write mode 3 of the VGA.
|
||||
; Draws 8x8 characters at arbitrary locations without disturbing
|
||||
; the background, using VGA's 8x8 ROM font. Designed
|
||||
; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h.
|
||||
; Runs only on VGAs (in Models 50 & up and IBM Display Adapter
|
||||
; and 100% compatibles).
|
||||
; Assembled with MASM
|
||||
; By Michael Abrash
|
||||
;
|
||||
stack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
|
||||
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
|
||||
FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char
|
||||
;
|
||||
; VGA register equates.
|
||||
;
|
||||
SC_INDEX equ 3c4h ;SC index register
|
||||
SC_MAP_MASK equ 2 ;SC map mask register index
|
||||
GC_INDEX equ 3ceh ;GC index register
|
||||
GC_SET_RESET equ 0 ;GC set/reset register index
|
||||
GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index
|
||||
GC_ROTATE equ 3 ;GC data rotate/logical function
|
||||
; 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
|
||||
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
|
||||
db ‘Hello, world!',0 ;test string to print.
|
||||
FontPointer dd ? ;font offset
|
||||
dseg ends
|
||||
;
|
||||
cseg segment para public ‘CODE'
|
||||
assume cs:cseg, ds:dseg
|
||||
start proc near
|
||||
mov ax,dseg
|
||||
mov ds,ax
|
||||
;
|
||||
; Select 640x480 graphics mode.
|
||||
;
|
||||
mov ax,012h
|
||||
int 10h
|
||||
;
|
||||
; 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
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
or al,1 ;blue plane only set, others reset
|
||||
out dx,al
|
||||
dec dx
|
||||
mov al,GC_ENABLE_SET_RESET
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
or al,0fh ;enable set/reset for all planes
|
||||
out dx,al
|
||||
mov dx,VGA_VIDEO_SEGMENT
|
||||
mov es,dx ;point to display memory
|
||||
mov di,0
|
||||
mov cx,8000h ;fill all 32k words
|
||||
mov ax,0ffffh ;because of set/reset, the value
|
||||
; written actually doesn't matter
|
||||
rep stosw ;fill with blue
|
||||
;
|
||||
; Set driver to use the 8x8 font.
|
||||
;
|
||||
mov ah,11h ;VGA BIOS character generator function,
|
||||
mov al,30h ; return info subfunction
|
||||
mov bh,3 ;get 8x8 font pointer
|
||||
int 10h
|
||||
call SelectFont
|
||||
;
|
||||
; Print the test string, cycling through colors.
|
||||
;
|
||||
mov si,offset TestString
|
||||
mov bx,TEST_TEXT_ROW
|
||||
mov cx,TEST_TEXT_COL
|
||||
mov ah,0 ;start with color 0
|
||||
StringOutLoop:
|
||||
lodsb
|
||||
and al,al
|
||||
jz StringOutDone
|
||||
push ax ;preserve color
|
||||
call DrawChar
|
||||
pop ax ;restore color
|
||||
inc ah ;next color
|
||||
and ah,0fh ;colors range from 0 to 15
|
||||
add cx,TEST_TEXT_WIDTH
|
||||
jmp StringOutLoop
|
||||
StringOutDone:
|
||||
;
|
||||
; Wait for a key, then set to text mode & end.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h ;wait for a key
|
||||
mov ax,3
|
||||
int 10h ;restore text mode
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
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
|
||||
; make up the character is preserved.
|
||||
; Font used should be pointed to by FontPointer.
|
||||
;
|
||||
; Input:
|
||||
; AL = character to draw
|
||||
; AH = color to draw character in (0-15)
|
||||
; BX = row to draw text character at
|
||||
; CX = column to draw text character at
|
||||
;
|
||||
; Forces ALU function to "move".
|
||||
; Forces write mode 3.
|
||||
;
|
||||
DrawChar proc near
|
||||
push ax
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
push si
|
||||
push di
|
||||
push bp
|
||||
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.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_SET_RESET
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
and ah,0fh
|
||||
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.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_MODE
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
or al,3
|
||||
out dx,al
|
||||
;
|
||||
; 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
|
||||
mov es,dx ;point to display memory
|
||||
;
|
||||
; Calculate screen address of byte character starts in.
|
||||
;
|
||||
pop ax ;get back character to draw in AL
|
||||
|
||||
push ds ;point to BIOS data segment
|
||||
sub dx,dx
|
||||
mov ds,dx
|
||||
xchg ax,bx
|
||||
mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS
|
||||
; screen width
|
||||
pop ds
|
||||
mul di ;calculate offset of start of row
|
||||
push di ;set aside screen width
|
||||
mov di,cx ;set aside the column
|
||||
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
|
||||
add di,ax ;and point to byte
|
||||
;
|
||||
; Calculate font address of character.
|
||||
;
|
||||
sub bh,bh
|
||||
shl bx,1 ;assumes 8 bytes per character; use
|
||||
shl bx,1 ; a multiply otherwise
|
||||
shl bx,1 ;offset in font of character
|
||||
add si,bx ;offset in font segment of character
|
||||
;
|
||||
; 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
|
||||
; 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
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0e0h
|
||||
or al,cl
|
||||
out dx,al
|
||||
;
|
||||
; Set up BH as bit mask for left half, BL as rotation for right half.
|
||||
;
|
||||
mov bx,0ffffh
|
||||
shr bh,cl
|
||||
neg cl
|
||||
add cl,8
|
||||
shl bl,cl
|
||||
;
|
||||
; Draw the character, left half first, then right half in the
|
||||
; succeeding byte, using the data rotation to position the character
|
||||
; 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 color) through only for the proper portion (where the
|
||||
; font bits for the character are 1) of the character for each byte.
|
||||
; Wherever the font bits for the character are 0, the background
|
||||
; color is preserved.
|
||||
; Does not check for case where character is byte-aligned and
|
||||
; no rotation and only one write is required.
|
||||
;
|
||||
mov bp,FONT_CHARACTER_SIZE
|
||||
mov dx,GC_INDEX
|
||||
pop cx ;get back screen width
|
||||
dec cx
|
||||
dec cx ; -2 because do two bytes for each char
|
||||
CharacterLoop:
|
||||
;
|
||||
; Set the bit mask for the left half of the character.
|
||||
;
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bh
|
||||
out dx,ax
|
||||
;
|
||||
; Get the next character byte & write it to display memory.
|
||||
; (Left half of character.)
|
||||
;
|
||||
mov al,[si] ;get character byte
|
||||
mov ah,es:[di] ;load latches
|
||||
stosb ;write character byte
|
||||
;
|
||||
; Set the bit mask for the right half of the character.
|
||||
;
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bl
|
||||
out dx,ax
|
||||
;
|
||||
; Get the character byte again & write it to display memory.
|
||||
; (Right half of character.)
|
||||
;
|
||||
lodsb ;get character byte
|
||||
mov ah,es:[di] ;load latches
|
||||
stosb ;write character byte
|
||||
;
|
||||
; Point to next line of character in display memory.
|
||||
;
|
||||
add di,cx
|
||||
;
|
||||
dec bp
|
||||
jnz CharacterLoop
|
||||
;
|
||||
pop ds
|
||||
pop bp
|
||||
pop di
|
||||
pop si
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
pop ax
|
||||
ret
|
||||
DrawChar endp
|
||||
;
|
||||
; Set the pointer to the font to draw from to ES:BP.
|
||||
;
|
||||
SelectFont proc near
|
||||
mov word ptr [FontPointer],bp ;save pointer
|
||||
mov word ptr [FontPointer+2],es
|
||||
ret
|
||||
SelectFont endp
|
||||
;
|
||||
cseg ends
|
||||
end start
|
||||
push ds ;point to BIOS data segment
|
||||
sub dx,dx
|
||||
mov ds,dx
|
||||
xchg ax,bx
|
||||
mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS
|
||||
; screen width
|
||||
pop ds
|
||||
mul di ;calculate offset of start of row
|
||||
push di ;set aside screen width
|
||||
mov di,cx ;set aside the column
|
||||
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
|
||||
add di,ax ;and point to byte
|
||||
;
|
||||
; Calculate font address of character.
|
||||
;
|
||||
sub bh,bh
|
||||
shl bx,1 ;assumes 8 bytes per character; use
|
||||
shl bx,1 ; a multiply otherwise
|
||||
shl bx,1 ;offset in font of character
|
||||
add si,bx ;offset in font segment of character
|
||||
;
|
||||
; 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
|
||||
; 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
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0e0h
|
||||
or al,cl
|
||||
out dx,al
|
||||
;
|
||||
; Set up BH as bit mask for left half, BL as rotation for right half.
|
||||
;
|
||||
mov bx,0ffffh
|
||||
shr bh,cl
|
||||
neg cl
|
||||
add cl,8
|
||||
shl bl,cl
|
||||
;
|
||||
; Draw the character, left half first, then right half in the
|
||||
; succeeding byte, using the data rotation to position the character
|
||||
; 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 color) through only for the proper portion (where the
|
||||
; font bits for the character are 1) of the character for each byte.
|
||||
; Wherever the font bits for the character are 0, the background
|
||||
; color is preserved.
|
||||
; Does not check for case where character is byte-aligned and
|
||||
; no rotation and only one write is required.
|
||||
;
|
||||
mov bp,FONT_CHARACTER_SIZE
|
||||
mov dx,GC_INDEX
|
||||
pop cx ;get back screen width
|
||||
dec cx
|
||||
dec cx ; -2 because do two bytes for each char
|
||||
CharacterLoop:
|
||||
;
|
||||
; Set the bit mask for the left half of the character.
|
||||
;
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bh
|
||||
out dx,ax
|
||||
;
|
||||
; Get the next character byte & write it to display memory.
|
||||
; (Left half of character.)
|
||||
;
|
||||
mov al,[si] ;get character byte
|
||||
mov ah,es:[di] ;load latches
|
||||
stosb ;write character byte
|
||||
;
|
||||
; Set the bit mask for the right half of the character.
|
||||
;
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bl
|
||||
out dx,ax
|
||||
;
|
||||
; Get the character byte again & write it to display memory.
|
||||
; (Right half of character.)
|
||||
;
|
||||
lodsb ;get character byte
|
||||
mov ah,es:[di] ;load latches
|
||||
stosb ;write character byte
|
||||
;
|
||||
; Point to next line of character in display memory.
|
||||
;
|
||||
add di,cx
|
||||
;
|
||||
dec bp
|
||||
jnz CharacterLoop
|
||||
;
|
||||
pop ds
|
||||
pop bp
|
||||
pop di
|
||||
pop si
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
pop ax
|
||||
ret
|
||||
DrawChar endp
|
||||
;
|
||||
; Set the pointer to the font to draw from to ES:BP.
|
||||
;
|
||||
SelectFont proc near
|
||||
mov word ptr [FontPointer],bp ;save pointer
|
||||
mov word ptr [FontPointer+2],es
|
||||
ret
|
||||
SelectFont endp
|
||||
;
|
||||
cseg ends
|
||||
end start
|
||||
```
|
||||
|
|
|
|||
636
26-03.md
636
26-03.md
|
|
@ -80,324 +80,326 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into
|
|||
|
||||
**LISTING 26.2 L26-2.ASM**
|
||||
|
||||
; Program to illustrate high-speed text-drawing operation of
|
||||
; write mode 3 of the VGA.
|
||||
; Draws a string of 8x14 characters at arbitrary locations
|
||||
; without disturbing the background, using VGA's 8x14 ROM font.
|
||||
; Designed for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h.
|
||||
; Runs only on VGAs (in Models 50 & up and IBM Display Adapter
|
||||
; and 100% compatibles).
|
||||
; Assembled with MASM
|
||||
; By Michael Abrash
|
||||
;
|
||||
stack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
|
||||
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
|
||||
FONT_CHARACTER_SIZE equ 14 ;# bytes in each font char
|
||||
;
|
||||
; VGA register equates.
|
||||
;
|
||||
SC_INDEX equ 3c4h ;SC index register
|
||||
SC_MAP_MASK equ 2 ;SC map mask register index
|
||||
GC_INDEX equ 3ceh ;GC index register
|
||||
GC_SET_RESET equ 0 ;GC set/reset register index
|
||||
GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index
|
||||
GC_ROTATE equ 3 ;GC data rotate/logical function
|
||||
; 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
|
||||
TEST_TEXT_COL equ 17 ;column to display test text at
|
||||
TEST_TEXT_COLOR equ 0fh ;high intensity white
|
||||
TestString label byte
|
||||
db ‘Hello, world!',0 ;test string to print.
|
||||
FontPointer dd ? ;font offset
|
||||
dseg ends
|
||||
;
|
||||
cseg segment para public ‘CODE'
|
||||
assume cs:cseg, ds:dseg
|
||||
start proc near
|
||||
mov ax,dseg
|
||||
mov ds,ax
|
||||
;
|
||||
; Select 640x480 graphics mode.
|
||||
;
|
||||
mov ax,012h
|
||||
int 10h
|
||||
;
|
||||
; 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
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
or al,1 ;blue plane only set, others reset
|
||||
out dx,al
|
||||
dec dx
|
||||
mov al,GC_ENABLE_SET_RESET
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
or al,0fh ;enable set/reset for all planes
|
||||
out dx,al
|
||||
mov dx,VGA_VIDEO_SEGMENT
|
||||
mov es,dx ;point to display memory
|
||||
mov di,0
|
||||
mov cx,8000h ;fill all 32k words
|
||||
mov ax,0ffffh ;because of set/reset, the value
|
||||
; written actually doesn't matter
|
||||
rep stosw ;fill with blue
|
||||
;
|
||||
; Set driver to use the 8x14 font.
|
||||
;
|
||||
mov ah,11h ;VGA BIOS character generator function,
|
||||
mov al,30h ; return info subfunction
|
||||
mov bh,2 ;get 8x14 font pointer
|
||||
int 10h
|
||||
call SelectFont
|
||||
;
|
||||
; Print the test string.
|
||||
;
|
||||
mov si,offset TestString
|
||||
mov bx,TEST_TEXT_ROW
|
||||
mov cx,TEST_TEXT_COL
|
||||
mov ah,TEST_TEXT_COLOR
|
||||
call DrawString
|
||||
;
|
||||
; Wait for a key, then set to text mode & end.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h ;wait for a key
|
||||
mov ax,3
|
||||
int 10h ;restore text mode
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
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
|
||||
; characters. Background around the pixels that make up the
|
||||
; characters is preserved.
|
||||
; Font used should be pointed to by FontPointer.
|
||||
;
|
||||
; Input:
|
||||
; AH = color to draw string in
|
||||
; BX = row to draw string on
|
||||
; CX = column to start string at
|
||||
; DS:SI = string to draw
|
||||
;
|
||||
; Forces ALU function to "move".
|
||||
; Forces write mode 3.
|
||||
;
|
||||
DrawString proc near
|
||||
push ax
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
push si
|
||||
push di
|
||||
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.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_SET_RESET
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
and ah,0fh
|
||||
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.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_MODE
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
or al,3
|
||||
out dx,al
|
||||
mov dx,VGA_VIDEO_SEGMENT
|
||||
mov es,dx ;point to display memory
|
||||
;
|
||||
; Calculate screen address of byte character starts in.
|
||||
;
|
||||
push ds ;point to BIOS data segment
|
||||
sub dx,dx
|
||||
mov ds,dx
|
||||
mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS
|
||||
; screen width
|
||||
pop ds
|
||||
mov ax,bx ;row
|
||||
mul di ;calculate offset of start of row
|
||||
push di ;set aside screen width
|
||||
mov di,cx ;set aside the column
|
||||
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
|
||||
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
|
||||
; 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
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0e0h
|
||||
or al,cl
|
||||
out dx,al
|
||||
;
|
||||
; Set up BH as bit mask for left half, BL as rotation for right half.
|
||||
;
|
||||
mov bx,0ffffh
|
||||
shr bh,cl
|
||||
neg 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
|
||||
; 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 color) through only for the proper portion (where the
|
||||
; font bits for the character are 1) of the character for each byte.
|
||||
; Wherever the font bits for the character are 0, the background
|
||||
; color is preserved.
|
||||
; 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.
|
||||
;
|
||||
pop cx ;get back screen width
|
||||
push si
|
||||
push di
|
||||
push bx
|
||||
;
|
||||
; Set the bit mask for the left half of the character.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bh
|
||||
out dx,ax
|
||||
LeftHalfLoop:
|
||||
lodsb
|
||||
and al,al
|
||||
jz LeftHalfLoopDone
|
||||
call CharacterUp
|
||||
inc di ;point to next character location
|
||||
jmp LeftHalfLoop
|
||||
LeftHalfLoopDone:
|
||||
pop bx
|
||||
pop di
|
||||
pop si
|
||||
;
|
||||
; Draw the right portion of each character in the string.
|
||||
;
|
||||
inc di ;right portion of each character is across
|
||||
; byte boundary
|
||||
;
|
||||
; Set the bit mask for the right half of the character.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bl
|
||||
out dx,ax
|
||||
RightHalfLoop:
|
||||
lodsb
|
||||
and al,al
|
||||
jz RightHalfLoopDone
|
||||
call CharacterUp
|
||||
inc di ;point to next character location
|
||||
jmp RightHalfLoop
|
||||
RightHalfLoopDone:
|
||||
;
|
||||
pop ds
|
||||
pop bp
|
||||
pop di
|
||||
pop si
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
pop ax
|
||||
ret
|
||||
DrawString endp
|
||||
;
|
||||
; Draw a character.
|
||||
;
|
||||
; Input:
|
||||
; AL = character
|
||||
; CX = screen width
|
||||
; ES:DI = address to draw character at
|
||||
;
|
||||
CharacterUp proc near
|
||||
push cx
|
||||
push si
|
||||
push di
|
||||
push ds
|
||||
;
|
||||
; Set DS:SI to point to font and ES to point to display memory.
|
||||
;
|
||||
lds si,[FontPointer] ;point to font
|
||||
;
|
||||
; Calculate font address of character.
|
||||
;
|
||||
mov bl,14 ;14 bytes per character
|
||||
mul bl
|
||||
add si,ax ;offset in font segment of character
|
||||
```nasm
|
||||
; Program to illustrate high-speed text-drawing operation of
|
||||
; write mode 3 of the VGA.
|
||||
; Draws a string of 8x14 characters at arbitrary locations
|
||||
; without disturbing the background, using VGA's 8x14 ROM font.
|
||||
; Designed for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h.
|
||||
; Runs only on VGAs (in Models 50 & up and IBM Display Adapter
|
||||
; and 100% compatibles).
|
||||
; Assembled with MASM
|
||||
; By Michael Abrash
|
||||
;
|
||||
stack segment para stack ‘STACK'
|
||||
db 512 dup(?)
|
||||
stack ends
|
||||
;
|
||||
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
|
||||
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
|
||||
FONT_CHARACTER_SIZE equ 14 ;# bytes in each font char
|
||||
;
|
||||
; VGA register equates.
|
||||
;
|
||||
SC_INDEX equ 3c4h ;SC index register
|
||||
SC_MAP_MASK equ 2 ;SC map mask register index
|
||||
GC_INDEX equ 3ceh ;GC index register
|
||||
GC_SET_RESET equ 0 ;GC set/reset register index
|
||||
GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index
|
||||
GC_ROTATE equ 3 ;GC data rotate/logical function
|
||||
; 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
|
||||
TEST_TEXT_COL equ 17 ;column to display test text at
|
||||
TEST_TEXT_COLOR equ 0fh ;high intensity white
|
||||
TestString label byte
|
||||
db ‘Hello, world!',0 ;test string to print.
|
||||
FontPointer dd ? ;font offset
|
||||
dseg ends
|
||||
;
|
||||
cseg segment para public ‘CODE'
|
||||
assume cs:cseg, ds:dseg
|
||||
start proc near
|
||||
mov ax,dseg
|
||||
mov ds,ax
|
||||
;
|
||||
; Select 640x480 graphics mode.
|
||||
;
|
||||
mov ax,012h
|
||||
int 10h
|
||||
;
|
||||
; 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
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
or al,1 ;blue plane only set, others reset
|
||||
out dx,al
|
||||
dec dx
|
||||
mov al,GC_ENABLE_SET_RESET
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
or al,0fh ;enable set/reset for all planes
|
||||
out dx,al
|
||||
mov dx,VGA_VIDEO_SEGMENT
|
||||
mov es,dx ;point to display memory
|
||||
mov di,0
|
||||
mov cx,8000h ;fill all 32k words
|
||||
mov ax,0ffffh ;because of set/reset, the value
|
||||
; written actually doesn't matter
|
||||
rep stosw ;fill with blue
|
||||
;
|
||||
; Set driver to use the 8x14 font.
|
||||
;
|
||||
mov ah,11h ;VGA BIOS character generator function,
|
||||
mov al,30h ; return info subfunction
|
||||
mov bh,2 ;get 8x14 font pointer
|
||||
int 10h
|
||||
call SelectFont
|
||||
;
|
||||
; Print the test string.
|
||||
;
|
||||
mov si,offset TestString
|
||||
mov bx,TEST_TEXT_ROW
|
||||
mov cx,TEST_TEXT_COL
|
||||
mov ah,TEST_TEXT_COLOR
|
||||
call DrawString
|
||||
;
|
||||
; Wait for a key, then set to text mode & end.
|
||||
;
|
||||
mov ah,1
|
||||
int 21h ;wait for a key
|
||||
mov ax,3
|
||||
int 10h ;restore text mode
|
||||
;
|
||||
; Exit to DOS.
|
||||
;
|
||||
mov ah,4ch
|
||||
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
|
||||
; characters. Background around the pixels that make up the
|
||||
; characters is preserved.
|
||||
; Font used should be pointed to by FontPointer.
|
||||
;
|
||||
; Input:
|
||||
; AH = color to draw string in
|
||||
; BX = row to draw string on
|
||||
; CX = column to start string at
|
||||
; DS:SI = string to draw
|
||||
;
|
||||
; Forces ALU function to "move".
|
||||
; Forces write mode 3.
|
||||
;
|
||||
DrawString proc near
|
||||
push ax
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
push si
|
||||
push di
|
||||
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.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_SET_RESET
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0f0h
|
||||
and ah,0fh
|
||||
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.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_MODE
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
or al,3
|
||||
out dx,al
|
||||
mov dx,VGA_VIDEO_SEGMENT
|
||||
mov es,dx ;point to display memory
|
||||
;
|
||||
; Calculate screen address of byte character starts in.
|
||||
;
|
||||
push ds ;point to BIOS data segment
|
||||
sub dx,dx
|
||||
mov ds,dx
|
||||
mov di,ds:[SCREEN_WIDTH_IN_BYTES] ;retrieve BIOS
|
||||
; screen width
|
||||
pop ds
|
||||
mov ax,bx ;row
|
||||
mul di ;calculate offset of start of row
|
||||
push di ;set aside screen width
|
||||
mov di,cx ;set aside the column
|
||||
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
|
||||
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
|
||||
; 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
|
||||
out dx,al
|
||||
inc dx
|
||||
in al,dx
|
||||
and al,0e0h
|
||||
or al,cl
|
||||
out dx,al
|
||||
;
|
||||
; Set up BH as bit mask for left half, BL as rotation for right half.
|
||||
;
|
||||
mov bx,0ffffh
|
||||
shr bh,cl
|
||||
neg 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
|
||||
; 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 color) through only for the proper portion (where the
|
||||
; font bits for the character are 1) of the character for each byte.
|
||||
; Wherever the font bits for the character are 0, the background
|
||||
; color is preserved.
|
||||
; 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.
|
||||
;
|
||||
pop cx ;get back screen width
|
||||
push si
|
||||
push di
|
||||
push bx
|
||||
;
|
||||
; Set the bit mask for the left half of the character.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bh
|
||||
out dx,ax
|
||||
LeftHalfLoop:
|
||||
lodsb
|
||||
and al,al
|
||||
jz LeftHalfLoopDone
|
||||
call CharacterUp
|
||||
inc di ;point to next character location
|
||||
jmp LeftHalfLoop
|
||||
LeftHalfLoopDone:
|
||||
pop bx
|
||||
pop di
|
||||
pop si
|
||||
;
|
||||
; Draw the right portion of each character in the string.
|
||||
;
|
||||
inc di ;right portion of each character is across
|
||||
; byte boundary
|
||||
;
|
||||
; Set the bit mask for the right half of the character.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GC_BIT_MASK
|
||||
mov ah,bl
|
||||
out dx,ax
|
||||
RightHalfLoop:
|
||||
lodsb
|
||||
and al,al
|
||||
jz RightHalfLoopDone
|
||||
call CharacterUp
|
||||
inc di ;point to next character location
|
||||
jmp RightHalfLoop
|
||||
RightHalfLoopDone:
|
||||
;
|
||||
pop ds
|
||||
pop bp
|
||||
pop di
|
||||
pop si
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
pop ax
|
||||
ret
|
||||
DrawString endp
|
||||
;
|
||||
; Draw a character.
|
||||
;
|
||||
; Input:
|
||||
; AL = character
|
||||
; CX = screen width
|
||||
; ES:DI = address to draw character at
|
||||
;
|
||||
CharacterUp proc near
|
||||
push cx
|
||||
push si
|
||||
push di
|
||||
push ds
|
||||
;
|
||||
; Set DS:SI to point to font and ES to point to display memory.
|
||||
;
|
||||
lds si,[FontPointer] ;point to font
|
||||
;
|
||||
; Calculate font address of character.
|
||||
;
|
||||
mov bl,14 ;14 bytes per character
|
||||
mul bl
|
||||
add si,ax ;offset in font segment of character
|
||||
|
||||
mov bp,FONT_CHARACTER_SIZE
|
||||
dec cx ; -1 because one byte per char
|
||||
CharacterLoop:
|
||||
lodsb ;get character byte
|
||||
mov ah,es:[di] ;load latches
|
||||
stosb ;write character byte
|
||||
;
|
||||
; Point to next line of character in display memory.
|
||||
;
|
||||
add di,cx
|
||||
;
|
||||
dec bp
|
||||
jnz CharacterLoop
|
||||
;
|
||||
pop ds
|
||||
pop di
|
||||
pop si
|
||||
pop cx
|
||||
ret
|
||||
CharacterUp endp
|
||||
;
|
||||
; Set the pointer to the font to draw from to ES:BP.
|
||||
;
|
||||
SelectFont proc near
|
||||
mov word ptr [FontPointer],bp ;save pointer
|
||||
mov word ptr [FontPointer+2],es
|
||||
ret
|
||||
SelectFont endp
|
||||
;
|
||||
cseg ends
|
||||
end start
|
||||
mov bp,FONT_CHARACTER_SIZE
|
||||
dec cx ; -1 because one byte per char
|
||||
CharacterLoop:
|
||||
lodsb ;get character byte
|
||||
mov ah,es:[di] ;load latches
|
||||
stosb ;write character byte
|
||||
;
|
||||
; Point to next line of character in display memory.
|
||||
;
|
||||
add di,cx
|
||||
;
|
||||
dec bp
|
||||
jnz CharacterLoop
|
||||
;
|
||||
pop ds
|
||||
pop di
|
||||
pop si
|
||||
pop cx
|
||||
ret
|
||||
CharacterUp endp
|
||||
;
|
||||
; Set the pointer to the font to draw from to ES:BP.
|
||||
;
|
||||
SelectFont proc near
|
||||
mov word ptr [FontPointer],bp ;save pointer
|
||||
mov word ptr [FontPointer+2],es
|
||||
ret
|
||||
SelectFont endp
|
||||
;
|
||||
cseg ends
|
||||
end start
|
||||
```
|
||||
|
||||
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,
|
||||
|
|
|
|||
414
27-02.md
414
27-02.md
|
|
@ -52,220 +52,222 @@ image.
|
|||
|
||||
**LISTING 27.1 L27-1.ASM**
|
||||
|
||||
; Program to illustrate one use of write mode 2 of the VGA and EGA by
|
||||
; 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
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
Stack segment para stack ‘STACK'
|
||||
db 512 dup(0)
|
||||
Stack ends
|
||||
```nasm
|
||||
; Program to illustrate one use of write mode 2 of the VGA and EGA by
|
||||
; 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
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
Stack segment para stack ‘STACK'
|
||||
db 512 dup(0)
|
||||
Stack ends
|
||||
|
||||
SCREEN_WIDTH_IN_BYTES equ 80
|
||||
DISPLAY_MEMORY_SEGMENT equ 0a000h
|
||||
SC_INDEX equ 3c4h ;Sequence Controller Index register
|
||||
MAP_MASK equ 2 ;index of Map Mask register
|
||||
GC_INDEX equ 03ceh ;Graphics Controller Index reg
|
||||
GRAPHICS_MODE equ 5 ;index of Graphics Mode reg
|
||||
BIT_MASKequ 8 ;index of Bit Mask reg
|
||||
SCREEN_WIDTH_IN_BYTES equ 80
|
||||
DISPLAY_MEMORY_SEGMENT equ 0a000h
|
||||
SC_INDEX equ 3c4h ;Sequence Controller Index register
|
||||
MAP_MASK equ 2 ;index of Map Mask register
|
||||
GC_INDEX equ 03ceh ;Graphics Controller Index reg
|
||||
GRAPHICS_MODE equ 5 ;index of Graphics Mode reg
|
||||
BIT_MASKequ 8 ;index of Bit Mask reg
|
||||
|
||||
Data segment para common ‘DATA'
|
||||
;
|
||||
; Current location of "A" as it is animated across the screen.
|
||||
;
|
||||
CurrentX dw ?
|
||||
CurrentY dw ?
|
||||
RemainingLength dw ?
|
||||
;
|
||||
; Chunky bit-map image of a yellow "A" on a bright blue background
|
||||
;
|
||||
AImage label byte
|
||||
dw 13, 13 ;width, height in pixels
|
||||
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, 0e9h, 099h, 099h, 000h
|
||||
db 009h, 099h, 09eh, 0eeh, 099h, 099h, 000h
|
||||
db 009h, 099h, 0eeh, 09eh, 0e9h, 099h, 000h
|
||||
db 009h, 09eh, 0e9h, 099h, 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, 099h, 099h, 099h, 099h, 099h, 000h
|
||||
db 009h, 099h, 099h, 099h, 099h, 099h, 000h
|
||||
db 000h, 000h, 000h, 000h, 000h, 000h, 000h
|
||||
Data ends
|
||||
Data segment para common ‘DATA'
|
||||
;
|
||||
; Current location of "A" as it is animated across the screen.
|
||||
;
|
||||
CurrentX dw ?
|
||||
CurrentY dw ?
|
||||
RemainingLength dw ?
|
||||
;
|
||||
; Chunky bit-map image of a yellow "A" on a bright blue background
|
||||
;
|
||||
AImage label byte
|
||||
dw 13, 13 ;width, height in pixels
|
||||
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, 0e9h, 099h, 099h, 000h
|
||||
db 009h, 099h, 09eh, 0eeh, 099h, 099h, 000h
|
||||
db 009h, 099h, 0eeh, 09eh, 0e9h, 099h, 000h
|
||||
db 009h, 09eh, 0e9h, 099h, 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, 099h, 099h, 099h, 099h, 099h, 000h
|
||||
db 009h, 099h, 099h, 099h, 099h, 099h, 000h
|
||||
db 000h, 000h, 000h, 000h, 000h, 000h, 000h
|
||||
Data ends
|
||||
|
||||
Code segment para public ‘CODE'
|
||||
assume cs:Code, ds:Data
|
||||
Start proc near
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov ax,10h
|
||||
int 10h ;select video mode 10h (640x350)
|
||||
;
|
||||
; Prepare for animation.
|
||||
;
|
||||
mov [CurrentX],0
|
||||
mov [CurrentY],200
|
||||
mov [RemainingLength],600 ;move 600 times
|
||||
;
|
||||
; Animate, repeating RemainingLength times. It's unnecessary to erase
|
||||
; 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.
|
||||
;
|
||||
AnimationLoop:
|
||||
mov bx,[CurrentX]
|
||||
mov cx,[CurrentY]
|
||||
mov si,offset AImage
|
||||
call DrawFromChunkyBitmap ;draw the "A" image
|
||||
inc [CurrentX] ;move one pixel to the right
|
||||
Code segment para public ‘CODE'
|
||||
assume cs:Code, ds:Data
|
||||
Start proc near
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov ax,10h
|
||||
int 10h ;select video mode 10h (640x350)
|
||||
;
|
||||
; Prepare for animation.
|
||||
;
|
||||
mov [CurrentX],0
|
||||
mov [CurrentY],200
|
||||
mov [RemainingLength],600 ;move 600 times
|
||||
;
|
||||
; Animate, repeating RemainingLength times. It's unnecessary to erase
|
||||
; 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.
|
||||
;
|
||||
AnimationLoop:
|
||||
mov bx,[CurrentX]
|
||||
mov cx,[CurrentY]
|
||||
mov si,offset AImage
|
||||
call DrawFromChunkyBitmap ;draw the "A" image
|
||||
inc [CurrentX] ;move one pixel to the right
|
||||
|
||||
mov cx,0 ;delay so we don't move the
|
||||
DelayLoop: ; image too fast; adjust as
|
||||
; needed
|
||||
loop DelayLoop
|
||||
mov cx,0 ;delay so we don't move the
|
||||
DelayLoop: ; image too fast; adjust as
|
||||
; needed
|
||||
loop DelayLoop
|
||||
|
||||
dec [RemainingLength]
|
||||
jnz AnimationLoop
|
||||
;
|
||||
; Wait for a key before returning to text mode and ending.
|
||||
;
|
||||
mov ah,01h
|
||||
int 21h
|
||||
mov ax,03h
|
||||
int 10h
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Start endp
|
||||
;
|
||||
; Draw an image stored in a chunky-bit map into planar VGA/EGA memory
|
||||
; at the specified location.
|
||||
;
|
||||
; Input:
|
||||
; BX = X screen location at which to draw the upper-left corner
|
||||
; of the image
|
||||
; CX = Y screen location at which to draw the upper-left corner
|
||||
; of the image
|
||||
; DS:SI = pointer to chunky image to draw, as follows:
|
||||
; word at 0: width of image, in pixels
|
||||
; word at 2: height of image, in pixels
|
||||
; byte at 4: msb/lsb = first & second chunky pixels,
|
||||
; repeating for the remainder of the scan line
|
||||
; of the image, then for all scan lines. Images
|
||||
; with odd widths have an unused null nibble
|
||||
; padding each scan line out to a byte width
|
||||
;
|
||||
; AX, BX, CX, DX, SI, DI, ES destroyed.
|
||||
;
|
||||
DrawFromChunkyBitmap proc near
|
||||
cld
|
||||
;
|
||||
; Select write mode 2.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GRAPHICS_MODE
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,02h
|
||||
out dx,al
|
||||
;
|
||||
; Enable writes to all 4 planes.
|
||||
;
|
||||
mov dx,SC_INDEX
|
||||
mov al,MAP_MASK
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,0fh
|
||||
out dx,al
|
||||
;
|
||||
; 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
|
||||
; pixel within the addressed byte.
|
||||
;
|
||||
mov ax,SCREEN_WIDTH_IN_BYTES
|
||||
mul cx ;offset of start of top scan line
|
||||
mov di,ax
|
||||
mov cl,bl
|
||||
and cl,111b
|
||||
mov ah,80h ;set AH to the bit mask for the
|
||||
shr ah,cl ; initial pixel
|
||||
shr bx,1
|
||||
shr bx,1
|
||||
shr bx,1 ;X in bytes
|
||||
add di,bx ;offset of upper-left byte of image
|
||||
mov bx,DISPLAY_MEMORY_SEGMENT
|
||||
mov es,bx ;ES:DI points to the byte at which the
|
||||
; upper left of the image goes
|
||||
;
|
||||
; Get the width and height of the image.
|
||||
;
|
||||
mov cx,[si] ;get the width
|
||||
inc si
|
||||
inc si
|
||||
mov bx,[si] ;get the height
|
||||
inc si
|
||||
inc si
|
||||
mov dx,GC_INDEX
|
||||
mov al,BIT_MASK
|
||||
out dx,al ;leave the GC Index register pointing
|
||||
inc dx ; to the Bit Mask register
|
||||
RowLoop:
|
||||
dec [RemainingLength]
|
||||
jnz AnimationLoop
|
||||
;
|
||||
; Wait for a key before returning to text mode and ending.
|
||||
;
|
||||
mov ah,01h
|
||||
int 21h
|
||||
mov ax,03h
|
||||
int 10h
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Start endp
|
||||
;
|
||||
; Draw an image stored in a chunky-bit map into planar VGA/EGA memory
|
||||
; at the specified location.
|
||||
;
|
||||
; Input:
|
||||
; BX = X screen location at which to draw the upper-left corner
|
||||
; of the image
|
||||
; CX = Y screen location at which to draw the upper-left corner
|
||||
; of the image
|
||||
; DS:SI = pointer to chunky image to draw, as follows:
|
||||
; word at 0: width of image, in pixels
|
||||
; word at 2: height of image, in pixels
|
||||
; byte at 4: msb/lsb = first & second chunky pixels,
|
||||
; repeating for the remainder of the scan line
|
||||
; of the image, then for all scan lines. Images
|
||||
; with odd widths have an unused null nibble
|
||||
; padding each scan line out to a byte width
|
||||
;
|
||||
; AX, BX, CX, DX, SI, DI, ES destroyed.
|
||||
;
|
||||
DrawFromChunkyBitmap proc near
|
||||
cld
|
||||
;
|
||||
; Select write mode 2.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GRAPHICS_MODE
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,02h
|
||||
out dx,al
|
||||
;
|
||||
; Enable writes to all 4 planes.
|
||||
;
|
||||
mov dx,SC_INDEX
|
||||
mov al,MAP_MASK
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,0fh
|
||||
out dx,al
|
||||
;
|
||||
; 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
|
||||
; pixel within the addressed byte.
|
||||
;
|
||||
mov ax,SCREEN_WIDTH_IN_BYTES
|
||||
mul cx ;offset of start of top scan line
|
||||
mov di,ax
|
||||
mov cl,bl
|
||||
and cl,111b
|
||||
mov ah,80h ;set AH to the bit mask for the
|
||||
shr ah,cl ; initial pixel
|
||||
shr bx,1
|
||||
shr bx,1
|
||||
shr bx,1 ;X in bytes
|
||||
add di,bx ;offset of upper-left byte of image
|
||||
mov bx,DISPLAY_MEMORY_SEGMENT
|
||||
mov es,bx ;ES:DI points to the byte at which the
|
||||
; upper left of the image goes
|
||||
;
|
||||
; Get the width and height of the image.
|
||||
;
|
||||
mov cx,[si] ;get the width
|
||||
inc si
|
||||
inc si
|
||||
mov bx,[si] ;get the height
|
||||
inc si
|
||||
inc si
|
||||
mov dx,GC_INDEX
|
||||
mov al,BIT_MASK
|
||||
out dx,al ;leave the GC Index register pointing
|
||||
inc dx ; to the Bit Mask register
|
||||
RowLoop:
|
||||
|
||||
push ax ;preserve the left column's bit mask
|
||||
push cx ;preserve the width
|
||||
push di ;preserve the destination offset
|
||||
push ax ;preserve the left column's bit mask
|
||||
push cx ;preserve the width
|
||||
push di ;preserve the destination offset
|
||||
|
||||
ColumnLoop:
|
||||
mov al,ah
|
||||
out dx,al ;set the bit mask to draw this pixel
|
||||
mov al,es:[di] ;load the latches
|
||||
mov al,[si] ;get the next two chunky pixels
|
||||
shr al,1
|
||||
shr al,1
|
||||
shr al,1
|
||||
shr al,1 ;move the first pixel into the lsb
|
||||
stosb ;draw the first pixel
|
||||
ror ah,1 ;move mask to next pixel position
|
||||
jc CheckMorePixels ;is next pixel in the adjacent byte?
|
||||
dec di ;no
|
||||
ColumnLoop:
|
||||
mov al,ah
|
||||
out dx,al ;set the bit mask to draw this pixel
|
||||
mov al,es:[di] ;load the latches
|
||||
mov al,[si] ;get the next two chunky pixels
|
||||
shr al,1
|
||||
shr al,1
|
||||
shr al,1
|
||||
shr al,1 ;move the first pixel into the lsb
|
||||
stosb ;draw the first pixel
|
||||
ror ah,1 ;move mask to next pixel position
|
||||
jc CheckMorePixels ;is next pixel in the adjacent byte?
|
||||
dec di ;no
|
||||
|
||||
CheckMorePixels:
|
||||
dec cx ;see if there are any more pixels
|
||||
jz AdvanceToNextScanLine ; across in image
|
||||
mov al,ah
|
||||
out dx,al ;set the bit mask to draw this pixel
|
||||
mov al,es:[di] ;load the latches
|
||||
lodsb ;get the same two chunky pixels again
|
||||
; and advance pointer to the next
|
||||
; two pixels
|
||||
stosb ;draw the second of the two pixels
|
||||
ror ah,1 ;move mask to next pixel position
|
||||
jc CheckMorePixels2 ;is next pixel in the adjacent byte?
|
||||
dec di ;no
|
||||
CheckMorePixels:
|
||||
dec cx ;see if there are any more pixels
|
||||
jz AdvanceToNextScanLine ; across in image
|
||||
mov al,ah
|
||||
out dx,al ;set the bit mask to draw this pixel
|
||||
mov al,es:[di] ;load the latches
|
||||
lodsb ;get the same two chunky pixels again
|
||||
; and advance pointer to the next
|
||||
; two pixels
|
||||
stosb ;draw the second of the two pixels
|
||||
ror ah,1 ;move mask to next pixel position
|
||||
jc CheckMorePixels2 ;is next pixel in the adjacent byte?
|
||||
dec di ;no
|
||||
|
||||
CheckMorePixels2:
|
||||
loop ColumnLoop ;see if there are any more pixels
|
||||
; across in the image
|
||||
jmp short CheckMoreScanLines
|
||||
CheckMorePixels2:
|
||||
loop ColumnLoop ;see if there are any more pixels
|
||||
; across in the image
|
||||
jmp short CheckMoreScanLines
|
||||
|
||||
AdvanceToNextScanLine:
|
||||
inc si ;advance to the start of the next
|
||||
; scan line in the image
|
||||
AdvanceToNextScanLine:
|
||||
inc si ;advance to the start of the next
|
||||
; scan line in the image
|
||||
|
||||
CheckMoreScanLines:
|
||||
pop di ;get back the destination offset
|
||||
pop cx ;get back the width
|
||||
pop ax ;get back the left column's bit mask
|
||||
add di,SCREEN_WIDTH_IN_BYTES
|
||||
;point to the start of the next scan
|
||||
; line of the image
|
||||
dec bx ;see if there are any more scan lines
|
||||
jnz RowLoop ; in the image
|
||||
ret
|
||||
DrawFromChunkyBitmap endp
|
||||
Code ends
|
||||
end Start
|
||||
CheckMoreScanLines:
|
||||
pop di ;get back the destination offset
|
||||
pop cx ;get back the width
|
||||
pop ax ;get back the left column's bit mask
|
||||
add di,SCREEN_WIDTH_IN_BYTES
|
||||
;point to the start of the next scan
|
||||
; line of the image
|
||||
dec bx ;see if there are any more scan lines
|
||||
jnz RowLoop ; in the image
|
||||
ret
|
||||
DrawFromChunkyBitmap endp
|
||||
Code ends
|
||||
end Start
|
||||
```
|
||||
|
|
|
|||
628
27-03.md
628
27-03.md
|
|
@ -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**
|
||||
|
||||
; 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
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
Stack segment para stack ‘STACK'
|
||||
db 512 dup(0)
|
||||
Stack ends
|
||||
```nasm
|
||||
; 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
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
Stack segment para stack ‘STACK'
|
||||
db 512 dup(0)
|
||||
Stack ends
|
||||
|
||||
SCREEN_WIDTH_IN_BYTES equ 80
|
||||
GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment
|
||||
SC_INDEX equ 3c4h ;Sequence Controller Index register
|
||||
MAP_MASK equ 2 ;index of Map Mask register
|
||||
GC_INDEX equ 03ceh ;Graphics Controller Index reg
|
||||
GRAPHICS_MODE equ 5 ;index of Graphics Mode reg
|
||||
BIT_MASK equ 8 ;index of Bit Mask reg
|
||||
SCREEN_WIDTH_IN_BYTES equ 80
|
||||
GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment
|
||||
SC_INDEX equ 3c4h ;Sequence Controller Index register
|
||||
MAP_MASK equ 2 ;index of Map Mask register
|
||||
GC_INDEX equ 03ceh ;Graphics Controller Index reg
|
||||
GRAPHICS_MODE equ 5 ;index of Graphics Mode reg
|
||||
BIT_MASK equ 8 ;index of Bit Mask reg
|
||||
|
||||
Data segment para common ‘DATA'
|
||||
Pattern0 db 16
|
||||
db 0, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
db 9, 10, 11, 12, 13, 14, 15
|
||||
Pattern1 db 6
|
||||
db 2, 2, 2, 10, 10, 10
|
||||
Pattern2 db 8
|
||||
db 15, 15, 15, 0, 0, 15, 0, 0
|
||||
Pattern3 db 9
|
||||
db 1, 1, 1, 2, 2, 2, 4, 4, 4
|
||||
Data ends
|
||||
Data segment para common ‘DATA'
|
||||
Pattern0 db 16
|
||||
db 0, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
db 9, 10, 11, 12, 13, 14, 15
|
||||
Pattern1 db 6
|
||||
db 2, 2, 2, 10, 10, 10
|
||||
Pattern2 db 8
|
||||
db 15, 15, 15, 0, 0, 15, 0, 0
|
||||
Pattern3 db 9
|
||||
db 1, 1, 1, 2, 2, 2, 4, 4, 4
|
||||
Data ends
|
||||
|
||||
Code segment para public ‘CODE'
|
||||
assume cs:Code, ds:Data
|
||||
Start proc near
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov ax,10h
|
||||
int 10h ;select video mode 10h (640x350)
|
||||
;
|
||||
; Draw 8 radial lines in upper-left quadrant in pattern 0.
|
||||
;
|
||||
mov bx,0
|
||||
mov cx,0
|
||||
mov si,offset Pattern0
|
||||
call QuadrantUp
|
||||
;
|
||||
; Draw 8 radial lines in upper-right quadrant in pattern 1.
|
||||
;
|
||||
mov bx,320
|
||||
mov cx,0
|
||||
mov si,offset Pattern1
|
||||
call QuadrantUp
|
||||
;
|
||||
; Draw 8 radial lines in lower-left quadrant in pattern 2.
|
||||
;
|
||||
mov bx,0
|
||||
mov cx,175
|
||||
mov si,offset Pattern2
|
||||
call QuadrantUp
|
||||
;
|
||||
; Draw 8 radial lines in lower-right quadrant in pattern 3.
|
||||
;
|
||||
mov bx,320
|
||||
mov cx,175
|
||||
mov si,offset Pattern3
|
||||
call QuadrantUp
|
||||
;
|
||||
; Wait for a key before returning to text mode and ending.
|
||||
;
|
||||
mov ah,01h
|
||||
int 21h
|
||||
mov ax,03h
|
||||
int 10h
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
;
|
||||
; Draws 8 radial lines with specified pattern in specified mode 10h
|
||||
; quadrant.
|
||||
;
|
||||
; Input:
|
||||
; BX = X coordinate of upper left corner of quadrant
|
||||
; CX = Y coordinate of upper left corner of quadrant
|
||||
; SI = pointer to pattern, in following form:
|
||||
; Byte 0: Length of pattern
|
||||
; Byte 1: Start of pattern, one color per byte
|
||||
;
|
||||
; AX, BX, CX, DX destroyed
|
||||
;
|
||||
QuadrantUp proc near
|
||||
add bx,160
|
||||
add cx,87 ;point to the center of the quadrant
|
||||
mov ax,0
|
||||
mov dx,160
|
||||
call LineUp ;draw horizontal line to right edge
|
||||
mov ax,1
|
||||
mov dx,88
|
||||
call LineUp ;draw diagonal line to upper right
|
||||
mov ax,2
|
||||
mov dx,88
|
||||
call LineUp ;draw vertical line to top edge
|
||||
mov ax,3
|
||||
mov dx,88
|
||||
call LineUp ;draw diagonal line to upper left
|
||||
mov ax,4
|
||||
mov dx,161
|
||||
call LineUp ;draw horizontal line to left edge
|
||||
mov ax,5
|
||||
mov dx,88
|
||||
call LineUp ;draw diagonal line to lower left
|
||||
mov ax,6
|
||||
mov dx,88
|
||||
call LineUp ;draw vertical line to bottom edge
|
||||
mov ax,7
|
||||
mov dx,88
|
||||
call LineUp ;draw diagonal line to bottom right
|
||||
ret
|
||||
QuadrantUp endp
|
||||
;
|
||||
; Draws a horizontal, vertical, or diagonal line (one of the eight
|
||||
; possible radial lines) of the specified length from the specified
|
||||
; starting point.
|
||||
;
|
||||
; Input:
|
||||
; AX = line direction, as follows:
|
||||
; 3 2 1
|
||||
; 4 * 0
|
||||
; 5 6 7
|
||||
; BX = X coordinate of starting point
|
||||
; CX = Y coordinate of starting point
|
||||
; DX = length of line (number of pixels drawn)
|
||||
;
|
||||
; All registers preserved.
|
||||
;
|
||||
; Table of vectors to routines for each of the 8 possible lines.
|
||||
;
|
||||
LineUpVectors label word
|
||||
dw LineUp0, LineUp1, LineUp2, LineUp3
|
||||
dw LineUp4, LineUp5, LineUp6, LineUp7
|
||||
Code segment para public ‘CODE'
|
||||
assume cs:Code, ds:Data
|
||||
Start proc near
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov ax,10h
|
||||
int 10h ;select video mode 10h (640x350)
|
||||
;
|
||||
; Draw 8 radial lines in upper-left quadrant in pattern 0.
|
||||
;
|
||||
mov bx,0
|
||||
mov cx,0
|
||||
mov si,offset Pattern0
|
||||
call QuadrantUp
|
||||
;
|
||||
; Draw 8 radial lines in upper-right quadrant in pattern 1.
|
||||
;
|
||||
mov bx,320
|
||||
mov cx,0
|
||||
mov si,offset Pattern1
|
||||
call QuadrantUp
|
||||
;
|
||||
; Draw 8 radial lines in lower-left quadrant in pattern 2.
|
||||
;
|
||||
mov bx,0
|
||||
mov cx,175
|
||||
mov si,offset Pattern2
|
||||
call QuadrantUp
|
||||
;
|
||||
; Draw 8 radial lines in lower-right quadrant in pattern 3.
|
||||
;
|
||||
mov bx,320
|
||||
mov cx,175
|
||||
mov si,offset Pattern3
|
||||
call QuadrantUp
|
||||
;
|
||||
; Wait for a key before returning to text mode and ending.
|
||||
;
|
||||
mov ah,01h
|
||||
int 21h
|
||||
mov ax,03h
|
||||
int 10h
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
;
|
||||
; Draws 8 radial lines with specified pattern in specified mode 10h
|
||||
; quadrant.
|
||||
;
|
||||
; Input:
|
||||
; BX = X coordinate of upper left corner of quadrant
|
||||
; CX = Y coordinate of upper left corner of quadrant
|
||||
; SI = pointer to pattern, in following form:
|
||||
; Byte 0: Length of pattern
|
||||
; Byte 1: Start of pattern, one color per byte
|
||||
;
|
||||
; AX, BX, CX, DX destroyed
|
||||
;
|
||||
QuadrantUp proc near
|
||||
add bx,160
|
||||
add cx,87 ;point to the center of the quadrant
|
||||
mov ax,0
|
||||
mov dx,160
|
||||
call LineUp ;draw horizontal line to right edge
|
||||
mov ax,1
|
||||
mov dx,88
|
||||
call LineUp ;draw diagonal line to upper right
|
||||
mov ax,2
|
||||
mov dx,88
|
||||
call LineUp ;draw vertical line to top edge
|
||||
mov ax,3
|
||||
mov dx,88
|
||||
call LineUp ;draw diagonal line to upper left
|
||||
mov ax,4
|
||||
mov dx,161
|
||||
call LineUp ;draw horizontal line to left edge
|
||||
mov ax,5
|
||||
mov dx,88
|
||||
call LineUp ;draw diagonal line to lower left
|
||||
mov ax,6
|
||||
mov dx,88
|
||||
call LineUp ;draw vertical line to bottom edge
|
||||
mov ax,7
|
||||
mov dx,88
|
||||
call LineUp ;draw diagonal line to bottom right
|
||||
ret
|
||||
QuadrantUp endp
|
||||
;
|
||||
; Draws a horizontal, vertical, or diagonal line (one of the eight
|
||||
; possible radial lines) of the specified length from the specified
|
||||
; starting point.
|
||||
;
|
||||
; Input:
|
||||
; AX = line direction, as follows:
|
||||
; 3 2 1
|
||||
; 4 * 0
|
||||
; 5 6 7
|
||||
; BX = X coordinate of starting point
|
||||
; CX = Y coordinate of starting point
|
||||
; DX = length of line (number of pixels drawn)
|
||||
;
|
||||
; All registers preserved.
|
||||
;
|
||||
; Table of vectors to routines for each of the 8 possible lines.
|
||||
;
|
||||
LineUpVectors label word
|
||||
dw LineUp0, LineUp1, LineUp2, LineUp3
|
||||
dw LineUp4, LineUp5, LineUp6, LineUp7
|
||||
|
||||
;
|
||||
; Macro to draw horizontal, vertical, or diagonal line.
|
||||
;
|
||||
; Input:
|
||||
; 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.
|
||||
; BX = X start location
|
||||
; CX = Y start location
|
||||
; DX = number of pixels to draw
|
||||
; DS:SI = line pattern
|
||||
;
|
||||
MLineUp macro XParm, YParm
|
||||
local LineUpLoop, CheckMoreLine
|
||||
mov di,si ;set aside start offset of pattern
|
||||
lodsb ;get length of pattern
|
||||
mov ah,al
|
||||
;
|
||||
; Macro to draw horizontal, vertical, or diagonal line.
|
||||
;
|
||||
; Input:
|
||||
; 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.
|
||||
; BX = X start location
|
||||
; CX = Y start location
|
||||
; DX = number of pixels to draw
|
||||
; DS:SI = line pattern
|
||||
;
|
||||
MLineUp macro XParm, YParm
|
||||
local LineUpLoop, CheckMoreLine
|
||||
mov di,si ;set aside start offset of pattern
|
||||
lodsb ;get length of pattern
|
||||
mov ah,al
|
||||
|
||||
LineUpLoop:
|
||||
lodsb ;get color of this pixel...
|
||||
call DotUpInColor ;...and draw it
|
||||
if XParm EQ 1
|
||||
inc bx
|
||||
endif
|
||||
if XParm EQ -1
|
||||
dec bx
|
||||
endif
|
||||
if YParm EQ 1
|
||||
inc cx
|
||||
endif
|
||||
if YParm EQ -1
|
||||
dec cx
|
||||
endif
|
||||
dec ah ;at end of pattern?
|
||||
jnz CheckMoreLine
|
||||
mov si,di ;get back start of pattern
|
||||
lodsb
|
||||
mov ah,al ;reset pattern count
|
||||
LineUpLoop:
|
||||
lodsb ;get color of this pixel...
|
||||
call DotUpInColor ;...and draw it
|
||||
if XParm EQ 1
|
||||
inc bx
|
||||
endif
|
||||
if XParm EQ -1
|
||||
dec bx
|
||||
endif
|
||||
if YParm EQ 1
|
||||
inc cx
|
||||
endif
|
||||
if YParm EQ -1
|
||||
dec cx
|
||||
endif
|
||||
dec ah ;at end of pattern?
|
||||
jnz CheckMoreLine
|
||||
mov si,di ;get back start of pattern
|
||||
lodsb
|
||||
mov ah,al ;reset pattern count
|
||||
|
||||
CheckMoreLine:
|
||||
dec dx
|
||||
jnz LineUpLoop
|
||||
jmp LineUpEnd
|
||||
endm
|
||||
CheckMoreLine:
|
||||
dec dx
|
||||
jnz LineUpLoop
|
||||
jmp LineUpEnd
|
||||
endm
|
||||
|
||||
LineUp proc near
|
||||
push ax
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
push si
|
||||
push di
|
||||
push es
|
||||
LineUp proc near
|
||||
push ax
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
push si
|
||||
push di
|
||||
push es
|
||||
|
||||
mov di,ax
|
||||
mov di,ax
|
||||
|
||||
mov ax,GRAPHICS_SEGMENT
|
||||
mov es,ax
|
||||
mov ax,GRAPHICS_SEGMENT
|
||||
mov es,ax
|
||||
|
||||
push dx ;save line length
|
||||
;
|
||||
; Enable writes to all planes.
|
||||
;
|
||||
mov dx,SC_INDEX
|
||||
mov al,MAP_MASK
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,0fh
|
||||
out dx,al
|
||||
;
|
||||
; Select write mode 2.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GRAPHICS_MODE
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,02h
|
||||
out dx,al
|
||||
;
|
||||
; Vector to proper routine.
|
||||
;
|
||||
pop dx ;get back line length
|
||||
push dx ;save line length
|
||||
;
|
||||
; Enable writes to all planes.
|
||||
;
|
||||
mov dx,SC_INDEX
|
||||
mov al,MAP_MASK
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,0fh
|
||||
out dx,al
|
||||
;
|
||||
; Select write mode 2.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,GRAPHICS_MODE
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,02h
|
||||
out dx,al
|
||||
;
|
||||
; Vector to proper routine.
|
||||
;
|
||||
pop dx ;get back line length
|
||||
|
||||
shl di,1
|
||||
jmp cs:[LineUpVectors+di]
|
||||
;
|
||||
; Horizontal line to right.
|
||||
;
|
||||
LineUp0:
|
||||
MLineUp 1, 0
|
||||
;
|
||||
; Diagonal line to upper right.
|
||||
;
|
||||
LineUp1:
|
||||
MLineUp 1, -1
|
||||
;
|
||||
; Vertical line to top.
|
||||
;
|
||||
LineUp2:
|
||||
MLineUp 0, -1
|
||||
;
|
||||
; Diagonal line to upper left.
|
||||
;
|
||||
LineUp3:
|
||||
MLineUp -1, -1
|
||||
;
|
||||
; Horizontal line to left.
|
||||
;
|
||||
LineUp4:
|
||||
MLineUp -1, 0
|
||||
;
|
||||
; Diagonal line to bottom left.
|
||||
;
|
||||
LineUp5:
|
||||
MLineUp -1, 1
|
||||
;
|
||||
; Vertical line to bottom.
|
||||
;
|
||||
LineUp6:
|
||||
MLineUp 0, 1
|
||||
;
|
||||
; Diagonal line to bottom right.
|
||||
;
|
||||
LineUp7:
|
||||
MLineUp 1, 1
|
||||
shl di,1
|
||||
jmp cs:[LineUpVectors+di]
|
||||
;
|
||||
; Horizontal line to right.
|
||||
;
|
||||
LineUp0:
|
||||
MLineUp 1, 0
|
||||
;
|
||||
; Diagonal line to upper right.
|
||||
;
|
||||
LineUp1:
|
||||
MLineUp 1, -1
|
||||
;
|
||||
; Vertical line to top.
|
||||
;
|
||||
LineUp2:
|
||||
MLineUp 0, -1
|
||||
;
|
||||
; Diagonal line to upper left.
|
||||
;
|
||||
LineUp3:
|
||||
MLineUp -1, -1
|
||||
;
|
||||
; Horizontal line to left.
|
||||
;
|
||||
LineUp4:
|
||||
MLineUp -1, 0
|
||||
;
|
||||
; Diagonal line to bottom left.
|
||||
;
|
||||
LineUp5:
|
||||
MLineUp -1, 1
|
||||
;
|
||||
; Vertical line to bottom.
|
||||
;
|
||||
LineUp6:
|
||||
MLineUp 0, 1
|
||||
;
|
||||
; Diagonal line to bottom right.
|
||||
;
|
||||
LineUp7:
|
||||
MLineUp 1, 1
|
||||
|
||||
LineUpEnd:
|
||||
pop es
|
||||
pop di
|
||||
pop si
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
pop ax
|
||||
ret
|
||||
LineUp endp
|
||||
;
|
||||
; 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
|
||||
; enabled and that ES points to display memory.
|
||||
;
|
||||
; Input:
|
||||
; AL = dot color
|
||||
; BX = X coordinate of dot
|
||||
; CX = Y coordinate of dot
|
||||
; ES = display memory segment
|
||||
;
|
||||
; All registers preserved.
|
||||
;
|
||||
DotUpInColor proc near
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
push di
|
||||
;
|
||||
; 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.
|
||||
;
|
||||
push ax ;preserve dot color
|
||||
mov ax,SCREEN_WIDTH_IN_BYTES
|
||||
mul cx ;offset of start of top scan line
|
||||
mov di,ax
|
||||
mov cl,bl
|
||||
and cl,111b
|
||||
mov dx,GC_INDEX
|
||||
mov al,BIT_MASK
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,80h
|
||||
shr al,cl
|
||||
out dx,al ;set the bit mask for the pixel
|
||||
shr bx,1
|
||||
shr bx,1
|
||||
shr bx,1 ;X in bytes
|
||||
add di,bx ;offset of byte pixel is in
|
||||
mov al,es:[di] ;load latches
|
||||
pop ax ;get back dot color
|
||||
stosb ;write dot in desired color
|
||||
LineUpEnd:
|
||||
pop es
|
||||
pop di
|
||||
pop si
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
pop ax
|
||||
ret
|
||||
LineUp endp
|
||||
;
|
||||
; 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
|
||||
; enabled and that ES points to display memory.
|
||||
;
|
||||
; Input:
|
||||
; AL = dot color
|
||||
; BX = X coordinate of dot
|
||||
; CX = Y coordinate of dot
|
||||
; ES = display memory segment
|
||||
;
|
||||
; All registers preserved.
|
||||
;
|
||||
DotUpInColor proc near
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
push di
|
||||
;
|
||||
; 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.
|
||||
;
|
||||
push ax ;preserve dot color
|
||||
mov ax,SCREEN_WIDTH_IN_BYTES
|
||||
mul cx ;offset of start of top scan line
|
||||
mov di,ax
|
||||
mov cl,bl
|
||||
and cl,111b
|
||||
mov dx,GC_INDEX
|
||||
mov al,BIT_MASK
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,80h
|
||||
shr al,cl
|
||||
out dx,al ;set the bit mask for the pixel
|
||||
shr bx,1
|
||||
shr bx,1
|
||||
shr bx,1 ;X in bytes
|
||||
add di,bx ;offset of byte pixel is in
|
||||
mov al,es:[di] ;load latches
|
||||
pop ax ;get back dot color
|
||||
stosb ;write dot in desired color
|
||||
|
||||
pop di
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
ret
|
||||
DotUpInColor endp
|
||||
Start endp
|
||||
Code ends
|
||||
end Start
|
||||
pop di
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
ret
|
||||
DotUpInColor endp
|
||||
Start endp
|
||||
Code ends
|
||||
end Start
|
||||
```
|
||||
|
|
|
|||
368
27-05.md
368
27-05.md
|
|
@ -53,194 +53,196 @@ rewarding!) VGA to cover.
|
|||
|
||||
**LISTING 27.3 L27-3.ASM**
|
||||
|
||||
; 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
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
Stack segment para stack ‘STACK'
|
||||
db 512 dup(0)
|
||||
Stack ends
|
||||
```nasm
|
||||
; 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
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
Stack segment para stack ‘STACK'
|
||||
db 512 dup(0)
|
||||
Stack ends
|
||||
|
||||
GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment
|
||||
TEXT_SEGMENT equ 0b800h ;mode 3 bit-map segment
|
||||
SC_INDEX equ 3c4h ;Sequence Controller Index register
|
||||
MAP_MASK equ 2 ;index of Map Mask register
|
||||
GC_INDEX equ 3ceh ;Graphics Controller Index register
|
||||
READ_MAP equ 4 ;index of Read Map register
|
||||
GRAPHICS_SEGMENT equ 0a000h ;mode 10 bit-map segment
|
||||
TEXT_SEGMENT equ 0b800h ;mode 3 bit-map segment
|
||||
SC_INDEX equ 3c4h ;Sequence Controller Index register
|
||||
MAP_MASK equ 2 ;index of Map Mask register
|
||||
GC_INDEX equ 3ceh ;Graphics Controller Index register
|
||||
READ_MAP equ 4 ;index of Read Map register
|
||||
|
||||
Data segment para common ‘DATA'
|
||||
Data segment para common ‘DATA'
|
||||
|
||||
GStrikeAnyKeyMsg0 label byte
|
||||
db 0dh, 0ah, ‘Graphics mode', 0dh, 0ah
|
||||
db ‘Strike any key to continue...', 0dh, 0ah, ‘$'
|
||||
GStrikeAnyKeyMsg0 label byte
|
||||
db 0dh, 0ah, ‘Graphics mode', 0dh, 0ah
|
||||
db ‘Strike any key to continue...', 0dh, 0ah, ‘$'
|
||||
|
||||
GStrikeAnyKeyMsg1 label byte
|
||||
db 0dh, 0ah, ‘Graphics mode again', 0dh, 0ah
|
||||
db ‘Strike any key to continue...', 0dh, 0ah, ‘$'
|
||||
GStrikeAnyKeyMsg1 label byte
|
||||
db 0dh, 0ah, ‘Graphics mode again', 0dh, 0ah
|
||||
db ‘Strike any key to continue...', 0dh, 0ah, ‘$'
|
||||
|
||||
TStrikeAnyKeyMsg label byte
|
||||
db 0dh, 0ah, ‘Text mode', 0dh, 0ah
|
||||
db ‘Strike any key to continue...', 0dh, 0ah, ‘$'
|
||||
TStrikeAnyKeyMsg label byte
|
||||
db 0dh, 0ah, ‘Text mode', 0dh, 0ah
|
||||
db ‘Strike any key to continue...', 0dh, 0ah, ‘$'
|
||||
|
||||
Plane2Save db 2000h dup (?) ;save area for plane 2 data
|
||||
; where font gets loaded
|
||||
CharAttSave db 4000 dup (?) ;save area for memory wiped
|
||||
; out by character/attribute
|
||||
; data in text mode
|
||||
Data ends
|
||||
Plane2Save db 2000h dup (?) ;save area for plane 2 data
|
||||
; where font gets loaded
|
||||
CharAttSave db 4000 dup (?) ;save area for memory wiped
|
||||
; out by character/attribute
|
||||
; data in text mode
|
||||
Data ends
|
||||
|
||||
Code segment para public ‘CODE'
|
||||
assume cs:Code, ds:Data
|
||||
Start proc near
|
||||
mov ax,10h
|
||||
int 10h ;select video mode 10h (640x350)
|
||||
;
|
||||
; Fill the graphics bit-map with a colored pattern.
|
||||
;
|
||||
cld
|
||||
mov ax,GRAPHICS_SEGMENT
|
||||
mov es,ax
|
||||
mov ah,3 ;initial fill pattern
|
||||
mov cx,4 ;four planes to fill
|
||||
mov dx,SC_INDEX
|
||||
mov al,MAP_MASK
|
||||
out dx,al ;leave the SC Index pointing to the
|
||||
inc dx ; Map Mask register
|
||||
Code segment para public ‘CODE'
|
||||
assume cs:Code, ds:Data
|
||||
Start proc near
|
||||
mov ax,10h
|
||||
int 10h ;select video mode 10h (640x350)
|
||||
;
|
||||
; Fill the graphics bit-map with a colored pattern.
|
||||
;
|
||||
cld
|
||||
mov ax,GRAPHICS_SEGMENT
|
||||
mov es,ax
|
||||
mov ah,3 ;initial fill pattern
|
||||
mov cx,4 ;four planes to fill
|
||||
mov dx,SC_INDEX
|
||||
mov al,MAP_MASK
|
||||
out dx,al ;leave the SC Index pointing to the
|
||||
inc dx ; Map Mask register
|
||||
|
||||
FillBitMap:
|
||||
mov al,10h
|
||||
shr al,cl ;generate map mask for this plane
|
||||
out dx,al ;set map mask for this plane
|
||||
sub di,di ;start at offset 0
|
||||
mov al,ah ;get the fill pattern
|
||||
push cx ;preserve plane count
|
||||
mov cx,8000h ;fill 32K words
|
||||
rep stosw ;do fill for this plane
|
||||
pop cx ;get back plane count
|
||||
shl ah,1
|
||||
shl ah,1
|
||||
loop FillBitMap
|
||||
;
|
||||
; Put up "strike any key" message.
|
||||
;
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov dx,offset GStrikeAnyKeyMsg0
|
||||
mov ah,9
|
||||
int 21h
|
||||
;
|
||||
; Wait for a key.
|
||||
;
|
||||
mov ah,01h
|
||||
int 21h
|
||||
;
|
||||
; Save the 8K of plane 2 that will be used by the font.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,READ_MAP
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,2
|
||||
out dx,al ;set up to read from plane 2
|
||||
mov ax,Data
|
||||
mov es,ax
|
||||
mov ax,GRAPHICS_SEGMENT
|
||||
mov ds,ax
|
||||
sub si,si
|
||||
mov di,offset Plane2Save
|
||||
mov cx,2000h/2 ;save 8K (length of default font)
|
||||
rep movsw
|
||||
;
|
||||
; Go to text mode without clearing display memory.
|
||||
;
|
||||
mov ax,083h
|
||||
int 10h
|
||||
;
|
||||
; Save the text mode bit-map.
|
||||
;
|
||||
mov ax,Data
|
||||
mov es,ax
|
||||
mov ax,TEXT_SEGMENT
|
||||
mov ds,ax
|
||||
sub si,si
|
||||
mov di,offset CharAttSave
|
||||
mov cx,4000/2 ;length of one text screen in words
|
||||
rep movsw
|
||||
;
|
||||
; Fill the text mode screen with dots and put up "strike any key"
|
||||
; message.
|
||||
;
|
||||
mov ax,TEXT_SEGMENT
|
||||
mov es,ax
|
||||
sub di,di
|
||||
mov al,‘.' ;fill character
|
||||
mov ah,7 ;fill attribute
|
||||
mov cx,4000/2 ;length of one text screen in words
|
||||
rep stosw
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov dx,offset TStrikeAnyKeyMsg
|
||||
mov ah,9
|
||||
int 21h
|
||||
;
|
||||
; Wait for a key.
|
||||
;
|
||||
mov ah,01h
|
||||
int 21h
|
||||
;
|
||||
; Restore the text mode screen to the state it was in on entering
|
||||
; text mode.
|
||||
;
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov ax,TEXT_SEGMENT
|
||||
mov es,ax
|
||||
mov si,offset CharAttSave
|
||||
sub di,di
|
||||
mov cx,4000/2 ;length of one text screen in words
|
||||
rep movsw
|
||||
;
|
||||
; Return to mode 10h without clearing display memory.
|
||||
;
|
||||
mov ax,90h
|
||||
int 10h
|
||||
;
|
||||
; Restore the portion of plane 2 that was wiped out by the font.
|
||||
;
|
||||
mov dx,SC_INDEX
|
||||
mov al,MAP_MASK
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,4
|
||||
out dx,al ;set up to write to plane 2
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov ax,GRAPHICS_SEGMENT
|
||||
mov es,ax
|
||||
mov si,offset Plane2Save
|
||||
sub di,di
|
||||
mov cx,2000h/2 ;restore 8K (length of default font)
|
||||
rep movsw
|
||||
;
|
||||
; Put up "strike any key" message.
|
||||
;
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov dx,offset GStrikeAnyKeyMsg1
|
||||
mov ah,9
|
||||
int 21h
|
||||
;
|
||||
; Wait for a key before returning to text mode and ending.
|
||||
;
|
||||
mov ah,01h
|
||||
int 21h
|
||||
mov ax,03h
|
||||
int 10h
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Start endp
|
||||
Code ends
|
||||
end Start
|
||||
FillBitMap:
|
||||
mov al,10h
|
||||
shr al,cl ;generate map mask for this plane
|
||||
out dx,al ;set map mask for this plane
|
||||
sub di,di ;start at offset 0
|
||||
mov al,ah ;get the fill pattern
|
||||
push cx ;preserve plane count
|
||||
mov cx,8000h ;fill 32K words
|
||||
rep stosw ;do fill for this plane
|
||||
pop cx ;get back plane count
|
||||
shl ah,1
|
||||
shl ah,1
|
||||
loop FillBitMap
|
||||
;
|
||||
; Put up "strike any key" message.
|
||||
;
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov dx,offset GStrikeAnyKeyMsg0
|
||||
mov ah,9
|
||||
int 21h
|
||||
;
|
||||
; Wait for a key.
|
||||
;
|
||||
mov ah,01h
|
||||
int 21h
|
||||
;
|
||||
; Save the 8K of plane 2 that will be used by the font.
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
mov al,READ_MAP
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,2
|
||||
out dx,al ;set up to read from plane 2
|
||||
mov ax,Data
|
||||
mov es,ax
|
||||
mov ax,GRAPHICS_SEGMENT
|
||||
mov ds,ax
|
||||
sub si,si
|
||||
mov di,offset Plane2Save
|
||||
mov cx,2000h/2 ;save 8K (length of default font)
|
||||
rep movsw
|
||||
;
|
||||
; Go to text mode without clearing display memory.
|
||||
;
|
||||
mov ax,083h
|
||||
int 10h
|
||||
;
|
||||
; Save the text mode bit-map.
|
||||
;
|
||||
mov ax,Data
|
||||
mov es,ax
|
||||
mov ax,TEXT_SEGMENT
|
||||
mov ds,ax
|
||||
sub si,si
|
||||
mov di,offset CharAttSave
|
||||
mov cx,4000/2 ;length of one text screen in words
|
||||
rep movsw
|
||||
;
|
||||
; Fill the text mode screen with dots and put up "strike any key"
|
||||
; message.
|
||||
;
|
||||
mov ax,TEXT_SEGMENT
|
||||
mov es,ax
|
||||
sub di,di
|
||||
mov al,‘.' ;fill character
|
||||
mov ah,7 ;fill attribute
|
||||
mov cx,4000/2 ;length of one text screen in words
|
||||
rep stosw
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov dx,offset TStrikeAnyKeyMsg
|
||||
mov ah,9
|
||||
int 21h
|
||||
;
|
||||
; Wait for a key.
|
||||
;
|
||||
mov ah,01h
|
||||
int 21h
|
||||
;
|
||||
; Restore the text mode screen to the state it was in on entering
|
||||
; text mode.
|
||||
;
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov ax,TEXT_SEGMENT
|
||||
mov es,ax
|
||||
mov si,offset CharAttSave
|
||||
sub di,di
|
||||
mov cx,4000/2 ;length of one text screen in words
|
||||
rep movsw
|
||||
;
|
||||
; Return to mode 10h without clearing display memory.
|
||||
;
|
||||
mov ax,90h
|
||||
int 10h
|
||||
;
|
||||
; Restore the portion of plane 2 that was wiped out by the font.
|
||||
;
|
||||
mov dx,SC_INDEX
|
||||
mov al,MAP_MASK
|
||||
out dx,al
|
||||
inc dx
|
||||
mov al,4
|
||||
out dx,al ;set up to write to plane 2
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov ax,GRAPHICS_SEGMENT
|
||||
mov es,ax
|
||||
mov si,offset Plane2Save
|
||||
sub di,di
|
||||
mov cx,2000h/2 ;restore 8K (length of default font)
|
||||
rep movsw
|
||||
;
|
||||
; Put up "strike any key" message.
|
||||
;
|
||||
mov ax,Data
|
||||
mov ds,ax
|
||||
mov dx,offset GStrikeAnyKeyMsg1
|
||||
mov ah,9
|
||||
int 21h
|
||||
;
|
||||
; Wait for a key before returning to text mode and ending.
|
||||
;
|
||||
mov ah,01h
|
||||
int 21h
|
||||
mov ax,03h
|
||||
int 10h
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Start endp
|
||||
Code ends
|
||||
end Start
|
||||
```
|
||||
|
|
|
|||
472
28-02.md
472
28-02.md
|
|
@ -12,238 +12,240 @@ pages: 526-530
|
|||
|
||||
**LISTING 28.1 L28-1.ASM**
|
||||
|
||||
; Program to illustrate the use of the Read Map register in read mode 0.
|
||||
; Animates by copying a 16-color image from VGA memory to system memory,
|
||||
; one plane at a time, then copying the image back to a new location
|
||||
; in VGA memory.
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
stacksegmentword stack 'STACK'
|
||||
db512 dup (?)
|
||||
stackends
|
||||
;
|
||||
datasegment word 'DATA'
|
||||
IMAGE_WIDTHEQU 4 ;in bytes
|
||||
IMAGE_HEIGHT EQU 32 ;in pixels
|
||||
LEFT_BOUND EQU 10 ;in bytes
|
||||
RIGHT_BOUND EQU 66 ;in bytes
|
||||
VGA_SEGMENT EQU 0a000h
|
||||
SCREEN_WIDTH EQU 80 ;in bytes
|
||||
SC_INDEX EQU 3c4h ;Sequence Controller Index register
|
||||
GC_INDEX EQU 3ceh ;Graphics Controller Index register
|
||||
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.
|
||||
;
|
||||
PatternPlane0 label byte
|
||||
db 32 dup (0ffh,0ffh,0,0)
|
||||
PatternPlane1 labelbyte
|
||||
db 32 dup (0ffh,0,0ffh,0)
|
||||
PatternPlane2 labelbyte
|
||||
db 32 dup (0f0h,0f0h,0f0h,0f0h)
|
||||
PatternPlane3 labelbyte
|
||||
db 32 dup (0cch,0cch,0cch,0cch)
|
||||
;
|
||||
; Temporary storage for 16-color image during animation.
|
||||
;
|
||||
ImagePlane0 db 32*4 dup (?)
|
||||
ImagePlane1 db 32*4 dup (?)
|
||||
ImagePlane2 db 32*4 dup (?)
|
||||
ImagePlane3 db 32*4 dup (?)
|
||||
;
|
||||
; Current image location & direction.
|
||||
;
|
||||
ImageX dw 40 ;in bytes
|
||||
ImageY dw 100 ;in pixels
|
||||
ImageXDirection dw 1 ;in bytes
|
||||
dataends
|
||||
;
|
||||
code segment word 'CODE'
|
||||
assume cs:code,ds:data
|
||||
Start proc near
|
||||
cld
|
||||
mov ax,data
|
||||
mov ds,ax
|
||||
;
|
||||
; Select graphics mode 10h.
|
||||
;
|
||||
mov ax,10h
|
||||
int 10h
|
||||
;
|
||||
; Draw the initial image.
|
||||
;
|
||||
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
|
||||
; location in VGA memory. Ends when a key is hit.
|
||||
;
|
||||
AnimateLoop:
|
||||
;
|
||||
; Copy the image from VGA memory to system memory.
|
||||
;
|
||||
mov di,offset ImagePlane0
|
||||
call GetImage
|
||||
;
|
||||
; Clear the image from VGA memory.
|
||||
;
|
||||
call EraseImage
|
||||
;
|
||||
; Advance the image X coordinate, reversing direction if either edge
|
||||
; of the screen has been reached.
|
||||
;
|
||||
mov ax,[ImageX]
|
||||
cmp ax,LEFT_BOUND
|
||||
jz ReverseDirection
|
||||
cmp ax,RIGHT_BOUND
|
||||
jnz SetNewX
|
||||
ReverseDirection:
|
||||
neg [ImageXDirection]
|
||||
SetNewX:
|
||||
add ax,[ImageXDirection]
|
||||
mov [ImageX],ax
|
||||
;
|
||||
; Draw the image by copying it from system memory to VGA memory.
|
||||
;
|
||||
mov si,offset ImagePlane0
|
||||
call DrawImage
|
||||
;
|
||||
; Slow things down a bit for visibility (adjust as needed).
|
||||
;
|
||||
mov cx,0
|
||||
DelayLoop:
|
||||
loop DelayLoop
|
||||
;
|
||||
; See if a key has been hit, ending the program.
|
||||
;
|
||||
mov ah,1
|
||||
int 16h
|
||||
jz AnimateLoop
|
||||
;
|
||||
; Clear the key, return to text mode, and return to DOS.
|
||||
;
|
||||
sub ah,ah
|
||||
int 16h
|
||||
mov ax,3
|
||||
int 10h
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Startendp
|
||||
;
|
||||
; Draws the image at offset DS:SI to the current image location in
|
||||
; VGA memory.
|
||||
;
|
||||
DrawImageprocnear
|
||||
mov ax,VGA_SEGMENT
|
||||
mov es,ax
|
||||
call GetImageOffset ;ES:DI is the destination address for the
|
||||
; image in VGA memory
|
||||
mov dx,SC_INDEX
|
||||
mov al,1 ;do plane 0 first
|
||||
DrawImagePlaneLoop:
|
||||
push di ;image is drawn at the same offset in
|
||||
; each plane
|
||||
push ax ;preserve plane select
|
||||
mov al,MAP_MASK ;Map Mask index
|
||||
out dx,al ;point SC Index to the Map Mask register
|
||||
pop ax ;get back plane select
|
||||
inc dx ;point to SC index register
|
||||
out dx,al ;set up the Map Mask to allow writes to
|
||||
; the plane of interest
|
||||
dec dx ;point back to SC Data register
|
||||
mov bx,IMAGE_HEIGHT ;# of scan lines in image
|
||||
DrawImageLoop:
|
||||
mov cx,IMAGE_WIDTH ;# of bytes across image
|
||||
rep movsb
|
||||
add di,SCREEN_WIDTH-IMAGE_WIDTH
|
||||
;point to next scan line of image
|
||||
dec bx ;any more scan lines?
|
||||
jnz DrawImageLoop
|
||||
pop di ;get back image start offset in VGA memory
|
||||
shl al,1 ;Map Mask setting for next plane
|
||||
cmp al,10h ;have we done all four planes?
|
||||
jnz DrawImagePlaneLoop
|
||||
ret
|
||||
DrawImageendp
|
||||
;
|
||||
; 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
|
||||
call GetImageOffset ;DI is offset of image in VGA memory
|
||||
xchg si,di ;SI is offset of image, DI is destination offset
|
||||
push ds
|
||||
pop es ;ES:DI is destination
|
||||
mov ax,VGA_SEGMENT
|
||||
mov ds,ax ;DS:SI is source
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
sub al,al;do plane 0 first
|
||||
GetImagePlaneLoop:
|
||||
push si ;image comes from same offset in each plane
|
||||
push ax ;preserve plane select
|
||||
mov al,READ_MAP ;Read Map index
|
||||
out dx,al ;point GC Index to Read Map register
|
||||
pop ax ;get back plane select
|
||||
inc dx ;point to GC Index register
|
||||
out dx,al ;set up the Read Map to select reads from
|
||||
; the plane of interest
|
||||
dec dx ;point back to GC data register
|
||||
mov bx,IMAGE_HEIGHT ;# of scan lines in image
|
||||
GetImageLoop:
|
||||
mov cx,IMAGE_WIDTH ;# of bytes across image
|
||||
rep movsb
|
||||
add si,SCREEN_WIDTH-IMAGE_WIDTH
|
||||
;point to next scan line of image
|
||||
dec bx ;any more scan lines?
|
||||
jnz GetImageLoop
|
||||
pop si ;get back image start offset
|
||||
inc al ;Read Map setting for next plane
|
||||
cmp al,4 ;have we done all four planes?
|
||||
jnz GetImagePlaneLoop
|
||||
push es
|
||||
pop ds ;restore original DS
|
||||
ret
|
||||
GetImageendp
|
||||
;
|
||||
; Erases the image at its current location.
|
||||
;
|
||||
EraseImage proc near
|
||||
mov dx,SC_INDEX
|
||||
mov al,MAP_MASK
|
||||
out dx,al ;point SC Index to the Map Mask register
|
||||
inc dx ;point to SC Data register
|
||||
mov al,0fh
|
||||
out dx,al ;set up the Map Mask to allow writes to go to
|
||||
; all 4 planes
|
||||
mov ax,VGA_SEGMENT
|
||||
mov es,ax
|
||||
call GetImageOffset ;ES:DI points to the start address
|
||||
; of the image
|
||||
sub al,al ;erase with zeros
|
||||
mov bx,IMAGE_HEIGHT ;# of scan lines in image
|
||||
EraseImageLoop:
|
||||
mov cx,IMAGE_WIDTH ;# of bytes across image
|
||||
rep stosb
|
||||
add di,SCREEN_WIDTH-IMAGE_WIDTH
|
||||
;point to next scan line of image
|
||||
dec bx ;any more scan lines?
|
||||
jnz EraseImageLoop
|
||||
ret
|
||||
EraseImage endp
|
||||
;
|
||||
; Returns the current offset of the image in the VGA segment in DI.
|
||||
;
|
||||
GetImageOffset proc near
|
||||
mov ax,SCREEN_WIDTH
|
||||
mul [ImageY]
|
||||
add ax,[ImageX]
|
||||
mov di,ax
|
||||
ret
|
||||
GetImageOffset endp
|
||||
code ends
|
||||
end Start
|
||||
```nasm
|
||||
; Program to illustrate the use of the Read Map register in read mode 0.
|
||||
; Animates by copying a 16-color image from VGA memory to system memory,
|
||||
; one plane at a time, then copying the image back to a new location
|
||||
; in VGA memory.
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
stacksegmentword stack 'STACK'
|
||||
db512 dup (?)
|
||||
stackends
|
||||
;
|
||||
datasegment word 'DATA'
|
||||
IMAGE_WIDTHEQU 4 ;in bytes
|
||||
IMAGE_HEIGHT EQU 32 ;in pixels
|
||||
LEFT_BOUND EQU 10 ;in bytes
|
||||
RIGHT_BOUND EQU 66 ;in bytes
|
||||
VGA_SEGMENT EQU 0a000h
|
||||
SCREEN_WIDTH EQU 80 ;in bytes
|
||||
SC_INDEX EQU 3c4h ;Sequence Controller Index register
|
||||
GC_INDEX EQU 3ceh ;Graphics Controller Index register
|
||||
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.
|
||||
;
|
||||
PatternPlane0 label byte
|
||||
db 32 dup (0ffh,0ffh,0,0)
|
||||
PatternPlane1 labelbyte
|
||||
db 32 dup (0ffh,0,0ffh,0)
|
||||
PatternPlane2 labelbyte
|
||||
db 32 dup (0f0h,0f0h,0f0h,0f0h)
|
||||
PatternPlane3 labelbyte
|
||||
db 32 dup (0cch,0cch,0cch,0cch)
|
||||
;
|
||||
; Temporary storage for 16-color image during animation.
|
||||
;
|
||||
ImagePlane0 db 32*4 dup (?)
|
||||
ImagePlane1 db 32*4 dup (?)
|
||||
ImagePlane2 db 32*4 dup (?)
|
||||
ImagePlane3 db 32*4 dup (?)
|
||||
;
|
||||
; Current image location & direction.
|
||||
;
|
||||
ImageX dw 40 ;in bytes
|
||||
ImageY dw 100 ;in pixels
|
||||
ImageXDirection dw 1 ;in bytes
|
||||
dataends
|
||||
;
|
||||
code segment word 'CODE'
|
||||
assume cs:code,ds:data
|
||||
Start proc near
|
||||
cld
|
||||
mov ax,data
|
||||
mov ds,ax
|
||||
;
|
||||
; Select graphics mode 10h.
|
||||
;
|
||||
mov ax,10h
|
||||
int 10h
|
||||
;
|
||||
; Draw the initial image.
|
||||
;
|
||||
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
|
||||
; location in VGA memory. Ends when a key is hit.
|
||||
;
|
||||
AnimateLoop:
|
||||
;
|
||||
; Copy the image from VGA memory to system memory.
|
||||
;
|
||||
mov di,offset ImagePlane0
|
||||
call GetImage
|
||||
;
|
||||
; Clear the image from VGA memory.
|
||||
;
|
||||
call EraseImage
|
||||
;
|
||||
; Advance the image X coordinate, reversing direction if either edge
|
||||
; of the screen has been reached.
|
||||
;
|
||||
mov ax,[ImageX]
|
||||
cmp ax,LEFT_BOUND
|
||||
jz ReverseDirection
|
||||
cmp ax,RIGHT_BOUND
|
||||
jnz SetNewX
|
||||
ReverseDirection:
|
||||
neg [ImageXDirection]
|
||||
SetNewX:
|
||||
add ax,[ImageXDirection]
|
||||
mov [ImageX],ax
|
||||
;
|
||||
; Draw the image by copying it from system memory to VGA memory.
|
||||
;
|
||||
mov si,offset ImagePlane0
|
||||
call DrawImage
|
||||
;
|
||||
; Slow things down a bit for visibility (adjust as needed).
|
||||
;
|
||||
mov cx,0
|
||||
DelayLoop:
|
||||
loop DelayLoop
|
||||
;
|
||||
; See if a key has been hit, ending the program.
|
||||
;
|
||||
mov ah,1
|
||||
int 16h
|
||||
jz AnimateLoop
|
||||
;
|
||||
; Clear the key, return to text mode, and return to DOS.
|
||||
;
|
||||
sub ah,ah
|
||||
int 16h
|
||||
mov ax,3
|
||||
int 10h
|
||||
mov ah,4ch
|
||||
int 21h
|
||||
Startendp
|
||||
;
|
||||
; Draws the image at offset DS:SI to the current image location in
|
||||
; VGA memory.
|
||||
;
|
||||
DrawImageprocnear
|
||||
mov ax,VGA_SEGMENT
|
||||
mov es,ax
|
||||
call GetImageOffset ;ES:DI is the destination address for the
|
||||
; image in VGA memory
|
||||
mov dx,SC_INDEX
|
||||
mov al,1 ;do plane 0 first
|
||||
DrawImagePlaneLoop:
|
||||
push di ;image is drawn at the same offset in
|
||||
; each plane
|
||||
push ax ;preserve plane select
|
||||
mov al,MAP_MASK ;Map Mask index
|
||||
out dx,al ;point SC Index to the Map Mask register
|
||||
pop ax ;get back plane select
|
||||
inc dx ;point to SC index register
|
||||
out dx,al ;set up the Map Mask to allow writes to
|
||||
; the plane of interest
|
||||
dec dx ;point back to SC Data register
|
||||
mov bx,IMAGE_HEIGHT ;# of scan lines in image
|
||||
DrawImageLoop:
|
||||
mov cx,IMAGE_WIDTH ;# of bytes across image
|
||||
rep movsb
|
||||
add di,SCREEN_WIDTH-IMAGE_WIDTH
|
||||
;point to next scan line of image
|
||||
dec bx ;any more scan lines?
|
||||
jnz DrawImageLoop
|
||||
pop di ;get back image start offset in VGA memory
|
||||
shl al,1 ;Map Mask setting for next plane
|
||||
cmp al,10h ;have we done all four planes?
|
||||
jnz DrawImagePlaneLoop
|
||||
ret
|
||||
DrawImageendp
|
||||
;
|
||||
; 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
|
||||
call GetImageOffset ;DI is offset of image in VGA memory
|
||||
xchg si,di ;SI is offset of image, DI is destination offset
|
||||
push ds
|
||||
pop es ;ES:DI is destination
|
||||
mov ax,VGA_SEGMENT
|
||||
mov ds,ax ;DS:SI is source
|
||||
;
|
||||
mov dx,GC_INDEX
|
||||
sub al,al;do plane 0 first
|
||||
GetImagePlaneLoop:
|
||||
push si ;image comes from same offset in each plane
|
||||
push ax ;preserve plane select
|
||||
mov al,READ_MAP ;Read Map index
|
||||
out dx,al ;point GC Index to Read Map register
|
||||
pop ax ;get back plane select
|
||||
inc dx ;point to GC Index register
|
||||
out dx,al ;set up the Read Map to select reads from
|
||||
; the plane of interest
|
||||
dec dx ;point back to GC data register
|
||||
mov bx,IMAGE_HEIGHT ;# of scan lines in image
|
||||
GetImageLoop:
|
||||
mov cx,IMAGE_WIDTH ;# of bytes across image
|
||||
rep movsb
|
||||
add si,SCREEN_WIDTH-IMAGE_WIDTH
|
||||
;point to next scan line of image
|
||||
dec bx ;any more scan lines?
|
||||
jnz GetImageLoop
|
||||
pop si ;get back image start offset
|
||||
inc al ;Read Map setting for next plane
|
||||
cmp al,4 ;have we done all four planes?
|
||||
jnz GetImagePlaneLoop
|
||||
push es
|
||||
pop ds ;restore original DS
|
||||
ret
|
||||
GetImageendp
|
||||
;
|
||||
; Erases the image at its current location.
|
||||
;
|
||||
EraseImage proc near
|
||||
mov dx,SC_INDEX
|
||||
mov al,MAP_MASK
|
||||
out dx,al ;point SC Index to the Map Mask register
|
||||
inc dx ;point to SC Data register
|
||||
mov al,0fh
|
||||
out dx,al ;set up the Map Mask to allow writes to go to
|
||||
; all 4 planes
|
||||
mov ax,VGA_SEGMENT
|
||||
mov es,ax
|
||||
call GetImageOffset ;ES:DI points to the start address
|
||||
; of the image
|
||||
sub al,al ;erase with zeros
|
||||
mov bx,IMAGE_HEIGHT ;# of scan lines in image
|
||||
EraseImageLoop:
|
||||
mov cx,IMAGE_WIDTH ;# of bytes across image
|
||||
rep stosb
|
||||
add di,SCREEN_WIDTH-IMAGE_WIDTH
|
||||
;point to next scan line of image
|
||||
dec bx ;any more scan lines?
|
||||
jnz EraseImageLoop
|
||||
ret
|
||||
EraseImage endp
|
||||
;
|
||||
; Returns the current offset of the image in the VGA segment in DI.
|
||||
;
|
||||
GetImageOffset proc near
|
||||
mov ax,SCREEN_WIDTH
|
||||
mul [ImageY]
|
||||
add ax,[ImageX]
|
||||
mov di,ax
|
||||
ret
|
||||
GetImageOffset endp
|
||||
code ends
|
||||
end Start
|
||||
```
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue